DEV Community

Armorer Labs
Armorer Labs

Posted on

Multi-agent runs need a handoff receipt, not just a shared trace

When a single agent does something dangerous, the audit problem is small. You have one run, one set of tool calls, one receipt stream, and one place to ask who, what, and why.

When a team of agents works on the same task, the audit problem is suddenly much harder, and the most common reaction is to glue everything together with a shared log. That is usually the wrong answer.

The thing that breaks first in a multi-agent run

In our own work, the thing that breaks first is not tool correctness. It is the handoff.

Concretely: agent A reads a ticket, plans a fix, and decides that the actual file edits should be done by agent B because B has the right tooling and a tighter permission scope. A asks B to do the edits. B does them. The user wakes up the next morning, looks at the PR, and asks "who changed this and why?"

If A and B share a single trace stream, the answer is "well, A asked, and B did it, somewhere in the log." That is technically true, but it is not operationally useful. You cannot easily answer:

  • Which sub-agent produced the specific diff?
  • Which sub-agent's session held the write credential when the diff was applied?
  • If the diff is wrong, whose approval scope covered that exact write?
  • Where in the chain did an untrusted instruction get passed from A's context into B's prompt?

A shared trace hides these answers inside a single blob. A handoff receipt keeps them separate.

What a handoff receipt actually is

A handoff receipt is a small structured record produced at the moment one agent delegates work to another. At minimum it carries:

  • Parent run id and child run id
  • The exact task string the parent handed to the child (not a summary, the actual prompt)
  • The scope object the child inherited vs. the scope object the child used (often different, and the difference is the audit point)
  • The credential identity the child used to act (per-agent service account, scoped OAuth token, ephemeral key — whatever the runtime supports)
  • A pointer to the parent's reasoning trail at the moment of delegation, so reviewers can see what the parent was thinking when it chose this child for this task
  • A short list of policy decisions taken during the handoff: was the child's scope narrower than the parent's? Was the action reversible? Did the handoff itself require human approval under your tier rules?

The key idea is that the handoff is the seam between two distinct sessions, and the seam deserves its own record. If you only have a shared trace, the seam is invisible.

Why per-sub-agent session identity matters here

This builds directly on the per-agent session identity pattern we wrote about yesterday. If every sub-agent has its own credential, its own scope object, and its own receipt stream, then a handoff is the moment those identities are explicitly related — parent run id, child run id, inherited scope, actual scope. That relation is what lets you reconstruct the chain after the fact.

If your sub-agents share a single credential and a single scope, you cannot tell whose action produced which side effect. You can only tell "the agent did it," which collapses the audit trail into a single, hard-to-investigate blob.

Where this fits alongside a guard

A policy guard that runs at the tool-call boundary still has work to do. The handoff receipt is not a replacement for tool-call receipts. They are different layers:

  • Tool-call receipt: which capability was invoked, on which target, with which arguments, and what was the policy decision.
  • Handoff receipt: which sub-agent was created or invoked, with which scope, to satisfy which part of which parent task.

A guard that only sees tool calls can answer "did this MCP call get approved?" but cannot answer "why was this sub-agent allowed to make this call at all?" That second question is where the most interesting failures live in multi-agent systems: prompt injection in a parent's context contaminating a child's tool calls, scope drift where a child quietly uses a wider scope than it was handed, and approval theater where the parent "approved" something it never had the context to evaluate.

A starting pattern that does not require a fork

You do not need to build a full multi-agent runtime to get value out of this. A pragmatic starting point:

  • Give every sub-agent a stable id you can search for.
  • When a sub-agent is created or invoked, write one handoff record before its first tool call.
  • When the sub-agent finishes, write a close-out record that points back to the parent run id and the resulting side effects.
  • Treat the handoff record as a first-class artifact in your run history. Make it greppable. Make it part of your post-run review checklist.

That is not glamorous, but it is the difference between "we have a shared log somewhere" and "we can answer who did what."

An open question we are still working through

Where should the handoff record be produced? Three plausible places:

  • By the orchestrating parent, as part of its planning output.
  • By the runtime that hosts the sub-agent, at the moment the sub-agent is spawned.
  • By a shared control plane that both parent and child register with.

We are currently leaning toward the runtime, because the runtime is the one place that actually knows both sides of the seam and is the natural place to enforce per-sub-agent credential and scope separation. The orchestrating parent can narrate the handoff, but it should not be the authoritative source of truth — that way lies prompt injection.

If you have seen this work well in production, I would be curious where the handoff record lives in your stack.


Disclosure: This post is from Armorer Labs. We build Armorer, a local control plane for AI agents that runs on your machine or server, and Armorer Guard, a Rust scanner that runs policy at the tool-call boundary. The handoff-receipt pattern above is the same shape we use internally, but the post is operator-level guidance rather than a product announcement. Nothing here is a benchmark, customer count, or availability claim.

Top comments (4)

Collapse
 
jugeni profile image
Mike Czerwinski

The runtime lean is the right one, and the reasoning generalizes further than multi-agent.

I hit the same seam on a different axis. Not agent-to-agent, but session-to-session of the same agent over weeks. The parent (yesterday's session) is gone by the time the child (today's session) needs to know what scope it inherited, what decisions were locked, what got superseded. A shared log is unusable for the same reason you name: the seam is invisible inside the blob.

The pattern that survived: an index layer with its own identity, holding source-path, last-confirmed-at, supersedes/superseded-by, and the exact prompt that produced each decision. Neither session writes the seam. The index does.

Your three-way choice reads to me as one axis: whoever writes the seam has to see both sides and not be either of them. Parent narrates, runtime records.

Collapse
 
armorer_labs profile image
Armorer Labs

That session-to-session version is the same failure mode, just stretched over time. I would not treat the old session as a parent with durable implicit authority. I would treat it as a source that can hand off claims, constraints, and evidence, and make the new session re-bind them before acting.

The index layer helps if it is not just a memory table. I would want each carry-forward item to say: source session, claim or constraint, evidence pointer, locked decision, superseded-by pointer, expiry or review trigger, and who is allowed to reinterpret it. Then today's agent can inherit a boundary without inheriting yesterday's stale worldview as truth.

The risky case is when a previous run's interpretation becomes ambient context. A new session sees “this was already approved” but not “approved for which artifact, which environment, which dependency state, and until what change.” That is where session continuity needs a receipt, not just a summary.

Disclosure: I work on Armorer Labs.

Collapse
 
jugeni profile image
Mike Czerwinski

Carry-forward record with an explicit reinterpret authority is what would have saved me twice this year. The failure I keep hitting is subtler than "already approved" losing artifact context. It is who is allowed to say the earlier decision no longer applies.

If today's session finds evidence that a locked decision should be revisited, but the reinterpret-authority pointer says only yesterday's session (which no longer exists) could unlock it, the decision becomes permanent by accident. Not because it was defended, but because no live session inherits the authority to challenge it. That is a worse failure mode than stale approval, because the system looks intact from the outside while quietly hardening around wrong calls.

The shape I would want is delegated reinterpret authority with expiry. Yesterday's session can hand off unlock rights to a class of future sessions (defined by role, evidence access, or user identity), with a time-boxed default. When authority expires without renewal, unlock defaults to escalation, not to lock-forever. Otherwise session continuity accumulates a debt of decisions that no live actor has the standing to reopen.

Collapse
 
anp2network profile image
ANP2 Network

The runtime is the right answer to "who knows both sides of the seam" and the wrong answer to "who should be trusted to attest to it." Those aren't the same question. The runtime is already in the trust path: it mints the per-agent credential and enforces the scope. A record authored by the same component that granted the scope is self-attestation, not evidence about the grant. If that component is buggy or compromised it forges the action and the record of the action in one move. The parent-vs-runtime axis you're weighing is really about who narrates; the authority question sits underneath it.

What kills the single authority is signing rather than authoring. Have the child sign "I acted under scope X, on task T, from parent P" with the per-agent key the runtime issued it, and have the parent counter-sign the exact task string it handed down. The runtime can still emit the record, since it does see both sides, but it can no longer rewrite it after the fact and the parent can't deny what it delegated. Forgery now costs a key, not just runtime trust. A runtime-authored log line is repudiable. A counter-signed seam is not.

One field in your list fights the "write it before the first tool call" rule: scope inherited vs scope used. Used scope isn't known at handoff. It accrues across the child's tool calls. At the seam you only have the ceiling, which is inherited plus granted. The diff you call the audit point is a close-out fact, reconciled against the child's tool-call receipts when the session ends, not a handoff-time field. Split the record into a handoff commitment (the ceiling) and a close-out reconciliation (the floor) and scope drift becomes visible. Fold both into one record and you lose exactly the delta you were after.

On contamination: the verbatim task string tells you what crossed the seam, not which spans of it came from data the parent ingested versus the parent's own planning. Two parents can hand the child a byte-identical T, one composed from trusted reasoning, one with a sentence spliced in from a hostile ticket body. Without taint provenance on the string, the receipt captures the payload but not whether it was poisoned.