Get startedGuidesCross-agent workflow

Guide

Cross-agent workflow

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.

Source: TypeScript SDK workflow helpers