返回 CodeWhale
delegated-coordination.md
根目录 / docs / architecture / delegated-coordination.md
1 # Delegated coordination contract
2
3 Codewhale records the small amount of shared state that parallel work needs to
4 remain attributable. This is coordination metadata, not an approval system and
5 not a store for model reasoning or transcripts.
6
7 ## Launch and write ownership
8
9 Every write-capable child persists the same `ChildLaunchManifest` used by the
10 runtime. Its mutation claim contains normalized repo-relative directory roots,
11 exact files, and named contracts. Paths that are absolute or escape with `..`
12 fail validation.
13
14 A prompt-only general child starts read-only. Callers that want a writer must
15 declare at least one `write_roots`, `exact_files`, or
16 `coordination_contracts` value. Codewhale does not infer a repo-wide `.` claim.
17 An active shared-workspace claim blocks another active owner when either tree
18 contains the other, exact files collide, or a named contract matches. A real
19 isolated worktree may proceed concurrently. Scope expansion uses
20 `agent action=claim` (`write_roots`, `exact_files`,
21 `coordination_contracts`); a collision records a bounded contention receipt and
22 fails before mutation without opening a permission modal.
23
24 Fleet workers follow the same rule. Write-capable Fleet tasks declare
25 `workspace.writable_paths` or `metadata.coordination_contracts`, and the
26 resolved values are persisted in their launch manifest.
27
28 ### Embedding state boundary
29
30 By default, delegated control-plane state remains workspace-scoped at
31 `<workspace>/.codewhale/state`: the worker ledger, complete transcript
32 artifacts, and coordination lock share that root. An embedding host may set
33 `EngineConfig::subagent_state_root` to keep those files under a session-owned
34 root without changing child cwd, tool path authority, or the execution
35 workspace recorded in receipts.
36
37 Different state roots intentionally form different coordination domains. They
38 do not exchange write claims or contention receipts even when their execution
39 workspace is the same. A host choosing that layout must serialize conflicting
40 writes itself or give writers isolated worktrees; the state-root override is a
41 storage and lifecycle boundary, not cross-session write arbitration.
42
43 This record is a cooperative Codewhale coordination boundary, not an operating
44 system sandbox. Fleet carries a machine-readable outer cap into each worker,
45 rechecks structured mutation targets, rejects symlink aliases, and denies
46 unbounded shell, Git, code, plugin, and mutating MCP execution. Those checks
47 prevent one Codewhale worker from silently exceeding its declared claim; they
48 do not promise containment against a separate hostile process racing filesystem
49 paths. Use an OS sandbox or an isolated host when that adversarial boundary is
50 required.
51
52 Authority-bound Fleet subprocesses are explicit leaves in v0.9.1. Their MCP,
53 LSP, snapshot, custom-tool, plugin, shell, and nested-agent startup surfaces are
54 disabled so configured background executables cannot bypass the structured
55 mutation path. The persisted receipt reports `max_spawn_depth = 0`.
56
57 ## Decisions and projected context
58
59 Coordination schema version 1 persists decision records with a stable id,
60 subject, proposed/accepted/superseded status, one owner, applicability scope,
61 concise constraints, evidence handles, version, and sequence. Only the owner
62 may change a decision's status. A second accepted decision for the same subject
63 cannot silently replace the first.
64
65 At child launch, Codewhale projects only accepted decisions whose scope matches
66 the child's declared paths, contracts, role, or tool capabilities. The
67 projection is deduplicated, limited to eight decisions and 4096 UTF-8 bytes,
68 and receipted by child id and decision ids. The task prompt may separately
69 carry at most eight explicit dependency facts and eight observable acceptance
70 checks. Parent transcripts, secrets, and raw reasoning are never projected.
71
72 ## Neutral fan-in
73
74 Conflicting candidates remain preserved as branch, patch, or artifact handles.
75 The neutral owner is the nearest common Planner/manager/operator in the
76 persisted parent tree, falling back to the root release owner. Neither candidate
77 author may claim that role. Reconciliation records:
78
79 - both or all input decision ids and candidate handles;
80 - a retry count and a limit of at most three;
81 - distinct independent Reviewer and Verifier evidence handles;
82 - a verified, failed, or blocked verification outcome; and
83 - the neutral disposition and bounded evidence handles.
84
85 Retry exhaustion is a terminal, inspectable receipt, not permission to discard
86 either candidate. Restart/replay preserves the schema, decisions, claims,
87 contention, projections, and reconciliation sequence.
88
89 ## Inspection
90
91 `agent action=status` exposes concise per-child claims and accepted decisions.
92 The bounded decision, claim, contention, projection, and reconciliation
93 receipts, plus deterministic hottest-path counts, reach the TUI through
94 `CoordinationDetailProjection`; the `agents/coordinate action=inspect` tool that
95 used to serve them to the model is registered for transcript replay only and is
96 no longer advertised in the model catalog. Metrics without an authoritative
97 source, such as package growth or route cost, remain explicitly null instead of
98 being inferred.
99
100 ## One model-facing surface
101
102 `agent` is the only sub-agent tool in the model catalog. The six narrow
103 `agents/*` tools stay registered so a persisted transcript replays against the
104 same implementations, but they declare `model_visible() -> false`: they are not
105 sent in the initial catalog and `tool_search` cannot return them. Everything
106 they did is reachable through an `agent` action — `status`/`peek` for `list`,
107 `message`, `followup`, `interrupt`, `wait`, and `claim` for the one capability
108 that had no equivalent, write-scope expansion.
109
110 ## The workspace lock, and what losing it does and does not mean
111
112 The ledger lives in one file, `.codewhale/state/subagents.v1.json`, written as a
113 whole-document atomic replace. Two processes rewriting that file would be
114 last-rename-wins, and the loser's `write_claims` would vanish — which silently
115 re-opens concurrent overlapping mutation of the same paths after a restart. So
116 one per-workspace advisory flock (`subagents.v1.lock`) decides who may *write*
117 the file. That is the whole of its job.
118
119 Opening a second Codewhale session in the same workspace is ordinary usage, so
120 losing that flock is an ordinary state, not a failure:
121
122 - **It does not affect liveness.** A session that cannot write the ledger runs
123 its own agents normally. Whether an agent is alive is decided by heartbeat
124 evidence, never by lock ownership. (Before v0.9.4 the cleanup pass
125 terminalized every running agent with no live task handle purely because this
126 process lacked the flock; that coupling is gone.)
127 - **It does not affect reads.** The boot-time load is unconditional. A second
128 session sees the workspace's decisions and write claims even though it cannot
129 append to them. Gating the load on the write flock previously left the second
130 session holding an empty default ledger, which it would write straight over
131 the real one the moment the first session exited and the flock became
132 acquirable.
133 - **It does mean no durable ledger appends.** Decision, claim, contention, and
134 reconciliation mutations still require the flock, and so does any
135 shared-workspace write-capable launch, because such a launch must be durably
136 replayable before it executes. A second session can therefore delegate
137 read-only and isolated-worktree work, but not shared-workspace writers.
138
139 Known gap: a lock-less session holds the ledger as of its own boot. If the lock
140 owner appends more records and then exits, the second session can acquire the
141 flock and persist its boot-time snapshot, losing the records appended in
142 between. Closing that — and letting a second session launch shared-workspace
143 writers — needs per-session ledger segments unioned on read, so that no two
144 processes ever write the same file and the claim-overlap check runs against the
145 union. That work is not done.
146
146 lines MARKDOWN