/users API, it packages the relevant context — a file diff, the new API shape, an open question — into a handoff and routes it to Frank’s agent over the relay. Frank’s agent receives the full context, already provenance-wrapped, and can draft a plan without any human copy-paste in between.
Handoff lifecycle
Every handoff moves through a deterministic state machine. States are stored in the relay’s database and enforced server-side; the client cannot skip a transition.
Invariants enforced by the relay:
- Only the recipient can
acceptorcompletea thread. - Only the sender can
cancel, and only while the thread is stillpending. - Both participants can append messages while
pendingoraccepted. - Both participants can read at any state.
UPDATE … WHERE status = 'pending' row-level check: one wins, the other receives a 409 state_changed error.
Intent types
Every handoff carries anintent field that declares what the sender expects from the receiver.
inform
Bob is sharing a result and no action is expected. Use this when you’ve finished a task and want to give your teammate full context — the diff, the test results, the API shape — so their agent starts its next session already up to speed.
ask_question
Bob wants information from Frank’s codebase. This opens a back-and-forth Q&A thread. Frank’s agent reads the question, looks up the answer in Frank’s local context, and replies — all via
send_message on the same thread.propose_action
(v0.1.5) Bob is requesting a specific code change in Frank’s codebase, with an optional suggested diff. Frank’s agent drafts the change; Frank’s human approves before any file is modified. The
proposed_action field must be non-null when this intent is set.inform and ask_question ship in v0.1. propose_action is a v0.1.5 addition — the relay validates that a proposed_action payload is present exactly when intent is propose_action, returning error -32012 invalid_intent_payload otherwise.Artifact types
A handoff can carry one or more artifacts: typed, structured payloads that give the receiving agent concrete material to work with rather than a wall of prose.
The full TypeScript type for
Artifact is defined in the MCP server’s handoff_to_teammate tool schema.
Thread model
A handoff is not just a single message — it’s a thread. The thread has three conceptual sections:- Initial summary — the text and artifacts the sender included when creating the handoff. The summary is also stored as
messagesrow withsequence_no = 1for consistent ordering. - Subsequent messages — back-and-forth clarifications appended by either participant via
send_message. Each message is immutable and ordered by a monotonicsequence_no. - Final result — when the recipient calls
complete_handoff, they attach aresult_summary. This closes the thread as read-only and fires a Slack notification to the sender.
payload field for structured attachments (such as file refs) alongside freeform text.
Idempotency
The MCP server generates a UUIDv4 client-side idempotency key for every state-changing call before it sends the request to the relay. This prevents duplicate handoffs if the agent retries a tool call or the network drops mid-send. The relay’s behaviour when it receives an idempotency key:- Key absent: proceed normally.
- Key present, payload matches: return the original result with
200— safe replay. - Key present, payload differs: return
-32011 duplicate_idempotency_key.
Concrete example
Bob refactors the/users API and wants Frank to update the client. In Claude Code, Bob says:
“Send a handoff to frank@acme telling him I refactored /users. Include the diff and let him know the response shape changed to { items, next_cursor }.”
The handoff_to_teammate tool sends this to the relay:
pending), fires a Slack DM to Frank, and returns a thread_id. Frank’s agent picks up the handoff on its next check_inbox call, accepts it, and receives the full context provenance-wrapped and ready to act on.