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.
11 KiB
Worker Git Workflow — worktree · branch · PR
Status: design (defining the fleet's working model). Builds on the CB-301 session manager and the one-shot/no-reuse decision.
Guiding principle — a worker is a full peer of the primary
The whole point of the bridge is the same Claude Code agent running against a different LLM
provider. A worker must be functionally identical to the primary in context and knowledge —
same project + user CLAUDE.md, same skills, same memory, same MCP tools, same local/private
settings — and differ only in ANTHROPIC_BASE_URL/ANTHROPIC_MODEL. The worktree exists
solely for git isolation (a branchable checkout for commits + code reference). It must never
strip the worker of the configuration a main-tree session has. Config parity is a hard
requirement, not a nice-to-have.
Decisions
- One-shot, no reuse (CB-301) — each task gets a fresh worker, torn down after. The PR is the durable artifact; no context is carried across workers.
- Worktree provisioned by the daemon —
SessionManagercreates a dedicated git worktree + branch per session, hydrates it to full config parity (below), and tears it down on release. - Worker opens its own PR — the worker commits, pushes its branch, and opens the PR/MR itself,
returning the PR URL in its
fleet_reply.
Why worktrees (the hazard being fixed)
Today every bridge worker inherits the primary's own working tree as its cwd
(WorkerService cwd resolution → caller cwd). A single worker editing at a time is safe, but two
parallel implementers would stomp each other's files. A worktree per session gives each worker
an isolated checkout on its own branch — the precondition for fanning out implementation work.
Config parity — the worktree is code-only; it must NOT strip the worker
The trap: git worktree add creates a checkout of tracked files only. Untracked / gitignored
files do not come along. So a worker moved from the primary's main tree into a bare worktree
silently loses exactly the local/private configuration that makes it a full peer — while the
shared-tree it runs in today gives it all of this for free. Moving to worktrees must preserve
that, not regress it.
Classify every source of "what a main session knows", by whether a worktree keeps it:
flowchart TD
subgraph keeps["Inherited automatically — no action"]
A["User-global config<br/>~/.claude/CLAUDE.md, ~/.ccs memory"]:::ok
B["Tracked project config<br/>CLAUDE.md, committed .claude/skills, committed .mcp.json"]:::ok
C["CLAUDE_CONFIG_DIR<br/>(daemon already injects per profile)"]:::ok
D["Bridge MCP<br/>(injected via --mcp-config launch flag)"]:::ok
end
subgraph gap["LOST by a bare worktree — must be hydrated"]
E["settings.local.json<br/>local .claude/* overrides"]:::warn
F["local .mcp.json mods<br/>(the M .mcp.json in git status)"]:::warn
G[".env / .envrc / direnv<br/>local secrets + tokens"]:::warn
H["any other gitignored local config"]:::warn
end
keeps --> R["Worker = full peer of primary"]:::goal
gap -->|"overlay step at provision"| R
classDef ok fill:#2f855a,stroke:#22543d,color:#ffffff;
classDef warn fill:#b7791f,stroke:#7b341e,color:#ffffff;
classDef goal fill:#2b6cb0,stroke:#2a4365,color:#ffffff;
Green is inherited by path (home dir / CLAUDE_CONFIG_DIR) or lives in tracked files that the
worktree checks out anyway. Amber is the real gap — untracked local config the worktree drops.
Mechanism — worktree hydration (part of SessionManager.acquire, after git worktree add):
- Inherit by path, don't copy — keep the worker's
$HOME,CLAUDE_CONFIG_DIR, and memory dir identical to the primary's. Everything home-scoped (userCLAUDE.md, memory, auth, global skills) is already parity for free; only cwd-relative project-local files are the gap. - Overlay the untracked project-local set from the primary tree into the worktree — a defined,
configurable list:
settings.local.json(+ any local.claude/*), the locally-modified.mcp.json,.env/.envrc, and any other gitignored config the primary depends on. Symlink (read-only parity, stays live, nothing to go stale) rather than copy where possible; copy only what a worker may write. - Never overlay the git plumbing — the worktree's own
.gitfile/branch is what gives isolation; that's the one thing that must differ from the main tree.
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 (
.env,.mcp.jsonauth, gitea token) become visible to an off-subscription worker running against a third-party model endpoint. That is the accepted consequence of "workers must be full peers" — but it is a real trust expansion over a worker that only sees tracked code. Keep the overlay list explicit and minimal; don't blanket-symlink the whole tree..mcp.jsonandwiki/remain excluded from all worker commits regardless of being present for reference.
Lifecycle
sequenceDiagram
autonumber
participant P as Primary
participant SM as SessionManager (daemon)
participant G as git / gitea
participant W as Worker
P->>SM: acquire(ticket, profile)
SM->>G: git worktree add wt -b worker/ticket-nonce main
SM->>SM: overlay parity config into wt
SM->>W: spawn (cwd = wt, on its branch)
Note over W: implement in the isolated worktree
W->>G: git commit + git push (SSH, same user)
W->>G: open PR (branch to main)
W-->>P: fleet_reply (prUrl, branch, summary, tests)
P->>SM: release(paneId)
SM->>G: git worktree remove wt
Note over G: branch + PR persist for review/merge
P->>G: review PR, merge on green
The worker's "checkpoint" (CB-302) is exactly steps 6–8: commit → push → open PR. This replaces the
earlier STATE.md idea — a PR is reviewable, mergeable, and self-describing.
Infra facts (verified this session)
- 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 cangit pushwith no extra credential. - gitea is NOT in the project
.mcp.json(onlyjetbrains,intellij-index,fleetd). The primary's gitea MCP comes from a global/user config, so workers do not inherit it. A worker gets only thebridgeMCP mounted (via--mcp-configlaunch flag). - No gitea CLI (
tea) installed;glabis present but is the GitLab CLI (wrong backend).
Implication: git push is free for workers; only PR creation needs a new mechanism.
Open decision — how the worker creates the PR
| Option | Mechanism | Trade-off |
|---|---|---|
| A. gitea REST + token | Worker curls POST /api/v1/repos/fleet/fleetd/pulls with a scoped token injected by the daemon into the worker env |
Minimal, no new server; token lives in the off-subscription worker's env (scope it tightly) |
| B. mount gitea MCP into workers | Add the gitea MCP to the worker's --mcp-config alongside bridge |
Clean tool call, but the gitea MCP's own auth/token must be provisioned per worker; more moving parts |
C. install tea CLI |
Worker runs tea pr create with a token |
Another dependency to install + configure; same token question as A |
Recommendation: A (gitea REST + a repo-scoped token). Smallest surface, reuses SSH for push,
and the token is a single scoped secret the daemon injects like it already injects
ANTHROPIC_AUTH_TOKEN. The implementer skill wraps the curl in one documented step.
Trust / token scope (the real cost of "worker opens its own PR")
- Off-subscription workers already could push (SSH, same user). The incremental grant is PR-create, i.e. a gitea API token.
- Scope the token minimally: the
fleet/fleetdrepo,write:repository(create branch + PR), not merge/admin/org. A leaked token can open PRs, not merge them — the primary/human is still the merge gate. - Inject via the daemon (env var, e.g.
GITEA_TOKEN), never written to the worker's config dir — same non-invasive pattern as the ANTHROPIC token. Guard is unaffected (it concernsANTHROPIC_BASE_URL, not git).
Implementation plan
| Piece | Where | Notes |
|---|---|---|
| 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 | FleetConfig.Worker.parityOverlay — repo-relative paths, sane defaults |
auditable, per-repo tunable; keep explicit + minimal (trust) |
| Branch naming | worker/<ticket-slug>-<nonce> 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 + FleetConfig |
repo-scoped, minimal perms |
| PR review + merge | Primary (has gitea MCP + judgment) | merge on green; the human/primary gate stays |
Implementer skill (outline)
A worker-facing playbook (sibling to the existing reviewer skill):
- You are in a git worktree on a dedicated branch — check
git status/git branch; do all work here, never onmain. - Implement the task; keep commits focused and message them clearly.
- Push your branch (
git push -u origin HEAD). - Open a PR to
main(option Acurl, or the decided mechanism) with a title/body describing the change and referencing the ticket. - Reply via
fleet_replywith the PR URL, branch name, files changed, and test names — that reply is the whole handoff. - Do not merge; do not touch
.mcp.jsonorwiki/.
Sequencing
- Finish + verify CB-301 core (in flight) — registry/FSM.
- Extend CB-301 with worktree provisioning (this doc) once the PR mechanism is chosen.
- Add the implementer skill + token injection.
- CB-302 = wire the worker checkpoint (commit/push/PR) as the release-time handoff.