# Cross-agent workflow

Hand off a decision, implementation fix, and bounded context between named agents using the existing SDK and MCP contracts.

- Canonical: https://docs.xmemo.dev/docs/operations/cross-agent-workflow
- Locale: en-US
- Content-Locale: en-US
- Canonical-Content-Digest: 601017a1cc889872d5526e3d5cb8ee1ac73c89512eb78d284d88811d2349b611
- Edition-Digest: 1108d246b150144b9f1ffa4380c32e9978327aff99543bed9e785a1ae8d425df
- Source-Revision: sha256:9e1f7f01cc46df74b247f9cecea3b932f1b046cd1751624acdfd046a7b7fedd2

## 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.

```text
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.

```ts
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.

```ts
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.

```text
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.

```json
{
  "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.

```text
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.
```

