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.
- checksums.yaml +7 -0
- data/.ace-defaults/coworker/config.yml +6 -0
- data/.ace-defaults/coworker/roles/integrator.yml +15 -0
- data/.ace-defaults/coworker/roles/reviewer.yml +15 -0
- data/.ace-defaults/coworker/roles/spec.yml +14 -0
- data/.ace-defaults/coworker/roles/worker.yml +13 -0
- data/.ace-defaults/coworker/sequences/integrator.yml +20 -0
- data/.ace-defaults/coworker/sequences/reviewer.yml +20 -0
- data/.ace-defaults/coworker/sequences/spec.yml +16 -0
- data/.ace-defaults/coworker/sequences/worker.yml +21 -0
- data/.ace-defaults/nav/protocols/wfi-sources/ace-coworker.yml +19 -0
- data/CHANGELOG.md +255 -0
- data/README.md +98 -0
- data/Rakefile +13 -0
- data/docs/usage.md +564 -0
- data/exe/ace-coworker +16 -0
- data/handbook/workflow-instructions/coworker/integrator.wf.md +162 -0
- data/handbook/workflow-instructions/coworker/reviewer.wf.md +211 -0
- data/handbook/workflow-instructions/coworker/spec.wf.md +100 -0
- data/handbook/workflow-instructions/coworker/worker.wf.md +103 -0
- data/lib/ace/coworker/atoms/active_executor_rule.rb +71 -0
- data/lib/ace/coworker/atoms/brief_formatter.rb +33 -0
- data/lib/ace/coworker/atoms/config_schema.rb +121 -0
- data/lib/ace/coworker/atoms/integration_gate.rb +92 -0
- data/lib/ace/coworker/atoms/outcome_planner.rb +79 -0
- data/lib/ace/coworker/atoms/prepare_decision.rb +75 -0
- data/lib/ace/coworker/atoms/prompt_template.rb +28 -0
- data/lib/ace/coworker/atoms/report_record_formatter.rb +60 -0
- data/lib/ace/coworker/atoms/resume_brief_formatter.rb +84 -0
- data/lib/ace/coworker/atoms/resume_planner.rb +78 -0
- data/lib/ace/coworker/atoms/role_preset_schema.rb +91 -0
- data/lib/ace/coworker/atoms/round_record_schema.rb +62 -0
- data/lib/ace/coworker/atoms/round_rule.rb +105 -0
- data/lib/ace/coworker/cli/commands/commission.rb +100 -0
- data/lib/ace/coworker/cli/commands/gate.rb +91 -0
- data/lib/ace/coworker/cli/commands/prepare.rb +138 -0
- data/lib/ace/coworker/cli/commands/report.rb +79 -0
- data/lib/ace/coworker/cli/commands/resume.rb +57 -0
- data/lib/ace/coworker/cli/commands/roles.rb +60 -0
- data/lib/ace/coworker/cli/commands/rounds.rb +99 -0
- data/lib/ace/coworker/cli/commands/start.rb +39 -0
- data/lib/ace/coworker/cli/commands/status.rb +95 -0
- data/lib/ace/coworker/cli.rb +83 -0
- data/lib/ace/coworker/errors.rb +10 -0
- data/lib/ace/coworker/models/assignment_snapshot.rb +65 -0
- data/lib/ace/coworker/models/brief.rb +38 -0
- data/lib/ace/coworker/models/config.rb +54 -0
- data/lib/ace/coworker/models/outcome.rb +39 -0
- data/lib/ace/coworker/models/preparation.rb +67 -0
- data/lib/ace/coworker/models/resume_plan.rb +47 -0
- data/lib/ace/coworker/molecules/assignment_metadata.rb +57 -0
- data/lib/ace/coworker/molecules/assignment_probe.rb +57 -0
- data/lib/ace/coworker/molecules/config_loader.rb +119 -0
- data/lib/ace/coworker/molecules/git_state_probe.rb +63 -0
- data/lib/ace/coworker/molecules/hitl_reporter.rb +145 -0
- data/lib/ace/coworker/molecules/instruction_resolver.rb +82 -0
- data/lib/ace/coworker/molecules/project_root.rb +26 -0
- data/lib/ace/coworker/molecules/report_state_store.rb +53 -0
- data/lib/ace/coworker/molecules/review_round_store.rb +115 -0
- data/lib/ace/coworker/molecules/role_preset_loader.rb +95 -0
- data/lib/ace/coworker/molecules/role_sequence_composer.rb +245 -0
- data/lib/ace/coworker/molecules/step_evidence_reader.rb +60 -0
- data/lib/ace/coworker/molecules/step_report_reader.rb +39 -0
- data/lib/ace/coworker/molecules/task_probe.rb +40 -0
- data/lib/ace/coworker/molecules/task_record_writer.rb +44 -0
- data/lib/ace/coworker/molecules/worktree_executor_probe.rb +139 -0
- data/lib/ace/coworker/molecules/worktree_provision_probe.rb +137 -0
- data/lib/ace/coworker/organisms/commission_orchestrator.rb +148 -0
- data/lib/ace/coworker/organisms/prepare_orchestrator.rb +374 -0
- data/lib/ace/coworker/organisms/report_orchestrator.rb +192 -0
- data/lib/ace/coworker/organisms/resume_orchestrator.rb +91 -0
- data/lib/ace/coworker/organisms/start_config_writer.rb +127 -0
- data/lib/ace/coworker/organisms/start_orchestrator.rb +45 -0
- data/lib/ace/coworker/organisms/status_orchestrator.rb +62 -0
- data/lib/ace/coworker/version.rb +7 -0
- data/lib/ace/coworker.rb +86 -0
- 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
|