Part of #145 (CB-632). Documentation only, plus one internal literal. Unit 1 renamed the package and classes, which left every doc describing classes that no longer exist. This fixes the prose across README.md, docs/ and bridged/docs/ -- 18 files. Renamed: dev.ltms.bridged -> dev.ltms.fleet, the five class names, and "bridged" where it names the daemon as a product rather than a path. Also renamed two literals, because a doc that disagrees with the code is worse than one that is out of date: - bridged-local-noauth -> fleetd-local-noauth. A placeholder apiKey OpenCodeLauncher sends when a profile resolves no token, to a local endpoint that does not check it. No test asserts the old string. - the vnd.ltms.bridged.* media type in the M4 design doc. It appears in no Java file, so nothing implements it yet. Deliberately NOT renamed, because each is still literally true today and changes only at the cutover: - paths: bridged/, bridged.yaml, bridged.example.yaml, bridged.jar, .bridged-worktrees, deploy/dev.ltms.bridged.plist, scripts/redeploy-bridged.sh, bridged-launchd-wrapper.sh - bridged_* metric names -- renaming these after the monitoring is wired would break dashboard continuity, so they move before it is - bridge_* MCP tool names, which answer alongside fleet_* on purpose - BRIDGED_* environment variables, read by a file outside this repo Method note: perl, not sed. BSD sed has no \b and no lookaround, and a word-boundary expression there fails silently. The prose replace uses (?<![\w./-])bridged(?![\w./-]) so it cannot touch a path or an identifier, then every remaining hit was read by hand. Verified: mvn clean install green, 51 classes, 878 tests, 0 failures.
7.1 KiB
Worker startup: working directory & the folder-trust prompt
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
wrong. How this is handled depends on two things:
- The worker's working directory — which folder the CLI is asked to trust.
- Which CLI launches the worker — each has its own first-run/trust behaviour.
This doc pins the current assumption (ccs is the launcher), how its trust prompt works, the
rule that a worker inherits the primary's directory (never $HOME), and how other CLIs differ.
flowchart TD
A["fleet_spawn / POST /workers"] --> B{"explicit cwd?<br/>(profile cwd or spawn arg)"}
B -->|"yes — told otherwise"| C["use that cwd"]
B -->|"no"| D{"caller PID resolvable?<br/>(MCP peer PID)"}
D -->|"yes"| E["cwd = the primary's cwd<br/>lsof -a -p PID -d cwd"]
D -->|"no (REST / off-host)"| F["cwd = fleetd daemon cwd<br/>(never $HOME by assumption)"]
C --> G["ensureWorkspace → tab.create → agent.start {cwd}"]
E --> G
F --> G
G --> H{"does the CLI trust this folder?"}
H -->|"yes"| I["worker reaches its prompt → injectable"]
H -->|"no"| J["worker BLOCKS on the trust dialog<br/>never injectable"]
classDef good fill:#2f855a,stroke:#22543d,color:#ffffff;
classDef bad fill:#9b2c2c,stroke:#63171b,color:#ffffff;
class I good
class J bad
Figure 1 — spawn resolves a working directory, then the CLI's trust check gates readiness.
Working directory: inherit the primary's path
Rule: a worker opens the same directory the primary (main) session is working in, unless told
otherwise. Never assume $HOME. If the primary is in /Users/you/LTMS/claude-bridge, its workers
open there too — so delegated tasks share the same relative paths and the same (already-trusted)
project folder.
The herdr seam. An agent.start pane does not inherit its tab's or workspace's cwd — it
starts in $HOME unless told otherwise. herdr's agent.start honours an (undocumented) cwd
param, verified live: setting it roots the worker process at that directory. So the worker's cwd is
threaded onto agent.start {…, cwd}, not the placement step (workspace.create/tab.create cwd
only affect the seed shell, which the bridge closes).
Resolution order (first match wins):
| # | Source | When |
|---|---|---|
| 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 fleetd daemon's own cwd |
REST spawn / off-host caller — never $HOME |
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 <pid> -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.
sequenceDiagram
participant P as "Primary (main)"
participant B as "fleetd"
participant O as "OS (lsof)"
participant H as "herdr"
P->>B: "fleet_spawn {profile} (no cwd)"
B->>O: "peer PID for this connection's port"
O-->>B: "pid"
B->>O: "cwd of pid (lsof -d cwd)"
O-->>B: "/Users/you/LTMS/claude-bridge"
B->>H: "tab.create (placement)"
B->>H: "agent.start {argv, env, tab_id, cwd}"
H-->>B: "worker in the primary's directory"
Figure 2 — a no-cwd spawn inherits the primary's directory from the caller's PID.
Status: implemented (CB-112).
fleetdthreads the resolvedcwdontoagent.start {cwd}(verified: the worker process is rooted there), keeping the single shared worker space. On an MCPfleet_spawnthe primary's cwd is auto-detected from the caller's PID; over REST (no MCP caller) it is the explicitcwdparam else the daemon's cwd. Both placements (taband legacypane) carry it, since it ridesagent.start.
Assumed launcher: ccs (Claude Code)
For now the fleet assumes ccs (Claude Code under the hood) as the worker CLI — argv: ["ccs", "<profile>"]. Its startup gate is the folder-trust dialog.
How ccs/Claude Code decides whether to prompt
Trust is recorded per-directory, per config dir. Each ccs profile is an isolated instance with
its own config dir (~/.ccs/instances/<profile>/) and its own .claude.json:
// ~/.ccs/instances/<profile>/.claude.json
"projects": {
"/Users/you/LTMS/claude-bridge": { "hasTrustDialogAccepted": true }, // trusted → no prompt
"/Users/you": { "hasTrustDialogAccepted": false } // untrusted → prompts
}
The worker prompts iff its cwd is not marked hasTrustDialogAccepted: true for that instance.
This is why the directory rule above matters: land workers in the primary's project folder and you
grant trust once per profile, instead of scattering trust across $HOME and ad-hoc dirs.
Clearing the prompt (ranked)
- Inherit the primary's project dir (the rule above) and trust that folder once per profile.
- Pre-trust interactively: run
ccs <profile>in the target folder and accept — persistshasTrustDialogAccepted: truefor that path in the instance's.claude.json. - Set the flag directly (scriptable, no interaction): set
projects["<cwd>"].hasTrustDialogAccepted = truein~/.ccs/instances/<profile>/.claude.json. - Do not reach for
--dangerously-skip-permissions— it disables all permission gating, not just this dialog, which defeats running off-subscription workers autonomously.
Other CLIs: different launchers, different prompts
ccs is the current assumption, not a hard dependency — a worker profile's argv can be any CLI.
Each launcher has its own first-run/trust gate, so the "clear the prompt" step is CLI-specific
and belongs with the profile, not hard-coded:
Launcher (argv) |
Startup gate | How to clear it |
|---|---|---|
ccs <profile> (Claude Code) |
Folder-trust dialog | hasTrustDialogAccepted per project in the instance's .claude.json (above) |
Other Claude-compatible runtimes via ccs (codex, gemini, cursor, …) |
Each has its own first-run / trust / login prompt | Per-runtime; document per launcher as it is adopted |
A bare command (bash -c …, mechanics probe) |
None | n/a — used for non-interactive smoke tests |
When adding a new launcher, capture its startup-prompt behaviour here (what blocks, and the non-interactive way to satisfy it) so a spawned worker of that kind reaches an injectable prompt unattended.
See also
docs/MCP-Contract.md— the tool surface (fleet_spawn,fleet_profiles, …).wiki/2-Message-Server.md— the herdragent.*/workspace.*schema (workspace.create {cwd}).