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:
~/.botbus/agents/<id>.jsonIt 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 listto see the agents BotBus found, where each came from, whether it is enabled, and any manifests that didn’t take effect and why.botbuslives 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 registry | Ship a manifest | Reverse extension | |
|---|---|---|---|
| What you do | Submit to the ACP registry | Your installer writes ~/.botbus/agents/<id>.json | On top of either one, your process connects to ~/.botbus/run/acp.sock |
| Auto-discovery | Yes, found locally by executable name; takes effect with the next BotBus release | Yes, effective as soon as the file is in place, no restart | Manifests for long-running agents can omit command |
| Start, continue, approve, interrupt, read transcripts from the phone | Yes, BotBus spawns the process on demand | Yes | Per the capabilities declared in the handshake |
| Sessions started in your own UI | Listed if you support session/list: title, directory and time only | Same | Live: 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 undernode_modules/<package>/. - A registry agent’s first handshake is its validation; if it fails, the agent is hidden. An
initializetimeout 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.
{
"id": "my-agent",
"name": "My Agent",
"command": "/usr/local/bin/my-agent",
"args": ["--acp"],
"env": { "MY_AGENT_LOG": "warn" }
}| Field | Rules |
|---|---|
id | Required. [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 |
name | Required, non-empty, truncated beyond 40 characters. Shown on the phone and in the menu |
command | Optional. 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 |
args | Optional, array of strings |
env | Optional, 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 phone | ACP | Notes |
|---|---|---|
| New task | initialize → session/new → session/prompt | Spawned on demand. The task appears on the phone as soon as session/new returns |
| Continue | session/prompt; session/load first if the session isn’t in the process | Fails without loadSession, or while the current turn is still running |
| Approve | Reply to session/request_permission | See “Approvals” below |
| Interrupt | session/cancel | Pending approvals are answered cancelled at the same time |
| Message with images | image content blocks (base64) | Fails outright without promptCapabilities.image, rather than silently sending text only |
| Read transcript | Collected session/updates | Replayed with session/load when not in memory |
| View uncommitted changes | Doesn’t go through ACP | BotBus 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 sendsprotocolVersion: 1and your reply must be exactly 1; no reply within 10 seconds is a timeout. BotBus does not providefsorterminal: 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.
{"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 toallow_always; when it rejects,reject_oncefirst, thenreject_always. If none exists, the action fails. - Tool kind
executeis shown as a command,edit/delete/moveas a file change, and everything else as a general permission. - The
toolCallin a permission request often carries only atoolCallId, so send the matchingtool_callnotification 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.
{
"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 phone | When |
|---|---|
| Running | session/prompt hasn’t returned yet (reverse connection: after _botbus/turn started) |
| Needs approval | A session/request_permission is pending |
| Done | stopReason = end_turn |
| Interrupted | stopReason = cancelled; a turn still running when the reverse connection drops |
| Failed | refusal, 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
→ {"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
| Message | Meaning |
|---|---|
_botbus/session | {sessionId, cwd, title?}. Announce the session first; BotBus only accepts its messages afterwards. cwd is required |
session/update | Same as ACP |
_botbus/turn | {sessionId, state, stopReason?}. There is no session/prompt response to wait for, so report turn started / ended explicitly |
session/request_permission | A 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 |
{"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 capability | Message | When |
|---|---|---|
prompt | session/prompt | Continuing 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” |
cancel | session/cancel | Interrupt tapped on the phone. Without it the phone says “Can only be interrupted on the computer” |
newSession | session/new + session/prompt | New 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 viasession/promptis already recorded. -32001means 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.
$ 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$ botbus agent check my-agent --prompt "hi"
ACP 协议版本: 1
loadSession: 是
图片: 否
session/list: 否
登录方式: 0 种
会话: 7f3c…
agent: 你好!
结束原因: end_turnlistonly 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.checkspawns the agent, runsinitializeand prints its capabilities; with--promptit runs one turn in a temporary directory. It doesn’t inject thebotbusMCP server and answers every approval withcancelled. Agents without acommandcan’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/listcarry 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.