and lead-rollover source-text guards Replaces three FleetdAssembly.java source-text guards (each scraped Fleetd.java for a call site that fleetd #612 Unit A moved into FleetdAssembly.java) with tests that drive the real assembled objects through FleetdAssembly.assembleAndStart(...) -> FleetdRuntime.mcp(), per the step-2 B-unit split (issue #612 comment 17513). - Deleted FleetdLeadSeatWiringTest (fleetd #176): pinned that FleetMcp's LeadSeatSource construction still wires Fleetd.leadSeatLookup(...) by scraping the constructor call's text. Replaced by FleetdLeadSeatAssemblyTest, which seeds one FakeHerdr tab labelled to match a configured fleet.leaders.opus.tab and asserts the REAL assembled LeadSeatSource (via runtime.mcp().leadSeatSource()) reports the live lead's seat against its own subscription profile -- 1, not the 0 LeadSeatSource.none() (the inert stand-in) could ever report. - Deleted FleetdBackendQuarantineWiringTest (fleetd #466): pinned that the escalating BackendQuarantine.withEscalation(...) text was present and the flat two-argument constructor's text was absent. Replaced by FleetdBackendQuarantineAssemblyTest, which quarantines the same credential twice through the REAL assembled BackendQuarantine (via runtime.mcp().quarantineSource().quarantine()) at controlled fake-clock offsets and asserts the second cooldown doubles (200s vs 100s) -- the one behavioural difference escalation and the flat constructor actually produce. - Deleted FleetdLeadRolloverWiringTest (fleetd #480), all three methods: unrelatedAnchorStillPresent was a scaffold anchor with no independent claim, needing no replacement. mainStillCallsTheLeadRolloverFactory pinned the leadRollover assignment's call-site text. factoryGatesOnConfigPresence pinned that an absent leadRollover: config yields no LeadRollover. Replaced by FleetdLeadRolloverAssemblyTest's two tests: assembledLeadRolloverRunsTheRealClearAndBootstrapSequence drives the REAL assembled LeadRollover (via runtime.mcp().leadRollover()) through open()/confirm() end to end and asserts /clear then bootstrapText were actually sent through the real herdr router, reaching ROLLED. absentLeadRolloverConfigMeansNoRolloverIsBuilt calls Fleetd.leadRollover(...) directly with no leadRollover: block and asserts null -- this claim was found uncovered elsewhere (LeadRolloverTest's only related assertion is vacuous, assertNull (null), and never calls the real factory). Each of the three FleetdAssembly.java call sites (quarantine line 179-180, leadRollover line 408, lead seats line 479) was mutated to its named inert variant, run against ONLY its new test (RED), reverted, touch'd (Maven mtime trap) and re-run (GREEN) -- six proven runs, pasted in the PR body. FleetMcp.java: adds three accessors (quarantineSource(), leadSeatSource(), leadRollover()) alongside the existing registeredTools() -- but public, not package-private, and this is a deliberate deviation from that precedent, not an oversight: these new assembly tests cannot live in package dev.ltms.fleet.mcp the way registeredTools()'s callers do, because they also build the ResourcePorts FleetdAssembly.assembleAndStart(...) needs, and ResourcePorts' methods return Fleetd-nested types visible only from package dev.ltms.fleet. Package-private would compile but be unreachable from there. Full mvn -o test in fleetd/: Tests run: 1879, Failures: 6 (down from the branch baseline's 1880/9 by exactly the 3 guards this unit deletes) -- the remaining 6 are FleetdCompletionResolverWiringTest (4) and FleetdConnectionIdentityConstructionTest / FleetdFleetAppConstructionTest (1 each), all out of this unit's scope (B1/B2).
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 (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 (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 to run it.
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 — andfleetditself is a plain daemon (no Anthropic quota), so it may poll/subscribe freely. - One gateway (unified MCP setup):
fleetdis the sole communication path for every Claude session. Primary and workers each mount it as an MCP server (oneclaude mcp addline, same on both) and talk over MCP tools —fleet_send/fleet_reply/fleet_status(withfleet_askplanned for the blocked-worker path). No Claude session ever addresses a broker, a peer, or the network directly; any queue isfleetd-internal. MCP tool I/O never setsANTHROPIC_BASE_URL, so mounting the bridge is subscription-safe by construction. Tool naming: the tools arefleet_*(renamed frombridge_*in CB-622). The oldbridge_*names were removed in CB-634 — onlyfleet_*answers now. - How the primary consumes a reply: a single blocking MCP call (
fleet_send);fleetdholds it open until the worker callsfleet_replyor its turn hitsagent_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,fleetdinjects 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 ownStop-hook, which pollsfleetd(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) 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,
vendored here as a submodule under wiki/:
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_URLinjected only into the worker's env; status-gated injector; blockingfleet_sendwith reply rendezvous; MCP server as a thin adapter over the REST core. - MCP tools —
fleet_send/fleet_reply/fleet_status(messaging) andfleet_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→idleturn 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_askreverse 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_ttlreaper,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
PeerLauncherSPI with two in-tree adapters,claude-codeandopencode, routed by akind:discriminator (CB-401/CB-402).
Next (see the roadmap) — 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.