BOTBUS / DOCS

Connect your own agent

BotBus lets you watch and control the agents on your computer from your phone and watch. Besides the six built-in, deeply integrated agents, any agent that implements ACP (Agent Client Protocol) can join: starting tasks, continuing conversations, approvals, interrupts and reading transcripts all work from the phone, just like the built-in ones.

Requires BotBus for Mac 1.1 or later on your computer. The agent must be installed on the same computer as BotBus.

01For users

Install an agent, see it on your phone

Most of the time there is nothing to do. Here is how BotBus finds agents, and where to look when it doesn’t.

Registry agents: install and they appear

If your computer has an agent from the official ACP registry installed, BotBus recognizes it automatically, with no configuration. These common ones show their own icons on the phone and watch:

  • Gemini CLI
  • goose
  • OpenCode
  • Qwen Code
  • Kimi CLI
  • Cursor
  • GitHub Copilot
  • Cline
  • Mistral Vibe
  • Kilo
  • and every other agent in the registry
  • Discovered agents are enabled by default. To keep one off your phone, turn it off in BotBus settings on the computer under “Local Agents”; you can also toggle it from the watch’s device page.
  • BotBus ships a snapshot of the registry that updates with BotBus. An agent that just joined the registry appears with the next BotBus release.
  • Other registry agents, and agents with a hand-written manifest, show the default >_ icon.

Your own or your company’s agent

An agent that isn’t in the registry can still join, as long as it implements ACP. Ask its developers for a manifest file (many installers put it in place for you) and drop it into this folder:

Manifest folder
~/.botbus/agents/<id>.json

It takes effect as soon as the file is there, with no BotBus restart. Delete the file and the agent disappears from your phone.

Don’t see your agent?

  • The manifest has a mistakeThe “General” page of BotBus settings gains a “These ACP Manifests Didn’t Load” section listing each file and the reason (for example, the file name must be my-agent.json). Next to it are “Open Manifest Folder” and “Check Again”.
  • Check from the terminalRun botbus agent list to see the agents BotBus found, where each came from, whether it is enabled, and any manifests that didn’t take effect and why. botbus lives in /Applications/BotBus.app/Contents/Helpers/.
  • It failed on first launchIf a registry agent fails its very first connection (won’t start, doesn’t speak ACP, wrong version), BotBus hides it until it is reinstalled or updated, or BotBus restarts.
  • It says “Sign in on your computer”The agent needs you to sign in first. Sign in once on the computer the way that agent expects; you can’t sign in from the phone.
  • It’s turned offCheck its switch in BotBus settings under “Local Agents”. A disabled agent shows as “isn’t enabled” on the phone.
  • Sessions started on the computer don’t show live progressSessions you start in the agent’s own terminal or IDE only appear live if the agent implements BotBus’s reverse extension; otherwise you see at most their title and time in the list.

02For developers

Bring your agent to BotBus

BotBus on the computer drives local agents over ACP v1. You only need to implement ACP; pairing, credentials and the connection to the phone are handled by BotBus. A small “reverse extension” additionally lets sessions started in your own UI show up live on the phone.

Three ways to integrate

Join the ACP registryShip a manifestReverse extension
What you doSubmit to the ACP registryYour installer writes ~/.botbus/agents/<id>.jsonOn top of either one, your process connects to ~/.botbus/run/acp.sock
Auto-discoveryYes, found locally by executable name; takes effect with the next BotBus releaseYes, effective as soon as the file is in place, no restartManifests for long-running agents can omit command
Start, continue, approve, interrupt, read transcripts from the phoneYes, BotBus spawns the process on demandYesPer the capabilities declared in the handshake
Sessions started in your own UIListed if you support session/list: title, directory and time onlySameLive: status, messages, approvals
  • The six built-in agents take precedence; their registry adapters (claude-acp, codex-acp, pi-acp) are skipped. For the same id, a manifest wins over the registry.
  • A registry entry only counts if it is actually installed: BotBus looks in PATH and common global locations (Homebrew, the global bin of npm / pnpm / bun / volta, ~/.local/bin, and so on). For npm-distributed agents, the executable’s real path must also be under node_modules/<package>/.
  • A registry agent’s first handshake is its validation; if it fails, the agent is hidden. An initialize timeout or a sign-in requirement doesn’t count as a failure. Manifest agents that fail stay visible and report the error.

Manifest file

Put it at ~/.botbus/agents/<id>.json. BotBus watches the folder, so additions, edits and deletions take effect immediately. Installers should write the file into the existing folder rather than deleting the whole folder and swapping in a new one.

~/.botbus/agents/my-agent.jsonJSON
{
  "id": "my-agent",
  "name": "My Agent",
  "command": "/usr/local/bin/my-agent",
  "args": ["--acp"],
  "env": { "MY_AGENT_LOG": "warn" }
}
FieldRules
idRequired. [a-z0-9-], 1–32 characters, equal to the file name. Reserved ids are not allowed: codex claude hermes pi openclaw dsh acp claude-acp codex-acp pi-acp
nameRequired, non-empty, truncated beyond 40 characters. Shown on the phone and in the menu
commandOptional. An absolute path (~ allowed, must be executable), or a name found in PATH or a common install location. Omitted = reachable only through the reverse extension
argsOptional, array of strings
envOptional, string to string, merged over BotBus’s own environment. The executable’s directory is prepended to PATH (node scripts need this)

The child process runs in the user’s home directory; the session directory is passed as cwd in session/new. Manifests can’t set a custom icon yet.

The ACP BotBus uses

Actions on the phone

On the phoneACPNotes
New taskinitialize → session/new → session/promptSpawned on demand. The task appears on the phone as soon as session/new returns
Continuesession/prompt; session/load first if the session isn’t in the processFails without loadSession, or while the current turn is still running
ApproveReply to session/request_permissionSee “Approvals” below
Interruptsession/cancelPending approvals are answered cancelled at the same time
Message with imagesimage content blocks (base64)Fails outright without promptCapabilities.image, rather than silently sending text only
Read transcriptCollected session/updatesReplayed with session/load when not in memory
View uncommitted changesDoesn’t go through ACPBotBus reads git in cwd, read-only

Child processes are shut down after 10 minutes idle; a crashed process isn’t restarted automatically, only when the next command arrives.

Capabilities

  • initialize: BotBus sends protocolVersion: 1 and your reply must be exactly 1; no reply within 10 seconds is a timeout. BotBus does not provide fs or terminal: the agent runs its own tools, BotBus only watches and approves.
  • promptCapabilities.image: required to send images from the phone.
  • agentCapabilities.loadSession: needed to continue sessions not in the current process and to view past transcripts.
  • sessionCapabilities.list: lets the phone see existing sessions on the computer (title, directory and time only, updated within the last 7 days). Refreshed every 60 seconds while the process runs; otherwise the process is briefly spawned every 10 minutes.
BotBus → agentinitialize
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{
  "protocolVersion":1,
  "clientCapabilities":{"fs":{"readTextFile":false,"writeTextFile":false},"terminal":false},
  "clientInfo":{"name":"botbus","version":"1.0"}}}

Approvals

  • When the phone allows, BotBus picks allow_once, falling back to allow_always; when it rejects, reject_once first, then reject_always. If none exists, the action fails.
  • Tool kind execute is shown as a command, edit / delete / move as a file change, and everything else as a general permission.
  • The toolCall in a permission request often carries only a toolCallId, so send the matching tool_call notification first or the phone has no title to show.

Sharing results back to the phone

session/new and session/load started from the phone include a botbus stdio server in mcpServers. Start it as usual and your agent can share screenshots, files, links and local previews to the phone, with no extra work.

mcpServers[0]JSON
{
  "name": "botbus",
  "command": "/Applications/BotBus.app/Contents/Helpers/botbus",
  "args": ["mcp"],
  "env": [
    { "name": "BOTBUS_CLI", "value": "…" },
    { "name": "BOTBUS_TASK_TOKEN", "value": "…" },
    { "name": "BOTBUS_TOOLS_URL", "value": "http://127.0.0.1:…" }
  ]
}

Sign-in

If initialize returns authMethods and a call then fails with auth_required (-32000), the phone shows “Sign in to <name> on your computer”. BotBus never signs in from the phone.

Status

On the phoneWhen
Runningsession/prompt hasn’t returned yet (reverse connection: after _botbus/turn started)
Needs approvalA session/request_permission is pending
DonestopReason = end_turn
InterruptedstopReason = cancelled; a turn still running when the reverse connection drops
Failedrefusal, max_tokens, max_turn_requests, a JSON-RPC error, a process crash

The title comes from session_info_update first, then session/list, and finally the first 80 characters of the first prompt. Thinking isn’t shown.

Reverse extension v1

Lets sessions started in your own terminal or IDE show up live on the phone. The connection direction is reversed but the roles are not: your process is still the ACP agent and BotBus is still the client.

  • Socket: ~/.botbus/run/acp.sock. Check for it when a session starts: connect if it exists, do nothing if it doesn’t (BotBus isn’t running).
  • JSON-RPC 2.0, one message per line (UTF-8, terminated by \n, same as ACP stdio), up to 16 MiB per line.
  • Only processes of the same user can connect: the folder is 0700, the socket 0600, and BotBus also checks the peer uid. No task token needed.

Handshake

_botbus/hello→ sent / ← received
→ {"jsonrpc":"2.0","id":1,"method":"_botbus/hello",
   "params":{"id":"my-agent","version":1,"pid":4321,
             "capabilities":{"prompt":true,"cancel":true,"newSession":false}}}
← {"jsonrpc":"2.0","id":1,"result":{"accepted":true}}
← {"jsonrpc":"2.0","id":1,"result":{"accepted":false,"reason":"用户在 BotBus 里停用了这个 agent"}}

id must be an agent BotBus has discovered (manifest or registry) and that isn’t disabled; version currently only accepts 1; capabilities missing from capabilities count as false.

agent → BotBus

MessageMeaning
_botbus/session{sessionId, cwd, title?}. Announce the session first; BotBus only accepts its messages afterwards. cwd is required
session/updateSame as ACP
_botbus/turn{sessionId, state, stopReason?}. There is no session/prompt response to wait for, so report turn started / ended explicitly
session/request_permissionA request. Ask the user in your terminal as usual at the same time; whichever answer comes first wins
_botbus/permission_resolved{sessionId, toolCallId}. The terminal answered first; BotBus withdraws the approval from the phone
One turnone per line
{"jsonrpc":"2.0","method":"_botbus/session","params":{"sessionId":"s-42","cwd":"/Users/me/proj","title":"修登录页"}}
{"jsonrpc":"2.0","method":"_botbus/turn","params":{"sessionId":"s-42","state":"started"}}
{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"s-42","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"好的,我先看看"}}}}
{"jsonrpc":"2.0","id":7,"method":"session/request_permission","params":{"sessionId":"s-42","toolCall":{"toolCallId":"call-1"},"options":[{"optionId":"a1","name":"允许","kind":"allow_once"},{"optionId":"r1","name":"拒绝","kind":"reject_once"}]}}
{"jsonrpc":"2.0","method":"_botbus/permission_resolved","params":{"sessionId":"s-42","toolCallId":"call-1"}}
{"jsonrpc":"2.0","method":"_botbus/turn","params":{"sessionId":"s-42","state":"ended","stopReason":"end_turn"}}

BotBus → agent

Declared capabilityMessageWhen
promptsession/promptContinuing from the phone. Show the message in your UI as if the user typed it on the computer, and reply with stopReason as usual. Without it the phone says “Continue on your computer”
cancelsession/cancelInterrupt tapped on the phone. Without it the phone says “Can only be interrupted on the computer”
newSessionsession/new + session/promptNew task from the phone, for long-running agents. An agent with no command that doesn’t declare it isn’t offered in the new-task list

Rules

  • Start reporting only after accepted: true. Notifications sent before the handshake are buffered (up to 100) on a best-effort basis; requests before the handshake always get an error. The connection is dropped if the handshake doesn’t complete within 10 seconds.
  • Don’t echo prompts BotBus sends you. What the user types in your own UI can be forwarded as user_message_chunk; the prompt BotBus sent via session/prompt is already recorded.
  • -32001 means BotBus has no answer (the session doesn’t belong to this connection, a newer approval replaced it, the turn already ended, or the terminal answered first): keep waiting for the answer in your terminal and don’t treat it as a user cancel. A real cancel is {"outcome":{"outcome":"cancelled"}}.
  • Each connection can carry up to 50 sessions. Sessions already driven by another connection, or by a BotBus-spawned child process, aren’t taken over.
  • When the user disables the agent, deletes its manifest, or BotBus quits, BotBus simply disconnects. After that, stop reporting and reconnect when the next session starts; a turn still running is recorded as interrupted.

Self-check

botbus is at BotBus.app/Contents/Helpers/botbus. The two subcommands below don’t need BotBus to be running. Their output is currently in Chinese.

List discovered agentsspawns nothing
$ botbus agent list
gemini	Gemini CLI	注册表	已启用	/opt/homebrew/bin/gemini --acp
my-agent	My Agent	清单	已启用	/usr/local/bin/my-agent --acp
my-daemon	My Daemon	清单	已停用	(没有启动命令,只能经反向扩展接入)
✗ /Users/me/.botbus/agents/bad.json:文件名必须是 my-bad.json
Run a real turnwaits up to 5 minutes
$ botbus agent check my-agent --prompt "hi"
ACP 协议版本: 1
loadSession: 是
图片: 否
session/list: 否
登录方式: 0 种
会话: 7f3c…
agent: 你好!
结束原因: end_turn
  • list only reads manifests, the registry snapshot and the on/off settings; being listed doesn’t guarantee it works, since a registry agent may be hidden after its first handshake.
  • check spawns the agent, runs initialize and prints its capabilities; with --prompt it runs one turn in a temporary directory. It doesn’t inject the botbus MCP server and answers every approval with cancelled. Agents without a command can’t be checked. Exit code 0 means success, 1 failure, 2 a usage error.

Known limitations

  • Only the macOS desktop app is supported, and the agent must run on the computer where BotBus is installed.
  • ACP has no signal for “the agent is asking the user a question”, so questions appear as ordinary replies rather than “waiting for reply”.
  • Sessions seen through session/list carry only static information; for agents that don’t implement it, you only see sessions BotBus spawned or that were reported over the reverse extension.
  • When a reverse connection drops, the running turn is recorded as interrupted even if the agent is actually still working. Messages over the reverse connection can’t carry images yet.
  • A computer can connect up to 11 ACP agents at once; any beyond that are dropped after sorting by display name.
  • Only the last bit of stderr is kept in memory, to show why something failed; it isn’t written to a log.

Updated 2026-09-26