@octostaff/reef
v0.4.3
Published
OctoStaff reef connector worker — bridges Bubble and hosted A2A agents.
Readme
@octostaff/reef
Connect OctoStaff Bubble threads to hosted A2A agents and ACP agents.
@octostaff/reef is a connector worker that authenticates to Bubble as a bot
principal, watches the Bubble threads that principal has joined, forwards user
runs to an agent, and writes the agent's response stream back into Bubble. It
speaks two protocols: A2A (a hosted agent behind an HTTP streaming endpoint)
and ACP (Agent Client Protocol — a
subprocess reef spawns and drives over stdio).
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.
Install
npm install @octostaff/reefThe package includes the reef executable. Install it globally if you want the
command on your shell path:
npm install -g @octostaff/reefFor local installs, run the executable from an npm script or with npx:
npx reefPrerequisites
- A running OctoStaff Bubble server.
- A Bubble bot principal and bearer token for that principal.
- A hosted A2A agent that serves an agent card and supports streaming. Reef
supports the standard
/.well-known/agent-card.jsonlocation and Azure AI Foundry's project-scoped A2A endpoint. - Network access from the Reef worker to both Bubble and the hosted agent.
Quick Start
Create reef.config.json:
{
"bubble": {
"url": "https://bubble.example.com",
"principalId": "hosted-agent-bot",
"token": { "env": "BUBBLE_BOT_TOKEN" }
},
"a2a": {
"agentCardUrl": "https://agent.example.com/.well-known/agent-card.json",
"authToken": { "env": "A2A_AGENT_TOKEN" }
}
}Run the connector:
BUBBLE_BOT_TOKEN=... A2A_AGENT_TOKEN=... reefFor Azure AI Foundry A2A, point Reef at the project endpoint and agent name:
{
"bubble": {
"url": "https://bubble.example.com",
"principalId": "foundry-agent-bot",
"token": { "env": "BUBBLE_BOT_TOKEN" }
},
"a2a": {
"provider": "azure-foundry",
"baseUrl": "https://acct.services.ai.azure.com/api/projects/project-name",
"agentName": "your-agent-name",
"auth": { "type": "azure-identity" }
}
}To use a token from the Azure CLI instead, set auth to a bearer env ref:
"auth": { "type": "bearer", "token": { "env": "AZURE_FOUNDRY_TOKEN" } }AZURE_FOUNDRY_TOKEN=$(az account get-access-token --resource https://ai.azure.com --query accessToken -o tsv)
BUBBLE_BOT_TOKEN=... reefBy default, Reef reads ./reef.config.json. Point it at another file with
--config (-c):
reef --config /etc/octostaff/reef.jsonPing
Use the executable to test only the configured A2A agent without starting the Bubble connector:
reef ping --config ./reef.azure.json --prompt "hi"The ping fetches the agent card, sends one streaming A2A user message, and prints any text returned by the agent. For Azure AI Foundry with an Azure CLI token:
export AZURE_FOUNDRY_TOKEN=$(az account get-access-token --resource https://ai.azure.com --query accessToken -o tsv)
reef ping --config ./reef.azure.json --prompt "hi"BUBBLE_BOT_TOKEN does not need to be set for the ping; Reef parses the
Bubble config but does not resolve the Bubble token or contact Bubble. A2A auth env
refs still must be set.
Configuration
bubble
url: Base URL of the Bubble server.principalId: Bubble bot principal ID Reef authenticates as.token: Bearer token for the principal. Use a string or{ "env": "NAME" }.consumer: Optional cursor policy for thread consumption.
Default consumer policy:
{
"coldStart": { "mode": "from-head" },
"resume": { "mode": "durable" }
}The durable resume default is intentional: Reef is usually deployed as infrastructure, so missed user prompts should be replayed after a restart.
a2a
Generic A2A agents:
agentCardUrl: URL of the hosted agent's card, or the hosted agent's base origin.agentCardPath: Optional card path to append whenagentCardUrlis a base endpoint. Defaults to/.well-known/agent-card.json.authToken: Optional bearer token sent to the hosted agent. Use a string or{ "env": "NAME" }.
Azure AI Foundry A2A agents:
provider: Set to"azure-foundry".baseUrl: Project endpoint, e.g.https://{account}.services.ai.azure.com/api/projects/{project}.agentName: Foundry agent name in that project.protocolVersion: Optional"1.0"or"0.3"; defaults to"1.0".auth: Optional. Defaults to{ "type": "azure-identity" }, which uses@azure/identityDefaultAzureCredentialwith thehttps://ai.azure.com/.defaultscope. Use{ "type": "bearer", "token": { "env": "AZURE_FOUNDRY_TOKEN" } }for a token supplied byaz account get-access-token.
Reef supports the A2A HTTP JSON streaming binding and the canonical JSON-RPC streaming binding exposed by compatible A2A servers.
acp
Set acp instead of a2a to bridge an Agent Client
Protocol agent. Reef acts as the ACP
client: it spawns the configured command as a subprocess and speaks JSON-RPC
over its stdio, opening one ACP session per Bubble thread and forwarding user
turns as session/prompt. The agent's session/update stream (message chunks,
thoughts, tool calls) is translated back into Bubble events.
{
"bubble": {
"url": "https://bubble.example.com",
"principalId": "acp-agent-bot",
"token": { "env": "BUBBLE_BOT_TOKEN" }
},
"acp": {
"command": "my-acp-agent",
"args": ["--stdio"],
"cwd": "/workspace"
}
}command: Executable to spawn (any ACP-compatible agent).args: Optional command-line arguments.env: Optional extra environment variables, merged over reef's process env.cwd: Optional absolute working directory passed as the sessioncwdonsession/new. Defaults to reef's process working directory.protocolVersion: Optional ACP protocol version to negotiate. Defaults to the SDK's current version.
Exactly one of a2a or acp must be set. In this release reef auto-approves the
agent's permission requests and advertises no filesystem/terminal client
capabilities; reef ping is A2A-only.
logger
Optional. Controls reef's output.
level: One oftracedebuginfowarnerror. Defaults toinfo(or theLOG_LEVELenv var). Sets the threshold for the stdout[reef]log.pretty: Force colour (true) or plain (false) output. Defaults to TTY-detection.runLog: Per-run audit logging to disk for debugging. Off by default.enabled: Settrueto turn it on. Requiresdir.dir: Directory the log files are written to (created on demand).
When runLog is enabled, each bot reply opens one NDJSON file named:
<UTC-timestamp>__<principalId>__thread-<threadId>__run-<runId>.jsonlEvery line is { ts, principalId, threadId, runId, kind, … }, so you can find a
problem by thread id or run id — list the files, or grep a run id across
the whole dir. Each file captures the A2A exchange for that run:
a2a_request— the message reef forwarded to the hosted agent.a2a_response— each stream response the agent sent back.event— lifecycle markers (run_started,task_id,interrupted,error,run_finished).
{
"bubble": { "url": "https://bubble.example.com", "token": { "env": "BUBBLE_BOT_TOKEN" } },
"a2a": { "agentCardUrl": "https://agent.example.com/.well-known/agent-card.json" },
"logger": { "level": "debug", "runLog": { "enabled": true, "dir": "./logs" } }
}It is off by default because the files accumulate one-per-run; enable it on the
worker you are debugging and point dir at scratch storage.
Library Usage
import { ReefConnector, loadReefConfig } from '@octostaff/reef';
const config = await loadReefConfig('./reef.config.json');
const logger = console;
const connector = new ReefConnector(config, logger);
await connector.start();
process.once('SIGTERM', () => {
void connector.stop();
});Operations
- Reef starts one relay per Bubble thread where the configured principal has a participant role.
- Adding the principal to a thread starts a relay for that thread.
- Revoking the principal's thread grant stops the relay.
- User runs are forwarded to the hosted A2A agent as streaming messages.
- Agent text, task status, artifacts, and recognized tool data are translated back into Bubble events.
run_interrupt_requestedevents cancel the active A2A task when a task ID is known.- Send
SIGINTorSIGTERMfor graceful shutdown.
Package Contents
The npm package ships compiled, minified JavaScript, the reef executable, and
TypeScript declarations. The runtime bundles the Bubble SDK code it uses. Public
declarations may reference @octostaff/sdk where public APIs expose shared
Bubble protocol types.
