mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-11-02 05:45:20 +08:00
375 lines
18 KiB
Markdown
375 lines
18 KiB
Markdown
# ACP Runner
|
|
|
|
`acp-agent-runner` runs an Agent Client Protocol compatible agent process as a LangBot Runner.
|
|
|
|
It is a thin runtime adapter:
|
|
|
|
- LangBot remains the control plane.
|
|
- The plugin starts an ACP server over stdio, such as `npx -y @zed-industries/codex-acp`, `npx -y @agentclientprotocol/[email protected]`, `opencode acp`, or `npx -y @google/gemini-cli --acp`.
|
|
- In daemon mode, a user-side `langbot-runner-daemon` connects outward to the plugin and starts the ACP process on the user's machine.
|
|
- The plugin speaks ACP JSON-RPC: `initialize`, `session/new`, `session/load` or `session/resume`, and `session/prompt`.
|
|
- ACP `session/update` text chunks are streamed back to LangBot.
|
|
- LangBot multimodal input is mapped to ACP prompt content blocks when the selected ACP runtime advertises the matching prompt capability.
|
|
- LangBot tools, knowledge bases, and history are exposed through the SDK-owned run-scoped MCP bridge.
|
|
|
|
## Runner ID
|
|
|
|
`plugin:langbot-team/ACPAgentRunner/default`
|
|
|
|
## Configuration
|
|
|
|
Common local examples:
|
|
|
|
```text
|
|
provider = claude-code
|
|
location = local
|
|
workspace = /path/to/workspace
|
|
```
|
|
|
|
```text
|
|
provider = codex
|
|
location = local
|
|
workspace = /path/to/workspace
|
|
```
|
|
|
|
For custom ACP commands, set `provider=custom` and `acp-command`, for example:
|
|
|
|
```text
|
|
provider = custom
|
|
location = local
|
|
workspace = /path/to/workspace
|
|
acp-command = opencode acp
|
|
```
|
|
|
|
For a user workstation behind NAT, enable the plugin daemon hub and run the user-side daemon:
|
|
|
|
```text
|
|
# Plugin config
|
|
daemon-enabled = true
|
|
daemon-host = 0.0.0.0
|
|
daemon-port = 8766
|
|
daemon-token = <shared-token>
|
|
```
|
|
|
|
```bash
|
|
cd acp-agent-runner
|
|
python daemon.py \
|
|
--url ws://<langbot-public-host>:8766 \
|
|
--daemon-id alice-laptop \
|
|
--token <shared-token>
|
|
```
|
|
|
|
Then configure the runner:
|
|
|
|
```text
|
|
provider = codex
|
|
location = daemon
|
|
daemon-id = alice-laptop
|
|
workspace = /Users/alice/project
|
|
```
|
|
|
|
Custom mode is for ACP stdio commands that are not built in, or for users who
|
|
accept weaker/no ACP session recovery for a specific runtime:
|
|
|
|
```text
|
|
provider = custom
|
|
location = local
|
|
workspace = /path/to/workspace
|
|
acp-command = kimi acp
|
|
```
|
|
|
|
```text
|
|
provider = custom
|
|
location = local
|
|
workspace = /path/to/workspace
|
|
acp-command = agent acp
|
|
```
|
|
|
|
```text
|
|
provider = custom
|
|
location = local
|
|
workspace = /path/to/workspace
|
|
acp-command = goose acp
|
|
```
|
|
|
|
`acp-command` must start an ACP JSON-RPC stdio server, not an interactive-only
|
|
CLI. The runner still calls `initialize`, then `session/resume` or
|
|
`session/load` when a stored session id exists and the runtime advertises that
|
|
capability, otherwise `session/new`. Set `reuse-session=false` to force a fresh
|
|
ACP session each run:
|
|
|
|
```text
|
|
provider = custom
|
|
location = local
|
|
workspace = /path/to/workspace
|
|
acp-command = kimi acp
|
|
reuse-session = false
|
|
```
|
|
|
|
Useful options:
|
|
|
|
- `provider`: built-in ACP launch preset. Current presets are limited to ACP runtimes that advertise `loadSession` or `sessionCapabilities.resume` and have no known public cross-process resume blocker. Use `custom` for anything else.
|
|
- `location`: `local`, `remote-ssh`, or `daemon`.
|
|
- `workspace`: agent workspace directory. In `remote-ssh` and `daemon` modes this path is on the remote/user machine.
|
|
- `advanced-settings`: reveals bridge, timeout, session, and process tuning controls. It only changes form visibility.
|
|
- `ssh-target`: SSH target for `remote-ssh`, for example `[email protected]`.
|
|
- `ssh-port`: SSH port. Defaults to `22`.
|
|
- `ssh-identity-file`: optional private key path on the LangBot host.
|
|
- `daemon-id`: stable ID of a connected user-side runner daemon for `location=daemon`.
|
|
- `daemon-connect-timeout`: seconds to wait for the selected daemon to come online.
|
|
- `acp-command`: optional command override. Required only for `provider=custom`.
|
|
- `env-json`: JSON object merged into the process environment.
|
|
- `reuse-session`: persists and reuses `external.acp_session_id` when the ACP agent supports `session/resume` or `session/load`.
|
|
- `knowledge-bases`: selects the LangBot knowledge bases authorized for retrieval in this runner.
|
|
- `langbot-assets-enabled`: injects the LangBot run-scoped MCP bridge into ACP `mcpServers`.
|
|
- `langbot-assets-mode`: `auto`, `ephemeral`, or `gateway`. `auto` currently preserves the existing per-run bridge behavior; `gateway` registers the run in the SDK-owned long-lived HTTP MCP gateway.
|
|
- `langbot-assets-gateway-port`: optional fixed port for `langbot-assets-mode=gateway`. Use a fixed port when another platform such as Dify needs to register the gateway URL.
|
|
- `langbot-assets-gateway-public-url`: optional externally reachable MCP URL, for example `https://example.com/mcp`.
|
|
|
|
Headless ACP permission requests are answered with `allow_once` by default.
|
|
LangBot does not yet expose an interactive approval UI for these requests.
|
|
|
|
## Steering (follow-up input)
|
|
|
|
This runner declares `capabilities.steering: true`. When a run is still in
|
|
progress and the user sends another message, LangBot absorbs it into the active
|
|
run instead of starting a new one. The runner drains these follow-ups at each
|
|
turn boundary via `steering_pull` and runs them as additional ACP
|
|
`session/prompt` turns that resume the same session (`session/resume` or
|
|
`session/load`). The run emits a single terminal `run.completed` once no
|
|
follow-ups remain.
|
|
|
|
Notes:
|
|
|
|
- Follow-ups are injected between turns, not mid-token.
|
|
- Follow-up turns currently carry text only; attachments on follow-ups are not
|
|
yet forwarded (the first turn still maps full multimodal input as described
|
|
below).
|
|
- In `daemon` mode follow-ups are still drained (no message loss), but session
|
|
continuity across follow-up turns is best-effort.
|
|
- Steering only applies when the run has a conversation scope; otherwise the
|
|
runner transparently falls back to single-turn execution.
|
|
|
|
## Multimodal Input
|
|
|
|
The runner accepts LangBot structured input and attachments. It maps them to ACP
|
|
`session/prompt` content blocks according to the selected runtime's
|
|
`agentCapabilities.promptCapabilities`:
|
|
|
|
- inline image base64/data URLs are sent as ACP `image` blocks only when the
|
|
runtime advertises image prompt support;
|
|
- inline file base64/data URLs are sent as embedded ACP `resource` blocks only
|
|
when the runtime advertises embedded context support;
|
|
- URL-backed images or files are sent as ACP `resource_link` blocks;
|
|
- unsupported inline attachments are not silently dropped; the prompt receives a
|
|
short attachment note explaining which content was not sent.
|
|
|
|
Provider support still depends on the ACP executable being launched. A runtime
|
|
that does not advertise image prompt support can still handle normal text input
|
|
and URL resource links, but it will not receive inline image bytes.
|
|
|
|
Built-in provider commands are intentionally unpinned and assume the selected
|
|
runtime can run on the target machine. Built-in presets must support ACP session
|
|
recovery through `loadSession` or `sessionCapabilities.resume`; otherwise this
|
|
runner would silently degrade into single-turn execution after each stdio
|
|
process exits.
|
|
|
|
| Provider | Default ACP command | Verified session recovery signal |
|
|
| --- | --- | --- |
|
|
| `auggie` | `npx -y @augmentcode/auggie --acp` | `loadSession` |
|
|
| `autohand` | `npx -y @autohandai/autohand-acp` | `loadSession`, `session/resume` |
|
|
| `claude-code` | `npx -y @agentclientprotocol/[email protected]` | `loadSession`, `session/resume` |
|
|
| `codebuddy-code` | `npx -y @tencent-ai/codebuddy-code --acp` | `loadSession` |
|
|
| `codex` | `npx -y @zed-industries/codex-acp` | `loadSession`, `session/resume` |
|
|
| `deepagents` | `npx -y deepagents-acp` | `loadSession` |
|
|
| `dimcode` | `npx -y dimcode acp` | `loadSession`, `session/resume` |
|
|
| `dirac` | `npx -y dirac-cli --acp` | `loadSession` |
|
|
| `factory-droid` | `npx -y droid exec --output-format acp-daemon` | `loadSession`, `session/resume` |
|
|
| `gemini` | `npx -y @google/gemini-cli --acp` | `loadSession` |
|
|
| `glm-agent` | `npx -y glm-acp-agent` | `loadSession`, `session/resume` |
|
|
| `kilo` | `npx -y @kilocode/cli acp` | `loadSession`, `session/resume` |
|
|
| `opencode` | `opencode acp` | `loadSession`, `session/resume` |
|
|
| `pi-acp` | `npx -y pi-acp` | `loadSession` |
|
|
| `qwen-code` | `npx -y @qwen-code/qwen-code --acp --experimental-skills` | `loadSession`, `session/resume` |
|
|
|
|
The following ACP-capable or ACP-adjacent CLIs are not built-in presets because
|
|
they do not currently meet the session recovery bar for this runner:
|
|
|
|
| Provider | Reason |
|
|
| --- | --- |
|
|
| `agoragentic` | `initialize` reports `loadSession: false` and no `session/resume`. |
|
|
| `cline` | Headless `initialize` did not complete during verification, so session recovery could not be confirmed. |
|
|
| `cursor` | Public reports show `loadSession: true` is advertised, but `session/load` fails for ACP-created sessions. |
|
|
| `github-copilot` | Registry npx launch did not start a usable ACP binary during verification. |
|
|
| `goose` | Upstream docs say ACP providers do not support session resume or fork yet. |
|
|
| `kimi` | Public issue reports ACP load/switch starts with blank history even though CLI history exists. |
|
|
| `langcli` | No registry/npm-verifiable ACP stdio package was available during verification. |
|
|
| `nova` | Headless `initialize` did not complete during verification, so session recovery could not be confirmed. |
|
|
| `qoder` | Public report shows ACP-created sessions cannot be loaded across process restarts despite `loadSession: true`. |
|
|
| `sigit` | Packaged binary could not be verified in the test environment and no public session recovery evidence was found. |
|
|
|
|
Other ACP-compatible runtimes from the public ACP registry can still be used via
|
|
`provider=custom` and `acp-command`. DeepSeek's official Deep Code CLI is not a
|
|
built-in preset yet because its current public docs describe the interactive
|
|
`deepcode` CLI and MCP support, but do not document an ACP stdio mode.
|
|
|
|
For a remote Claude ACP process over SSH:
|
|
|
|
```text
|
|
provider = claude-code
|
|
location = remote-ssh
|
|
ssh-target = [email protected]
|
|
workspace = /home/yhh/langbot-e2e/acp-workspace
|
|
```
|
|
|
|
Remote mode does not connect to a long-running ACP TCP service. LangBot starts
|
|
the ACP process itself over SSH:
|
|
|
|
```text
|
|
LangBot host
|
|
-> ssh user@host "cd <workspace> && exec <acp-command>"
|
|
-> ACP JSON-RPC over ssh stdio
|
|
```
|
|
|
|
When LangBot assets are enabled, the runner also starts a temporary run-scoped
|
|
MCP bridge on the LangBot host and adds an SSH reverse tunnel on the same SSH
|
|
connection:
|
|
|
|
```text
|
|
remote ACP process
|
|
-> http://127.0.0.1:<forwarded-port>/mcp
|
|
-> SSH -R tunnel
|
|
-> LangBot run-scoped MCP bridge
|
|
```
|
|
|
|
The remote host must allow non-interactive SSH login from the LangBot host and
|
|
must already have the selected agent runtime installed, authenticated, and
|
|
available on `PATH` for non-interactive shells.
|
|
|
|
Platform notes:
|
|
|
|
- Linux remote: default path. The wrapper uses `bash -lc`, creates the workspace
|
|
directory, changes into it, then execs `acp-command`.
|
|
- macOS remote: same as Linux when the remote has `bash` and the selected agent
|
|
runtime available to non-interactive SSH sessions. If a Homebrew/npm path is
|
|
missing, set it through `env-json` or use an absolute command path.
|
|
- Windows remote: not identical to Linux/macOS. Configure the hidden
|
|
`remote-shell=powershell` key manually; the wrapper uses PowerShell to create
|
|
the workspace and run `acp-command`. Windows OpenSSH must support reverse
|
|
forwarding for LangBot MCP assets to work. A first-class Windows UI option is
|
|
not exposed yet.
|
|
|
|
If a network policy blocks SSH reverse forwarding, set
|
|
`mcp-bridge-transport=http` plus `mcp-public-url` to a URL reachable from the
|
|
remote host, or disable LangBot asset injection with `langbot-assets-enabled=false`.
|
|
|
|
## Run-Scoped LangBot Assets
|
|
|
|
By default the plugin starts a temporary run-scoped MCP bridge for each run. The
|
|
bridge URL and token are injected into ACP `mcpServers`, and the bridge is
|
|
stopped when the run finishes.
|
|
|
|
In `location=daemon`, the ACP process does not call the plugin's MCP bridge
|
|
directly. The user-side daemon starts a localhost HTTP MCP proxy, injects that
|
|
local URL into ACP, then forwards MCP JSON-RPC requests over the already-open
|
|
WebSocket connection back to the plugin. The plugin handles those requests with
|
|
the current run's `RunnerAPIProxy`, so LangBot assets remain scoped by
|
|
`run_id` and the Host authorization snapshot. The user's workstation does not
|
|
need a public IP or inbound port for this path.
|
|
Runner code should import the SDK proxy from
|
|
`langbot_plugin.api.proxies.runner`.
|
|
|
|
To test the long-lived HTTP MCP gateway path with ACP, set:
|
|
|
|
```text
|
|
langbot-assets-enabled = true
|
|
langbot-assets-mode = gateway
|
|
langbot-assets-gateway-port = 8765
|
|
```
|
|
|
|
In gateway mode, the SDK starts one process-long HTTP MCP gateway and registers
|
|
each LangBot run with a short-lived run token. ACP receives the gateway MCP URL
|
|
and token through `mcpServers` headers, so the model can call stable LangBot
|
|
gateway tools without seeing the token. The gateway tool list is stable and
|
|
includes:
|
|
|
|
- `langbot_list_assets`
|
|
- `langbot_get_current_event`
|
|
- `langbot_history_page`
|
|
- `langbot_retrieve_knowledge`
|
|
- `langbot_get_tool_detail`
|
|
- `langbot_call_tool`
|
|
|
|
Host-authorized platform and event actions are projected through the same MCP
|
|
surface as `tool_type=platform` tools. Use `langbot_list_assets` with
|
|
`asset_types=["platform_tools"]` to inspect them, then call them through
|
|
`langbot_call_tool`. Event-level action targets are frozen by LangBot for the
|
|
current run; platform-level actions may accept explicit target IDs. The ACP
|
|
process never receives the platform adapter or its credentials directly.
|
|
|
|
The stable tool list is intentionally compatible with platforms such as Dify
|
|
that cache MCP provider tools. For those platforms, the same gateway can accept
|
|
the short-lived `run_token` as a tool argument instead of an HTTP header.
|
|
Register the provider URL with the `/mcp` path, for example
|
|
`http://<langbot-host>:8765/mcp` or the value configured in
|
|
`langbot-assets-gateway-public-url`.
|
|
|
|
The current LangBot `run_id` is included in the prompt for diagnostics only. ACP agents should follow MCP tool schemas exactly and should not add `run_id` to tool calls unless a specific tool schema asks for it.
|
|
|
|
## Complete configuration index
|
|
|
|
In addition to the common fields described above, the current runner exposes:
|
|
|
|
- `daemon-connect-timeout` for waiting on a selected daemon.
|
|
- `langbot-assets-gateway-host`, `langbot-assets-gateway-port`, `langbot-assets-gateway-public-url`, and `langbot-assets-token-ttl` for Asset Gateway mode.
|
|
- `ssh-connect-timeout`, `ssh-extra-options`, and `ssh-identity-file` for SSH execution.
|
|
- `startup-timeout` and `initialize-timeout` for ACP process startup and handshake deadlines.
|
|
- `create-session-if-missing` for recovery fallback behavior.
|
|
- `streaming` for incremental output.
|
|
- `append-run-scope-prompt` for adding LangBot run-scope guidance.
|
|
- `mcp-servers-json` for additional MCP server configuration.
|
|
|
|
## Scope
|
|
|
|
This plugin intentionally does not implement an agent platform, task board, workspace manager, or provider-specific CLI behavior. Provider setup, login, model selection, and tool permission semantics still belong to the ACP agent executable being launched.
|
|
|
|
|
|
## Shared-worker candidate / daemon upgrade
|
|
|
|
This version pins `langbot-plugin==0.6.0b5` and remains dedicated; no shared-worker eligibility is claimed.
|
|
It is a candidate, not a certificate or proof of vendor execution. Local,
|
|
remote-SSH and daemon modes remain available. Native children use the worker's
|
|
nsjail/cgroup policy, not the separate Box managed-process quota.
|
|
|
|
- Shared native mode defaults to **`/data/workspace`**, an installation-private
|
|
writable directory. Explicit native paths must exist and be writable inside
|
|
the jail; `/plugin` is read-only. Dedicated/OSS defaults are unchanged.
|
|
- Shared daemon listeners require a **different random token per installation**
|
|
(at least 32 characters, at least 12 distinct characters; generate with
|
|
`python -c 'import secrets; print(secrets.token_urlsafe(32))'`). Length checks
|
|
cannot prove randomness or cross-installation uniqueness: provision unique
|
|
secrets, do not reuse the example/config default. Loopback is shared between
|
|
workers. Assign a unique explicit `daemon-port` per installation and configure
|
|
TLS routing for remote clients; port conflicts fail with configuration guidance.
|
|
- Upgrade **both `daemon.py` and its entire `pkg/` directory** from this exact
|
|
plugin version on the target. Stock b5/older clients are rejected: the bundled
|
|
plugin-owned relay negotiates `coding-relay-v1`. No installed SDK is patched.
|
|
- A daemon ID cannot replace a connected socket. Jobs/events/MCP/finish belong
|
|
to the accepted socket incarnation. Cancellation revokes run tools and waits
|
|
for client process-group cleanup before acknowledging, or fences that daemon.
|
|
A disconnect cancels and awaits the client's jobs before any reconnect.
|
|
- Unresolved shared jobs retain `.pending` markers under
|
|
`/data/.coding-relay-v1/<runner-prefix>/`; worker restart does **not** clear
|
|
them. Stop and verify all remote jobs on that target before an operator removes
|
|
its marker and reconnects. Never rename the target to bypass a fence. An
|
|
already dispatched MCP/SSH/upstream side effect is not rolled back by cancel.
|
|
- Remote SSH/daemon accounts are **trusted execution targets**, not independent
|
|
tenant sandboxes. Terminating local SSH does not prove remote process-tree
|
|
exit; use a controlled target and verify remote quiescence before reuse.
|
|
POSIX cleanup covers descendants that remain in the launched process group;
|
|
deliberately detached processes require target-owner cleanup.
|
|
- Install reviewed CLI/SSH binaries at jail-visible locations and provision
|
|
installation-owned credentials. These are functional prerequisites, separate
|
|
from certification, signing, and two-Workspace live acceptance. Do not relax
|
|
global Box policy or use unreviewed automatic installers to satisfy them.
|