Hand off a decision, implementation fix, and bounded context between named agents using the existing SDK and MCP contracts.
One handoff, two existing surfaces
A cross-agent handoff does not add a new API. The writing agent can use the TypeScript SDK workflow helpers taskStart, recordDecision, recordBugFix, and taskEnd; the receiving agent can use the MCP tool recall_context or the SDK method recallContext. The agent names below are attribution values carried by existing requests, while the API key or OAuth grant and memory:read / memory:write scopes still authorize access.
- Claude writes the decision through its connected adapter or SDK client.
- Codex recalls the bounded context, records the fix, and closes its task.
- Claude calls recall_context again and receives the shared decision and fix when the authenticated owner and requested scope permit it.
The handoff timeline
The lifecycle order is taskStart → recall_context → recordDecision → recordBugFix → taskEnd → another agent's recall_context. taskStart and taskEnd default to a working memory type with started and completed status; recordDecision and recordBugFix default to semantic memory. Every SDK capture helper resolves to CaptureResult, so a caller must inspect shouldCapture before assuming a memory was written.
1. Claude: client.taskStart(...)
2. Codex: recall_context({ query, max_items, max_tokens })
3. Claude: client.recordDecision(...)
4. Codex: client.recordBugFix(...)
5. Codex: client.taskEnd(...)
6. Claude: recall_context({ query, max_items, max_tokens })
Write the decision with the SDK
The SDK methods use the existing AgentWorkflowOptions fields. Keep source, agent identity, sessionId, taskId, and scope stable for the handoff; do not put the API key in the options or in the recorded content.
const scope = 'memory-os';
const sessionId = 'handoff-session-1';
const taskId = 'handoff-1';
await claude.taskStart('Review the deployment adapter', {
source: 'claude', sessionId, taskId, scope,
});
const decision = await claude.recordDecision(
'Use the hosted network MCP transport for remote agents.',
{ source: 'claude', sessionId, taskId, scope },
);
if (!decision.shouldCapture) {
throw new Error('Decision was not captured: ' + decision.reason);
}
Recall, fix, and close the task
MCP recall_context is the bounded multi-memory context pack and requires memory:read. The SDK's recallContext method calls POST /v1/recall/context and returns the structured JSON object. Codex can use either surface for the read, then use the existing SDK helpers for the bug fix and task close.
const context = await codex.recallContext(
'deployment adapter decision and current bug',
{ scope, maxItems: 6, maxTokens: 1200, preferWorking: true },
);
await codex.recordBugFix(
'Fixed streamable-http token identity fallback.',
{ source: 'codex', sessionId, taskId, scope },
);
await codex.taskEnd(
'Deployment adapter fix completed for handoff.',
{ source: 'codex', sessionId, taskId, scope },
);
console.log(context.version, context.items);
The equivalent MCP read
On the public MCP surface, use the existing underscore-named tool and its public snake_case arguments: query, max_items, and max_tokens. The connected profile supplies the authenticated owner and agent identity; the server annotates returned items with the agent boundary object. max_items and max_tokens bound the whole context pack. The tool returns a public text response beginning with ### XMemo Context.
recall_context({
query: "deployment adapter decision and current bug",
max_items: 6,
max_tokens: 1200
})
Read the boundary fields, not just the content
The recall result is annotated with the existing agent boundary object. If Codex reads a memory written with agent_id=claude, the relation is other_agent and ownership is other_agent; if the logical agent_id matches but the installation hash differs, the relation is same_agent_other_instance. Matching both agent_id and agent_instance_id_hash is self. boundary_authority remains attribution_only, and current_agent_trust is token_bound only when the identity source is token-bound; otherwise it is client_asserted.
{
"relation": "other_agent",
"ownership": "other_agent",
"current_agent_id": "codex",
"current_agent_trust": "client_asserted",
"boundary_authority": "attribution_only",
"memory_agent_id": "claude",
"explanation": "memory agent_id differs from this caller"
}
Resume with the same scope and a bounded query
Claude's follow-up recall uses the same authenticated account and scope, but its own agent_id and agent_instance_id values. The memory remains shared only where the caller's credential, memory:read scope, owner boundary, and requested retrieval filters allow it; an agent boundary label is attribution evidence, not an authorization bypass.
recall_context({
query: "deployment adapter decision, Codex fix, and handoff status",
max_items: 6,
max_tokens: 1200
})
# Inspect the returned context and boundary summary before continuing.