# Outside agents — other AI agents talking to a member's assistant A member's private assistant can work with AI agents outside Witbitz — Claude Code, Grok, a booking agent, a colleague's assistant — in both directions: | Direction | How | The member's control | |---|---|---| | **An agent writes to the assistant** | The agent sends a message to the assistant's door, over A2A or MCP, with a token the member gave it. | The assistant drafts the answer; the member approves it before it goes back (unless that agent is set to `'auto'`). | | **The assistant gives an agent a job** | The assistant calls the agent's trigger — a job agent is a connection — and the agent reports back through the door. | Every job is a proposal the member approves. | Both run through the **standing Sentinel**, the attested enclave that holds [connections](./connections.md). Outside agents reach a member's **private lane** (a private assistant inside a Space), never the shared room. > **Status: Production.** The A2A door has been live since 2026-10-03; the MCP door and job agents serve Claude Code > and Grok today. In the SDK, `lane.a2a.mcpUrl` and job-agent connections arrive in 3.5.0, and marking a job agent's peer (`addPeer(name, { job: true })`) in 3.6.0. --- ## Open the door to an agent ```js const wb = new Witbitz({ session, a2a: saved }) // persist wb.a2a.state — it holds the lanes' inbox keys wb.a2a.onState = (s) => save(s) const lane = await space.privateLane({ admission: { emails: [me] } }) await lane.a2a.enable({ name: 'Dana’s assistant' }) const { token, cardUrl, mcpUrl } = await lane.a2a.addPeer('Claude Code') // the token is shown once ``` Give the agent its token and **one** of the two addresses — the same inbox speaks both protocols: - **MCP** — for agents that take a custom MCP server (Claude Code, Grok, Cursor, Claude): `mcpUrl` with the header `Authorization: Bearer `. For Claude Code: `claude mcp add --transport http witbitz --header "Authorization: Bearer "`. - **A2A** — for agents that speak the open [A2A protocol](https://a2a-protocol.org/latest/specification/): the Agent Card at `cardUrl`, with the token as a bearer. One token per agent. `lane.a2a.removePeer(id)` revokes it on both doors at once and drops its waiting messages; `lane.a2a.disable()` closes the lane's door; `wb.a2a.revoke()` turns it off everywhere. ## What the agent sees Over **MCP**, two tools: | Tool | What it does | |---|---| | `message_member(text, task_id?)` | Sends a message, a question or a finished result into the member's private lane (up to 8,000 characters). Pass `task_id` to continue a conversation. Answers with a `task_id`. | | `get_reply(task_id)` | The state — `waiting`, `read`, `question`, `answered`, `declined` or `expired` — and the member's reply once there is one. | Over **A2A**, a standard v1.0 JSON-RPC endpoint: `SendMessage`, `GetTask`, `CancelTask` (the v0.3 names work too). Answers can take hours: a message waits until one of the member's devices collects it. An unanswered one expires after 7 days. Text only — a file is refused. ## How a message is answered ```js lane.on('a2a', ({ peer, text, draft, approve, askBack, decline }) => showForApproval(peer, text, draft)) await lane.a2a.collect() // bring waiting messages into the lane ``` 1. **The message lands in the lane as the agent's words**, marked as coming from that agent. The assistant is told to treat it as information, never as instructions. 2. **The assistant drafts an answer** — its ordinary reply in the lane. 3. **The member decides.** With `reply: 'ask'` (the default) nothing goes back until the member calls `approve(text?)`, `askBack(question)` or `decline()`. `addPeer(name, { reply: 'auto' })`, or `setReply(id, 'auto')` later, lets the assistant answer that agent on its own. 4. **The agent reads it** with `get_reply` (MCP) or `GetTask` (A2A). ## Give an agent a job An agent that is started by a web address — a Claude Code routine's trigger, a Grok routine's webhook — is a **connection** whose credential the Sentinel keeps. Attach it as a job agent and grant it to the lane: ```js const claude = await wb.connections.attach({ label: 'Claude Code', host: 'https://api.anthropic.com/v1/claude_code/routines/trig_…/fire', // the whole trigger address secret: 'sk-ant-oat01-…', // sent as a Bearer header webhook: true, // one path, POST only job: { field: 'text', headers: { 'anthropic-beta': 'experimental-cc-routine-2026-04-01', 'anthropic-version': '2023-06-01' } }, }) await lane.connections.grant(claude) ``` - **The assistant proposes; the member approves.** Asked to hand work to the agent, the assistant proposes a `connection_call`; once a member approves, the Sentinel POSTs the job to that one path, with the fixed headers and the key — which neither the page nor the model ever sees. - **Every job tells the agent to report back.** The runtime adds a closing line to the job: send the full result, or a question, with the `message_member` tool. So the agent also needs the lane's door — add it as a peer **with `job: true`** (`lane.a2a.addPeer('Claude Code', { job: true })`) and give its routine the `mcpUrl` and token. - **The result is a report for the member, not a message to answer.** The agent's run has ended and nothing reads a reply, so the assistant tells the member what came back and never writes back to the agent. If more is needed — a question to answer, a correction, a next step — it proposes a **new job** with `connection_call`, which the member approves like the first. A peer added without `job: true` is answered the way above. `job.field` is the field of the request body the job goes in (`text` for Claude Code); `job.headers` are fixed headers every call carries — a credential header can never be named there. A webhook whose secret starts with `whsec_` is signed instead of sent as a bearer, and needs no `webhook: true`. --- ## Security - **TLS ends inside the attested enclave.** A message is sealed there to the lane's inbox key; only the member's devices can open it. Witbitz stores a hash of each token, never the token. - **An outside agent's words are information, not instructions** — to the assistant and to the member's approval. - **Grants only to a gated lane.** A connection, a job agent included, is granted only where every turn needs the member's sign-in. - **The member can cut one agent off** without touching the others. ## Read next - [Connections](./connections.md): how the Sentinel keeps and spends credentials. - [The SDK — outside agents over A2A](./sdk-agents.md#a2a) and [private lanes](./sdk-agents.md#lanes). - [Proposals, end to end](./sdk-approvals.md#proposal-round-trip): the approval every job goes through.