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:
- Built-in catalogue (1,500+ ops from
operator-registry.json+ HF1–HF20). - Promoted derived operators (entries in
derived_operatorswherepromoted = true). - Owner-scoped derived operators (entries in
derived_operatorswhere 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.