From ecc590f344d57037f8b6077788db251095a2b472 Mon Sep 17 00:00:00 2001 From: Dai Ha Date: Sun, 23 Aug 2026 06:46:34 +0200 Subject: [PATCH] CB-632 unit 5: rename the daemon and classes in the docs prose 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 (?(Claude Code, env CLEAN)
MCP client"] - subgraph BD["bridged — standalone daemon (not a claude process)"] + subgraph BD["fleetd — standalone daemon (not a claude process)"] SRV["SERVER face
MCP · REST/SSE · policy"] CLI["CLIENT face
status-gated injector · herdr socket"] SRV --> CLI @@ -47,27 +47,27 @@ flowchart LR ``` - **Subscription boundary:** the *primary* never sets `ANTHROPIC_BASE_URL` (stays on - Pro/Max). Only the *secondary* process is off-subscription — and `bridged` itself is a + 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):** `bridged` is the **sole communication path** for every +- **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 `bridged`-internal. MCP tool I/O never sets `ANTHROPIC_BASE_URL`, so + 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`); - `bridged` holds it open until the worker calls `fleet_reply` or its turn hits + `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 `bridged`'s **MCP rendezvous** — the reply resolves the primary's - blocking call (or, for detached work, `bridged` **injects the primary's idle pane** when it's +- **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 **`bridged`** (never a broker). See the wiki for the two topologies. + 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 @@ -90,7 +90,7 @@ Gitea wiki. ## Status -🟢 **Implemented & dogfooded** — the herdr-centric **`bridged`** message server is built and in +🟢 **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 diff --git a/bridged/src/main/java/dev/ltms/fleet/member/OpenCodeLauncher.java b/bridged/src/main/java/dev/ltms/fleet/member/OpenCodeLauncher.java index 9b760c7..f018f9b 100644 --- a/bridged/src/main/java/dev/ltms/fleet/member/OpenCodeLauncher.java +++ b/bridged/src/main/java/dev/ltms/fleet/member/OpenCodeLauncher.java @@ -362,7 +362,7 @@ public final class OpenCodeLauncher extends HerdrPeerLauncher { options.put("baseURL", openAiBaseUrl(cfg.baseUrl())); // vLLM and friends usually ignore the key, but the AI SDK still requires a non-empty one. String token = resolveEnv(cfg.tokenEnv()); - options.put("apiKey", (token == null || token.isBlank()) ? "bridged-local-noauth" : token); + options.put("apiKey", (token == null || token.isBlank()) ? "fleetd-local-noauth" : token); provider.putObject("models").putObject(modelId).put("name", modelId); } diff --git a/docs/CB-301-Session-Manager.md b/docs/CB-301-Session-Manager.md index 57ad7fd..a4305f4 100644 --- a/docs/CB-301-Session-Manager.md +++ b/docs/CB-301-Session-Manager.md @@ -2,7 +2,7 @@ **Status:** design spec for review → delegate implementation. **Grounded in:** `WorkerService`, `Injector`/`StatusPoller`/`TurnListener`, `MessageService`, -`BridgeMcp`, `BridgedApp` (see [wiki 9. Implementation](../wiki/9-Implementation.md)). +`FleetMcp`, `FleetApp` (see [wiki 9. Implementation](../wiki/9-Implementation.md)). ## Problem @@ -43,7 +43,7 @@ Under no-reuse, a released session is terminal. A new `acquire` always creates a subscription-guarded spawn/teardown mechanics; `SessionManager` adds the registry, lifecycle, and ownership on top. -**Package:** new `dev.ltms.bridged.session` — keeps the registry/lifecycle concern separate from +**Package:** new `dev.ltms.fleet.session` — keeps the registry/lifecycle concern separate from the `worker` spawn mechanics. Holds `SessionManager` + `WorkerSession`. ### `WorkerSession` (record or small mutable holder) @@ -96,10 +96,10 @@ final class SessionManager { ### Integration points -- **`Bridged.main`** — construct `SessionManager(workerService, ...)`; wire it as/decorating the +- **`Fleetd.main`** — construct `SessionManager(workerService, ...)`; wire it as/decorating the `TurnListener` alongside `CompletionResolver` so it sees turn boundaries, and give it the `WorkerPresence` signal for `READY`. -- **`BridgeMcp.spawn` / `BridgedApp.spawnWorker`** — route spawn through `SessionManager.acquire` +- **`FleetMcp.spawn` / `FleetApp.spawnWorker`** — route spawn through `SessionManager.acquire` (carry `callerTerminal` as `ownerTerminal`). **`fleet_stop` / `DELETE /workers/{paneId}`** → `SessionManager.release`. - **`fleet_list` / `GET /sessions` (CB-304 later)** — read `SessionManager.roster()`. diff --git a/docs/CB-301-ext-Worktree-Provisioning.md b/docs/CB-301-ext-Worktree-Provisioning.md index 6f75678..eac46c6 100644 --- a/docs/CB-301-ext-Worktree-Provisioning.md +++ b/docs/CB-301-ext-Worktree-Provisioning.md @@ -2,12 +2,12 @@ **Status:** ✅ shipped — implemented at commit `97ecc71` (per-worker git worktree + config-parity overlay). As-built: `session/GitWorktrees.java` behind the `Worktrees` port, wired in -`Bridged.main` and configurable via `worktreeRoot` / per-profile `parityOverlay` +`Fleetd.main` and configurable via `worktreeRoot` / per-profile `parityOverlay` (see `bridged.example.yaml`). Branch/worktree surface in `fleet_list` landed with CB-304 (`9fe04bf`); the worker-opened-PR checkpoint landed as CB-302 (`64e70ef`). **Extends:** [CB-301 Session Manager](CB-301-Session-Manager.md) (shipped, commit `54d907c`). **Realizes:** the config-parity requirement in [Worker Git Workflow](Worker-Git-Workflow.md). -**Grounded in:** `SessionManager`, `WorkerService.spawn/effectiveCwd`, `BridgedConfig.Worker`, +**Grounded in:** `SessionManager`, `WorkerService.spawn/effectiveCwd`, `FleetConfig.Worker`, `inject/…LsofPeerPidLookup` (the `ProcessBuilder` exec pattern). ## Problem @@ -43,7 +43,7 @@ provider. ### `WorktreeRequest` (new, nullable = "no worktree") ```java -package dev.ltms.bridged.session; +package dev.ltms.fleet.session; /** Ask acquire() to provision an isolated worktree. null ⇒ run in the shared primary tree. */ public record WorktreeRequest(String ticketSlug, String baseRef) { // ticketSlug seeds the branch name; baseRef null/blank ⇒ current HEAD of the repo. @@ -63,7 +63,7 @@ untouched. ### `Worktrees` seam (new) ```java -package dev.ltms.bridged.session; +package dev.ltms.fleet.session; public interface Worktrees { /** git -C worktree add -b . Returns the worktree path. */ String add(String repoRoot, String branch, String baseRef); @@ -112,7 +112,7 @@ public void release(String paneId) { } ``` -### Config — `BridgedConfig.Worker.parityOverlay` + a `worktreeRoot` +### Config — `FleetConfig.Worker.parityOverlay` + a `worktreeRoot` - Add `List parityOverlay` to the `Worker` record (12th field). Compact-constructor default when null/empty: `[".mcp.json", ".claude/settings.local.json", ".env", ".envrc"]` (missing paths are diff --git a/docs/CB-306-Spawn-Readiness-Gate.md b/docs/CB-306-Spawn-Readiness-Gate.md index 9a97bf0..e104d26 100644 --- a/docs/CB-306-Spawn-Readiness-Gate.md +++ b/docs/CB-306-Spawn-Readiness-Gate.md @@ -55,7 +55,7 @@ a crash, and a slow start all present as "never becomes injectable" and all corr or `spawnReadyTimeoutMs` elapses. 3. **Ready** → return the `WorkerHandle(paneId, terminalId)` as today. 4. **Timeout** → the launcher **closes the pane it started** (and its tab, via the same path - `release`/`stop` uses) and throws **`PeerUnreachableException`** (new, in `dev.ltms.bridged.peer`). + `release`/`stop` uses) and throws **`PeerUnreachableException`** (new, in `dev.ltms.fleet.peer`). No orphan pane is left behind — the launcher cleans up its own failed birth. `spawnReadyTimeoutMs == 0` (or unset) **disables** the gate = legacy non-blocking behaviour, so the @@ -80,7 +80,7 @@ Unit tests (add to the existing `ClaudeCodeLauncher` test): ## 5. Config Add to the launcher-level config (a bridged-level knob, not per-profile) in `bridged.yaml` + -`BridgedConfig`: +`FleetConfig`: ```yaml spawn_ready_timeout_ms: 20000 # 0 disables the gate (legacy non-blocking spawn) @@ -88,7 +88,7 @@ spawn_ready_poll_ms: 300 ``` Jackson ignores unknown keys, so omitting them in existing YAML is safe; pick sane defaults in code -(`20000` / `300`). Keep the names consistent with existing config field style in `BridgedConfig`. +(`20000` / `300`). Keep the names consistent with existing config field style in `FleetConfig`. ## 6. Core / MCP propagation @@ -100,7 +100,7 @@ Jackson ignores unknown keys, so omitting them in existing YAML is safe; pick sa - The **non-worktree** path registers the session only *after* `spawn` returns, so a throw means no half-live `SPAWNING` session is ever registered — confirm this and add a test. - `fleet_spawn` (MCP verb) must return an **error result** carrying the exception message, not a - success with a dead session. Trace `BridgeMcp`/`BridgedApp` spawn handlers and make sure the + success with a dead session. Trace `FleetMcp`/`FleetApp` spawn handlers and make sure the exception becomes a clean tool error, not an uncaught 500 with a stack trace. **Out of scope (do NOT do here):** gating `fleet_send` on session `READY` (existing status-gate + @@ -111,7 +111,7 @@ any config `kind:` discriminator, any second adapter. - `ClaudeCodeLauncher.spawn` blocks until injectable or throws `PeerUnreachableException` + self-reaps the pane; gate disabled when timeout is 0. -- New `PeerUnreachableException` in `dev.ltms.bridged.peer`. +- New `PeerUnreachableException` in `dev.ltms.fleet.peer`. - Config knobs wired (`spawn_ready_timeout_ms`, `spawn_ready_poll_ms`) with safe defaults. - Existing `SPAWNING→READY` MCP-contact transition untouched. - New unit tests (ready / timeout+reap / disabled) green; **all existing tests still pass unchanged**. diff --git a/docs/CB-307-Reliable-Delivery.md b/docs/CB-307-Reliable-Delivery.md index 5de1d1e..e827e09 100644 --- a/docs/CB-307-Reliable-Delivery.md +++ b/docs/CB-307-Reliable-Delivery.md @@ -19,8 +19,8 @@ is currently open** for that worker: - `Rendezvous.resolve(session, content)` → `complete(...)` → `waiters.get(session) == null` → returns `false` (`msg/Rendezvous.java:212-215`). - The `content` string is **never retained** — it is dropped. The worker is told it failed: - `BridgeMcp.reply` returns `error("no send is awaiting a reply for this worker")` (`mcp/BridgeMcp.java:270-272`); - REST returns `409 no_pending_send` (`rest/BridgedApp.java:339-345`). + `FleetMcp.reply` returns `error("no send is awaiting a reply for this worker")` (`mcp/FleetMcp.java:270-272`); + REST returns `409 no_pending_send` (`rest/FleetApp.java:339-345`). This is the observed "communication break": a worker that finishes just after its `fleet_send` timed out (the ~60s sync window) replies into the void. There is **no message-id, dedup, or ack** anywhere in @@ -28,13 +28,13 @@ the message path today. ## 2. What to build -### 2.1 The port — `dev.ltms.bridged.msg.ReplyInbox` +### 2.1 The port — `dev.ltms.fleet.msg.ReplyInbox` A thin interface owned by the `msg` layer. The in-memory adapter is Stage 1; the AMQP adapter (Stage 2) implements the **same** interface, so keep it broker-agnostic. ```java -package dev.ltms.bridged.msg; +package dev.ltms.fleet.msg; import java.util.List; @@ -70,7 +70,7 @@ public interface ReplyInbox { seen-set — your call; preserve insertion order). - `peek` returns an immutable copy; `ack` removes by `msgId`. Thread-safe (concurrent publish vs. drain). - **This is soft-state, NOT persistence.** Lost on a `java -jar` bounce — that is correct and consistent - with "bridged stays soft-state." Do **not** add any file/DB backing. + with "fleetd stays soft-state." Do **not** add any file/DB backing. ### 2.3 Publish seam — route reply through the service layer @@ -89,9 +89,9 @@ in `MessageService`, which already owns the `Rendezvous` and will own the `Reply } ``` - Repoint the two callers off the bare `rendezvous.resolve(...)` onto `messages.reply(...)`: - - `BridgeMcp.reply` (`mcp/BridgeMcp.java:262-273`) — on success return a normal ack; **remove** the + - `FleetMcp.reply` (`mcp/FleetMcp.java:262-273`) — on success return a normal ack; **remove** the `error("no send is awaiting a reply…")` branch (that case is now a successful queue). - - `BridgedApp.replyMessage` (`rest/BridgedApp.java:330-346`) — return `200` (queued) instead of + - `FleetApp.replyMessage` (`rest/FleetApp.java:330-346`) — return `200` (queued) instead of `409 no_pending_send`. **DO NOT touch the QUESTION path.** `fleet_ask` / `rendezvous.resolveQuestion` must keep today's @@ -120,12 +120,12 @@ The primary re-checks a worker it delegated to. Expose a drain keyed by **worker ## 3. Config **None for Stage 1.** The in-memory adapter is the unconditional default — wire `new InMemoryReplyInbox()` -into `MessageService` in `Bridged.main`. Do **not** add a `broker:` config block (that arrives with the +into `MessageService` in `Fleetd.main`. Do **not** add a `broker:` config block (that arrives with the Stage-2 AMQP adapter: absent → in-memory, present → AMQP). ## 4. Acceptance criteria (what the primary will verify) -1. New `ReplyInbox` + `InboxMessage` + `InMemoryReplyInbox` in `dev.ltms.bridged.msg`. +1. New `ReplyInbox` + `InboxMessage` + `InMemoryReplyInbox` in `dev.ltms.fleet.msg`. 2. `fleet_reply` with **no open send** now **succeeds and queues** (no more `error` / `409`); the reply is later retrievable and identical. 3. The queued reply is drainable by the primary keyed by target; draining **acks** it (a second drain @@ -152,7 +152,7 @@ Stage-2 AMQP adapter: absent → in-memory, present → AMQP). - **`.mcp.json` is `--skip-worktree` in your worktree — never edit, `git add`, or commit it.** - **`wiki/` is a submodule — never run git in it; never touch it.** - Commit only your feature changes (the new port/adapter, the `msg`/`mcp`/`rest` wiring, tests, and if - you add config wiring in `Bridged.java`). Nothing else. + you add config wiring in `Fleetd.java`). Nothing else. - Work only inside your assigned worktree on your feature branch. The primary fast-forwards `main` after re-gating — do not touch `main`. - Java 25 idioms are welcome (unnamed `_` params, records). Keep the diff minimal and match surrounding style. @@ -171,7 +171,7 @@ removal, msgId dedup, cross-restart redelivery). It is `@Tag("contract")`, so th untouched. Run it explicitly when Docker (or a broker) is available: ```bash -cd bridged +cd fleetd mvn -Pcontract test -Dtest=AmqpReplyInboxContractTest # local: spins a RabbitMQ Testcontainers fixture ``` diff --git a/docs/CB-308-Multi-Host-Federation.md b/docs/CB-308-Multi-Host-Federation.md index 6e0730f..67c5b48 100644 --- a/docs/CB-308-Multi-Host-Federation.md +++ b/docs/CB-308-Multi-Host-Federation.md @@ -18,7 +18,7 @@ The design rests on three pieces (the shape this ticket proposes): 1. **Dedicated per-agent channels** — every agent has its own addressable inbox on the broker. 2. **A federated agent directory** — a global "who/where/status" lookup, assembled from per-host presence, not a central database. -3. **A per-host gateway** — each host runs a `bridged` that owns its local herdr, registers/manages +3. **A per-host gateway** — each host runs a `fleetd` that owns its local herdr, registers/manages its own sessions, and proxies messages to/from other hosts over the broker. ## 2. What is single-host today (the assumptions to break) @@ -27,7 +27,7 @@ The design rests on three pieces (the shape this ticket proposes): flowchart TB subgraph host["Single host (today)"] primary["primary
(MCP client)"] - daemon["bridged daemon
127.0.0.1:8765"] + daemon["fleetd daemon
127.0.0.1:8765"] reg["in-process registry
keyed by PeerHandle.id() == paneId"] herdr["herdr
(local unix-socket PTY mux)"] w1["worker pane wQ:p1"] @@ -48,21 +48,21 @@ Three concrete bake-ins assume one host: |---|---|---| | **herdr is local** | `herdr/` unix socket `~/.config/herdr/herdr.sock` | You cannot drive another host's PTYs → each host **must** own its herdr. This is why a per-host gateway is mandatory. | | **registry is in-process, keyed by `paneId`** | `session/SessionManager` | `paneId` (e.g. `wQ:p2B`) is a herdr-local coordinate — meaningless off-host. Routing needs a host-unique id. | -| **loopback, no authn** | `rest/BridgedApp` binds `127.0.0.1:8765` | Fine on one host; the moment a second host can talk to a gateway, that link is a trust boundary. | +| **loopback, no authn** | `rest/FleetApp` binds `127.0.0.1:8765` | Fine on one host; the moment a second host can talk to a gateway, that link is a trust boundary. | ## 3. Target architecture ```mermaid flowchart TB subgraph hostA["HOST A"] - gA["gateway = bridged A"] + gA["gateway = fleetd A"] regA["local registry + herdr"] primary["primary (MCP client)"] gA --- regA primary --- gA end subgraph hostB["HOST B"] - gB["gateway = bridged B"] + gB["gateway = fleetd B"] regB["local registry + herdr"] wb["worker panes"] gB --- regB @@ -96,13 +96,13 @@ host's terminals.* delayed-message exchange (the remind/backoff loop for free) — the same reasons CB-307 picked it. - **Federated agent directory** = a **soft-state, bridge-owned** roster, *not* a broker-stored - database. Per the persistence-boundary decision (bridged is soft-state; the broker owns *message* + database. Per the persistence-boundary decision (fleetd is soft-state; the broker owns *message* durability, not *who/where/status*), each gateway announces its local agents `(globalId, host, status, capabilities)` on a `roster.*` presence topic with periodic heartbeats. Every gateway builds an eventually-consistent **union view** — literally CB-304's `rosterView`, federated. A stale entry expires by missed heartbeat (reuses CB-303's idle/TTL thinking). -- **Per-host gateway** = today's `bridged` daemon, evolved. It already registers/manages sessions +- **Per-host gateway** = today's `fleetd` daemon, evolved. It already registers/manages sessions and controls its local herdr; multi-host adds exactly two responsibilities: (a) a broker client that consumes its agents' inboxes and injects into local herdr, and (b) presence announce + union-roster assembly. Evolution, not rewrite. @@ -123,7 +123,7 @@ broker. A sender is oblivious to which branch it took.* ## 4. What CB-307 already provides vs. what is net-new **CB-307 delivers the transport half** and is independently valuable on a single host: the AMQP -broker fabric, the `bridged → broker` client/adapter, at-least-once + idempotent (dedup-by-id) +broker fabric, the `fleetd → broker` client/adapter, at-least-once + idempotent (dedup-by-id) delivery, DLQ, and delayed-retry (remind). That *is* the "proxy cross-host message" backbone; extending the same broker from "worker→primary reliability" to "gateway↔gateway" is incremental. diff --git a/docs/CB-401-Peer-Launcher-SPI.md b/docs/CB-401-Peer-Launcher-SPI.md index 2e334bd..b72aa22 100644 --- a/docs/CB-401-Peer-Launcher-SPI.md +++ b/docs/CB-401-Peer-Launcher-SPI.md @@ -60,7 +60,7 @@ Where Claude/herdr specifics actually live today: | `claude---` naming, `WORKER_NAME` regex, orphan reap (CB-117) | `WorkerService` | **→ adapter** (naming is a herdr-label detail) | | `GITEA_TOKEN` / `GITEA_HOST` injection (CB-302 checkpoint) | `WorkerService.spawn` | **→ adapter** + a **capability** (§6) | | tab/pane placement, worker space, tab labels | `WorkerService.spawnInTab/spawnAsPane` via herdr `WorkspaceControl` | **→ adapter** (herdr transport detail) | -| `BridgedConfig.Worker` profile shape (`baseUrl`, `model`, `configDir`, …) | `config` | **mostly adapter-shaped** — see §5 | +| `FleetConfig.Worker` profile shape (`baseUrl`, `model`, `configDir`, …) | `config` | **mostly adapter-shaped** — see §5 | | FSM, registry, roster, `reapIdle`/`drainAll`/`contextCap`, `rosterView` | `SessionManager` | **stays core** | | turn/completion detection (`TurnListener`, `CompletionResolver`, `StatusPoller`, `WorkerPresence`) | `inject/` | **stays core**, but reads herdr terminal output → transport-coupled (§4b) | | message store & routing | `msg/` | **stays core** | @@ -118,7 +118,7 @@ hard-wire "turns come from herdr". ## 5. Config shape -`BridgedConfig.Worker` is Claude-shaped (`baseUrl`, `model`, `configDir`, `tokenEnv`). Rather than +`FleetConfig.Worker` is Claude-shaped (`baseUrl`, `model`, `configDir`, `tokenEnv`). Rather than break existing YAML, CB-401 keeps `workers:` exactly as-is and treats those fields as the **ClaudeCodeLauncher's** profile schema. A future peer kind adds a `kind:` discriminator (default `"claude-code"`) selecting the launcher; unknown-kind → clear config error. No migration of @@ -185,13 +185,13 @@ messages, not verified facts. Deliverable for CB-401 Stage A — mechanical, behaviour-preserving: 1. `PeerLauncher` interface + `PeerHandle` (opaque id) + `SpawnRequest` (profile, requestedCwd, - callerCwd) + `Capability` enum, new package `dev.ltms.bridged.peer`. + callerCwd) + `Capability` enum, new package `dev.ltms.fleet.peer`. 2. `ClaudeCodeLauncher implements PeerLauncher` = today's `WorkerService`, adapted: `spawn(...)` returns a `PeerHandle` (id = paneId), `capabilities()` declares `MID_TURN_ASK, SELF_PR(when token), WORKTREE, ORPHAN_REAP`. 3. `SessionManager` depends on `PeerLauncher`, not `WorkerService` concretely; routing keys on `PeerHandle.id()` (== paneId today, so zero value change). -4. `Bridged.main` wires the concrete `ClaudeCodeLauncher` behind the interface. +4. `Fleetd.main` wires the concrete `ClaudeCodeLauncher` behind the interface. 5. **No behaviour change, no config change.** Full green gate: `ide_sync` → `ide_diagnostics` (0 errors/0 warnings) → `mvn clean install` with `MVN_EXIT` captured (no masking pipe). All existing tests pass unchanged; add tests only for the new `PeerHandle` indirection. diff --git a/docs/CB-402-OpenCode-Adapter.md b/docs/CB-402-OpenCode-Adapter.md index 282fd03..c1de564 100644 --- a/docs/CB-402-OpenCode-Adapter.md +++ b/docs/CB-402-OpenCode-Adapter.md @@ -11,7 +11,7 @@ All five increments of §4 are done, including increment 5 (the §5 live checkli ## 1. Goal -Prove the [`PeerLauncher`](../bridged/src/main/java/dev/ltms/bridged/peer/PeerLauncher.java) SPI +Prove the [`PeerLauncher`](../bridged/src/main/java/dev/ltms/fleet/peer/PeerLauncher.java) SPI actually holds for a **non-Claude** coding agent by shipping a second, first-class in-tree adapter: **opencode** (`opencode` 1.1.31, a provider-agnostic terminal coding agent). @@ -56,8 +56,8 @@ flowchart TB *Figure 1 — the two concerns tangled inside today's single launcher; CB-402 splits them.* -There is also a **Stage-A deferral** to finish: `Bridged.main` still casts -`(ClaudeCodeLauncher) workers` at the `BridgeMcp` and `BridgedApp` constructors. Those two +There is also a **Stage-A deferral** to finish: `Fleetd.main` still casts +`(ClaudeCodeLauncher) workers` at the `FleetMcp` and `FleetApp` constructors. Those two callers only invoke `profiles()`, `defaultProfile()`, and `list()` — **all already on the `PeerLauncher` interface**. The cast survives for one reason only: `PeerLauncher.list()` returns `List` (element type erased) while the callers use `Agent` element methods in their @@ -68,7 +68,7 @@ roster join. Finishing the migration is therefore small and contained (§4.D). ## 3. Target design Template-Method base + two thin adapters + a routing composite that keeps the Stage-A seam -(one `PeerLauncher` reference held by `SessionManager` / `BridgeMcp` / `BridgedApp`) intact. +(one `PeerLauncher` reference held by `SessionManager` / `FleetMcp` / `FleetApp`) intact. ```mermaid flowchart TB @@ -114,7 +114,7 @@ Two adapter **hooks** (abstract): protected abstract String namePrefix(); // "claude" | "opencode" /** Build the peer-specific launch: env map + argv. Runs any pre-spawn guard here. */ -protected abstract Launch buildLaunch(BridgedConfig.Worker cfg, SpawnRequest req); +protected abstract Launch buildLaunch(FleetConfig.Worker cfg, SpawnRequest req); record Launch(Map env, List argv) {} ``` @@ -126,7 +126,7 @@ so the opencode adapter never reaps a `claude-*` pane and vice-versa. The compos ### B. `kind:` config discriminator -Add one field to `BridgedConfig.Worker`: +Add one field to `FleetConfig.Worker`: ```java String kind // "claude-code" (default) | "opencode" @@ -164,13 +164,13 @@ String kind // "claude-code" (default) | "opencode" ### D. `CompositePeerLauncher` + finish the Stage-A migration -- `Bridged.main` groups configured profiles by `kind`, instantiates one launcher per kind +- `Fleetd.main` groups configured profiles by `kind`, instantiates one launcher per kind present, and wraps them in `CompositePeerLauncher implements PeerLauncher`. - Routing methods (`spawn(req)`, `effectiveCwd(req)`, `parityOverlay(name)`) dispatch by the profile's kind. Fan-out methods (`list()`, `reapOrphanWorkers()`, `capabilities()`, `profiles()`, `defaultProfile()`) merge across sub-launchers. `stop(id)` tries each (teardown only knows the pane id) — already best-effort/idempotent. -- **Migrate `BridgeMcp` + `BridgedApp` to the `PeerLauncher` interface**, dropping both +- **Migrate `FleetMcp` + `FleetApp` to the `PeerLauncher` interface**, dropping both `(ClaudeCodeLauncher)` casts. Only friction is `list()`'s `List`; resolve by giving the SPI a typed roster element (small neutral `PeerAgent` view exposing `id()`/`name()`/status) that the CB-304 roster join consumes — or, minimally, narrow at the callsite. Prefer the typed view. @@ -179,7 +179,7 @@ String kind // "claude-code" (default) | "opencode" sequenceDiagram autonumber participant P as Primary - participant M as BridgeMcp / REST + participant M as FleetMcp / REST participant C as CompositePeerLauncher participant O as OpenCodeLauncher participant B as HerdrPeerLauncher (base) @@ -244,7 +244,7 @@ until it is "major" (Stage-B whole), per the CB-401 bar. - **Unit (hermetic):** base-extraction regression (existing `ClaudeCodeLauncher` tests pass unchanged); `OpenCodeLauncher.buildLaunch` env/argv/config assertions; `kind` normalization - in `BridgedConfigTest`; `CompositePeerLauncher` routing + fan-out (merge of `profiles()`, + in `FleetConfigTest`; `CompositePeerLauncher` routing + fan-out (merge of `profiles()`, summed `reapOrphanWorkers()`, per-kind reap isolation) with fake sub-launchers. - **Live (dogfood, manual):** the §5 checklist on the running daemon. - **Gate (primary):** IDE diagnostics 0/0 on every changed file, `mvn clean install` green with @@ -269,7 +269,7 @@ until it is "major" (Stage-B whole), per the CB-401 bar. ## 8. As-built — live dogfood (2026-07-29) -Run against `bridged` on `127.0.0.1:8766` at main `19cdf8d`, with opencode **1.18.5** installed +Run against `fleetd` on `127.0.0.1:8766` at main `19cdf8d`, with opencode **1.18.5** installed via Homebrew. Every §5 risk is now a verified fact rather than an assumption. **The version-drift risk was the real one, and it did not bite.** This adapter was designed against diff --git a/docs/CB-500-Multi-Tier-Coordination.md b/docs/CB-500-Multi-Tier-Coordination.md index 9bc3d59..964c32c 100644 --- a/docs/CB-500-Multi-Tier-Coordination.md +++ b/docs/CB-500-Multi-Tier-Coordination.md @@ -38,7 +38,7 @@ never toolchain ownership (§7). flowchart TB human["human (types)"] primary["PRIMARY (Opus)
MCP client — pull-only"] - daemon["bridged daemon
127.0.0.1:8765 (single host)"] + daemon["fleetd daemon
127.0.0.1:8765 (single host)"] comp["CompositePeerLauncher
routes by kind"] cc["ClaudeCodeLauncher"] oc["OpenCodeLauncher"] @@ -80,7 +80,7 @@ flowchart TB m1["main A: Opus
MCP client"] m2["main B: cloud module
MCP client"] end - subgraph bus["bridged fabric (CB-307/308 substrate)"] + subgraph bus["fleetd fabric (CB-307/308 substrate)"] chan["per-agent inbox channels
agent.<globalId>.inbox"] roster["federated roster (union view)"] end @@ -146,7 +146,7 @@ env-manager" rule (§7).* ```mermaid sequenceDiagram participant M as main (delegator) - participant D as bridged + participant D as fleetd participant SL as SandboxLauncher participant SB as sandbox (peer-owned) participant A as agent in sandbox @@ -189,7 +189,7 @@ flowchart TB m2["main B: cloud module
MCP client (pull-only)"] end reg["PrimaryRegistry → multi-slot
(terminal per main)"] - subgraph fabric["bridged"] + subgraph fabric["fleetd"] ca["agent.A.inbox"] cb["agent.B.inbox"] push["ReplyPushLoop → N terminals"] @@ -211,7 +211,7 @@ transport is already peer-neutral — what was missing is N pull endpoints).* ```mermaid sequenceDiagram participant MA as main A (Opus) - participant BR as bridged / broker + participant BR as fleetd / broker participant MB as main B (cloud) MA->>BR: fleet_send(to = main B, msg) BR->>BR: publish agent.B.inbox (durable, msg id) @@ -233,7 +233,7 @@ push-loop fan-out; relax "orchestration tools only the primary calls" to "any re ## 6. Development C — Orchestrator tier > **SUPERSEDED — do not implement this model.** The operator rejected a supervisor above the lead. -> The human continues to drive the pre-existing lead directly; bridged neither spawns nor resumes that +> The human continues to drive the pre-existing lead directly; fleetd neither spawns nor resumes that > lead. What replaced this proposal is **one lead, two short-lived advisory architects, and N workers**: > the lead engages architects sideways for a strong-model assessment, then discards them. Architect > slots are declared in `architects:` (see Gitea issue #16), rather than making leads managed sessions. @@ -243,7 +243,7 @@ push-loop fan-out; relax "orchestration tools only the primary calls" to "any re > **Historical alternative retained.** The text and figures below record the considered model and why it > was rejected: it re-rooted the human-facing session above the lead, violating the still-true premise -> that configured leaders pre-exist, are recognised, and cannot be resumed by bridged. +> that configured leaders pre-exist, are recognised, and cannot be resumed by fleetd. The orchestrator is **`SessionManager` recursed one tier up**: today it spawns/names/reaps *worker* sessions; the orchestrator does the same for *main* sessions, and adds **context scoping**. @@ -385,7 +385,7 @@ ticket is an extension of an existing pattern (CB-402 for A, CB-308 for B/C). The follow-up question — *"clarify the architecture when we have distributed agents in sandboxes"* — resolves the fork left open in §4 and §9. **Decision: Development A (sandbox launcher) and CB-308 -(per-host federation) *compose*, not compete — each host runs a `bridged` gateway whose launcher +(per-host federation) *compose*, not compete — each host runs a `fleetd` gateway whose launcher spawns agents into that host's *local* sandboxes.** A sandbox is never reached across the network; it is reached by the gateway sitting next to it. @@ -393,7 +393,7 @@ is reached by the gateway sitting next to it. The bus delivers a turn by **herdr keystroke-injection** — `Injector → AgentControl.send` writes into a PTY that its **local** herdr owns. The broker moves *messages and presence*, **never keystrokes**. -So an agent's PTY must live in a herdr that *some* `bridged` instance drives locally: a remote +So an agent's PTY must live in a herdr that *some* `fleetd` instance drives locally: a remote container with no local herdr **cannot be injected into**. That rules out a central daemon reaching remote PTYs, and collapses the design to a single identity: @@ -403,7 +403,7 @@ remote PTYs, and collapses the design to a single identity: flowchart TB subgraph hostA["HOST A — gateway"] mA["main / orchestrator
MCP client → LOCAL gateway"] - gA["bridged A
herdr + CompositePeerLauncher
(incl. SandboxLauncher)"] + gA["fleetd A
herdr + CompositePeerLauncher
(incl. SandboxLauncher)"] cBEa["sandbox: backend
(local container)"] cFEa["sandbox: frontend
(local container)"] mA --- gA @@ -415,7 +415,7 @@ flowchart TB roster["roster.* (federated presence)"] end subgraph hostB["HOST B — gateway"] - gB["bridged B
herdr + SandboxLauncher"] + gB["fleetd B
herdr + SandboxLauncher"] cBEb["sandbox: backend
(local container)"] gB -->|"spawn → PTY in B's herdr"| cBEb end @@ -470,7 +470,7 @@ unchanged — the sandbox is transparent to it.* | Concern | Provided by | |---|---| -| Per-host gateway (owns local herdr + sessions) | **CB-308** (today's `bridged`, evolved) | +| Per-host gateway (owns local herdr + sessions) | **CB-308** (today's `fleetd`, evolved) | | Spawn into a local sandbox / role→image | **Development A** `SandboxLauncher` (§4), routed by `CompositePeerLauncher` | | Addressing a remote sandboxed agent | **CB-308** global id + federated roster (host + role as metadata) | | Orphan reap after a gateway restart | **CB-117** per-gateway, summed by the composite — each reaps only its **local** herdr | diff --git a/docs/CB-591-Gateway-Migration.md b/docs/CB-591-Gateway-Migration.md index f690ed9..e18d40f 100644 --- a/docs/CB-591-Gateway-Migration.md +++ b/docs/CB-591-Gateway-Migration.md @@ -51,7 +51,7 @@ Three facts about our side that decide the shape of this work: `ANTHROPIC_AUTH_TOKEN` (the value is read from a host env var and never stored in config). `local` sets no `tokenEnv` today, because a direct vLLM needs no token. 2. **`SubscriptionGuard` refuses any host not on an allowlist**, and that allowlist is - `guard.offSubscriptionHosts: [gx00.gw]`. It is built once in `Bridged.java:93` and handed to the + `guard.offSubscriptionHosts: [gx00.gw]`. It is built once in `Fleetd.java:93` and handed to the launcher, so **it is a restart-required key**, not a hot one. Changing `baseUrl` without changing this makes every `local` spawn throw. 3. **The wiki names us as a blocker.** Under *Not done yet*: retiring the shared `legacy` token is @@ -132,7 +132,7 @@ own provider credentials. So 3b needs **no allowlist change**; only 3a does. **Both still need a restart, for a different reason.** `tokenEnv` is resolved by `HerdrPeerLauncher.resolveEnv` → `env.apply(name)`, which reads the **daemon's own process -environment**. The running `bridged` inherited its environment when it started, so a variable added to +environment**. The running `fleetd` inherited its environment when it started, so a variable added to `secrets.sh` afterwards is simply not there — the launcher would inject an empty token and the gateway would answer 401. This is the same failure as trap 1 in `scripts/redeploy-bridged.sh` (`WORKER_GITEA_TOKEN`), and it has the same fix: **restart from a login shell**, and use @@ -207,7 +207,7 @@ worker's honesty rule leans on it ("never claim the result of a check you had no - **Keep bridge-only.** The invariant stays true and simple. Workers stay cheap and narrow. - **Add the gateway MCP.** Implementers get context7 documentation lookups, which is genuinely useful - for library work. But `mcpUrl` in `BridgedConfig.Profile` is a **single `String`**, so a + for library work. But `mcpUrl` in `FleetConfig.Profile` is a **single `String`**, so a claude-code member can mount exactly one MCP — this needs a code change, not a config edit. Note the invariant is **already inaccurate**: `opencode.json` gives `sol` and `terra` both context7 @@ -383,7 +383,7 @@ one turn; this one costs the whole task and is indistinguishable from a slow wor - `/v1/models` returns exactly `["deepseek-v4-flash"]`, so trap 3 is clear. - **Reasoning survives both surfaces** — see §3b above. - The launcher's generated opencode provider block is correct, carrying a real 48-character `llmk-` - key rather than the `bridged-local-noauth` placeholder. + key rather than the `fleetd-local-noauth` placeholder. - `SubscriptionGuard` accepted `llm.ltms.dev` after the allowlist edit and the restart: `local` spawned without throwing, which is the check that catches a missed restart. diff --git a/docs/CB-5xx-Hardening.md b/docs/CB-5xx-Hardening.md index 48d2d27..ada5f87 100644 --- a/docs/CB-5xx-Hardening.md +++ b/docs/CB-5xx-Hardening.md @@ -12,12 +12,12 @@ not after it. ## 1. Why this stage is not optional bookkeeping -`bridged` today has **exactly one security control: the loopback bind**. Every other guarantee +`fleetd` today has **exactly one security control: the loopback bind**. Every other guarantee rests on it. The identity model (`mcp/ConnectionIdentity.java`) resolves a caller from the connection alone — the OS reports the connecting PID, herdr owns the PID→pane map, so a worker cannot forge another -worker. Its own javadoc is explicit: *"Single-host only (the herd shares the `bridged` host); the +worker. Its own javadoc is explicit: *"Single-host only (the herd shares the `fleetd` host); the token path is the split-host fallback."* The token path does not exist yet. That leaves a seam that is **latent today and load-bearing the moment the bind moves**: @@ -112,7 +112,7 @@ The roadmap says "systemd unit". **This host is macOS — there is no systemd on not found), and the daemon that has been dogfooded for weeks runs as a bare foreground `java -jar`. Ship **both**: -- `deploy/dev.ltms.bridged.plist` — launchd agent, the *actual* runtime here, with `KeepAlive` and +- `deploy/dev.ltms.fleet.plist` — launchd agent, the *actual* runtime here, with `KeepAlive` and ordered start after herdr. - `deploy/bridged.service` — systemd unit for the Linux gateways CB-308 introduces. @@ -158,10 +158,10 @@ configured. It already leaks nothing but herdr's version and up/down. ### 3.1 There are TWO entry paths, and only one of them has identity today The wiki describes MCP as "a thin adapter over the REST core". **At the code level that is not -literally true, and the difference is security-relevant.** `BridgeMcp` calls `MessageService` / +literally true, and the difference is security-relevant.** `FleetMcp` calls `MessageService` / `SessionManager` *directly*; it never issues an HTTP request against a Javalin route. And `/mcp` is mounted as a raw servlet on Jetty's `ServletContextHandler` -(`BridgedApp.build → cfg.jetty.modifyServletContextHandler`), so it does **not** pass through +(`FleetApp.build → cfg.jetty.modifyServletContextHandler`), so it does **not** pass through Javalin's `before` filters at all. The current split is the mirror image of what you'd expect: @@ -226,7 +226,7 @@ was answered from the forge itself. Kept here as the decision record.* 1. ✅ **TLS scope (D3) — confirmed.** Bearer auth + the fail-fast bind guard ship in the daemon; TLS terminates at a reverse proxy, documented with a worked example. AMQP gets TLS via an - `amqps://` URI. No keystore handling in `bridged`. + `amqps://` URI. No keystore handling in `fleetd`. 2. ✅ **Micrometer (D4) — confirmed dropped.** Zero-dependency Prometheus text renderer, for the reasons in D4 (pom reconciliation burden + the mandated CVE gate being un-runnable this session). Revisit if a push-gateway or JVM-metrics requirement appears; the endpoint is the diff --git a/docs/M4-Fleet-Health.md b/docs/M4-Fleet-Health.md index 99c74d7..dd9da54 100644 --- a/docs/M4-Fleet-Health.md +++ b/docs/M4-Fleet-Health.md @@ -72,7 +72,7 @@ post-teardown invariant so a later regression becomes `DELEGATION_ORPHANED`. ### 2.2 Corrections made during design -The first state table missed `BUSY` in bridged plus `IDLE` or `DONE` in herdr. It would have found +The first state table missed `BUSY` in fleetd plus `IDLE` or `DONE` in herdr. It would have found the real trace only through a late, weak stall timer. The final model adds `TURN_BOUNDARY_LOST` as a strong disagreement state. @@ -161,7 +161,7 @@ request or merge state. If committed work appears with `REPLY_STRANDED` or | State | Exact evidence | Certainty and action | |---|---|---| -| `CONTROL_LINK_DOWN` | Two full-fleet `agent.list` calls fail across the grace, and herdr `ping` also fails | Certain for the bridged-to-herdr link. Retry calls, record the incident, and use human escalation if no lead can be reached. | +| `CONTROL_LINK_DOWN` | Two full-fleet `agent.list` calls fail across the grace, and herdr `ping` also fails | Certain for the fleetd-to-herdr link. Retry calls, record the incident, and use human escalation if no lead can be reached. | A failed fleet list alone is not a dead-member claim. A single `_not_found` with a healthy global link is a target fault, not a control-link fault. @@ -298,7 +298,7 @@ Compiled brakes apply even if config asks for more: - at most two pane reads occur in one fleet tick; - targets rotate fairly; - only a normalised digest and optional clipped local excerpt are stored; -- no pane excerpt leaves bridged in a human webhook. +- no pane excerpt leaves fleetd in a human webhook. Use `detection` for tested screen signatures. Use normalised `recent_unwrapped` only for progress comparison. @@ -330,7 +330,7 @@ The reader uses AMQP `content_type`, never body sniffing: ```text Legacy v0: text/plain -Typed family: application/vnd.ltms.bridged.inbox-message+json +Typed family: application/vnd.ltms.fleet.inbox-message+json ``` A legacy reply may begin with `{`. It remains plain text because its media type is `text/plain`. @@ -520,7 +520,7 @@ A repaired result uses distinct `RECONCILED_COMPLETION` values in `Rendezvous`, task poll source, and metrics. The lead sees: ```text -[repaired completion - bridged detected a lost turn boundary. The member did not call +[repaired completion - fleetd detected a lost turn boundary. The member did not call fleet_reply; pane-derived text follows and may be partial] ``` diff --git a/docs/MCP-Contract.md b/docs/MCP-Contract.md index 4fac394..2e79765 100644 --- a/docs/MCP-Contract.md +++ b/docs/MCP-Contract.md @@ -1,4 +1,4 @@ -# MCP Contract — `bridged`'s unified gateway +# MCP Contract — `fleetd`'s unified gateway > **Status: 🔴 HISTORICAL DESIGN — do NOT use as the tool reference.** Written 2026-07-14, before > any MCP code existed. The system shipped and this page never caught up, so **its tool names, @@ -14,13 +14,13 @@ > > **The authoritative tool surface is the live MCP schema** (each tool's own description and > parameters, as mounted), with the intent→tool table in `CLAUDE.md` as the short form. Both were -> checked against `mcp/BridgeMcp.java` on 2026-08-17 and are accurate. +> checked against `mcp/FleetMcp.java` on 2026-08-17 and are accurate. > > What is still worth reading here is **§6 — the flows and the error model** (rendezvous, > `fleet_ask`, detached delivery, the turn-done fallback). The shapes it describes are the ones > that shipped; only the names around them drifted. Rewriting this page is tracked as **CB-609**. -`bridged` is the **sole communication gateway** for every Claude session in the bridge. Both +`fleetd` is the **sole communication gateway** for every Claude session in the bridge. Both the **primary** (Opus, on subscription) and every **worker** (off-subscription Claude Code) mount the *same* MCP server with a single `claude mcp add` line, and talk only through its tools. No Claude session ever addresses a broker, a peer, or the network directly. @@ -35,18 +35,18 @@ semantics, and how it maps onto the code already in the tree. These come from the project's core invariants and bound every decision below. 1. **One server, both roles.** The primary and all workers mount an identical server. The - catalog must serve both, and `bridged` must decide *who is calling* from the connection — + catalog must serve both, and `fleetd` must decide *who is calling* from the connection — never from a caller-supplied argument that could be spoofed. 2. **Subscription-safe by construction.** No MCP tool ever reads, sets, or forwards `ANTHROPIC_BASE_URL`. Mounting the bridge cannot move a session off subscription. Enforced today by [`SubscriptionGuard`](1-Architecture). 3. **Blocking rendezvous, no busy-poll.** The primary consumes a worker's reply through a - *single* MCP call that `bridged` holds open — never a cross-turn poll loop that would burn + *single* MCP call that `fleetd` holds open — never a cross-turn poll loop that would burn subscription quota. 4. **Status-gated delivery.** Anything that puts text into a worker flows through the existing [`Injector`](1-Architecture): delivered only when the worker is `idle`/`blocked`, at most one message per turn. -5. **`bridged` owns policy; herdr owns PTYs.** MCP tools express *intent*; `bridged` +5. **`fleetd` owns policy; herdr owns PTYs.** MCP tools express *intent*; `fleetd` translates it into guard checks, rendezvous bookkeeping, and herdr `agent.*` calls. --- @@ -59,7 +59,7 @@ face** is the herdr Unix socket. REST/SSE remains only for non-Claude clients an ```mermaid flowchart LR OPUS["Opus — primary
(Claude Code, env CLEAN)
MCP client"] - subgraph BD["bridged — standalone daemon"] + subgraph BD["fleetd — standalone daemon"] MCP["MCP server (north face)
bridge_send · bridge_reply
bridge_ask · bridge_status · lifecycle"] RDV["rendezvous registry
(blocking-call waiters)"] INJ["Injector + StatusPoller
(status-gated writer)"] @@ -87,10 +87,10 @@ flowchart LR ## 3. Identity & addressing -Because the same server is mounted by everyone, `bridged` resolves the caller's role on every +Because the same server is mounted by everyone, `fleetd` resolves the caller's role on every request — this is the linchpin of the whole contract and has no code yet. -- **Workers are known.** `bridged` spawns every worker +- **Workers are known.** `fleetd` spawns every worker ([`WorkerService`](1-Architecture)) and records its herdr session UUID / `terminal_id` on the returned [`Agent`]. When a call arrives on a connection that maps to a known worker, the caller is *that* worker — so **workers never pass a target**; routing is implicit. @@ -106,13 +106,13 @@ request — this is the linchpin of the whole contract and has no code yet. ## 4. Transport -`bridged` is a long-lived daemon serving **multiple** concurrent clients (one primary + N +`fleetd` is a long-lived daemon serving **multiple** concurrent clients (one primary + N workers), so a per-client stdio child is the wrong shape. The recommended transport is **streamable-HTTP / SSE** on the same bind as the REST face: ```bash # identical on primary and every worker -claude mcp add --transport http bridged http://127.0.0.1:8080/mcp +claude mcp add --transport http fleetd http://127.0.0.1:8080/mcp ``` This adds an MCP-server dependency the pom does not yet carry. See [Open decisions](#10-open-decisions). @@ -157,7 +157,7 @@ This adds an MCP-server dependency the pom does not yet carry. See [Open decisio - **Params:** `text` (required); `final` (default `true`). - **Behavior:** resolve the primary waiter registered against this worker with `text`. If no - waiter exists (detached delegation), `bridged` **injects the primary's idle pane** instead. + waiter exists (detached delegation), `fleetd` **injects the primary's idle pane** instead. Returns `{ delivered:true, mode:"resolved"|"injected" }`. No `target` — identity is implicit. #### `bridge_ask` @@ -216,7 +216,7 @@ One blocking call, zero polls. ```mermaid sequenceDiagram participant P as Primary (Opus) - participant B as bridged (MCP + Injector) + participant B as fleetd (MCP + Injector) participant H as herdr participant W as Worker (Claude) @@ -237,7 +237,7 @@ The worker pauses mid-turn to ask; the primary answers; the worker resumes in th ```mermaid sequenceDiagram participant P as Primary - participant B as bridged + participant B as fleetd participant W as Worker P->>B: fleet_send("do X", target=w) — blocks @@ -258,7 +258,7 @@ The primary does not block; the reply arrives later in its idle pane. ```mermaid sequenceDiagram participant P as Primary - participant B as bridged + participant B as fleetd participant W as Worker P->>B: fleet_send("do X", target=w, block=false) @@ -272,13 +272,13 @@ sequenceDiagram ### 6.4 Uncooperative worker — turn-done fallback -A worker that never calls `fleet_reply` still returns a result: `bridged` reads its terminal +A worker that never calls `fleet_reply` still returns a result: `fleetd` reads its terminal tail when the turn completes. ```mermaid sequenceDiagram participant P as Primary - participant B as bridged + participant B as fleetd participant W as Worker P->>B: fleet_send("do X", target=w) — blocks @@ -358,7 +358,7 @@ seam. Only the **rendezvous registry** and the **caller-identity resolver** are | `bridge_spawn` / `list` / `stop` | `WorkerService.{spawn,list,stop}` | MCP adapter only | | `bridge_read` | `AgentControl.read` | MCP adapter only | -Because the REST routes in `BridgedApp` already exercise the collaborators, MCP tools are +Because the REST routes in `FleetApp` already exercise the collaborators, MCP tools are validated by **parity** against those routes, not by re-testing behavior. --- diff --git a/docs/RELEASE-NOTES-1.0.0.md b/docs/RELEASE-NOTES-1.0.0.md index 82c96c5..d902ab4 100644 --- a/docs/RELEASE-NOTES-1.0.0.md +++ b/docs/RELEASE-NOTES-1.0.0.md @@ -1,8 +1,8 @@ # v1.0.0 — One leader, one host, complete -This is the first release of **`bridged`**. +This is the first release of **`fleetd`**. -`bridged` lets one main Claude Code session (the **leader**, on your Pro/Max subscription) +`fleetd` lets one main Claude Code session (the **leader**, on your Pro/Max subscription) run a team of **workers** — extra Claude Code sessions on a cheaper or local model, and non-Claude agents too. The leader's own session is never touched: it stays on subscription, with a clean environment. diff --git a/docs/Team.md b/docs/Team.md index d6ccb11..aeffc84 100644 --- a/docs/Team.md +++ b/docs/Team.md @@ -1,9 +1,9 @@ # Team — lead orchestrating a mixed Claude + local-LLM fleet -The message server (`bridged`) delivers **one turn into one worker**. A **team** is the +The message server (`fleetd`) delivers **one turn into one worker**. A **team** is the layer above it: a **Claude team-lead** that fans a job out across a **mixed fleet** of workers — some on Claude, some on the remote local LLM — and reduces their replies. Same -`bridged` delivery, same subscription boundary; this doc is only about **orchestration** — +`fleetd` delivery, same subscription boundary; this doc is only about **orchestration** — who the workers are, how the lead picks one, and how it runs many at once. > Delivery mechanics (blocking `POST /message`, status-gated reply envelope) live in the @@ -12,9 +12,9 @@ who the workers are, how the lead picks one, and how it runs many at once. ## The team - **Team-lead** — the primary **Opus** (Claude Code, env **CLEAN**, on Pro/Max). Not a - worker; a **thin client of `bridged`**. It plans, routes, dispatches, and integrates, and + worker; a **thin client of `fleetd`**. It plans, routes, dispatches, and integrates, and never sets `ANTHROPIC_BASE_URL`. -- **Workers** — a herd of `claude` panes in herdr, each an addressable `bridged` session +- **Workers** — a herd of `claude` panes in herdr, each an addressable `fleetd` session with its **own model/env**: - **Claude workers** (clean env, e.g. Sonnet) — reasoning-heavy or high-accuracy subtasks. - **Local workers** (`ANTHROPIC_BASE_URL=https://llm.ltms.dev/anthropic`) — bulk, cheap, or @@ -28,7 +28,7 @@ MCP) — only its model differs. Scale each kind horizontally by adding panes. ```mermaid flowchart TB LEAD["lead — Opus
(Claude Code, env CLEAN)"] - BD["bridged
message server + router"] + BD["fleetd
message server + router"] HERDR["herdr
panes · agent-status"] WC1["w-claude-1
Sonnet · CLEAN"] WC2["w-claude-2
Sonnet · CLEAN"] @@ -62,14 +62,14 @@ flowchart TB | `w-local-*` | `ANTHROPIC_BASE_URL` set | local LLM | task is bulk / cheap / embarrassingly parallel | The lead applies this rubric itself, guided by its `CLAUDE.md` team charter (below). Worker -selection is **policy in the lead**, not a `bridged` concern — `bridged` just delivers to +selection is **policy in the lead**, not a `fleetd` concern — `fleetd` just delivers to the session the lead names. ## Subscription boundary in a team Unchanged from the base architecture, and it scales with the fleet: **only local-worker panes** launch with `ANTHROPIC_BASE_URL`. The lead and every Claude worker stay env-clean on -the subscription. `bridged` enforces which panes may carry the off-subscription env, so +the subscription. `fleetd` enforces which panes may carry the off-subscription env, so adding workers never widens the boundary. ## Parallel fan-out (map / reduce) @@ -80,7 +80,7 @@ different workers at once, then results are gathered. ```mermaid sequenceDiagram participant L as lead (Opus) - participant B as bridged + participant B as fleetd participant WC as w-claude-1 participant WL as w-local-1 @@ -100,26 +100,26 @@ sequenceDiagram ``` - **Map:** the lead issues N concurrent blocking `POST /message` calls (one per subtask → its - chosen worker). Each call blocks only *that* request; `bridged` holds it open until the + chosen worker). Each call blocks only *that* request; `fleetd` holds it open until the worker's turn completes (status-gated) and returns the reply envelope. - **Reduce:** the lead collects the N envelopes and integrates. A slow local worker never blocks a fast Claude worker — wall-clock ≈ the slowest single subtask, not the sum. - **Detached / long jobs** use the async broker path instead of a held request (Channel 2 in the base architecture), so the lead never busy-polls across turns. -Fan-out is bounded by the herd size (pane count) and `bridged`'s concurrency policy, not by +Fan-out is bounded by the herd size (pane count) and `fleetd`'s concurrency policy, not by the lead. ## Knowing the roster -The lead discovers its team from `bridged` (session list / roles) rather than hard-coding +The lead discovers its team from `fleetd` (session list / roles) rather than hard-coding pane ids, so workers can be added or restarted without editing the lead. A minimal charter in the lead's `CLAUDE.md` turns Opus into the orchestrator: ```markdown -## Your team (via bridged) -You are the team-lead. Delegate through the bridged client — never launch workers yourself. -Roster: ask bridged for current sessions/roles. +## Your team (via fleetd) +You are the team-lead. Delegate through the fleetd client — never launch workers yourself. +Roster: ask fleetd for current sessions/roles. - w-claude-* — Claude Sonnet. Reasoning-heavy / high-accuracy subtasks. - w-local-* — remote local LLM. Bulk, cheap, or parallelizable subtasks. @@ -133,22 +133,22 @@ tool instead of hand-rolling the HTTP request. ## What this layer does NOT change -- **Delivery** is still `bridged` → herdr `pane.send_text` + status events (Message-Server). +- **Delivery** is still `fleetd` → herdr `pane.send_text` + status events (Message-Server). - **Completion timing** is still the worker status event; **reply content** still rides the worker `Stop`-hook envelope. - **Single-host** still applies: herdr's socket is local, so the whole herd lives on the - `bridged` host. The lead may be remote — it only needs HTTP to `bridged`. + `fleetd` host. The lead may be remote — it only needs HTTP to `fleetd`. ## Open questions -- **Routing intelligence:** rubric-in-`CLAUDE.md` (lead decides) vs. a `bridged` role-router +- **Routing intelligence:** rubric-in-`CLAUDE.md` (lead decides) vs. a `fleetd` role-router (label-based). Start with the former; promote to the latter if routing logic grows. -- **Backpressure:** per-role concurrency caps in `bridged` so a fan-out can't exhaust the +- **Backpressure:** per-role concurrency caps in `fleetd` so a fan-out can't exhaust the local gateway. - **Result schema:** whether reply envelopes should carry structured metadata (worker, model, tokens) to help the lead's reduce step. ## Status -🟡 Design (2026-07-11). Orchestration layer over the selected `bridged` server; inherits +🟡 Design (2026-07-11). Orchestration layer over the selected `fleetd` server; inherits herdr (chosen) + AgentAPI (fallback). Delivery unchanged — see the Message-Server design. diff --git a/docs/Worker-Git-Workflow.md b/docs/Worker-Git-Workflow.md index 4c45939..d6af7a5 100644 --- a/docs/Worker-Git-Workflow.md +++ b/docs/Worker-Git-Workflow.md @@ -76,7 +76,7 @@ worktree checks out anyway. Amber is the real gap — untracked local config the 3. **Never overlay the git plumbing** — the worktree's own `.git` file/branch is what gives isolation; that's the *one* thing that must differ from the main tree. -The overlay set lives in config (`BridgedConfig.Worker.parityOverlay` — a list of repo-relative +The overlay set lives in config (`FleetConfig.Worker.parityOverlay` — a list of repo-relative paths, with sane defaults) so it's auditable and per-repo tunable. > **Trust note (deliberate).** Hydrating local config means the primary's local secrets/tokens @@ -117,7 +117,7 @@ earlier `STATE.md` idea — a PR is reviewable, mergeable, and self-describing.* - **Remote:** `ssh://git@git.ltms.dev:2224/fleet/fleetd.git` (gitea). Push is over **SSH** — a worker running as the same user with the same keys can `git push` **with no extra credential**. -- **gitea is NOT in the project `.mcp.json`** (only `jetbrains`, `intellij-index`, `bridged`). The +- **gitea is NOT in the project `.mcp.json`** (only `jetbrains`, `intellij-index`, `fleetd`). The primary's gitea MCP comes from a global/user config, so **workers do not inherit it**. A worker gets only the `bridge` MCP mounted (via `--mcp-config` launch flag). - **No gitea CLI** (`tea`) installed; `glab` is present but is the GitLab CLI (wrong backend). @@ -153,11 +153,11 @@ and the token is a single scoped secret the daemon injects like it already injec |---|---|---| | Worktree provision/teardown | **CB-301 ext** — `SessionManager.acquire`/`release`; `WorkerSession` gains `worktree`, `branch` | daemon shells out to `git worktree add/remove` | | **Config-parity overlay** | **CB-301 ext** — `SessionManager.acquire`, after `git worktree add` | symlink/copy the `parityOverlay` set into the worktree so the worker is a full peer; **this is what makes worktrees viable, not a dead-end** | -| Overlay config | `BridgedConfig.Worker.parityOverlay` — repo-relative paths, sane defaults | auditable, per-repo tunable; keep explicit + minimal (trust) | +| Overlay config | `FleetConfig.Worker.parityOverlay` — repo-relative paths, sane defaults | auditable, per-repo tunable; keep explicit + minimal (trust) | | Branch naming | `worker/-` off `main` (or a configured base) | one branch per session | | Commit + push + PR handoff | **CB-302** — worker-driven, guided by the skill | push = SSH; PR = option A | | Implementer skill | `.claude/skills/implementer/SKILL.md` | worktree-aware playbook (see below); mounts automatically since workers inherit repo cwd | -| gitea token injection | `WorkerService` env + `BridgedConfig` | repo-scoped, minimal perms | +| gitea token injection | `WorkerService` env + `FleetConfig` | repo-scoped, minimal perms | | PR review + merge | Primary (has gitea MCP + judgment) | merge on green; the human/primary gate stays | ## Implementer skill (outline) diff --git a/docs/Worker-Startup-and-Trust.md b/docs/Worker-Startup-and-Trust.md index 121a865..a9edda8 100644 --- a/docs/Worker-Startup-and-Trust.md +++ b/docs/Worker-Startup-and-Trust.md @@ -1,6 +1,6 @@ # Worker startup: working directory & the folder-trust prompt -When `bridged` spawns a worker, the worker CLI may show an **interactive startup prompt** before it +When `fleetd` spawns a worker, the worker CLI may show an **interactive startup prompt** before it is ready to accept a task — most importantly a *"Do you trust the files in this folder?"* dialog. An unattended worker parked on that prompt never becomes injectable: the status-gated injector waits for `idle`/`blocked`, the task is never delivered, and (worst case) a stray Enter answers the dialog @@ -18,7 +18,7 @@ flowchart TD B -->|"yes — told otherwise"| C["use that cwd"] B -->|"no"| D{"caller PID resolvable?
(MCP peer PID)"} D -->|"yes"| E["cwd = the primary's cwd
lsof -a -p PID -d cwd"] - D -->|"no (REST / off-host)"| F["cwd = bridged daemon cwd
(never $HOME by assumption)"] + D -->|"no (REST / off-host)"| F["cwd = fleetd daemon cwd
(never $HOME by assumption)"] C --> G["ensureWorkspace → tab.create → agent.start {cwd}"] E --> G F --> G @@ -52,9 +52,9 @@ only affect the seed shell, which the bridge closes). |---|--------|------| | 1 | Explicit `cwd` — a per-profile `cwd:` in config, or a spawn argument | "told otherwise" — pin a fixed workdir | | 2 | The **primary's cwd**, auto-detected from the `fleet_spawn` caller | normal MCP spawn from the primary | -| 3 | The `bridged` daemon's own cwd | REST spawn / off-host caller — **never `$HOME`** | +| 3 | The `fleetd` daemon's own cwd | REST spawn / off-host caller — **never `$HOME`** | -The primary's cwd (source 2) is discoverable with no new plumbing: `bridged` already resolves the MCP +The primary's cwd (source 2) is discoverable with no new plumbing: `fleetd` already resolves the MCP caller's loopback **peer PID** for connection identity (`ConnectionIdentity` → `LsofPeerPidLookup`); the same PID yields its cwd via `lsof -a -p -d cwd -Fn` (the `n…` line). The primary maps to no worker pane (it is not a worker), but its PID and cwd are still readable. @@ -62,7 +62,7 @@ worker pane (it is not a worker), but its PID and cwd are still readable. ```mermaid sequenceDiagram participant P as "Primary (main)" - participant B as "bridged" + participant B as "fleetd" participant O as "OS (lsof)" participant H as "herdr" P->>B: "fleet_spawn {profile} (no cwd)" @@ -77,7 +77,7 @@ sequenceDiagram *Figure 2 — a no-cwd spawn inherits the primary's directory from the caller's PID.* -> **Status:** implemented (CB-112). `bridged` threads the resolved `cwd` onto **`agent.start {cwd}`** +> **Status:** implemented (CB-112). `fleetd` threads the resolved `cwd` onto **`agent.start {cwd}`** > (verified: the worker process is rooted there), keeping the single shared worker space. On an MCP > `fleet_spawn` the primary's cwd is auto-detected from the caller's PID; over REST (no MCP caller) > it is the explicit `cwd` param else the daemon's cwd. Both placements (`tab` and legacy `pane`)