跳至主要内容

Wiring to a state machine

Once you have a derived operator (BTU2KWH) and a registered API (weather), wiring them into a state machine is the same workflow as any built-in contract: write a definition, deploy it, watch it tick.

The three places a custom operator can appear

1. Direct compute calls. Anywhere you'd call /api/zeq/compute you can pass your operator ID alongside KO42:

curl -X POST https://zeqapi.com/api/zeq/compute \
-H "Authorization: Bearer zeq_ak_<your-key>" \
-d '{ "domain": "thermodynamics", "ops": ["KO42", "BTU2KWH"], "inputs": { "E_BTU": 1000 } }'

2. State contract transitions. The contract's operators array can mix built-in and custom operators freely. The kernel resolves both through getOperators() and runs them through the master equation in one pass:

{
"transitions": [
{
"trigger": { "kind": "every", "every_zeqonds": 60 },
"operators": ["KO42", "BTU2KWH", "QM10"],
"post_actions": [{ "kind": "set_state", "state": "joules_kwh" }]
}
]
}

3. Hyperagent operator sets. When you spawn a hyperagent you list its operator vocabulary. A custom operator goes in the list like any other:

{
"master_id": "your-slug",
"purpose": "Convert all incoming BTU readings to kWh and emit them",
"operators": ["KO42", "BTU2KWH"]
}

Resolution rules

When a contract or compute call references an operator by ID, the kernel resolves it in this order:

  1. Built-in catalogue (1,500+ ops from operator-registry.json + HF1–HF20).
  2. Promoted derived operators (entries in derived_operators where promoted = true).
  3. Owner-scoped derived operators (entries in derived_operators where the agent's owner ZID matches the caller). Promotion not required for the owner's own use.

If none match, the call fails with operator_not_found.

Per-machine operator vocabulary

Each machine ends up with its own effective operator catalogue: the public catalogue plus its own derived operators. You can query it:

GET /api/zeq/agent/operators?owner_zid=<your-zid>

Returns all operators derived under your ZID, with promotion status. This is the surface the workbench's APIs / Queries tabs read from when they paint operator pickers.

A complete worked path

Here's the lifecycle from "I need new math" to "it ticks on every Zeqond":

1. POST /api/zeq/agent/operators/derive
→ BTU2KWH registered, precision = 0.00008
2. POST /api/zeq/external-apis
→ "weather" API registered, encrypted at rest
3. POST /api/contracts/
→ contract uses pre_action api_call:"weather"
operators:["KO42","BTU2KWH"]
post_action set_state:"converted"
4. Contract starts ticking on the next Zeqond.
5. After ≥3 successful runs at ≤0.1% precision:
POST /api/zeq/agent/operators/promote
→ BTU2KWH joins the public catalogue.

Steps 1–3 are user-initiated. Step 4 is the framework. Step 5 is user-initiated when you decide to share.

Inspecting the wiring

The transparency oracle (/transparency/) publishes every operator call. If your custom operator is doing work, you'll see its ID in the oracle stream alongside the built-ins. The entangled state explorer (/state/?slug=<your-slug>) shows per-transition charges for compute that used your operator.

When something fails

If a derived operator regresses (a verification case starts failing after a math library update, for example) the kernel emits a verification_drift event and pauses contracts that reference it. You'll see this in the oracle and in your machine's entangled state. Re-derive with adjusted parents or report it as a framework bug.

Next

Read the full worked example: thermal router for an end-to-end build with both a custom operator and a custom protocol.