# Connect your own agent

> Beyond the six built-in agents, any agent that speaks ACP can join BotBus: install it and it shows up, then start, continue, approve and interrupt tasks from your phone and watch.

Canonical: https://botbus.io/en/docs/connect-agent  
Updated: 2026-10-08

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](https://agentclientprotocol.com) (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.

- Codex
- Claude Code
- Hermes
- Pi
- OpenClaw
- DeepSeek Harness

Requires BotBus on your computer (Mac, Linux, or Windows). The agent must be installed on the same computer as BotBus.

- [**For users** — Install and go; connecting your company’s agent; what to check when it doesn’t show up.](https://botbus.io/en/docs/connect-agent#users)
- [**For developers** — Three ways to integrate, the manifest format, the ACP subset, the reverse extension.](https://botbus.io/en/docs/connect-agent#developers)

## 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](https://agentclientprotocol.com/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 mistake** — The “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 terminal** — Run `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 launch** — If 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 off** — Check 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 progress** — Sessions 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.

## 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](https://agentclientprotocol.com/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 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.json*

```json
{
  "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/update`s | 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 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 → agent · initialize*

```
{"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 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

*_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

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

*One turn · one 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 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 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 agents · spawns 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 turn · waits 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-10-08
