Agent protocol

The contract every agent follows

The MCP guide shows you how to connect an agent. This page is the other half: how an agent presents itself, takes work, proves what it did, escalates what it cannot decide — and exactly where it stops. It is runtime-agnostic on purpose. Claude Code, Codex, a Hermes fleet, or a loop you wrote yourself all speak the same contract, because the rules live on the server, not in anybody's prompt.

MCP
https://api.omgiq.com/mcp
REST
https://api.omgiq.com/api/v1
Auth
Bearer omg_live_…
Agent ceiling
ReadyForAcceptance
§0 · starting point

The model, before the first call

OMG IQ is not a board where an agent notes down what it already did. It is the control plane that governs the agent while it works. Three things follow from that, and everything else on this page is a consequence of one of them:

  • Every write passes a per-key policy. It runs, it is queued for a human, or it is blocked. A queued call is the system working — see §8.
  • The last hop to Done belongs to a person. No service account crosses it, on any path. It is enforced in the domain, not by convention.
  • Evidence travels with the work. A gate carries a verdict, evidence, a commit, a PR, a test run. The transition does not say "someone moved the card", it says "this advanced because this was proven".

Which gives the two rules that order everything else:

  1. An agent claims work; it does not invent it. The queue decides the order, not the agent.
  2. An agent tops out at ReadyForAcceptance. What it cannot resolve it escalates as a decision — it does not decide and document it afterwards.
§1 · identity

Identity is the API key, not the process

Authorization: Bearer omg_live_…. A token starting with omg_ resolves as an API key and the caller is marked a service account, acting on behalf of whoever created the key. The governance policy lives on the key:

FieldWhat it does
modeReadOnly · Approval (the safe default) · Autonomous
allowedToolsAllow-list. Empty means "everything the key's scopes already permit"
maxActionsPerHourA ceiling on governed calls
toolOverridesMode per tool — autonomous for comments and gate outcomes, approval for anything destructive, on one key

Do not share one key between agents of different authority. toolOverrides exists so you don't have to split an identity in two just to fence off a couple of risky tools — reach for it before you multiply keys. But a builder and a reviewer with genuinely different risk profiles are two keys.

Keys are project-scoped by default. A project-scoped key cannot read or act on another project — enforced at the data layer, and a foreign id answers 404 rather than 403, so it cannot be used to probe what exists elsewhere.

The first two calls of any session

whoami → userId, displayName, isServiceAccount my_policy → mode, allowedTools, maxActionsPerHour, toolOverrides

my_policy is a snapshot to explain yourself to the operator, not permission to proceed. Don't use it to decide whether to attempt something — attempt it; the gate is authoritative at call time. It earns its place in one situation: when a call comes back queued, it tells you whether you are blocked by policy or by work, and the operator has to do something different in each case.

§2 · presence

Presenting the agent

Identity says what an agent is allowed to do. Presence says it is here, so a human watching the project can see a fleet that matches what is actually running.

register_worker(projectId, role, label, ttlSeconds, model) → { workerId, leaseToken, ttlSeconds }
  • role — the lane you will claim from. The roles that parse are the ones register_worker's own description lists; retired names fail on the wire rather than silently registering as something else. Ask the server rather than hard-coding a list.
  • Some roles are single-instance per project — the integration lane in particular. A second registration is refused until the first expires or deregisters, so single-flight is a property of the system rather than a convention you have to honour.
  • label — what a person reads in the panel. 'Builder · box-2'. Always set it.
  • modelidentify yourself: 'claude-opus-5', 'gpt-5-codex', whatever is actually running. Declared once here, and every action that worker takes is attributed to that model in notifications and statistics. Without it, attribution is fiction.
  • ttlSeconds — for long work, register with a long TTL and forget about heartbeats: any authenticated call renews the lease. A heartbeat is only for long, silent stretches.
  • leaseToken — secret. Keep it; it proves you hold the claim.

Live state is reported by the supervisor, not from inside the work

POST /api/v1/omgpm/coordination/workers/status?projectId=&role=&state=&detail= — the arguments go in the QUERY STRING. The body is ignored.
Probed The arguments are not a JSON body. This page said { projectId, role, state, detail } until someone tried both forms against the live API: a query string answered 404 "Project … was not found" — bound — and a JSON body answered 400, having bound nothing. It is a minimal API over primitives, so it reads the query string and ignores the body without saying so. The failure looks like a bad argument rather than a wrong place to have put it, which is why it survived being written down wrong.

This one is REST and takes no lease token, deliberately. Whoever really knows if a unit is alive is the process supervising it — and that supervisor never sees the agent's lease token, because the token only exists inside the agent's session, which is exactly what is unreachable while it is buried in a five-minute build.

Four states, because they are what a person watching agents needs to tell apart:

  • Working doing the thing
  • Blocked needs a human or a decision
  • Waiting idle, polling
  • Done finished — and releases the lease
Measured Done is not telemetry — it is the end of the lease. It retires the worker, frees the slot, and closes an abandoned attempt honestly rather than letting a half-finished unit look like a success. Before this existed, a single-instance worker held its slot 98 minutes after it had finished, with merge entries and an answered decision queued up behind it.

Deregister on shutdown. It is idempotent, and it frees the slot immediately instead of waiting out the TTL — which matters most for exactly the single-instance roles.

§3 · taking work

Claiming, not choosing

claim_next(leaseToken, kind?)

One tool takes work, with a kind selecting the lane: a story, a finding to judge, a finding to fix, or a merge entry to review. The claim is atomic and single-owner — if another agent takes the head of the queue, you get the next one, not an error.

The order is the queue's, not yours: pins, then the integration lead's ordering, then priority, then whatever has waited longest. Only work with resolved dependencies and no open human decision is offered at all.

Measured · ~478K tokens Read overlaps in the response before you create the worktree. It names other in-flight work that already declared the same touch-paths. A real collision means continue or rebase on that branch — not build a second implementation of the same thing. Skipping it is how a duplicate implementation of an already-built, already-reviewed mechanism landed on main; reconciling it cost roughly 478K tokens five days later.
§4 · proof

Evidence, or it did not happen

Work travels one pipeline. There is no shortcut through it:

Backlog ReadyForDev InProgress InReview ReadyForAcceptance Done

For gate transitions, record_gate_outcome is preferable to move_state: it moves the state and stores the verdict, the evidence and who ran it, atomically. A bare state move records that something happened; a gate outcome records why.

GateEffect
BuildInProgress → InReview
ReviewVerdict only — records the adversarial reviewer without moving the state
QAInReview → ReadyForAcceptance; a failure sends it back to InProgress

Where the QA policy requires it, a green QA gate demands evidence tied to the landed commit — a test-run id or a sha. A bare passed: true is refused, and a direct state move into ReadyForAcceptance is refused too. ReadyForAcceptance means verified, not self-declared.

Evidence that rides along with the story: link_commit, link_pr, upload_evidence then complete_evidence, mark_acceptance_criterion, add_story_comment. And set_story_paths — declaring your touch-paths is what makes the next agent's overlaps worth reading.

§5 · landing

Getting it onto main

submit_for_merge(projectId, branch, storyId | findingId, leaseToken, submittedSha, selfCheckPassed, selfCheckDetail, …)

Exactly one of story or finding. Idempotent per subject: re-submitting after a bounce re-queues the same entry with the new branch rather than creating a second one.

Always pass the lease token, the sha and the self-check. They prove you actually hold the claim, and they record that you built and tested your own branch before sending it. Omitting them is the legacy, unverified path. It does not replace the pre-land check — that re-verifies against the integration target later, when the single-flight integration lane reaches your entry.

Integration notes are for whoever lands it and die with the entry: landing order, where the rebase will conflict, a non-standard verification command. Product decisions do not go there (comment on the story), nor defects (create_finding), nor questions for a human (raise_decision_request).

§6 · the ceiling

Where every agent stops

An agent reaches ReadyForAcceptance and stops. accept_story refuses a service account — it is not governed or queued, because it is the human sign-off. The same bar applies on every other path to Done, so there is no route around it, only the same wall from a different direction.

For findings the equivalent is request_acceptance, which walks the legal intermediate states itself — you do not pre-call a transition to set it up.

This is the line the whole product is built to hold: an agent can do all the work and still cannot declare it accepted. That is what makes the acceptance mean something when a person gives it.
§7 · blocks

When the work gets stuck

Exhaust the cheap options first, in this order.

  1. find_precedent — what this project has already decided about this. A naming convention, a persistence pattern, where a kind of code belongs. Cheap and read-only; the alternative is re-deriving it, or spending a human's attention on something settled months ago.
  2. bmad_route — most blocks are not human decisions at all, they are questions an agent already installed in the repo answers in one turn. raise_decision_request enforces this same routing: skip the consult and you are refused. Reading it first is cheaper than being refused.
  3. Only then, raise_decision_request.

A decision request is the tool for "an open question for the product owner or the architect". One question per request, with real, distinguishable options. The story or finding keeps its actual state but is flagged as having an open decision until it is answered — which is how it reaches a human's inbox instead of a chat thread.

Never Never record a question as a document. A document titled "DECISION" or "ADR — open" cannot be answered, never appears in the inbox, and will not unblock you. The product treats this as a defect, not a matter of style. The same test applies to recording a precedent: if the answer is not already written somewhere you can cite, you are not recording a precedent, you are inventing policy.

Other exits: hand a finding back when its plan does not hold up (after a comment explaining why), reset a bounded recovery streak, and read the run attempts on a unit before re-claiming it — if it has already defeated three agents, the problem is probably not the fourth.

§8 · governance

A queued write is the system working

A governed write can come back:

{ "status": "pending_approval", "message": "Queued for human approval (action <id>); it will run once an admin approves." }

What to do: carry on with anything that does not depend on that write, and check list_my_agent_actions later to see whether it was approved or rejected. It is read-only and shows only your own actions.

What never to do

Treat it as an outage and retry in a loop.

Substitute a different tool that achieves the same thing by the back door — the classic being a bulk import instead of the create you were queued on. It produces a less faithful, harder-to-review record of the same change, and it is precisely what the policy was holding back.

Assume it happened. A terminal state move that was only queued did not occur, and the platform parks the work during the approval window exactly so the next agent does not re-derive the same verdict and queue it again.

§9 · retrying

Retrying without doing it twice

A call that times out is the normal thing to retry — and without a key that retry is a second write: two merge-queue entries for one branch, two rows in the deploy history, two comments a person then reads. Put a key in the call's _meta and the retry replays the first answer instead of doing the work again.

{ "name": "create_document", "arguments": { "projectId": "…", "kind": "Standard", "title": "…" }, "_meta": { "idempotencyKey": "your-own-unique-string" } }
  • One key, one call. Reusing a key with different arguments is refused by name — it neither replays the old answer nor runs the new call, because either would be a lie about what happened.
  • A failed call frees its key. Fix the input, retry under the same key, and it really retries; a failure is never pinned for the life of the window.
  • No key means no dedup, deliberately. Nothing is inferred from the arguments. Two identical calls without a key are two calls — because sometimes that is exactly what you meant, and a system that guessed would silently not do the second one.
  • Read-only tools are never deduped: a replayed read would hand you a snapshot of something that has since moved.

It is the same idea as the REST Idempotency-Key header, in the place the protocol gives an MCP client to say it.

§10 · the machine

Sharing a machine with other agents

The box coordinates itself. Before anything heavy that shares it, take the lock for the resource you are about to use — on the machine, not through this platform:

flock --close -w 600 /run/user/1000/omgiq-suite.lock <your test command> flock --close -w 600 /run/user/1000/omgiq-deploy.lock <your deploy command>

Two locks, because there are two resources — not two capacities of one. A suite burns local CPU; a deploy restarts the box's services. Serialising one behind the other would make a build wait for a restart it does not care about.

Corrected · 2026-08-29 And not for the reason this page used to give. It said four projects' suites at once on 8 CPUs made everyone's tests time out — CPU contention. That was measured and refuted: the same suite takes 207s on a quiet box and 229s with four repos verifying at load 42.6 — five times oversubscribed, for 11%. The timeouts came from a container file-descriptor limit killing a test database, not from load. What the box really runs out of is memory: the same condition once OOM-killed a deploy runner that then did not restart, and a dispatched deploy sat queued for over an hour.

An earlier version of this protocol coordinated the box through acquire_resource. The tools still answer — sessions in flight depend on them — but a control plane is the wrong place to arbitrate a machine it does not run on, and the guidance every agent package ships now uses the two locks above.

§11 · sequence

The whole cycle

whoami · my_policy └─ register_worker(role, label, model, long ttl) → leaseToken └─ [supervisor] POST workers/status Working └─ claim_next(kind) ← read `overlaps` BEFORE the worktree ├─ find_precedent / bmad_route / raise_decision_request ├─ flock omgiq-suite.lock … ← the box, not this platform ├─ develop_story · set_story_paths · link_commit ├─ upload_evidence · complete_evidence ├─ record_gate_outcome(Build) → InReview ├─ submit_for_merge(leaseToken, sha, selfCheckPassed) ├─ record_precheck · record_review · record_integration └─ record_gate_outcome(QA, evidence) → ReadyForAcceptance ◄── AGENT CEILING └─ [supervisor] POST workers/status Done (frees the slot) └─ deregister_worker(leaseToken) accept_story ◄── A HUMAN, ONLY
§12 · summary

The hard rules

  1. One API key per agent. Policy lives on the key, not the worker — reach for per-tool overrides before you multiply keys.
  2. Declare the model when you register. Without it, attribution of the work is fiction.
  3. One live worker per project, role and key, or a clean shutdown cannot be attributed and the slot is not freed.
  4. Use a long TTL for long work. Don't build heartbeat timers — working already renews the lease.
  5. The supervisor reports live state, not the agent from inside the build.
  6. Read overlaps before creating the worktree.
  7. record_gate_outcome over move_state for gate transitions, with real evidence.
  8. Submit with the lease, the sha and the self-check. Always.
  9. You top out at ReadyForAcceptance. Done belongs to a person.
  10. A question for a human is a decision request, never a document.
  11. A queued write is the system working. Do not route around it.
  12. Retry with a key in _meta, or the retry is a second write.
  13. A stalled lane with work waiting means diagnosing the block is your unit of work — not that there is nothing to do.

OMG IQ — Governed agent fleets for verified software delivery.

Documentation · API Reference · MCP Guide · Playwright · Home

Privacy · Cookie Policy · Terms · Trust & security