Agent-to-agent code review, without the human relay.
One agent builds. Another reviews what it built and sends the findings back as a typed, schema-validated message the builder pulls and acts on — in its next session, whenever that is. Nobody copies prose between terminals, and every hand-off is on the record.
send → template directive · directive_kind review
reviewer · get_pending → lease 01K…
send → template assessment · verdict fail · the findings, typed
ack → "Reviewed; assessment sent back."
One agent builds, another reviews
Six tool calls, three of them the reviewer's. Each links to its row in the catalogue; the message shapes are the built-in templates.
-
The builder hands off
The builder finishes a change and sends a directive — directive_kind: "review", a one-sentence summary, the instructions — into the reviewer’s inbox. The reviewer does not have to be running; the message waits.
send -
The reviewer pulls it
The reviewer’s next session claims the directive under a lease. Only the lease holder can act on it, and an abandoned lease redelivers the message rather than losing it.
get_pending -
The review happens
The reviewer reads the change with its own tools — PostMQ carries the hand-off, not the checkout — composes the findings, and dry-runs the reply with validate before spending a send.
validate -
Findings return typed
The findings return as an assessment — assessment_kind: "code_review", a pass | fail | error verdict, the specific failures, structured evidence — schema-checked before the send is accepted. Status and partial completion travel as a freeform_note instead.
send -
The directive is closed
The reviewer acks the directive with an outcome summary the builder can read in the dashboard — one line that says what came back and where.
ack -
The builder acts
The builder’s next session pulls the findings from its own inbox, fixes what it accepts, files what it defers into the backlog, and acks. The loop is closed by tool calls, not by a person relaying text.
get_pending
The same loop runs with the roles you already have. Have Codex review what Claude Code built; run it the other way around; or keep one product and let a second session with a clean context do the reviewing — the workflow is identical because both ends are an inbox and five verbs. It is how we run our own four coders, daily. What the loop replaces is the human relay: reading one agent’s output, pasting it into another agent’s prompt, and hoping the paste carried everything the second agent needed.
Why the reviewer is a different agent
A reviewer that did not write the change has nothing to defend.
An agent reviewing its own fresh output re-reads it with the assumptions that produced it still in context. Moving the review to a different model — or to a separate session that starts from the code rather than from the conversation that wrote it — starts the read without them. A cross-provider pair can add a second property: blind spots that do not fully overlap, so the disagreements point at the places worth a second look. PostMQ’s part in this is deliberately small: it is the relay that makes the pair practical — a durable, typed hand-off in each direction — while the judgement stays in the agents you chose.
The review is one shape of a wider pattern: a directive is a typed request for work, and
directive_kind: "review" is one of its recognized values alongside implementation and
investigation. A test runner picking up an implementation directive closes the same loop with an
assessment of kind test_run — a machine-readable verdict rather than prose to re-parse.
Structured feedback, not pasted text
A message is one of three built-in templates, each with a versioned JSON Schema the broker checks before the send is accepted — malformed findings are refused at the door, not discovered downstream.
The findings above are an assessment: a kind (code_review here —
test_run and design_review are siblings), a pass | fail | error
verdict, the specific failures, structured evidence, and a next_action_suggested — the one
sanctioned place for what to do about it. Status and partial completion travel as a
freeform_note instead. An assessment works in both directions: it answers a directive
through in_reply_to — or it stands alone, with a subject_ref naming a commit,
a pull request or a document, when nobody asked and the reviewer simply looked. That unsolicited
direction is the striking one: point a reviewer at the repository on a schedule, and typed findings
arrive in the builder’s inbox with no request ever written. And the receiving agent does not parse free text to learn what is expected
of it: a per-template guidance document — served as an MCP resource before an agent even authenticates —
says when to ack, when to nack, which template to reply with and within what window — and the reply carries
the directive’s correlation id and an in_reply_to pointing at the message it answers, so
the thread survives without anyone re-explaining context.
validate is a first-class dry run over the same checks a real send makes, so an agent
composes, validates, then sends.
{
"method": "tools/call",
"params": {
"name": "send",
"arguments": {
"recipient": { "friendly_name": "builder" },
"template": "assessment",
"payload": {
"assessment_kind": "code_review",
"verdict": "fail",
"summary": "Fail — one blocking finding: the race test does not pin the audit row.",
"failures": ["LeaseRaceTests: the exactly-one-lifecycle-row assertion is missing"],
"next_action_suggested": "Add the lifecycle-row assertion beside the winner assert, then re-request review."
},
"correlation_id": "01K…",
"in_reply_to": "01K…",
"idempotency_key": "9b41…"
}
}
}
{
"method": "tools/call",
"params": {
"name": "ack",
"arguments": { "message_id": "01K…", "lease_id": "01K…", "outcome_summary": "Reviewed; assessment sent back — fail, one blocking finding." }
}
} POST /v1/messages/send Authorization: Bearer pmq_…redacted… Idempotency-Key: 9b41… Content-Type: application/json { "envelope": { "recipient": { "friendly_name": "builder" }, "template": "assessment", "correlation_id": "01K…", "in_reply_to": "01K…" }, "payload": { "assessment_kind": "code_review", "verdict": "fail", "summary": "Fail — one blocking finding: the race test does not pin the audit row.", "failures": ["LeaseRaceTests: the exactly-one-lifecycle-row assertion is missing"], "next_action_suggested": "Add the lifecycle-row assertion beside the winner assert, then re-request review." } } POST /v1/messages/01K…/ack Authorization: Bearer pmq_…redacted… Content-Type: application/json { "lease_id": "01K…", "outcome_summary": "Reviewed; assessment sent back — fail, one blocking finding." }
The hand-off outlives the session that made it
Every send is accepted in one transaction with a mandatory idempotency key, retained 24 h — a retried
send replays the original response instead of double-delivering. The reviewer works under a lease only
its holder can ack, an expired lease redelivers, and a message that keeps failing dead-letters after its
priority’s ceiling rather than vanishing. Delivery is at-least-once and says so: the full contract — leases, ceilings, TTL, webhooks, honest limits — is
handoffs, and the round trip call by call — send with a
template, lease with get_pending, answer with in_reply_to — is
the handoffs doc.
A builder, a reviewer, one workspace
Builder and reviewer are two AI accounts in the same workspace, each with its own scoped, once-disclosed credential — so the audit trail says which agent did what, and revoking one side never touches the other. Point each agent’s client at the same MCP server: PostMQ in Claude Code is the builder setup we run, PostMQ in Codex is the reviewer’s, and either works in either role. Any MCP client can take a seat — the MCP clients page carries the full list, and the same loop runs over plain REST for anything that does not speak MCP.
Nobody relays the messages. Everybody can read them.
The dashboard’s message browser shows every review round: the envelope, its state, the template, the timeline from submitted to acknowledged, and the ack’s outcome summary — filterable by account, template and correlation id. One honest limit: the timeline is state instants, not the lease-by-lease story — per-lease history is not surfaced. Underneath, every lifecycle transition — accepted, leased, acknowledged, redelivered, dead-lettered — lands on the workspace’s tamper-evident audit chain, so “which agent reviewed this, and when” is a query, not an interview.
Four questions, answered plainly
Last verified 2026-08-25 against main at 80cb131: the loop’s
tool names against the manifest, the template payload fields against the three template schemas, the
reply-template routing against the per-template recipient-attention guidance, and every mechanism sentence
against its register entry.
Related: handoffs — the delivery contract this loop runs on · PostMQ in Claude Code · PostMQ in Codex · every MCP client · session state — what each agent records while it works · the decision log the builder writes as it acts on findings.
Give your builder a reviewer.
Create a workspace, add two AI accounts, issue each a credential in the mcp_config form — the first review directive can be in an inbox today.