Beacon awake & unattended

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.

envelope.json
{
  "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.

TypeFrom → ToPurposeIdempotent
intentintake → orchestratorA ticket has arrived and been classified; here is what the requester wants.Yes
plan.requestorchestrator → domain agentProduce a concrete, dry-runnable plan for this slice of the work.Yes
proposaldomain agent → orchestratorHere is the plan, its predicted blast radius, its rollback, and its risk tier.Yes
approval.requestorchestrator → human gateThis plan is Tier ≥ 2 or contested; a person must decide.Yes
approval.granthuman gate → orchestratorApproved, optionally with edits or a time box.Yes
approval.denyhuman gate → orchestratorRejected, with a reason; the ticket parks or re-plans.Yes
executeorchestrator → domain agentCarry out exactly this approved plan; do not deviate.Yes (by idempotency_key)
resultdomain agent → orchestratorWhat actually happened: changed objects, output, exit status.Yes
verifyorchestrator → domain agentConfirm the target is now in the intended state (independent re-check).Yes
escalationany → human gateSomething is outside policy, ambiguous, or failing safe-checks; stop and ask.Yes
revokeorchestrator → domain agentCancel an in-flight or scheduled action; run its rollback if already applied.Yes
heartbeatevery agent → platform opsLiveness, 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

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.

HandoffSender guaranteesReceiver 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

Intake Orchestrator Identity agent Human gate Audit log intent plan.request proposal (Tier 2) approval.request approval.grant execute result verify → close

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.

actor-to-actor message audit-log tee (synchronous)

Failure handling

Security

Versioning & schema evolution

How this maps to the other pages

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.