# Building a signed handoff protocol for AI coding agents

> Source: <https://dev.to/dbisina/building-a-signed-handoff-protocol-for-ai-coding-agents-37c6>
> Published: 2026-07-17 22:53:07+00:00

If you run more than one AI coding agent, you've hit this: Claude Code (or Codex, or whichever) gets deep into a task, hits its usage limit, and everything it knew evaporates. The plan, the half-finished edit, the "don't redo this migration" context. You paste a summary into the next tool by hand and hope you didn't forget anything.

I got tired of that enough to build [Relay](https://github.com/dbisina/relay): a Go daemon that sits in front of whichever agents you run and treats the handoff as a protocol instead of a manual migration.

Here's what actually happens when an agent hits a wall.

**1. Detect the breach.** Relay watches the active provider's quota in real time (proxy header when available, session file otherwise, request counting as a last resort). When usage crosses a threshold, it doesn't wait for a 429.

**2. Request a safe pause.** The adapter is asked for a safe point, usually right after a commit, never mid-edit. This is a real handshake, not a timeout guess:

`\`

`go`

type AdapterContract interface {

Capability() ProviderCapability

Run(ctx, opts RunOptions, ch chan<- AgentEvent) error

AwaitSafePauseWindow(ctx, breachReason string) (SafePoint, error)

ForceStop() error

}

\`\`

**3. Snapshot.** A git commit on a dedicated session worktree branch. Your main tree is never touched, agents work in `.relay/sessions/<id>/`

.

**4. Build and sign a continuation contract.** This is the actual unit of transfer: the original prompt, the plan, what's left to do, decisions made, constraints discovered, files touched with their SHA-256, and truncated snippets of in-flight code. Serialized as Markdown for the next agent to read and JSON for machine verification, HMAC-SHA256 signed with a project-local key:

`\`

`json`

{

"contractId": "c-abc123",

"taskGoal": "add a refund flow to the orders service",

"nextAction": "Wire POST /orders/:id/refund",

"doNotRedo": ["migration 0042 applied"],

"inFlightCode": [{"path": "orders/refund.go", "snippet": "func Refund(... // truncated"}],

"signature": "hex(HMAC-SHA256(canonical JSON, signing-key))"

}

\`\`

**5. Dispatch and verify.** The next agent (different model, different account, different provider, whatever's available) gets the contract injected as system context. It reads the signature before trusting anything in it. Tampered or forged contracts get rejected, not silently accepted.

**6. Resume.** A heartbeat confirms the new agent picked up the work, and the whole thing is one state transition in a small FSM (`RUNNING → PAUSING → SNAPSHOTTED → ENVELOPE_BUILT → DISPATCHED → RESUMING → RUNNING`

), durably written to disk before each step so a crash mid-handoff just resumes where it stopped on restart.

Once the handoff is a real protocol instead of a hope, a few other things become possible:

`relay detect`

It's not another agent. It doesn't write code itself. It's the layer underneath the agents you already use, and it explicitly does not try to be smarter than them, it just refuses to let their limits cost you the context you already paid for.

Go daemon, Rust desktop app (egui), a TUI, one binary. Apache-2.0. Still early, [detect/adopt](https://github.com/dbisina/relay/discussions), the quota wallet, and pipelines all shipped in the last couple weeks, so there's plenty rough around the edges and I'd genuinely like to know what breaks for you.

`\`

`bash`

curl -fsSL https://raw.githubusercontent.com/dbisina/relay/main/scripts/install.sh | bash

relay init && relay run "add a refund flow to the orders service"

\`\`
