diff --git a/README.md b/README.md
index 4154fee..1ba3762 100644
--- a/README.md
+++ b/README.md
@@ -56,9 +56,8 @@ flowchart LR
ever addresses a broker, a peer, or the network
directly**; any queue is `fleetd`-internal. MCP tool I/O never sets `ANTHROPIC_BASE_URL`, so
mounting the bridge is subscription-safe by construction.
- **Tool naming:** the tools were renamed from `bridge_*` to `fleet_*` (CB-622). The daemon
- still answers the old `bridge_*` names for one release, but they are deprecated — use the
- `fleet_*` names.
+ **Tool naming:** the tools are `fleet_*` (renamed from `bridge_*` in CB-622). The old
+ `bridge_*` names were removed in CB-634 — only `fleet_*` answers now.
- **How the primary consumes a reply:** a single **blocking MCP call** (`fleet_send`);
`fleetd` holds it open until the worker calls `fleet_reply` or its turn hits
`agent_status=done`, then returns the reply as the tool result. No cross-turn busy-poll, so
diff --git a/docs/MCP-Contract.md b/docs/MCP-Contract.md
index 2e79765..5281764 100644
--- a/docs/MCP-Contract.md
+++ b/docs/MCP-Contract.md
@@ -4,11 +4,11 @@
> any MCP code existed. The system shipped and this page never caught up, so **its tool names,
> parameter names and REST paths are wrong today**. Audited 2026-08-17; the specific drift:
>
-> - **Tools it names that do not exist:** `bridge_read`, `bridge_cancel`.
-> - **Shipped tools it omits:** `bridge_poll`, `bridge_ack`, `bridge_profiles`, `bridge_whoami`.
+> - **Tools it names that do not exist:** `fleet_read`, `fleet_cancel`.
+> - **Shipped tools it omits:** `fleet_poll`, `fleet_ack`, `fleet_profiles`, `fleet_whoami`.
> - **Parameter names are wrong nearly everywhere** — it says `message`/`target`/`timeout_seconds`/
> `block` where the code takes `content`/`sessionId`/`timeoutMs`/`wait`; `text` where
-> `bridge_reply` takes `content`; `target` where `bridge_stop` takes `paneId`.
+> `fleet_reply` takes `content`; `target` where `fleet_stop` takes `paneId`.
> - **REST paths are wrong:** it says `POST /workers` and `DELETE /workers/{paneId}`; the daemon
> serves `POST /members` and `DELETE /members/{paneId}`.
>
@@ -60,7 +60,7 @@ face** is the herdr Unix socket. REST/SSE remains only for non-Claude clients an
flowchart LR
OPUS["Opus — primary
(Claude Code, env CLEAN)
MCP client"]
subgraph BD["fleetd — standalone daemon"]
- MCP["MCP server (north face)
bridge_send · bridge_reply
bridge_ask · bridge_status · lifecycle"]
+ MCP["MCP server (north face)
fleet_send · fleet_reply
fleet_ask · fleet_status · lifecycle"]
RDV["rendezvous registry
(blocking-call waiters)"]
INJ["Injector + StatusPoller
(status-gated writer)"]
SOCK["herdr socket client (south face)"]
@@ -72,8 +72,8 @@ flowchart LR
HERDR["herdr
panes · agent-status"]
W["worker claude pane
ANTHROPIC_BASE_URL set
MCP client"]
- OPUS -->|"bridge_send (blocks)"| MCP
- W -.->|"bridge_reply / bridge_ask"| MCP
+ OPUS -->|"fleet_send (blocks)"| MCP
+ W -.->|"fleet_reply / fleet_ask"| MCP
SOCK -->|"agent.start · agent.send
agent.get · pane.close"| HERDR
HERDR -->|"drives PTY"| W
@@ -97,8 +97,8 @@ request — this is the linchpin of the whole contract and has no code yet.
- **The primary is "not a worker".** Any connection that does not map to a known worker is
treated as a primary. It addresses workers **explicitly** by `target` — a session UUID,
a `terminal_id`, or a friendly `profile` name.
-- **Turn correlation.** A blocking `bridge_send` registers a *waiter* keyed by worker
- identity. A worker's later `bridge_reply` / `bridge_ask` on the same identity resolves that
+- **Turn correlation.** A blocking `fleet_send` registers a *waiter* keyed by worker
+ identity. A worker's later `fleet_reply` / `fleet_ask` on the same identity resolves that
waiter. A `turn_id` is minted per exchange so a clarification round-trip
(§6.2) rejoins the right turn.
@@ -123,36 +123,36 @@ This adds an MCP-server dependency the pom does not yet carry. See [Open decisio
| Tool | Caller | Blocks? | Backing (exists today?) |
|---|---|---|---|
-| [`bridge_send`](#bridge_send) | primary | yes (default) | `Injector.enqueue` ✅ · rendezvous registry ❌ (CB-104) |
-| [`bridge_reply`](#bridge_reply) | worker | no | rendezvous ❌ · pane injection via `Injector` ✅ |
-| [`bridge_ask`](#bridge_ask) | worker | yes | reverse rendezvous ❌ |
-| [`bridge_status`](#bridge_status) | either | no | `AgentControl.status` ✅ · `Injector.activeTargets` ✅ |
-| [`bridge_spawn`](#lifecycle) | primary | no | `WorkerService.spawn` ✅ (`POST /workers`) |
-| [`bridge_list`](#lifecycle) | either | no | `WorkerService.list` ✅ (`/agents`) |
-| [`bridge_stop`](#lifecycle) | primary | no | `WorkerService.stop` ✅ (`DELETE /workers/{paneId}`) |
-| [`bridge_read`](#bridge_read) | primary | no | `AgentControl.read` ✅ |
-| [`bridge_cancel`](#bridge_cancel) | primary | no | — ❌ (future) |
+| [`fleet_send`](#fleet_send) | primary | yes (default) | `Injector.enqueue` ✅ · rendezvous registry ❌ (CB-104) |
+| [`fleet_reply`](#fleet_reply) | worker | no | rendezvous ❌ · pane injection via `Injector` ✅ |
+| [`fleet_ask`](#fleet_ask) | worker | yes | reverse rendezvous ❌ |
+| [`fleet_status`](#fleet_status) | either | no | `AgentControl.status` ✅ · `Injector.activeTargets` ✅ |
+| [`fleet_spawn`](#lifecycle) | primary | no | `WorkerService.spawn` ✅ (`POST /workers`) |
+| [`fleet_list`](#lifecycle) | either | no | `WorkerService.list` ✅ (`/agents`) |
+| [`fleet_stop`](#lifecycle) | primary | no | `WorkerService.stop` ✅ (`DELETE /workers/{paneId}`) |
+| [`fleet_read`](#fleet_read) | primary | no | `AgentControl.read` ✅ |
+| [`fleet_cancel`](#fleet_cancel) | primary | no | — ❌ (future) |
### Core: delegation & rendezvous
-#### `bridge_send`
+#### `fleet_send`
*(primary → worker — the headline tool, CB-104)*
- **Params:** `message` (required); `target` (optional — defaults to the sole worker / default
profile); `timeout_seconds` (default 600); `block` (default `true`); `auto_spawn`
- (default `true`); `turn_id` (optional — supplied when answering a worker's `bridge_ask`).
+ (default `true`); `turn_id` (optional — supplied when answering a worker's `fleet_ask`).
- **Blocking (`block:true`):** enqueue `message` via the `Injector`, then hold the call open
until exactly one of:
- - worker calls `bridge_reply` → `{ outcome:"reply", text }`
- - worker calls `bridge_ask` → `{ outcome:"question", text, turn_id }`
+ - worker calls `fleet_reply` → `{ outcome:"reply", text }`
+ - worker calls `fleet_ask` → `{ outcome:"question", text, turn_id }`
- worker's `agent_status` reaches done/idle with no reply → `{ outcome:"turn_done", text: }`
- deadline elapses → `{ outcome:"timeout" }`
- worker gone → error `worker_gone`
- **Detached (`block:false`):** enqueue and return `{ outcome:"dispatched", dispatch_id }`
immediately. The eventual reply is injected into the primary's idle pane (§6.3), or drained
- via `bridge_status` on a split-host primary.
+ via `fleet_status` on a split-host primary.
-#### `bridge_reply`
+#### `fleet_reply`
*(worker → primary)*
- **Params:** `text` (required); `final` (default `true`).
@@ -160,28 +160,28 @@ This adds an MCP-server dependency the pom does not yet carry. See [Open decisio
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`
+#### `fleet_ask`
*(worker → primary — the reverse rendezvous)*
- **Params:** `question` (required); `timeout_seconds`.
- **Behavior:** blocks the *worker's* call. Surfaces the question to the primary (resolving its
- open `bridge_send` with `outcome:"question"`, or injecting its pane). When the primary
- answers — a `bridge_send` carrying the matching `turn_id` — that unblocks this call and
+ open `fleet_send` with `outcome:"question"`, or injecting its pane). When the primary
+ answers — a `fleet_send` carrying the matching `turn_id` — that unblocks this call and
returns `{ answer }` to the worker, which continues **in the same turn**.
### Worker lifecycle
Thin adapters over [`WorkerService`](1-Architecture) — parity with the existing REST routes.
-- **`bridge_spawn`** — `{ profile? }` → worker view (`sessionId`, `terminalId`, `paneId`,
+- **`fleet_spawn`** — `{ profile? }` → worker view (`sessionId`, `terminalId`, `paneId`,
`status`). Guard-checked; a boundary breach returns error `subscription_boundary` (the
REST `403`).
-- **`bridge_list`** — no params → all workers + `agent_status`. Read-only, either role.
-- **`bridge_stop`** — `{ target }` → tears down the pane and its dedicated tab. Idempotent.
+- **`fleet_list`** — no params → all workers + `agent_status`. Read-only, either role.
+- **`fleet_stop`** — `{ target }` → tears down the pane and its dedicated tab. Idempotent.
### Observability
-#### `bridge_status`
+#### `fleet_status`
*(either role — the README's 4th named tool)*
- **Params:** `target?`.
@@ -190,7 +190,7 @@ Thin adapters over [`WorkerService`](1-Architecture) — parity with the existin
messages addressed to me** — the path a split-host primary's `Stop`-hook uses to wake and
collect replies without being injectable. Read-only, non-blocking.
-#### `bridge_read`
+#### `fleet_read`
*(primary)*
- **Params:** `target`; `source` ∈ `visible | recent | recent_unwrapped | detection`.
@@ -199,7 +199,7 @@ Thin adapters over [`WorkerService`](1-Architecture) — parity with the existin
### Control (future)
-#### `bridge_cancel`
+#### `fleet_cancel`
*(primary)*
- **Params:** `target`. Interrupt the worker's current turn / abandon the rendezvous. No
@@ -294,7 +294,7 @@ sequenceDiagram
## 7. Status gating
Delivery only happens in a safe window. This is the state machine the `Injector` already
-enforces via `AgentStatus.injectable()`; MCP `bridge_send` is simply its producer.
+enforces via `AgentStatus.injectable()`; MCP `fleet_send` is simply its producer.
```mermaid
stateDiagram-v2
@@ -330,17 +330,17 @@ touching this state machine.
## 8. Error model
-| Condition | `bridge_send` result | Notes |
+| Condition | `fleet_send` result | Notes |
|---|---|---|
| Worker replies | `{ outcome:"reply" }` | normal |
-| Worker asks | `{ outcome:"question", turn_id }` | answer with `bridge_send(turn_id)` |
+| Worker asks | `{ outcome:"question", turn_id }` | answer with `fleet_send(turn_id)` |
| Turn ends, no reply | `{ outcome:"turn_done" }` | terminal tail as text |
| Deadline elapsed | `{ outcome:"timeout" }` | message may still be queued/delivered |
| Worker vanished | error `worker_gone` | `Injector.drop` fails the queued future |
| Guard breach on spawn | error `subscription_boundary` | REST `403` parity |
| Delivery failed at herdr | error, message dropped | poisoned message not left blocking the FIFO |
-`bridge_reply` from a worker with no open waiter is **not** an error — it falls through to
+`fleet_reply` from a worker with no open waiter is **not** an error — it falls through to
detached pane injection (§6.3).
---
@@ -352,11 +352,11 @@ seam. Only the **rendezvous registry** and the **caller-identity resolver** are
| MCP tool | Existing collaborator | New work |
|---|---|---|
-| `bridge_send` | `Injector.enqueue`, `AgentControl.send` | waiter registry, timeout, outcome mux (CB-104) |
-| `bridge_reply` / `bridge_ask` | `Injector` (pane injection) | reverse rendezvous, identity resolver |
-| `bridge_status` | `AgentControl.status`, `Injector.activeTargets` | pending-drain projection |
-| `bridge_spawn` / `list` / `stop` | `WorkerService.{spawn,list,stop}` | MCP adapter only |
-| `bridge_read` | `AgentControl.read` | MCP adapter only |
+| `fleet_send` | `Injector.enqueue`, `AgentControl.send` | waiter registry, timeout, outcome mux (CB-104) |
+| `fleet_reply` / `fleet_ask` | `Injector` (pane injection) | reverse rendezvous, identity resolver |
+| `fleet_status` | `AgentControl.status`, `Injector.activeTargets` | pending-drain projection |
+| `fleet_spawn` / `list` / `stop` | `WorkerService.{spawn,list,stop}` | MCP adapter only |
+| `fleet_read` | `AgentControl.read` | MCP adapter only |
Because the REST routes in `FleetApp` already exercise the collaborators, MCP tools are
validated by **parity** against those routes, not by re-testing behavior.
@@ -365,14 +365,14 @@ validated by **parity** against those routes, not by re-testing behavior.
## 10. Open decisions
-1. **`bridge_ask` direction.** This page defines it as *worker-asks-primary* (a genuine reverse
+1. **`fleet_ask` direction.** This page defines it as *worker-asks-primary* (a genuine reverse
channel, matching the "inject the primary's pane" language). The alternative — a synonym for
a blocking primary→worker send — is weaker and produces different plumbing. **Recommend
worker-asks-primary.**
-2. **Detached delivery shape.** A `block:false` param on `bridge_send` (keeps the catalog
- small) vs. a separate `bridge_dispatch` tool. **Recommend the param.**
-3. **Auto-spawn on send.** `bridge_send` provisions a worker per profile when none exists
- (simplest primary UX) vs. requiring an explicit `bridge_spawn` first. **Recommend
+2. **Detached delivery shape.** A `block:false` param on `fleet_send` (keeps the catalog
+ small) vs. a separate `fleet_dispatch` tool. **Recommend the param.**
+3. **Auto-spawn on send.** `fleet_send` provisions a worker per profile when none exists
+ (simplest primary UX) vs. requiring an explicit `fleet_spawn` first. **Recommend
auto-spawn, defaulting on.**
4. **Transport & SDK.** Streamable-HTTP/SSE co-located with the REST bind (recommended) vs.
stdio. Requires choosing a Java MCP server SDK and adding it to the pom.
@@ -381,9 +381,9 @@ validated by **parity** against those routes, not by re-testing behavior.
## 11. Implementation staging
-- **CB-104** — blocking `bridge_send` + rendezvous registry + caller-identity resolver
+- **CB-104** — blocking `fleet_send` + rendezvous registry + caller-identity resolver
(the producer that finally drives the inert `StatusPoller`).
-- **CB-1xx** — `bridge_reply` / `bridge_ask` reverse rendezvous + detached pane injection.
-- **CB-1xx** — lifecycle + observability adapters (`bridge_spawn/list/stop/status/read`).
+- **CB-1xx** — `fleet_reply` / `fleet_ask` reverse rendezvous + detached pane injection.
+- **CB-1xx** — lifecycle + observability adapters (`fleet_spawn/list/stop/status/read`).
- **CB-1xx** — transport wiring + `claude mcp add` docs; parity tests vs. REST.
-- **Later** — `bridge_cancel`; swap `StatusPoller` for herdr `events.subscribe`.
+- **Later** — `fleet_cancel`; swap `StatusPoller` for herdr `events.subscribe`.
diff --git a/e2e/bridge_ask_test.py b/e2e/fleet_ask_test.py
similarity index 99%
rename from e2e/bridge_ask_test.py
rename to e2e/fleet_ask_test.py
index 7044fee..0dccc3c 100644
--- a/e2e/bridge_ask_test.py
+++ b/e2e/fleet_ask_test.py
@@ -24,7 +24,7 @@ Like the rest of the suite it talks ONLY to the bridge's REST face on loopback
ANTHROPIC_BASE_URL and never touches herdr, so it is subscription-safe by construction.
Usage:
- python3 bridge_ask_test.py [--base URL] [--profile NAME] [--repo DIR] [--out DIR]
+ python3 fleet_ask_test.py [--base URL] [--profile NAME] [--repo DIR] [--out DIR]
[--send-timeout SECS] [--answer-timeout SECS] [--keep-worker]
--base bridge REST base URL (default http://127.0.0.1:8765)
diff --git a/e2e/bridge_ask_transcript.md b/e2e/fleet_ask_transcript.md
similarity index 52%
rename from e2e/bridge_ask_transcript.md
rename to e2e/fleet_ask_transcript.md
index bfcc73c..546e944 100644
--- a/e2e/bridge_ask_transcript.md
+++ b/e2e/fleet_ask_transcript.md
@@ -1,12 +1,12 @@
-# Live bridge_ask — reverse rendezvous — 2026-07-16 16:30
+# Live fleet_ask — reverse rendezvous — 2026-07-16 16:30
One worker paused its delegated turn to ask the primary, then resumed with the answer (profile `default`). Result: **`OK`**.
## Round-trip
1. **primary → worker** (delegation): the ask-forcing task.
-2. **worker → primary** (`bridge_ask`, 6.6s): 'PICK A COLOR: red or blue?' — surfaced on the primary's blocked send as a `question` with `turnId=term_656bb47d2c42a9e#1`.
+2. **worker → primary** (`fleet_ask`, 6.6s): 'PICK A COLOR: red or blue?' — surfaced on the primary's blocked send as a `question` with `turnId=term_656bb47d2c42a9e#1`.
3. **primary → worker** (answer on that turn): `blue`.
-4. **worker → primary** (`bridge_reply`, 7.9s, source=reply): 'CHOSEN=BLUE'
+4. **worker → primary** (`fleet_reply`, 7.9s, source=reply): 'CHOSEN=BLUE'
> **OK:** asked, resumed the same turn, and the reply reflected the primary's answer
diff --git a/fleetd/pom.xml b/fleetd/pom.xml
index 18d48cb..b294c01 100644
--- a/fleetd/pom.xml
+++ b/fleetd/pom.xml
@@ -106,7 +106,7 @@
+ /mcp, exposing fleet_send/fleet_reply/fleet_status as thin adapters over REST. -->
io.modelcontextprotocol.sdk
mcp
diff --git a/fleetd/src/main/java/dev/ltms/fleet/herdr/LeadTabScanner.java b/fleetd/src/main/java/dev/ltms/fleet/herdr/LeadTabScanner.java
index 9b0a3c4..6a64b50 100644
--- a/fleetd/src/main/java/dev/ltms/fleet/herdr/LeadTabScanner.java
+++ b/fleetd/src/main/java/dev/ltms/fleet/herdr/LeadTabScanner.java
@@ -39,7 +39,7 @@ import java.util.function.Supplier;
* Worker spaces are excluded wholesale ({@code excludedWorkspaceLabels}), so a worker cannot
* become a lead by being placed — as a split, say — inside a matching tab.
* A worker cannot rename a tab: {@code tab.rename} is reachable only through
- * {@link WorkspaceControl}, which no {@code bridge_*} tool exposes. The label is writable by
+ * {@link WorkspaceControl}, which no {@code fleet_*} tool exposes. The label is writable by
* the human at the terminal and by nobody the bridge is defending against.
* The label is a name, not a capability. What a pane may do is decided by
* {@code Authz} against the role {@code CallerResolver} returns; a tab that calls itself a
diff --git a/fleetd/src/main/java/dev/ltms/fleet/mcp/FleetMcp.java b/fleetd/src/main/java/dev/ltms/fleet/mcp/FleetMcp.java
index 4572f5e..efff9dc 100644
--- a/fleetd/src/main/java/dev/ltms/fleet/mcp/FleetMcp.java
+++ b/fleetd/src/main/java/dev/ltms/fleet/mcp/FleetMcp.java
@@ -172,8 +172,7 @@ public final class FleetMcp {
CALLER_NAME, orEmpty(p.name())));
})
.build();
- // CB-622: each handler is built once and reused for BOTH its fleet_* tool and its
- // deprecated bridge_* twin (registered below), so the two names can never drift apart.
+ // Each handler is built once and wired to its fleet_* tool below.
BiFunction sendHandler =
(exchange, req) -> {
McpSchema.CallToolResult denied = deny(exchange, Authz.Action.SEND,