#168: revise the front matter, Approaches, and the OpenCode port
- Home and _Sidebar: renamed the product to fleet / fleetd. Dropped the claim that AgentAPI is a "swappable fallback injector" — it was never built, and no AgentAPI code exists under fleetd/src/main/java. - Home also lost two claims that chapters 1 and 2 removed today: there is no SSE route, and the page no longer names a gateway host as if it were fixed. The allowed hosts are a config value and differ per deployment. - Home no longer carries a release number, a ticket count or a test total. Those go stale in days and then read as current facts; 8-Roadmap holds the record. - 3-Approaches keeps AgentAPI as discarded research, which is that page's job, but never in the present tense. Evidence: a search for agentapi under fleetd/src/main/java finds nothing, and both Profile.kind values run through HerdrPeerLauncher. - 12-Claude-to-OpenCode: the sample mount name was `fleetd`, which teaches a second product name. Every launcher writes the same constant, PeerLauncher.MCP_MOUNT_NAME = "fleet". A hand-written config using another key mounts under a name the role-detection ladder never checks.
+22
-12
@@ -2,7 +2,7 @@
|
||||
|
||||
**What this page is for.** Taking a project a Claude Code session already works in, and making an
|
||||
OpenCode session a first-class participant in the same workspace — same instructions, same MCP
|
||||
servers, same bridge mount.
|
||||
servers, same `fleet` mount.
|
||||
|
||||
**The short version: there is almost nothing to port.** OpenCode reads `CLAUDE.md` natively. The
|
||||
only artifact you create is one `opencode.json` mapping MCP servers. Everything below was verified
|
||||
@@ -64,11 +64,18 @@ belong in the global file and shared ones in the project file.
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"instructions": ["CLAUDE.md"],
|
||||
"mcp": {
|
||||
"fleetd": { "type": "remote", "url": "http://127.0.0.1:8765/mcp", "enabled": true }
|
||||
"fleet": { "type": "remote", "url": "http://127.0.0.1:8765/mcp", "enabled": true }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The mount name here must be `fleet`, not `fleetd`. Every launcher writes this same constant name
|
||||
(`PeerLauncher.MCP_MOUNT_NAME = "fleet"`, `PeerLauncher.java:34`) into the config it builds for a
|
||||
spawned worker — `ClaudeCodeLauncher` into an inline `--mcp-config` (`ClaudeCodeLauncher.java:348`),
|
||||
`OpenCodeLauncher` into the `opencode.json` it writes (`OpenCodeLauncher.java:345`). A hand-written
|
||||
config using a different key mounts a server under a name the role-detection ladder in `CLAUDE.md`
|
||||
does not check, so an opencode peer set up by hand must match this name exactly.
|
||||
|
||||
Set `instructions` explicitly even though `CLAUDE.md` is found anyway — the fallback applies only
|
||||
while no `AGENTS.md` exists.
|
||||
|
||||
@@ -91,8 +98,9 @@ files opencode never reads. Verified here: a clean login shell
|
||||
exist only inside processes Claude Code spawned. Run `opencode mcp list` from such a process and
|
||||
everything is green; run it from a real terminal and context7 is `⚠ needs authentication`.
|
||||
|
||||
Worse, the two launch paths disagree on names. A bridge-spawned worker gets `GITEA_TOKEN` +
|
||||
`GITEA_HOST` from `applyGitToken` (see [Features](11-Features) → the git-forge token grant), not
|
||||
Worse, the two launch paths disagree on names. A fleetd-spawned worker gets `GITEA_TOKEN` +
|
||||
`GITEA_HOST` from `applyGitToken` (see [Features](11-Features) → the git-forge token grant; the
|
||||
method lives at `HerdrPeerLauncher.java:893-900` and both launchers call it), not
|
||||
`GITEA_ACCESS_TOKEN` — so an `opencode.json` written against the terminal's environment fails
|
||||
inside a worker, and vice versa.
|
||||
|
||||
@@ -101,7 +109,7 @@ So the supply route follows from who launches opencode:
|
||||
| Launcher | Route | Here |
|
||||
|---|---|---|
|
||||
| a human, from a terminal | `{file:.secrets/…}` | gitignored `.secrets/`, workspace-scoped |
|
||||
| a bridge-spawned worker | `{env:…}` | `applyGitToken` + the profile's `env:` block |
|
||||
| a fleetd-spawned worker | `{env:…}` | `applyGitToken` + the profile's `env:` block |
|
||||
|
||||
For the terminal case this repo uses `{file:}` with **workspace-relative** paths — verified: opencode
|
||||
resolves them against the project root, so no shell export and no direnv is needed, and the secret
|
||||
@@ -163,21 +171,23 @@ authenticated tool per server, and confirm nothing you withheld appears.
|
||||
|
||||
- **A leftover `AGENTS.md` silently shadows `CLAUDE.md`.** If one exists from an earlier port,
|
||||
delete it, or opencode reads the stale copy instead of the live rules. Check this first.
|
||||
- **Skills do not port.** OpenCode uses its own agent markdown under `.opencode/agents/`;
|
||||
`.claude/skills/**` is not read. A delegation brief that says "load the *implementer* skill" has
|
||||
no effect on an opencode peer — spell the procedure out in the brief instead.
|
||||
- **Skills do not port.** OpenCode uses its own agent markdown under `.opencode/agents/`
|
||||
(`HerdrPeerLauncher.java:322-326` builds that path for both launchers); `.claude/skills/**` is not
|
||||
read. A delegation brief that says "load the *implementer* skill" has no effect on an opencode
|
||||
peer — spell the procedure out in the brief instead.
|
||||
- **Identity comes from the terminal, not the runtime.** A peer must be spawned into its own pane;
|
||||
running `opencode` in the primary's pane resolves as the primary.
|
||||
- **The subscription guard doesn't apply.** It is `ANTHROPIC_BASE_URL`-shaped; opencode
|
||||
authenticates through its own credentials, so `maxLoad` is the only spend control that reaches it.
|
||||
- **The subscription guard doesn't apply.** It is `ANTHROPIC_BASE_URL`-shaped and lives only in
|
||||
`ClaudeCodeLauncher`; `OpenCodeLauncher.buildLaunch` never calls it. opencode authenticates
|
||||
through its own credentials, so `maxLoad` is the only spend control that reaches it.
|
||||
|
||||
---
|
||||
|
||||
## Appendix — the Codex port (superseded)
|
||||
|
||||
Chapter 12 previously documented porting to OpenAI Codex. That path was abandoned in favour of
|
||||
opencode, which the bridge already supports through `OpenCodeLauncher`. Four findings are kept
|
||||
because they were verified by hand and explain *why* the comparison went this way:
|
||||
opencode, which fleetd already supports through `OpenCodeLauncher`. Four findings are kept because
|
||||
they were verified by hand and explain *why* the comparison went this way:
|
||||
|
||||
- **Codex has no `CLAUDE.md` fallback.** Instructions had to be translated into `AGENTS.md` by a
|
||||
third-party tool, whose terminology map rewrote file paths into sentences that were wrong rather
|
||||
|
||||
+32
-21
@@ -20,7 +20,7 @@ flowchart TD
|
||||
Q -->|"raw keystrokes into the tmux pane"| TMUX["tmux send-keys<br/>/ PTY paste"]
|
||||
|
||||
HD --> V0["✅ our leading choice<br/>(status events + multiplex;<br/>symmetric single-host)"]
|
||||
AA --> V1["◐ fallback injector<br/>(swappable behind fleetd)"]
|
||||
AA --> V1["✕ never built<br/>(discarded research)"]
|
||||
SDK --> V2["✅ if driver is our own code"]
|
||||
BUS --> V3["⚠ async bus events only<br/>(lands at turn boundary)"]
|
||||
TMUX --> V4["⚠ fragile — the raw primitive<br/>herdr/AgentAPI productize"]
|
||||
@@ -71,26 +71,35 @@ Unix-socket JSON API. `fleetd` (see [Message Server](2-Message-Server)) drives i
|
||||
- **Subscription-safe:** only the worker pane launches with `ANTHROPIC_BASE_URL`; `fleetd`
|
||||
is a plain daemon (no quota) that enforces the boundary in code.
|
||||
- **Trade-off:** herdr's socket is **local-only** (`fleetd`'s MCP/HTTP spans hosts, not
|
||||
herdr), and it is a young, single-dev project — so `fleetd` keeps the injector **pluggable**
|
||||
and its durability in an **internal** queue behind the gateway. Replies are best carried as a
|
||||
herdr), and it is a young, single-dev project — so `fleetd` keeps delivery durability in an
|
||||
**internal** queue behind the gateway. The shipped injector is the herdr one: a
|
||||
status-gated, single writer per worker (`Injector.java:48`), and it is a `final` class —
|
||||
not a pluggable interface a second injector could slot into. Replies are best carried as a
|
||||
structured envelope, not scraped.
|
||||
|
||||
## 2. AgentAPI — HTTP over terminal emulation *(fallback injector)*
|
||||
## 2. AgentAPI — HTTP over terminal emulation *(never built — research only)*
|
||||
|
||||
[`coder/agentapi`](https://github.com/coder/agentapi) wraps the Claude Code **CLI** as an
|
||||
HTTP server and drives the CLI's terminal underneath (essentially a hardened, stateful
|
||||
`tmux send-keys` with parsing): `POST /message`, `GET /events` (SSE), `GET /status`. It was
|
||||
the original leading choice; herdr now supersedes it, but it remains a **swappable fallback
|
||||
injector** behind `fleetd`'s interface.
|
||||
the original leading choice, until herdr superseded it. **It was never built**: a
|
||||
case-insensitive search for `agentapi` under `fleetd/src/main/java` returns nothing, and
|
||||
both `Profile.kind` values (`claude-code`, the default, and `opencode`,
|
||||
`FleetConfig.java:265-270`) run through `HerdrPeerLauncher`
|
||||
(`ClaudeCodeLauncher.java:43`, `OpenCodeLauncher.java:54`) — herdr is the only injection
|
||||
path that ships. The mechanism below describes the external project, not this codebase.
|
||||
|
||||
- **Injects into a live session:** yes — but only the *worker* (it wraps one CLI); the
|
||||
primary direction still needs `fleetd`'s async path (idle-injection, or a split-host
|
||||
`Stop`-hook polling `fleetd`).
|
||||
- **Injects into a live session:** in its own design, yes — but only the *worker* (it wraps
|
||||
one CLI); the primary direction still needs `fleetd`'s async path (idle-injection, or a
|
||||
split-host `Stop`-hook polling `fleetd`).
|
||||
- **Completion signal:** a **screen-stability heuristic**, not structured events.
|
||||
- **Cross-host:** native HTTP — its one edge over herdr, but `fleetd` already provides the
|
||||
HTTP layer on top of herdr, so that edge is neutralized.
|
||||
- **When to reach for it:** if herdr can't run, or as the second injector implementation to
|
||||
de-risk herdr's immaturity. Its `msgfmt` reply parser is worth reusing regardless.
|
||||
- **Why it was dropped:** herdr's structured status events beat a screen-stability
|
||||
heuristic, and `fleetd` already covers the cross-host part, so that one edge buys
|
||||
nothing here. It stays as discarded research: a second injector implementation to
|
||||
de-risk herdr, and the possible reuse of its `msgfmt` reply parser, which is not in this
|
||||
codebase.
|
||||
|
||||
## 3. Agent SDK — streaming input *(good if the driver is our own code)*
|
||||
|
||||
@@ -106,7 +115,7 @@ events and permission callbacks instead of scraping a terminal.
|
||||
## 4. Message-queue + Stop-hook long-poll *(raw async primitive — behind the gateway in our design)*
|
||||
|
||||
The **only pure-hooks** way to pull an external message into the **same** session. In
|
||||
`claude-bridge` this is **not** how a Claude session normally receives async work — under the
|
||||
`fleet` this is **not** how a Claude session normally receives async work — under the
|
||||
sole-gateway rule `fleetd` delivers async by **injecting an idle pane**, and no Claude session
|
||||
polls a queue. The Stop-hook survives in exactly one place: a **split-host primary** that isn't
|
||||
a herdr pane, where the hook long-polls **`fleetd`** (not the queue) for wake-ups. The raw
|
||||
@@ -161,14 +170,15 @@ live session, works today, but it is terminal automation — timing-sensitive, n
|
||||
|
||||
**herdr's `send_text`/`send_keys` is this, productized** — with a stable session, structured
|
||||
status events, and multiplexing — which is why we build on herdr rather than hand-rolled
|
||||
`send-keys` (and why AgentAPI, the other productized form, is only the fallback).
|
||||
`send-keys` (AgentAPI is the other productized form of the same idea; it was never built
|
||||
and is research only).
|
||||
|
||||
## Research matrix
|
||||
|
||||
| Approach | Transport | Inject into running session? | Completion signal | Symmetric (both panes)? | Cross-host | Subscription-safe | Fragility |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| **herdr via `fleetd`** ✅ | socket → terminal + events | ✅ (idle-gated) | ✅ status events² | ◐ single-host¹ | via `fleetd` MCP/HTTP (sole gateway) | ✅ (guard in code) | Low–Med (herdr young) |
|
||||
| **AgentAPI** ◐ (fallback) | HTTP → terminal emulation | ✅ (worker only) | ⚠ screen-stability | ❌ | ✅ native HTTP | ✅ (worker-only env) | Low |
|
||||
| ~~AgentAPI~~ (never built — research) | HTTP → terminal emulation | ✅ (worker only) | ⚠ screen-stability | ❌ | ✅ native HTTP | ✅ (worker-only env) | Low |
|
||||
| **Agent SDK streaming** | in-process generator | ✅ | ✅ typed events | n/a | ✅ | ✅ | Low (driver = code) |
|
||||
| **Queue + Stop-hook** | hook long-poll | ✅ (turn boundary) | via turn end | ✅ (symmetric) | ✅ | ✅ | Medium |
|
||||
| **tmux `send-keys`** | keystrokes / PTY | ✅ | ❌ scrape `capture-pane` | ✅ | ⚠ ssh | ✅ | High |
|
||||
@@ -187,18 +197,20 @@ structured `fleet_reply` (preferred), with a `Stop`-hook envelope as fallback**
|
||||
|
||||
## Recommendation
|
||||
|
||||
- **Primary Opus → worker (the bridge's main path):** **herdr via `fleetd`** —
|
||||
- **Primary Opus → worker (the main path of this system):** **herdr via `fleetd`** —
|
||||
status-gated injection, structured completion/blocked events, symmetric (single-host), multiplexed,
|
||||
persistent, with the subscription boundary enforced in code. Selected. See
|
||||
[Message Server](2-Message-Server) / [Architecture](1-Architecture).
|
||||
- **Keep AgentAPI as a swappable fallback injector** behind `fleetd`'s interface, so
|
||||
herdr's immaturity is a de-riskable risk rather than a load-bearing one.
|
||||
- **AgentAPI — never built:** considered and dropped. This page keeps it as research
|
||||
(section 2), not as a fallback an operator can select; herdr's immaturity stands as a
|
||||
known risk, and `fleetd` de-risks it with the internal queue and status-gated injection
|
||||
instead of a second injector.
|
||||
- **External event bus → worker (async wake-ups):** the bus hits **`fleetd`'s REST ingress**;
|
||||
`fleetd` enqueues internally if needed and **injects the idle worker** — the worker runs no
|
||||
queue-polling hook. Complementary to the sync path, not a replacement — different trigger
|
||||
shape, same single gateway.
|
||||
- **Avoid hand-rolled `tmux send-keys`** unless neither herdr nor AgentAPI can run; it's the
|
||||
same idea with all the fragility left in.
|
||||
- **Avoid hand-rolled `tmux send-keys`** unless herdr can't run on the host; it's the same
|
||||
idea with all the fragility left in.
|
||||
|
||||
## Sources
|
||||
|
||||
@@ -209,5 +221,4 @@ structured `fleet_reply` (preferred), with a `Stop`-hook envelope as fallback**
|
||||
- [Stop-hook task-enforcement pattern](https://claudefa.st/blog/tools/hooks/stop-hook-task-enforcement)
|
||||
- [claude-mem hooks architecture](https://docs.claude-mem.ai/hooks-architecture)
|
||||
- [Issue #27441 — inter-agent message injection](https://github.com/anthropics/claude-code/issues/27441) · [Issue #24947 — `claude inject`](https://github.com/anthropics/claude-code/issues/24947)
|
||||
- [Claude-Code-Remote](https://github.com/JessyTsui/Claude-Code-Remote) · [OpenACP guide](https://dev.to/tigergethigher/how-to-control-claude-code-from-telegram-discord-or-slack-self-hosted-open-source-1jk8)
|
||||
</content>
|
||||
- [Claude-Code-Remote](https://github.com/JessyTsui/Claude-Code-Remote) · [OpenACP guide](https://dev.to/tigergethigher/how-to-control-claude-code-from-telegram-discord-or-slack-self-hosted-open-source-1jk8)
|
||||
+23
-19
@@ -1,4 +1,4 @@
|
||||
# claude-bridge
|
||||
# fleet
|
||||
|
||||
A **subscription-safe bridge** that lets a lead **Claude Code** session on Pro/Max delegate work to
|
||||
**members running on other models and other vendors** — without ever putting a proxy on the lead.
|
||||
@@ -8,7 +8,7 @@ A **subscription-safe bridge** that lets a lead **Claude Code** session on Pro/M
|
||||
> the shape of the design and why it was chosen.
|
||||
|
||||
> Sibling of [`crush-bridge`](https://git.ltms.dev/systems/vms) (Tier 2 → headless Crush
|
||||
> on GX10 DeepSeek). `claude-bridge` keeps a Claude member a *real Claude Code process* so it
|
||||
> on GX10 DeepSeek). `fleet` keeps a Claude member a *real Claude Code process* so it
|
||||
> inherits `CLAUDE.md`, hooks, skills, and MCP — just pointed at a cheaper model. It also runs
|
||||
> non-Claude members (`kind: opencode`) as first-class peers.
|
||||
|
||||
@@ -17,25 +17,26 @@ A **subscription-safe bridge** that lets a lead **Claude Code** session on Pro/M
|
||||
A small always-on message server, **`fleetd`**, controls
|
||||
[herdr](https://herdr.dev) (an agent multiplexer, "tmux for agents") 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
|
||||
workers mount** — one unified Claude setup and the **sole communication gateway**. A plain
|
||||
REST surface covers the same features for non-Claude clients; there is no event-stream route.
|
||||
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.
|
||||
delivery) and the client contract. A Claude member launches with `ANTHROPIC_BASE_URL` pointed at whichever gateway its profile
|
||||
names, plus a bearer token. The allowed hosts are a config value, so they differ per
|
||||
deployment. The lead stays env-clean and calls `fleetd`'s MCP tools.
|
||||
|
||||
```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"]
|
||||
SRV["SERVER face<br/>MCP · REST · policy"]
|
||||
CLI["CLIENT face<br/>status-gated injector · herdr socket"]
|
||||
SRV --> CLI
|
||||
end
|
||||
HERDR["herdr<br/>panes · agent-status"]
|
||||
W["member pane<br/>ANTHROPIC_BASE_URL set<br/>MCP client"]
|
||||
M["llm.ltms.dev<br/>(the one gateway)"]
|
||||
M["the configured gateway"]
|
||||
|
||||
OPUS -->|"MCP fleet_send (blocks)"| SRV
|
||||
W -.->|"MCP fleet_reply"| SRV
|
||||
@@ -70,11 +71,11 @@ flowchart LR
|
||||
**injects the lead's own idle pane** to wake it.
|
||||
- **Different model** per member 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)) was kept on paper as a
|
||||
swappable *fallback injector*. It was **never built** — `grep -ri agentapi fleetd/src/main`
|
||||
returns nothing, and the only injection path in the shipped code is the herdr one. Treat it as a
|
||||
discarded option, not a fallback you can switch to. See [Approaches](3-Approaches) for why herdr
|
||||
won and [Message Server](2-Message-Server) for the full design.
|
||||
- **AgentAPI** ([`coder/agentapi`](https://github.com/coder/agentapi)) was considered and dropped:
|
||||
it was **never built**. No AgentAPI code exists under `fleetd/src/main/java`, and the shipped
|
||||
injection path is the herdr one, which every profile selects through `Profile.kind`
|
||||
(`claude-code` or `opencode`, `FleetConfig.java:265-270`). See [Approaches](3-Approaches) for
|
||||
why herdr won and [Message Server](2-Message-Server) for the full design.
|
||||
|
||||
## Pages
|
||||
|
||||
@@ -96,11 +97,14 @@ Read in order (the sidebar mirrors this):
|
||||
|
||||
## Status
|
||||
|
||||
🟢 **Running.** Release 1.1 is code-complete (2026-08-16): 20 of 20 tickets closed, 870 tests green,
|
||||
the daemon live on this host. Chapters 4 and 5 were never written past their scope note; chapter 13
|
||||
replaced them.
|
||||
🟢 **Running.** The daemon is live on this host. Chapters 4 and 5 were never written past their
|
||||
scope note, and chapter 13 replaced them.
|
||||
|
||||
This page does not carry release numbers, ticket counts or test totals. Those go stale within days
|
||||
and then read as current facts. See [8 Roadmap](8-Roadmap) for the delivery record.
|
||||
|
||||
The **herdr-centric `fleetd` message server** was selected on 2026-07-11, superseding the AgentAPI
|
||||
plan of 2026-07-08. AgentAPI is retained as a fallback injector and has not been needed. See
|
||||
plan of 2026-07-08. AgentAPI was considered and dropped: it was never built, so there is no
|
||||
fallback injector to switch to. See
|
||||
**[Message Server](2-Message-Server)** for the design and **[13 User Guide](13-User-Guide)** for how
|
||||
to run it.
|
||||
to run it.
|
||||
+3
-3
@@ -1,4 +1,4 @@
|
||||
### 📖 claude-bridge
|
||||
### 📖 fleet
|
||||
|
||||
[Home](Home) — overview & the decision
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
5. [Operations](5-Operations) — ⚫ superseded by 13
|
||||
6. [Team](6-Team) — orchestrating a mixed fleet
|
||||
7. [Use Cases](7-Use-Cases) — the review scenario + mechanisms
|
||||
8. [Roadmap](8-Roadmap) — stages, tech stack, tickets
|
||||
8. [Roadmap](8-Roadmap) — delivery record: what is live, what is off, what was dropped
|
||||
9. [Implementation](9-Implementation) — as-built code map · classes · flows · state machines
|
||||
10. [Cross-Host Messaging](10-Cross-Host-Messaging) — broker topology · exchanges · queues per entity
|
||||
11. [Features](11-Features) — what it can do · the knob that turns it on · why · the gotcha
|
||||
@@ -19,4 +19,4 @@
|
||||
13. **[User Guide](13-User-Guide)** — 🟢 install · configure · run · delegate · the traps
|
||||
|
||||
---
|
||||
🟢 herdr-centric `fleetd` · AgentAPI = fallback
|
||||
🟢 herdr-centric `fleetd` · AgentAPI = research, never built
|
||||
|
||||
Reference in New Issue
Block a user