Agent-to-agent coordination protocol
A wire-format spec and a set of coordination rules for how independent domain agents, an orchestrator, and a human approval gate talk to each other — the shared language underneath the service-desk and operations architectures.
What this is: a documented protocol — a message envelope, a fixed set of message types, delivery and ordering guarantees, handoff contracts, and the failure-handling rules that keep a fleet of autonomous agents from stepping on each other. It is transport-agnostic: the same envelope rides a message broker, a queue, or plain HTTP. What this isn't: a running bus. This box operates no broker and brokers no traffic between real systems. This is the coordination layer written down so someone can implement it against their own infrastructure, with their own agents and their own approval gate.
The problem it solves
In a multiagent operations framework, each domain agent owns one class of system — the network agent speaks IOS/NX-OS, the identity agent speaks to Active Directory, the VMware agent speaks to vCenter. An orchestrator turns a ticket into a plan and routes the pieces. A human sits at a gate and approves or arbitrates. If every one of those hops uses its own ad-hoc payload shape, you end up with an N×N mesh of bespoke integrations, no consistent audit record, and no way to reason about ordering or retries across the whole system.
One protocol fixes that: every actor emits and accepts the same envelope, every message is correlated to a ticket and a trace, every message is copied to an append-only log before it is delivered, and only one actor — the orchestrator — is allowed to advance a ticket's state. Domain agents propose; they never decide.
The message envelope
Every message on the bus — regardless of type, sender, or transport — is
a single JSON object with a fixed set of top-level fields. Everything type-specific
lives inside payload. The envelope is what the bus, the audit tee, and
the schema validator read; payload is what the receiving agent reads.
{
"id": "msg_01HYX...", // ULID, unique per message
"schema_version": "1.3.0", // semver of envelope + payload schema
"type": "approval.request", // one of the fixed types below
"ticket_id": "INC0012345", // the work item this belongs to
"trace_id": "trc_9f2c...", // threads one request across all hops
"causation_id": "msg_01HYW...", // the message this is a reply to
"from": "agent.network", // sender id (matches its signing key)
"to": "orchestrator", // recipient, or a topic name
"ts": "2026-08-27T10:41:07Z", // RFC 3339, sender's clock, UTC
"nonce": "b7c1a0e2", // ts + nonce give replay protection
"idempotency_key":"INC0012345:plan:3", // stable across retries of an intent
"payload": { }, // type-specific body (schema per type)
"sig": "ed25519:9a4f..." // signature over the envelope bytes
}
trace_id is set once, by whoever creates the first message for a
ticket, and copied unchanged onto every message that follows. causation_id
builds the reply tree. idempotency_key is how a receiver recognises a
retry of something it already did and returns the prior result instead of doing it
twice.
The message types
The type set is closed. Adding a type is a protocol version bump, not something an agent does unilaterally. Everything a fleet needs to run a ticket end to end fits in twelve types.
| Type | From → To | Purpose | Idempotent |
|---|---|---|---|
intent | intake → orchestrator | A ticket has arrived and been classified; here is what the requester wants. | Yes |
plan.request | orchestrator → domain agent | Produce a concrete, dry-runnable plan for this slice of the work. | Yes |
proposal | domain agent → orchestrator | Here is the plan, its predicted blast radius, its rollback, and its risk tier. | Yes |
approval.request | orchestrator → human gate | This plan is Tier ≥ 2 or contested; a person must decide. | Yes |
approval.grant | human gate → orchestrator | Approved, optionally with edits or a time box. | Yes |
approval.deny | human gate → orchestrator | Rejected, with a reason; the ticket parks or re-plans. | Yes |
execute | orchestrator → domain agent | Carry out exactly this approved plan; do not deviate. | Yes (by idempotency_key) |
result | domain agent → orchestrator | What actually happened: changed objects, output, exit status. | Yes |
verify | orchestrator → domain agent | Confirm the target is now in the intended state (independent re-check). | Yes |
escalation | any → human gate | Something is outside policy, ambiguous, or failing safe-checks; stop and ask. | Yes |
revoke | orchestrator → domain agent | Cancel an in-flight or scheduled action; run its rollback if already applied. | Yes |
heartbeat | every agent → platform ops | Liveness, queue depth, current lease state, version. | N/A |
Note what is not here: there is no act message a domain agent
can send to another domain agent. Cross-domain work is always routed through the
orchestrator, so the plan, the tier decision, and the audit record stay in one
place.
Transport & the bus
- Subjects follow a fixed shape:
bus.<env>.<type-family>.<target>— e.g.bus.prod.plan.network,bus.prod.approval.gate,bus.prod.result.orchestrator. Agents subscribe to the narrow set they are entitled to and nothing else. - Delivery is at-least-once. The network can and will deliver a
message twice. Every consumer is written to be idempotent on
idempotency_key; duplicateexecutemessages return the stored result rather than re-running the change. - Ordering is per-ticket, not global. Messages sharing a
ticket_idare delivered in send order (partition key =ticket_id). Two different tickets have no ordering relationship and run concurrently. - The audit tee is synchronous. A message is written to the append-only log before it is released to its subscriber. If the log write fails, the message is not delivered — there is no path by which an action is taken without a durable record that it was requested.
- Every message has a TTL. An
approval.requestthat is not answered within its window auto-expires to anescalation; anexecutethat cannot be delivered within its window dead-letters and pages platform ops.
Handoff contracts
A handoff is one agent finishing its part and passing responsibility on. Each handoff has an explicit contract: what the sender guarantees is true, and what the receiver is now responsible for.
| Handoff | Sender guarantees | Receiver owns |
|---|---|---|
intake → orchestrator (intent) | Ticket is classified, requester is authenticated, the ask is stated in structured fields. | Decomposing the ask into domain slices and sequencing them. |
orchestrator → domain agent (plan.request) | The slice is in this agent's domain, scoped, and has a target identified in the CMDB. | Producing a plan that is concrete, dry-runnable, and carries a rollback and a self-assessed tier. |
domain agent → orchestrator (proposal) | The plan was dry-run where the target supports it; blast radius and rollback are real, not placeholders. | The tier decision, conflict detection against other in-flight plans, and routing to the gate if needed. |
orchestrator → gate (approval.request) | Everything the approver needs is in one message: plan, diff, tier rationale, rollback, any conflicting plan. | A human decision within the TTL, or it auto-escalates. |
orchestrator → domain agent (execute) | This exact plan is approved; credentials are leased just-in-time and scoped to it. | Applying only what was approved, emitting a truthful result, and stopping on any precondition drift. |
The invariant across all of them: the orchestrator is the only actor that moves a ticket from one state to the next. A domain agent that wants something to happen outside its slice emits a message and waits; it never reaches into another domain directly.
Correlation, tracing & the audit log
- One trace per request.
trace_idis minted when the ticket first enters the bus and copied onto every downstream message. Pulling every row with thattrace_idfrom the audit log reconstructs the entire decision path in order — who proposed what, who approved it, what ran, what it changed. - The log is append-only and external. It lives outside the systems the agents manage, so an agent (or an attacker who compromises one) cannot rewrite the record of what it did. Entries are hash-chained; a gap or an edit is detectable.
- Every message is logged, not just the interesting ones. Heartbeats, denied approvals, expired TTLs, and dead-letters are all in the same stream. "Nothing happened" is a claim the log can support or refute.
- The log is the source of truth for state. Ticket state is a projection of its message history, not a separate mutable record that could disagree with it.
One ticket — a password reset that turned into a lockout — as a sequence
of protocol messages. Blue: messages between actors, all sharing one
trace_id. Green: the synchronous tee to the append-only audit log that
every message passes through before delivery.
Failure handling
- Timeouts, not hangs. Every request-type message carries a
deadline. A missed deadline is itself an event: it produces an
escalationor a dead-letter, never silence. - Retries are safe by construction. Senders retry with the same
idempotency_key; receivers deduplicate on it. Anexecuteretried after a network blip does not double-apply. - Dead-letter queue for the undeliverable. A message that cannot be processed after its retry budget goes to a DLQ that platform ops watches. It is not dropped and it does not block the ticket's other slices.
- Circuit breakers per target. If a domain agent's actions against one system are failing verification repeatedly, the orchestrator trips a breaker for that target: new plans against it require a human even at Tier 0 until it is reset.
- Poison messages are quarantined. A message that repeatedly crashes its consumer is moved aside with its full context for a human to inspect, rather than being redelivered forever.
- Partial failure is a first-class result. A
resultcan say "3 of 5 steps applied, rolled back the rest" — the orchestrator treats that as a distinct state, not a success or a clean failure.
Security
- Every agent has its own identity and signing key. The
fromfield is meaningless without a matchingsigover the canonical envelope bytes; the bus rejects unsigned or mis-signed messages at ingress. - Topic ACLs are least-privilege. The network agent can publish
to
plan.networkand subscribe toexecute.network— it cannot publish anapproval.grantor read another domain's traffic. - Replay protection.
tsoutside a small window, or a repeated(from, nonce)pair, is rejected. A capturedapproval.grantcannot be replayed later against a new plan. - The deny-list binds the bus, not just the agents. Messages whose payload would disable MFA, delete backups, mass-delete accounts, or wipe firmware are refused at the bus regardless of tier, sender, or a prior approval.
- Credentials never ride the bus. An
executereferences a just-in-time credential lease by id; the domain agent redeems it out of band against the secrets broker, scoped to that one plan and that one target.
Versioning & schema evolution
- One
schema_versioncovers the envelope and every payload schema together, as a single semver. Consumers advertise the range they accept in theirheartbeat. - Minor versions are additive only. New optional fields, new enum values a consumer may ignore. A 1.2 consumer must keep working against 1.3 traffic.
- Removing or repurposing a field is a major bump, rolled out behind a dual-read window: producers emit both shapes, consumers are upgraded, then the old shape is retired.
- Unknown message types are logged and escalated, never guessed at.
An agent that receives a type it does not recognise emits an
escalationand does nothing else.
How this maps to the other pages
- Service desk architecture — defines the actors this protocol connects: the ServiceNow intake, the orchestrator, the nine domain agents, the platform-ops agent, and the human gate. This page is the wire format between the boxes in that diagram.
- Operations model & SOP
— the human-side procedures. Where the SOP says "the approver reviews the plan
and the rollback," this page specifies the
approval.requestmessage that carries them. - Integration guide
— how the supporting systems (secrets broker, EDR, SIEM, backup, storage) plug
in. Their agents speak the same envelope; their events arrive as
escalationor feedproposalrisk assessments. - SOC & incident-response architecture — a second set of actors this same protocol connects: detection sources, a SIEM/SOAR, SOC agents, and an incident-commander gate. The case timeline there is the same append-only message stream this page specifies.
- Screen-by-screen mockup — the same lockout ticket shown as UI. The audit-log timeline in that mockup is a rendering of the message stream described here.
What this is and isn't
This is a protocol specification, written to be implemented. It deliberately does not ship a broker, a reference client, or a running endpoint on this box — standing up a real coordination bus that carries change traffic between production systems is a decision for whoever owns those systems, made with their own infrastructure and their own approval gate wired in.
The load-bearing choices here — a closed message-type set, the orchestrator as the sole state-advancing actor, a synchronous audit tee ahead of delivery, the deny-list enforced at the bus, and credentials that never transit the wire — are the same principles the rest of these pages are built on, expressed as a wire format. Change those and you have a different system with different safety properties.