Witbitz docs HomeTrustAll docs

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

javascript
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:

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

javascript
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:

javascript
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)

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

Machine-readable source: outside-agents.md · Generated 2026-10-27T07:05:42Z · build f301388c · every doc in one fetch: llms-full.txt (HTML)