ecc590f344
Part of #145 (CB-632). Documentation only, plus one internal literal. Unit 1 renamed the package and classes, which left every doc describing classes that no longer exist. This fixes the prose across README.md, docs/ and bridged/docs/ -- 18 files. Renamed: dev.ltms.bridged -> dev.ltms.fleet, the five class names, and "bridged" where it names the daemon as a product rather than a path. Also renamed two literals, because a doc that disagrees with the code is worse than one that is out of date: - bridged-local-noauth -> fleetd-local-noauth. A placeholder apiKey OpenCodeLauncher sends when a profile resolves no token, to a local endpoint that does not check it. No test asserts the old string. - the vnd.ltms.bridged.* media type in the M4 design doc. It appears in no Java file, so nothing implements it yet. Deliberately NOT renamed, because each is still literally true today and changes only at the cutover: - paths: bridged/, bridged.yaml, bridged.example.yaml, bridged.jar, .bridged-worktrees, deploy/dev.ltms.bridged.plist, scripts/redeploy-bridged.sh, bridged-launchd-wrapper.sh - bridged_* metric names -- renaming these after the monitoring is wired would break dashboard continuity, so they move before it is - bridge_* MCP tool names, which answer alongside fleet_* on purpose - BRIDGED_* environment variables, read by a file outside this repo Method note: perl, not sed. BSD sed has no \b and no lookaround, and a word-boundary expression there fails silently. The prose replace uses (?<![\w./-])bridged(?![\w./-]) so it cannot touch a path or an identifier, then every remaining hit was read by hand. Verified: mvn clean install green, 51 classes, 878 tests, 0 failures.
128 lines
7.6 KiB
Markdown
128 lines
7.6 KiB
Markdown
# claude-bridge
|
|
|
|
A **subscription-safe bridge** that lets a primary **Claude Code (Opus 4.8, on Pro/Max)**
|
|
session drive a **secondary Claude agent running a different model** via its own
|
|
`ANTHROPIC_BASE_URL` — without ever putting a proxy on the primary session.
|
|
|
|
Sibling of [`crush-bridge`](https://git.ltms.dev/systems/vms) (which drives a headless
|
|
**Crush** worker on GX10 DeepSeek). `claude-bridge` keeps the worker a *real Claude Code
|
|
process*, so it inherits `CLAUDE.md`, hooks, skills, and MCP — just pointed at a
|
|
cheaper/local model.
|
|
|
|
## Leading approach — herdr-centric message server (`fleetd`)
|
|
|
|
A small always-on message server, **`fleetd`**, controls
|
|
[herdr](https://herdr.dev) (an agent multiplexer) over its Unix-socket API and exposes a
|
|
clean 2-way messaging API as an **MCP server that both the primary and the workers mount** —
|
|
one unified Claude setup and the **sole communication gateway** (REST/SSE stays for non-Claude
|
|
clients; any broker is `fleetd`-internal, below the gateway).
|
|
herdr owns the PTYs, multiplexing, persistence, and **agent-status events**; `fleetd` owns
|
|
policy (subscription boundary, session lifecycle, status-gated delivery) and the client
|
|
contract. A Claude member launches with `ANTHROPIC_BASE_URL` pointed at the gateway,
|
|
`https://llm.ltms.dev/anthropic`, plus a bearer token; the lead stays env-clean and calls
|
|
`fleetd`'s MCP tools. See the wiki's **[13 User Guide](wiki/13-User-Guide.md)** to run it.
|
|
|
|
```mermaid
|
|
flowchart LR
|
|
OPUS["Opus — primary<br/>(Claude Code, env CLEAN)<br/>MCP client"]
|
|
subgraph BD["fleetd — standalone daemon (not a claude process)"]
|
|
SRV["SERVER face<br/>MCP · REST/SSE · policy"]
|
|
CLI["CLIENT face<br/>status-gated injector · herdr socket"]
|
|
SRV --> CLI
|
|
end
|
|
HERDR["herdr<br/>panes · agent-status"]
|
|
W["worker claude pane<br/>ANTHROPIC_BASE_URL set<br/>MCP client"]
|
|
M["llm.ltms.dev<br/>(the one gateway)"]
|
|
|
|
OPUS -->|"MCP fleet_send (blocks)"| SRV
|
|
W -.->|"MCP fleet_reply"| SRV
|
|
CLI -->|"Unix socket<br/>send_text · events.subscribe"| HERDR
|
|
HERDR -->|"drives PTY"| W
|
|
W -->|"inference"| M
|
|
|
|
classDef ext fill:#2b6cb0,stroke:#1a365d,color:#ffffff;
|
|
classDef core fill:#2f855a,stroke:#22543d,color:#ffffff;
|
|
class OPUS ext
|
|
class SRV,CLI,HERDR core
|
|
```
|
|
|
|
- **Subscription boundary:** the *primary* never sets `ANTHROPIC_BASE_URL` (stays on
|
|
Pro/Max). Only the *secondary* process is off-subscription — and `fleetd` itself is a
|
|
plain daemon (no Anthropic quota), so it may poll/subscribe freely.
|
|
- **One gateway (unified MCP setup):** `fleetd` is the **sole communication path** for every
|
|
Claude session. Primary and workers each mount it as an MCP server (one `claude mcp add`
|
|
line, same on both) and talk over MCP tools — `fleet_send` / `fleet_reply` /
|
|
`fleet_status` (with `fleet_ask` planned for the blocked-worker path). **No Claude session
|
|
ever addresses a broker, a peer, or the network
|
|
directly**; any queue is `fleetd`-internal. MCP tool I/O never sets `ANTHROPIC_BASE_URL`, so
|
|
mounting the bridge is subscription-safe by construction.
|
|
**Tool naming:** the tools were renamed from `bridge_*` to `fleet_*` (CB-622). The daemon
|
|
still answers the old `bridge_*` names for one release, but they are deprecated — use the
|
|
`fleet_*` names.
|
|
- **How the primary consumes a reply:** a single **blocking MCP call** (`fleet_send`);
|
|
`fleetd` holds it open until the worker calls `fleet_reply` or its turn hits
|
|
`agent_status=done`, then returns the reply as the tool result. No cross-turn busy-poll, so
|
|
no quota burn. SSE is an optional side-channel for humans/dashboards watching status.
|
|
- **Worker → primary** rides `fleetd`'s **MCP rendezvous** — the reply resolves the primary's
|
|
blocking call (or, for detached work, `fleetd` **injects the primary's idle pane** when it's
|
|
ready), so *no keystroke-into-primary and no broker are involved, even single-host*. The one
|
|
exception: a split-host primary that isn't a herdr pane wakes via its own `Stop`-hook, which
|
|
polls **`fleetd`** (never a broker). See the wiki for the two topologies.
|
|
- **Different model per process** sidesteps Claude Code's lack of per-subagent provider
|
|
routing — the worker isn't a subagent, it's its own configured process.
|
|
- **AgentAPI** ([`coder/agentapi`](https://github.com/coder/agentapi)) is retained only as a
|
|
swappable *fallback injector* behind the same interface. See the wiki for the full
|
|
design, comparison, and rationale.
|
|
|
|
## Docs
|
|
|
|
Full design, setup, and operations live in the **[wiki](https://git.ltms.dev/fleet/fleetd/wiki)**,
|
|
vendored here as a submodule under [`wiki/`](./wiki):
|
|
|
|
```bash
|
|
git clone --recurse-submodules ssh://git@git.ltms.dev:2224/fleet/fleetd.git
|
|
# or, after a plain clone:
|
|
git submodule update --init
|
|
```
|
|
|
|
Edit docs in `wiki/`, then `cd wiki && git commit && git push` to publish them to the
|
|
Gitea wiki.
|
|
|
|
## Status
|
|
|
|
🟢 **Implemented & dogfooded** — the herdr-centric **`fleetd`** message server is built and in
|
|
real use: an Opus primary delegates tasks to off-subscription workers that reply through the
|
|
bridge (code reviews delegated this way have produced committed bug fixes). Selected as the
|
|
primary approach 2026-07-11, superseding the AgentAPI plan (2026-07-08); AgentAPI retained as a
|
|
fallback injector.
|
|
|
|
**Shipped** (Java 25 · Maven · 266 unit/acceptance tests green; the live-herdr and broker contract
|
|
tests run separately via `mvn test -Pcontract`):
|
|
|
|
- **Core gateway** — herdr socket client (contract-tested vs live 0.7.0); guard-checked worker
|
|
spawn with `ANTHROPIC_BASE_URL` injected only into the worker's env; status-gated injector;
|
|
blocking `fleet_send` with reply rendezvous; MCP server as a thin adapter over the REST core.
|
|
- **MCP tools** — `fleet_send` / `fleet_reply` / `fleet_status` (messaging) and `fleet_spawn`
|
|
/ `fleet_list` / `fleet_stop` / `fleet_profiles` / `fleet_poll` (fleet). Caller identity is
|
|
connection-based (loopback peer PID → herdr pane), so the same mount serves primary and workers.
|
|
- **Delivery reliability** — completion fallback (a confirmed `working→idle` turn resolves a
|
|
send); async fire-and-poll (beats the caller's MCP call timeout for long tasks); and failure
|
|
detection for wedged (`unknown`), vanished, and never-ready workers so a send never hangs.
|
|
- **Fleet** — multiple worker profiles, each with an independent base_url guard check; workers
|
|
inherit the primary's working directory (never `$HOME`); a readiness gate holds delivery until
|
|
a worker's Claude has connected the bridge MCP (no paste lost into its boot window).
|
|
- **Blocked-worker path** — `fleet_ask` reverse rendezvous: a worker pauses its delegated turn to
|
|
ask the primary and resumes the *same* turn with the answer (CB-205).
|
|
- **Session lifecycle** — session manager with spawn/reuse/recycle, `idle_ttl` reaper, `context_cap`,
|
|
and graceful drain on shutdown (CB-301/CB-303); per-worker git worktrees on their own branch with
|
|
a config-parity overlay, so parallel implementers never stomp each other (CB-301-ext).
|
|
- **Reliable worker→primary delivery** — a durable `ReplyInbox` (in-memory by default, AMQP/LavinMQ
|
|
for cross-restart durability) holds a reply that arrives with no open send, and an active
|
|
status-gated push loop nudges the primary to drain it (CB-307).
|
|
- **Pluggable peers** — a `PeerLauncher` SPI with two in-tree adapters, `claude-code` and `opencode`,
|
|
routed by a `kind:` discriminator (CB-401/CB-402).
|
|
|
|
**Next** (see the [roadmap](wiki/8-Roadmap.md)) — Stage 5 hardening (auth/TLS, `/metrics`, CI,
|
|
service supervision, per-session authz + audit), then cross-host: CB-308 multi-host federation and
|
|
CB-500 multi-tier coordination.
|