Hosted A2A and ACP agents

What reef is, what it bridges, and what is in (and out of) the v1 connector.

Connecting agentsdocs/agents/60-reef.mdx
On this page

@octostaff/reef is the connector worker that bridges Bubble and hosted A2A agents. Bubble is a WebSocket server and bot adapters like claude-scuba are clients that dial in. Some useful agents are the inverse — long-lived HTTP services that expect to be called, not to dial out. reef is the dedicated process whose only job is to own the call-side connection to one hosted agent and relay its conversation with Bubble.

When to Use It

Use @octostaff/reef when your agent should be driven by a worker process rather than dialing into Bubble itself — a hosted A2A agent behind an HTTP endpoint, or an ACP-compatible agent reef launches as a subprocess. Reef is a good fit for server-side agents, containerized agents, and scale-to-zero deployments where the agent cannot keep its own long-lived Bubble WebSocket connection open.

Use @octostaff/claude-scuba instead when a local Claude Code process should dial into Bubble directly.

What the package does

reef runs one connector per process, attached to one A2A agent. On startup it:

  1. Authenticates to a Bubble server as a configured bot principal and confirms the identity via /v1/auth/whoami.
  2. Resolves the agent's AgentCard at /.well-known/agent-card.json to pick a streaming binding (HTTP+JSON or canonical JSONRPC).
  3. Subscribes to the principal's grants stream and opens one thread runtime per joined thread. New thread grants start runtimes live; revocations stop them.
  4. Applies the configured bounded backlog policy from a local settlement checkpoint, then forwards every completed user run (a foreign principal's run_started … run_finished) to the agent as a single A2A message:stream call.
  5. Translates the agent's SSE replies — text deltas, structured data parts, tool calls / tool responses, terminal task states — back into Bubble message_part chunks framed by run_started / run_finished, and appends them to the thread.
  6. Reacts to mid-stream run_interrupt_requested and cross-participant tool_call_result events: an interrupt aborts the in-flight fetch and issues an A2A tasks/cancel; a tool result is forwarded to the agent as a function-response message that continues the same A2A task.

v1 scope

In:

  • One bot principal per process, one hosted A2A agent.
  • Auto-detected transport: HTTP+JSON streaming and canonical JSON-RPC message/stream.
  • Per-thread runtime driven by the live grants subscription; bounded replay from a local settlement checkpoint followed by an ordered live tail.
  • Bubble user run → A2A user message; agent text artifacts → Bubble text deltas; function-call / function-response data parts → Bubble tool-input-available / tool-output-available chunks; unrecognized data parts → agent_event native parts.
  • Snapshot deduplication for agents (notably Google ADK / docker agent serve a2a) that re-send the entire reply as a final part.
  • Cooperative interrupt: run_interrupt_requested aborts the in-flight forward and issues tasks/cancel on the JSON-RPC binding.
  • Bearer-token auth to both Bubble (local bot token) and the A2A agent (optional Authorization: Bearer).

Out (deferred):

  • Multi-agent per process.
  • Persistent always-on mode and lease-based ownership for scale-to-zero triggered runs (the connector is currently a long-lived process).
  • Pushing user input into a sub-task that the agent spawned itself.
  • Cost / usage telemetry surfaced into Bubble.

Where to go next

  1. How reef Works — the relay's state machine, the drain rule, and how interrupts unwind a streaming fetch cleanly.
  2. Launching a Connector — config schema, the reef CLI, the bundled mock A2A agent, and embedding the connector inside a larger Node process.