ace-coworker 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. checksums.yaml +7 -0
  2. data/.ace-defaults/coworker/config.yml +6 -0
  3. data/.ace-defaults/coworker/roles/integrator.yml +15 -0
  4. data/.ace-defaults/coworker/roles/reviewer.yml +15 -0
  5. data/.ace-defaults/coworker/roles/spec.yml +14 -0
  6. data/.ace-defaults/coworker/roles/worker.yml +13 -0
  7. data/.ace-defaults/coworker/sequences/integrator.yml +20 -0
  8. data/.ace-defaults/coworker/sequences/reviewer.yml +20 -0
  9. data/.ace-defaults/coworker/sequences/spec.yml +16 -0
  10. data/.ace-defaults/coworker/sequences/worker.yml +21 -0
  11. data/.ace-defaults/nav/protocols/wfi-sources/ace-coworker.yml +19 -0
  12. data/CHANGELOG.md +255 -0
  13. data/README.md +98 -0
  14. data/Rakefile +13 -0
  15. data/docs/usage.md +564 -0
  16. data/exe/ace-coworker +16 -0
  17. data/handbook/workflow-instructions/coworker/integrator.wf.md +162 -0
  18. data/handbook/workflow-instructions/coworker/reviewer.wf.md +211 -0
  19. data/handbook/workflow-instructions/coworker/spec.wf.md +100 -0
  20. data/handbook/workflow-instructions/coworker/worker.wf.md +103 -0
  21. data/lib/ace/coworker/atoms/active_executor_rule.rb +71 -0
  22. data/lib/ace/coworker/atoms/brief_formatter.rb +33 -0
  23. data/lib/ace/coworker/atoms/config_schema.rb +121 -0
  24. data/lib/ace/coworker/atoms/integration_gate.rb +92 -0
  25. data/lib/ace/coworker/atoms/outcome_planner.rb +79 -0
  26. data/lib/ace/coworker/atoms/prepare_decision.rb +75 -0
  27. data/lib/ace/coworker/atoms/prompt_template.rb +28 -0
  28. data/lib/ace/coworker/atoms/report_record_formatter.rb +60 -0
  29. data/lib/ace/coworker/atoms/resume_brief_formatter.rb +84 -0
  30. data/lib/ace/coworker/atoms/resume_planner.rb +78 -0
  31. data/lib/ace/coworker/atoms/role_preset_schema.rb +91 -0
  32. data/lib/ace/coworker/atoms/round_record_schema.rb +62 -0
  33. data/lib/ace/coworker/atoms/round_rule.rb +105 -0
  34. data/lib/ace/coworker/cli/commands/commission.rb +100 -0
  35. data/lib/ace/coworker/cli/commands/gate.rb +91 -0
  36. data/lib/ace/coworker/cli/commands/prepare.rb +138 -0
  37. data/lib/ace/coworker/cli/commands/report.rb +79 -0
  38. data/lib/ace/coworker/cli/commands/resume.rb +57 -0
  39. data/lib/ace/coworker/cli/commands/roles.rb +60 -0
  40. data/lib/ace/coworker/cli/commands/rounds.rb +99 -0
  41. data/lib/ace/coworker/cli/commands/start.rb +39 -0
  42. data/lib/ace/coworker/cli/commands/status.rb +95 -0
  43. data/lib/ace/coworker/cli.rb +83 -0
  44. data/lib/ace/coworker/errors.rb +10 -0
  45. data/lib/ace/coworker/models/assignment_snapshot.rb +65 -0
  46. data/lib/ace/coworker/models/brief.rb +38 -0
  47. data/lib/ace/coworker/models/config.rb +54 -0
  48. data/lib/ace/coworker/models/outcome.rb +39 -0
  49. data/lib/ace/coworker/models/preparation.rb +67 -0
  50. data/lib/ace/coworker/models/resume_plan.rb +47 -0
  51. data/lib/ace/coworker/molecules/assignment_metadata.rb +57 -0
  52. data/lib/ace/coworker/molecules/assignment_probe.rb +57 -0
  53. data/lib/ace/coworker/molecules/config_loader.rb +119 -0
  54. data/lib/ace/coworker/molecules/git_state_probe.rb +63 -0
  55. data/lib/ace/coworker/molecules/hitl_reporter.rb +145 -0
  56. data/lib/ace/coworker/molecules/instruction_resolver.rb +82 -0
  57. data/lib/ace/coworker/molecules/project_root.rb +26 -0
  58. data/lib/ace/coworker/molecules/report_state_store.rb +53 -0
  59. data/lib/ace/coworker/molecules/review_round_store.rb +115 -0
  60. data/lib/ace/coworker/molecules/role_preset_loader.rb +95 -0
  61. data/lib/ace/coworker/molecules/role_sequence_composer.rb +245 -0
  62. data/lib/ace/coworker/molecules/step_evidence_reader.rb +60 -0
  63. data/lib/ace/coworker/molecules/step_report_reader.rb +39 -0
  64. data/lib/ace/coworker/molecules/task_probe.rb +40 -0
  65. data/lib/ace/coworker/molecules/task_record_writer.rb +44 -0
  66. data/lib/ace/coworker/molecules/worktree_executor_probe.rb +139 -0
  67. data/lib/ace/coworker/molecules/worktree_provision_probe.rb +137 -0
  68. data/lib/ace/coworker/organisms/commission_orchestrator.rb +148 -0
  69. data/lib/ace/coworker/organisms/prepare_orchestrator.rb +374 -0
  70. data/lib/ace/coworker/organisms/report_orchestrator.rb +192 -0
  71. data/lib/ace/coworker/organisms/resume_orchestrator.rb +91 -0
  72. data/lib/ace/coworker/organisms/start_config_writer.rb +127 -0
  73. data/lib/ace/coworker/organisms/start_orchestrator.rb +45 -0
  74. data/lib/ace/coworker/organisms/status_orchestrator.rb +62 -0
  75. data/lib/ace/coworker/version.rb +7 -0
  76. data/lib/ace/coworker.rb +86 -0
  77. metadata +319 -0
data/docs/usage.md ADDED
@@ -0,0 +1,564 @@
1
+ ---
2
+ doc-type: usage
3
+ title: ace-coworker Usage
4
+ purpose: full command reference, role presets, and the start-config contract
5
+ ace-docs:
6
+ last-updated: 2026-10-19
7
+ last-checked: 2026-10-19
8
+ ---
9
+
10
+ # ace-coworker Usage
11
+
12
+ `ace-coworker` is the prepared executor of the file-based lab. The planner
13
+ (L4) writes the run configuration; `ace-coworker` reads, validates, and
14
+ renders what the executor agent must drive. This document is the single
15
+ source for the start-config format.
16
+
17
+ ## Start config — `.ace-local/coworker/config.yml`
18
+
19
+ ```yaml
20
+ role: worker # required, non-empty role name
21
+ instructions: "roles/worker.md" # required: file path relative to the
22
+ # project root, or wfi:// prompt:// tmpl://
23
+ start_prompt: "Drive assignment <id> for task <ref> as worker."
24
+ # required (or start_prompt_file: path
25
+ # relative to the project root, whose
26
+ # content becomes the prompt; the two
27
+ # are mutually exclusive)
28
+ task: "8xg.t.sbz.2" # required: task ref that must resolve
29
+ # in this worktree
30
+ assignment: "abc123" # required: assignment id that must exist
31
+ # under .ace-local/assign/ and whose
32
+ # task_id matches `task`
33
+ overseer: "overseer@machine" # required: reporting address (opaque
34
+ # string until the L2 addressing scheme
35
+ # is integrated)
36
+ reporting:
37
+ channel: hitl # optional, default hitl
38
+ errors:
39
+ on_failure: report # optional: report | stop (default report)
40
+ ```
41
+
42
+ Validation is field-specific: every missing or invalid field produces its own
43
+ error, and no substitute commission is guessed. The config never copies
44
+ assignment steps — the plan stays solely with `ace-assign`.
45
+
46
+ ## Roles and presets
47
+
48
+ `ace-coworker` ships role presets — static YAML assets, not a role allowlist.
49
+ The catalog lives in the gem's `.ace-defaults/coworker/roles/*.yml`; a project
50
+ overrides or extends it via `.ace/coworker/roles/*.yml` (same filename wins,
51
+ project beats gem). Every preset file is validated on load — including that
52
+ the `name` field matches the file's basename — so a project file `worker.yml`
53
+ must declare `name: worker`; the `role` field
54
+ of a config is never checked against the catalog (ADR-034: the overseer
55
+ decides roles).
56
+
57
+ ```yaml
58
+ ---
59
+ name: worker # required, non-empty
60
+ description: Implements and verifies # optional, non-empty when present
61
+ instructions: wfi://coworker/worker # required: same sources as the config
62
+ start_prompt: "Drive {{assignment}}…" # required template; placeholders:
63
+ # {{task}} {{assignment}} {{role}} {{overseer}}
64
+ reporting: {channel: hitl} # optional, ConfigSchema value sets
65
+ errors: {on_failure: report} # optional, ConfigSchema value sets
66
+ ```
67
+
68
+ Shipped presets: `worker` (implement and verify; failed required tests block
69
+ success), `spec` (complete behavior-first specification; significant
70
+ unresolved gaps are reported, never guessed), and `reviewer` (independent
71
+ review of the delivered change; fixes confirmed defects itself inside its own
72
+ assignment, checks each fix diff, and returns to full review rounds under the
73
+ 3/2/10 convergence rule - deterministically enforced by
74
+ `Ace::Coworker::Atoms::RoundRule` (:continue | :accept | :escalate) with the
75
+ persisted round history in `.ace-local/coworker/review-rounds/<assignment>.json`
76
+ (`Molecules::ReviewRoundStore`; rounds are recorded through
77
+ `ace-coworker rounds record`, and a restart reloads the counters instead of
78
+ resetting them); escalates instead of marking unresolved
79
+ High/Critical as success), and `integrator` (result-dependent integration of
80
+ ready candidates: before merging it checks that dependencies are integrated
81
+ predecessors, the candidate head still matches the reviewed head, tests were
82
+ executed and passed, and a fresh independent review verdict accepts the
83
+ exact candidate head - deterministically enforced by
84
+ `Ace::Coworker::Atoms::IntegrationGate` via `ace-coworker gate`
85
+ (:ready | :blocked with per-check reasons); a stale review, unsatisfied
86
+ dependency, or missing test evidence withholds the merge and escalates to
87
+ the overseer instead of merging past the gate). Their instruction workflows
88
+ are `wfi://coworker/worker`, `wfi://coworker/spec`,
89
+ `wfi://coworker/reviewer`, and `wfi://coworker/integrator`. Role assignments
90
+ are prepared by the planner (L4)
91
+ or commissioned ad hoc via `commission`; the reviewer (like the worker)
92
+ develops its own assignment through `ace-assign add`, so confirmed-defect
93
+ fixes stay inside one assignment without overseer round-trips.
94
+
95
+ ### Start configs per role leg — `.ace-local/coworker/prepare/<role>.yml`
96
+
97
+ `prepare` writes ONE start config per planned sequence leg, at
98
+ `.ace-local/coworker/prepare/<role>.yml` inside the task worktree, in the
99
+ same documented format as `config.yml` (above). The next role starts from
100
+ its own context with zero ad-hoc setup:
101
+
102
+ ```
103
+ ace-coworker start --config .ace-local/coworker/prepare/worker.yml
104
+ ```
105
+
106
+ (run inside the task worktree). Ownership split — one writer per file:
107
+
108
+ - `prepare` owns `.ace-local/coworker/prepare/<role>.yml`. Each leg config
109
+ is expanded through the SAME commission primitive (preset → identity
110
+ checks → schema validation → atomic write), so the round-trip guarantee
111
+ "what commission writes, start accepts" holds by construction; the
112
+ start prompt comes from the role preset's template rendered with the
113
+ leg's task, assignment, role, and overseer.
114
+ - `commission` remains the single writer of the default config target
115
+ (`config.yml` or the cascade's `config_path`). `prepare` never writes
116
+ it and never overwrites an active driver — the active-executor and
117
+ mismatched-worktree conflicts are detected before any config write.
118
+
119
+ Leg configs carry no secrets and no assignment steps — only the documented
120
+ config fields with the composed assignment id as the ace-assign reference.
121
+ An overseer address is required for every leg (`--to`, or the coworker
122
+ config's `overseer` read leniently); without one, prepare ends with a
123
+ `missing_overseer` gap — `preparation conflict: missing_overseer - no
124
+ overseer address for the start configs (--to or coworker config)` — and
125
+ writes no leg config. Error precedence is deliberate:
126
+ role-validation errors (unknown role, missing process definition) win
127
+ over the missing-address check, and the check runs AFTER assignment
128
+ composition — the worktree and the L4.2 assignments are a delivered
129
+ artifact at that point; re-running prepare with `--to` idempotently adds
130
+ the leg configs. Writes are idempotent: identical
131
+ content is reported as `reused` (no write), changed content as `updated`,
132
+ `--dry-run` reports `planned` with zero writes. All legs are resolved
133
+ and validated before the first file is written, so a late leg failure
134
+ cannot leave a partial set of configs behind.
135
+
136
+ ### `ace-coworker roles`
137
+
138
+ ```
139
+ ace-coworker roles [--format table|json] [-q] [-d]
140
+ ```
141
+
142
+ Lists the catalog (name, source `gem`|`project`, description, instruction
143
+ source). Read-only; exit 0 for any valid catalog state, including an empty
144
+ catalog (rendered as an explicit empty state); an invalid preset file fails
145
+ with a specific error (non-zero exit).
146
+
147
+ ### `ace-coworker rounds record`
148
+
149
+ ```
150
+ ace-coworker rounds record --assignment ID --type full|delta
151
+ --status completed|aborted
152
+ [--confirmed id:sev[,...]] [--resolved id[,...]]
153
+ [--format table|json] [-q] [-d]
154
+ ```
155
+
156
+ The reviewer role's round-recording surface for the 3/2/10 convergence rule
157
+ (8xg.t.sbz.4.2): appends one finished round to the coworker-owned history at
158
+ `.ace-local/coworker/review-rounds/<assignment>.json`
159
+ (`Molecules::ReviewRoundStore#record!`) and prints the resulting
160
+ `Ace::Coworker::Atoms::RoundRule` verdict with its counters
161
+ (verdict `continue | accept | escalate`, full rounds, completed full
162
+ rounds, consecutive clean, unresolved High/Critical ids). `--confirmed`
163
+ takes `id:severity` pairs (e.g. `f-1:high`); `--resolved` takes finding ids
164
+ resolved by this round. A corrupt or non-array history file fails with
165
+ `corrupt round history at <path>` instead of silently resetting the
166
+ counters; the same applies to a malformed record shape (actionable field
167
+ errors, non-zero exit).
168
+
169
+ ### `ace-coworker gate`
170
+
171
+ ```
172
+ ace-coworker gate --review-verdict accept|continue|escalate
173
+ [--candidate-head SHA] [--review-head SHA]
174
+ [--tests executed,passed] [--dep LEAF=integrated|missing ...]
175
+ [--format table|json] [-q] [-d]
176
+ ```
177
+
178
+ The integrator role's pre-merge gate (8xg.t.sbz.4.3): a pure, stateless
179
+ evaluation of the four project-defined checks. `--candidate-head` is the
180
+ current candidate head (omit it when genuinely unknown - the gate then
181
+ blocks on the missing evidence instead of guessing); `--review-head` is the
182
+ candidate head the independent review verdict covers; `--tests` takes the
183
+ `executed,passed` evidence flags (any other token is an error, not
184
+ silent missing evidence); each `--dep NAME=integrated|missing` pair
185
+ states one dependency leaf (`missing` or any other value = not
186
+ integrated; the pair must contain `=`, and repeating a name is an
187
+ error). Empty or omitted `--dep` means the caller enumerated no
188
+ dependencies - the gate trusts the caller's dependency enumeration.
189
+ Prints `ready` or `blocked` with per-check reasons (dependencies, candidate
190
+ drift, review freshness/verdict, executed+passing tests). `blocked` is a
191
+ legitimate gate outcome (exit 0) - the role withholds the merge and
192
+ escalates; only malformed input (an invalid `--dep` pair, a duplicate
193
+ `--dep` name, or an invalid `--tests` token) fails with a non-zero exit.
194
+ Heads are recorded and compared as full lowercase SHAs. Nothing is persisted and no git operation is performed: the
195
+ gate decides readiness only.
196
+
197
+ ### `ace-coworker commission`
198
+
199
+ ```
200
+ ace-coworker commission --role NAME --task REF --assignment ID --overseer ADDR
201
+ [--start-prompt TEXT] [--config PATH] [--force]
202
+ [--dry-run] [--format table|json] [-q] [-d]
203
+ ```
204
+
205
+ The role-instantiation primitive consumed by the planner (L4.2/L4.3 seam):
206
+ expand a role preset plus the commission identity into the documented
207
+ `.ace-local/coworker/config.yml` (target from the config cascade, or
208
+ `--config PATH` root-relative). `commission` is a single-role writer for the
209
+ location ace-coworker already owns — no sequencing, no active-driver
210
+ detection, no role policies; those belong to the planner, which decides role
211
+ order and guards against overwriting an active driver. `prepare` reuses this
212
+ primitive internally for its per-leg configs under
213
+ `.ace-local/coworker/prepare/`, but the default config target written here
214
+ stays `commission`'s alone.
215
+
216
+ Procedure:
217
+
218
+ 1. fetch and validate the preset (`unknown role '<name>' (available: …)`);
219
+ 2. render the preset's `start_prompt` template with the commission identity
220
+ (`{{task}}`, `{{assignment}}`, `{{role}}`, `{{overseer}}`; unknown
221
+ placeholders stay literal; an explicit `--start-prompt` wins over the
222
+ template; `start_prompt_file` is not accepted here — write that config
223
+ variant by hand);
224
+ 3. run the same identity checks as `start`: the task must resolve in this
225
+ worktree and the assignment must exist under `.ace-local/assign/` with a
226
+ matching `task_id` (same message vocabulary as `start`);
227
+ 4. validate the assembled config through the start-grade schema
228
+ (round-trip guarantee: what `commission` writes, `start` accepts);
229
+ 5. write atomically (temp file + rename in the target directory), creating
230
+ parent directories; the resolved target must stay inside the project root
231
+ (`config path '<path>' escapes the project root`) and must name a file,
232
+ not a directory; an existing target is refused without `--force`
233
+ (`refusing to overwrite '<path>'; use --force`);
234
+ 6. `--dry-run` resolves and renders the config without writing anything.
235
+
236
+ Result JSON (table renders the same fields): role, task, assignment,
237
+ overseer, config_path, preset_source (`gem`|`project`), dry_run, written, and
238
+ the resolved config mapping.
239
+
240
+ ## Commands
241
+
242
+ ### `ace-coworker start`
243
+
244
+ ```
245
+ ace-coworker start [--config PATH] [--format table|json] [-q] [-d]
246
+ ```
247
+
248
+ Reads the config (default `.ace-local/coworker/config.yml`, or a
249
+ planner-prepared leg config via `--config
250
+ .ace-local/coworker/prepare/<role>.yml` — same format, same checks), then:
251
+
252
+ 1. resolves the task ref in this worktree via `ace-task`;
253
+ 2. verifies the assignment exists under `.ace-local/assign/` and its
254
+ `task_id` matches the config task;
255
+ 3. reads assignment truth from `ace-assign status --assignment <id> --format json`;
256
+ 4. resolves the instruction source (file relative to the project root, or
257
+ protocol URI via `ace-nav resolve`);
258
+ 5. prints the executor brief: role, resolved instruction source, start
259
+ prompt, task ref + path, the pinned `--assignment <id>` drive target,
260
+ overseer address, error rules, and current assignment state.
261
+
262
+ `start` writes nothing. Exit codes: `0` ready; `1` with a specific message
263
+ (`not ready: …`, `invalid config …: …`, `task '<ref>' not found in this
264
+ worktree`, `assignment '<id>' not found …`, `assignment '<id>' belongs to task
265
+ '<other>', not '<ref>'`, `instruction file '<path>' not found in this
266
+ worktree`).
267
+
268
+ ### `ace-coworker status`
269
+
270
+ ```
271
+ ace-coworker status [--config PATH] [--assignment ID] [--format table|json] [-q] [-d]
272
+ ```
273
+
274
+ Readiness query — always exit `0`. Absent config renders
275
+ `not ready: .ace-local/coworker/config.yml not found`. Otherwise renders the
276
+ resolved role/task/assignment/overseer, the assignment progress, and the
277
+ completion verdict with remaining-work reasons (empty plan, failed steps,
278
+ pending/active steps).
279
+
280
+ The completion verdict is deterministic: `complete` iff the assignment state
281
+ is `completed`, the step plan is non-empty, and every step is `done`. Process
282
+ or session end never implies completion.
283
+
284
+ ### `ace-coworker report`
285
+
286
+ ```
287
+ ace-coworker report [--config PATH] [--assignment ID] [--result success|error]
288
+ [--message TEXT] [--step N] [--no-send] [--dry-run]
289
+ [--format table|json] [-q] [-d]
290
+ ```
291
+
292
+ Records the assignment outcome and reports it through `ace-hitl`:
293
+
294
+ 1. derives the outcome from the ace-assign verdict (`--result` optional;
295
+ forcing `success` on an incomplete assignment is refused before anything
296
+ is written; an error outcome requires a cause — `--message` or the failed
297
+ step's recorded `error:` — and lists the unfinished steps);
298
+ 2. writes the durable record into the task's own folder
299
+ (`<task>/coworker/<assignment>.md`) — the contract-sanctioned per-task
300
+ progress location;
301
+ 3. keeps helper state in `.ace-local/coworker/reports/<assignment>.json`;
302
+ 4. creates a HITL report event (`ace-hitl create --type report --result …
303
+ --task/--assignment/--step --to <overseer>`) and delivers it
304
+ (`ace-hitl send <id>`).
305
+
306
+ Outcome and delivery are separated: an undelivered report keeps the recorded
307
+ outcome and exits 1 with an explicit delivery error; a rerun with the same
308
+ result resends the existing event instead of duplicating it (retrying
309
+ delivery never retries the assignment). `--no-send` creates the event
310
+ without delivering; `--dry-run` derives the outcome with zero writes;
311
+ `errors.on_failure: stop` records errors locally without any HITL
312
+ subprocess. The `overseer` config field is the configured `ace-hitl`
313
+ recipient name (resolved via `--to`).
314
+
315
+ ### `ace-coworker resume`
316
+
317
+ ```
318
+ ace-coworker resume [--config PATH] [--assignment ID] [--dry-run] [--format table|json] [-q] [-d]
319
+ ```
320
+
321
+ After an interruption: reads the existing assignment, task, and the REAL
322
+ state of the same worktree, and renders the resume path. Same task,
323
+ assignment, and worktree — never a new one; local changes are never reset;
324
+ resume itself never drives assignment steps (the executor does, via
325
+ ace-assign).
326
+
327
+ Dispositions:
328
+
329
+ - `continue` — the assignment is incomplete: preserved done steps,
330
+ remaining work, the in-flight step (active, else failed) with its recorded
331
+ cause and report plus verify-before-repeat guidance, and the preserved
332
+ local-change list. Steps named like external effects
333
+ (merge/publish/deploy/release/push) add an effect-caution block: check the
334
+ actual state with existing tools (e.g. `ace-git status`, PR evidence) or
335
+ report a blocker — never replay blindly, never a new task/worktree.
336
+ - `report` — the assignment is complete but the report delivery is missing:
337
+ delegates to the report flow, resending the SAME HITL event ID (retrying
338
+ delivery never retries the assignment).
339
+ - `complete` — complete and delivered: informational, nothing to resume.
340
+
341
+ `--dry-run` derives the plan with zero writes. There is no journal or
342
+ checkpoint store: the worktree git state, the ace-assign step files, and
343
+ the report helper state ARE the checkpoint (ADR-034).
344
+
345
+ ### `ace-coworker prepare`
346
+
347
+ ```
348
+ ace-coworker prepare --task REF --roles worker,reviewer [--to overseer@addr]
349
+ [--config PATH] [--dry-run] [--format table|json] [-q] [-d]
350
+ ```
351
+
352
+ The planner side of the commission: accept a preparation commission (task
353
+ reference + expected role sequence) and find or prepare exactly ONE proper
354
+ worktree for that task, THEN compose the assignments for the WHOLE known
355
+ role sequence in one go (L4.2), so after start the roles hand off
356
+ sequentially without the overseer re-commissioning preparation per step.
357
+ Composition is idempotent: re-preparing the same task reuses its existing
358
+ worktree and the already-composed assignments (matching
359
+ `<task>-<role>` assignment names with a recorded task_id) with zero
360
+ redundant writes; only sequence legs not yet composed are added. Prepare
361
+ also writes the per-role executor start configs (L4.3) — one
362
+ `.ace-local/coworker/prepare/<role>.yml` per leg (see the section above) —
363
+ so `ace-coworker start --config .ace-local/coworker/prepare/<role>.yml`
364
+ inside the worktree begins any leg without ad-hoc setup; `commission`
365
+ stays the single-role writer of the default `config.yml` target. After
366
+ a successful preparation the planner sends the overseer a READINESS
367
+ report via ace-hitl (L4.4, see below). Note:
368
+ composing multi-leg sequences leaves ace-assign's `.ace-local/assign/.latest`
369
+ pointing at the LAST-composed leg (e.g. integrator); drivers must address
370
+ legs by the explicit assignment ids from the prepare result, never via
371
+ `.latest`-based resolution.
372
+
373
+ Sequence composition (no new state owner; `ace-assign` stores the plans in
374
+ the worktree's `.ace-local/assign/`, ADR-034):
375
+
376
+ - ONE assignment per role, in dependency order. Each records the task id
377
+ (the `commission` task↔assignment match works unchanged), the expected
378
+ outcome of the leg in its description, and the position
379
+ (`sequence N/M`). Every leg after the first opens with an explicit entry
380
+ condition naming the predecessor assignment and role.
381
+ - Each role's process comes from its own sequence definition — shipped
382
+ `.ace-defaults/coworker/sequences/<role>.yml`, project override
383
+ `.ace/coworker/sequences/<role>.yml` (whole-file, project wins; the
384
+ `role` field must match the filename's role). Worker and reviewer get
385
+ DIFFERENT processes matched to the same task: the worker leg requires
386
+ passing `bin/ace-test` per modified package, the reviewer leg embeds the
387
+ 3/2/10 round rule (`ace-coworker rounds record`, min 3 completed full
388
+ rounds with the last 2 consecutive clean and no unresolved earlier
389
+ High/Critical, max 10, then escalation), the integrator leg runs the
390
+ `ace-coworker gate` checks before any merge, and the spec leg raises
391
+ material requirement gaps as `ace-hitl` questions instead of guessing.
392
+ - A role without a preset (`unknown role '<name>'`) or without its own
393
+ process definition (`role '<name>' has no assignment process … refusing
394
+ to substitute another role's process`) is a `role_sequence` GAP
395
+ reported with exit 1, NOTHING composed (all-or-nothing), and a
396
+ decision question to the overseer when an escalation recipient
397
+ resolves: the planner never substitutes another role's process. The
398
+ worktree itself stays prepared and kept.
399
+ - `--dry-run` composes nothing (the would-be worktree does not exist), so
400
+ the result reports `assignments: []` and `start_configs: []` alongside
401
+ `would_create`/`would_recover`.
402
+
403
+ Start-config composition (L4.3; same owner, new artifact kind under
404
+ `.ace-local/coworker/`):
405
+
406
+ - ONE start config per leg at `.ace-local/coworker/prepare/<role>.yml` in
407
+ the worktree, expanded through the commission primitive from the same
408
+ role preset — role, instruction source, preset-rendered start prompt,
409
+ task, the leg's assignment id, the resolved overseer address, and the
410
+ preset's reporting/error rules. `start --config` accepts it unchanged
411
+ (round-trip guarantee); `commission`/the default `config.yml` target is
412
+ never touched by prepare.
413
+ - The overseer address resolves like conflict escalation: `--to` wins,
414
+ else the coworker config's `overseer` (lenient field-only read, so a
415
+ partial planner-side config still names it). No address anywhere: a
416
+ `missing_overseer` GAP — exit 1 with
417
+ `preparation conflict: missing_overseer - no overseer address for the
418
+ start configs (--to or coworker config); the commission is NOT ready -
419
+ no leg config is written…` (followed by the prepared/decision-needed
420
+ detail lines) and no leg config written —
421
+ never a config with an empty overseer and never a ready commission.
422
+ The gap detail states what IS prepared (worktree, assignments) and
423
+ the decision needed (supply `--to` and re-run prepare, idempotent).
424
+ - Idempotent: re-preparation reports an identical leg config as `reused`
425
+ without a write; changed content (e.g. a different overseer) is
426
+ rewritten as `updated` — prepare is the single writer of these files.
427
+ Conflict checks (active executor, mismatched worktree) run BEFORE any
428
+ config write, so preparing the next role never disturbs an active
429
+ driver. Leg configs never copy secrets or assignment steps.
430
+ - `--dry-run` reports each leg config as `planned` with zero writes (for
431
+ an existing worktree; a not-yet-existing worktree reports
432
+ `start_configs: []`).
433
+
434
+ Deterministic decision procedure (no new state; ace-git-worktree stays the
435
+ worktree owner):
436
+
437
+ 1. Resolve the project root and the task via ace-task; an unresolvable ref
438
+ fails with `task '<ref>' not found in this worktree` (invalid commission,
439
+ no HITL event).
440
+ 2. Find the task's worktree (association-aware `ace-git-worktree` switch):
441
+ a resolved worktree for this task is reused; a resolved worktree for
442
+ another task is a `mismatched_worktree` conflict; stale metadata (an
443
+ association whose directory is missing — detected via the association
444
+ inventory, since the owner's switch never resolves a missing directory)
445
+ recovers via prune + recreate, reattaching the surviving branch so its
446
+ committed work is never orphaned behind a freshly minted branch.
447
+ 3. Otherwise create the task worktree (idempotent owner call). A creation
448
+ failure while the expected location is occupied — on either the miss path
449
+ or the stale-recovery retry — is a `mismatched_worktree`
450
+ conflict; any other failure exits 1 with the structured error and its
451
+ recovery hint (operational error, no HITL event).
452
+ 4. Probe the resolved worktree for an active executor: the worktree's
453
+ `.ace-local/coworker/config.yml` names a commissioned assignment whose
454
+ state is anything other than `completed` (including unparseable/unknown —
455
+ never overwrite), or any uncommissioned assignment is `running`/`stalled`,
456
+ is an `active_executor` conflict — a completed commission falls through
457
+ to the uncommissioned scan, so it never masks another running/stalled
458
+ assignment. Pending/never-started assignments never block (the up-front
459
+ role sequence of L4.2 must not self-conflict); uncommissioned `failed`
460
+ assignments are surfaced as context only. On an `active_executor`
461
+ conflict the rule evidence (`assignment`, `state`, `source`) is emitted
462
+ structurally in `active_executor`.
463
+ 5. Reuse performs zero redundant writes: no git effects, no task frontmatter
464
+ rewrite, no config change; already-composed sequence assignments are
465
+ reported as `existing` with their original ids, and only sequence legs
466
+ not yet composed are created.
467
+
468
+ Conflicts mutate nothing: a decision-type HITL question for the overseer is
469
+ created and sent (`--type question --kind decision`, title
470
+ `Preparation conflict - <task>/<worktree-dir>`). The overseer fallback
471
+ reads the `overseer` field of `--config` leniently, so a partial
472
+ (planner-side) config still names its recipient; a config that fails full
473
+ validation AND supplies no overseer surfaces the load error alongside the
474
+ explicit `cannot report conflict: no overseer address` note. If the
475
+ ace-hitl CLI itself is unavailable, the structured conflict result is still
476
+ rendered with the failure folded into its detail. Without any escalation
477
+ recipient the conflict still exits 1. An undeliverable event keeps the
478
+ event id in the output for a later
479
+ `ace-hitl send <id>` retry — retrying delivery never retries preparation.
480
+
481
+ Readiness / gap reporting (L4.4; ace-hitl stays the transport and event
482
+ owner, no new state):
483
+
484
+ - Every SUCCESSFUL preparation (created / recovered / reused) ends with
485
+ a READINESS report to the overseer — `--type report --result success`
486
+ (`Preparation ready - <task>/<worktree-dir>`), a report expects no
487
+ reply. The body carries the complete set to run the first step:
488
+ the worktree (path/branch/disposition), every sequence leg
489
+ (`sequence N/M <role>: <assignment>`), every start config, and the
490
+ exact first step (`ace-coworker start --config
491
+ .ace-local/coworker/prepare/<first-role>.yml` in the worktree) — the
492
+ overseer never composes ace-assign details itself. The report is
493
+ delivered immediately; the result JSON carries it under `report`
494
+ (`{"kind": "readiness", "event": <id>, "delivered": …}`). A create or
495
+ delivery failure never undoes the preparation: the failure (or the
496
+ event id plus `ace-hitl send` retry hint) is recorded in `report`
497
+ instead.
498
+ - GAPS never pass as ready: `role_sequence` (role preset/process
499
+ errors) and `missing_overseer` join the existing
500
+ `active_executor` / `mismatched_worktree` conflicts — exit 1, nothing
501
+ unprepared is started, and the gap escalates to the overseer as a
502
+ decision question (`--type question --kind decision`) with the
503
+ concrete reasons: what is prepared, what is missing, and the decision
504
+ needed. Without an escalation recipient the gap stays fully visible
505
+ in the conflict detail (an undeliverable report is still recorded,
506
+ never silently dropped).
507
+ - `--dry-run` reports nothing: no readiness report, no escalation
508
+ (zero effects includes ace-hitl events).
509
+
510
+ Result JSON (table renders the same fields):
511
+
512
+ ```json
513
+ {
514
+ "task": "8xg.t.sbz.3",
515
+ "task_path": "/abs/.ace-tasks/8xg.t.sbz-lab…/",
516
+ "roles": ["worker", "reviewer"],
517
+ "worktree": { "path": "/abs/.ace-wt/t.8xg-t-sbz-3", "branch": "8xg-…", "disposition": "created" },
518
+ "assignments": [
519
+ {"role": "worker", "position": 1, "of": 2, "assignment": "abc123", "disposition": "created", "steps": 2, "outcome": "…"},
520
+ {"role": "reviewer", "position": 2, "of": 2, "assignment": "def456", "disposition": "created", "steps": 1, "outcome": "…"}
521
+ ],
522
+ "start_configs": [
523
+ {"role": "worker", "assignment": "abc123", "path": ".ace-local/coworker/prepare/worker.yml", "disposition": "created"},
524
+ {"role": "reviewer", "assignment": "def456", "path": ".ace-local/coworker/prepare/reviewer.yml", "disposition": "created"}
525
+ ],
526
+ "active_executor": null,
527
+ "worktree_assignments": [{"id": "abc123", "state": "paused", "progress": "0/2"}],
528
+ "report": {"kind": "readiness", "event": "8xhkep", "delivered": true},
529
+ "conflict": null
530
+ }
531
+ ```
532
+
533
+ `disposition` is `created` / `recovered` (stale metadata recovered) / `reused`
534
+ or, with `--dry-run`, `would_create` / `would_recover` (stale metadata that
535
+ the real run would recover). A non-null `conflict`
536
+ (with `kind`, `detail`, `hitl_event`) replaces `worktree`. `--dry-run`
537
+ performs the full resolution and conflict detection with zero writes (no
538
+ create, no prune-recovery, no HITL events); the exit code reflects the
539
+ would-be outcome.
540
+
541
+ ## Exit codes
542
+
543
+ | Command | 0 | 1 |
544
+ |---|---|---|
545
+ | `start` | config valid, task+assignment verified, brief rendered | missing/invalid/mismatched input |
546
+ | `status` | query rendered any state (including not-ready) | — |
547
+ | `report` | success outcome recorded (and delivered, or `--no-send`/`--dry-run`) | error outcome; delivery/create failure (outcome still recorded); refused input |
548
+ | `resume` | continue/complete rendered; report redelivered | delegated report failure keeps its exit 1 (plan still rendered) |
549
+ | `prepare` | worktree created/recovered/reused with the role-sequence assignments and per-leg start configs composed, readiness report sent (or recorded with its event id / failure); dry-run: would_create/would_recover, nothing composed, nothing reported | conflict (active_executor / mismatched_worktree / role_sequence / missing_overseer); invalid commission; operational preparation failure |
550
+ | `roles` | catalog rendered any valid state (including empty) | invalid preset file (specific error) |
551
+ | `rounds` | round recorded, verdict rendered (including escalate) | corrupt history; malformed record |
552
+ | `gate` | verdict rendered (`ready` or `blocked` - a withheld merge is a legitimate outcome) | malformed input (invalid or duplicate `--dep`, invalid `--tests` token) |
553
+ | `commission` | config written (dry-run: resolved, nothing written) | unknown role; invalid commission; task/assignment mismatch; refused overwrite; invalid assembly; config path escaping the project root; directory config target |
554
+
555
+ ## Boundaries
556
+
557
+ - `.ace-local/coworker/` is owned by `ace-coworker`; `.ace-local/assign/` by
558
+ `ace-assign`; `.ace-local/hitl/` by `ace-hitl`; `.ace-tasks/` by `ace-task`.
559
+ - No journal, queue, daemon, or second plan store (ADR-034).
560
+ - Delegation to `ace-assign` / `ace-nav` uses argv subprocess calls with an
561
+ explicit working directory (ADR-031).
562
+ - Reporting via `ace-hitl` and resume-after-interruption ship with
563
+ `ace-coworker` 0.4.0+; role presets resolve their `wfi://` instruction
564
+ sources through `ace-nav` (runtime dependency since 0.5.0).
data/exe/ace-coworker ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "../lib/ace/coworker"
5
+ require "ace/support/cli"
6
+
7
+ args = ARGV.empty? ? ["--help"] : ARGV
8
+
9
+ begin
10
+ Ace::Support::Cli::Runner.new(Ace::Coworker::CLI).call(args: args)
11
+ rescue Ace::Support::Cli::Error => e
12
+ warn e.message
13
+ exit(e.exit_code)
14
+ rescue Interrupt
15
+ exit(130)
16
+ end