@welluable/orch 1.2.0 → 1.4.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.
package/README.md CHANGED
@@ -34,7 +34,7 @@ orch: commit: a1b2c3d on orch/verbose-flag-x7q2
34
34
  worktree on an `orch/<slug>` branch, so your working tree stays untouched
35
35
  until you decide to merge.
36
36
  - **Agent-agnostic backends.** Pick the CLI you already trust with
37
- `--agent cursor|claude|agn` — orch owns the pipeline, the agent CLI does
37
+ `--agent cursor|claude|agn|opencode` — orch owns the pipeline, the agent CLI does
38
38
  the reading and writing.
39
39
  - **Readable runs.** Every stage prints a one-paragraph natural-language
40
40
  summary of what it did; add `-v` if you also want the raw thinking/output
@@ -50,8 +50,11 @@ npm install -g @welluable/orch
50
50
  ```
51
51
 
52
52
  Make sure an agent CLI is on your `PATH` — orch defaults to `--agent cursor`
53
- (the Cursor Agent CLI, command `agent`); `claude` and `agn` are also
54
- supported. See [Requirements](#requirements) for details.
53
+ (the Cursor Agent CLI, command `agent`); `claude`, `agn`, and `opencode` are also
54
+ supported. Pin a default with `orch config --agent <name>` so you don't need
55
+ `--agent` on every run (local `.orch/config` overrides global
56
+ `~/.orch/config`; CLI `--agent` still wins). See [Requirements](#requirements)
57
+ for details.
55
58
 
56
59
  ```bash
57
60
  orch "fix the typo in the README"
@@ -153,7 +156,8 @@ runs, since foreground runs already stream their output to your terminal.
153
156
 
154
157
  ┌──────────────────────┐
155
158
  │ agent backend adapter │
156
- │ (cursor / claude / agn)
159
+ │ (cursor/claude/agn/
160
+ │ opencode) │
157
161
  └──────────────────────┘
158
162
  ```
159
163
 
@@ -171,7 +175,8 @@ agent CLI does all the actual reading and writing of files.
171
175
  | `--detach` | Runs the pipeline in a background process and returns immediately, printing the run slug. Manage it with `orch list/status/pause/resume/stop/logs`. | You want to kick off a run and keep using your shell, or run several tasks concurrently. |
172
176
 
173
177
  For `--ask`, Cursor uses `--mode ask`, Claude uses `--permission-mode plan`,
174
- and `agn` is prompt-only best-effort (it has no dedicated read-only flag).
178
+ `agn` is prompt-only best-effort (it has no dedicated read-only flag), and
179
+ OpenCode uses `--agent plan` with `edit`/`bash` denied via `OPENCODE_PERMISSION`.
175
180
 
176
181
  `--detach` only controls backgrounding, not whether a job record exists —
177
182
  every non-`--dry-run` invocation gets one. Combining `--detach` with
@@ -203,16 +208,37 @@ Usage: orch [options] [command] <task...>
203
208
  implementer loop; defaults to `5`, ignored with `--ask` and `--quick`.
204
209
  - `--fan-out` — decomposes the task into parallel workers coordinated by this
205
210
  process instead of running the single-worktree pipeline (see
206
- [Fan-out](#fan-out)); rejects `--ask`/`--quick`/`--dry-run`.
211
+ [Fan-out](#fan-out)); rejects `--ask`/`--quick`/`--dry-run`/`--seq`.
212
+ - `--seq` — decomposes into ordered units, merges each into `orch/<slug>`,
213
+ then adjusts the near-term backlog (see
214
+ [Sequential multi-unit (`--seq`)](#sequential-multi-unit---seq)); rejects
215
+ `--fan-out`/`--ask`/`--quick`/`--dry-run`.
207
216
  - `--max-workers <n>` — max number of parallel fan-out workers; defaults to
208
217
  `4`; only meaningful with `--fan-out`.
218
+ - `--max-units <n>` — max number of sequential units; defaults to `8`; only
219
+ meaningful with `--seq`.
209
220
  - `--max-concurrency <n>` — optional hard ceiling on in-flight fan-out workers
210
221
  at once; omit to let the coordinator choose (typically the current layer's
211
222
  size); only meaningful with `--fan-out`.
212
- - `--agent <cursor|claude|agn>` — selects the backend for the whole pipeline;
213
- defaults to `cursor`.
223
+ - `--agent <cursor|claude|agn|opencode>` — selects the backend for the whole pipeline;
224
+ when omitted, uses local `.orch/config`, then global `~/.orch/config`, else
225
+ `cursor`.
226
+ - `--notify` / `--no-notify` — enable or disable a desktop notification when a
227
+ job reaches a terminal state (`done` / `failed` / `stopped` / `crashed`).
228
+ Default is on; config key `notify` and these flags share precedence
229
+ CLI > local > global > on. Dry-run never notifies.
214
230
  - `-h, --help` — displays help for the command.
215
231
 
232
+ Config:
233
+
234
+ - `orch config` — prints the effective agent and notify settings and which
235
+ file(s) contributed (local / global / default). Does not prompt.
236
+ - `orch config --agent <cursor|claude|agn|opencode> [--global|--local]` — writes the
237
+ default agent. Bare `--agent` (and `--global`) write `~/.orch/config`;
238
+ `--local` writes `<cwd>/.orch/config`. There is no `orch init`.
239
+ - `orch config --notify` / `--no-notify` `[--local|--global]` — set desktop
240
+ notify on or off without wiping `agent` (keys merge on write).
241
+
216
242
  Job-control subcommands (see [Headless runs](#headless-runs)):
217
243
 
218
244
  - `orch list` — lists all runs tracked under `.orch/` in the current directory.
@@ -220,13 +246,21 @@ Job-control subcommands (see [Headless runs](#headless-runs)):
220
246
  recently started run.
221
247
  - `orch pause <slug>` — requests a pause at the run's next stage-boundary
222
248
  checkpoint.
223
- - `orch resume <slug>` — resumes a paused (or pausing) run.
249
+ - `orch resume <slug>` — unpauses a paused/pausing run, or recovers a
250
+ failed/stopped/crashed complex job at its unfinished stage (thin recover +
251
+ reentry; see failure resume). Not the same as continue.
252
+ - `orch continue <slug> "new task"` — starts a new complex pipeline on a
253
+ **done** run's existing worktree/branch (same slug). Carries prior outcome
254
+ into research/plan. For crash recovery use `orch resume` instead. For fan-out
255
+ workers that finished, continue the worker slug, then `orch --integrate <parent>`.
224
256
  - `orch stop <slug>` — sends `SIGTERM` to a running job (or reconciles a dead
225
257
  one to `crashed`).
226
258
  - `orch logs <slug> [-f]` — prints a run's `orch.log`; `-f` follows it until
227
259
  the job reaches a terminal state.
228
260
  - `orch jobs clean` — deletes every run tracked under `.orch/` in the current
229
- directory, after a `y/N` confirmation prompt.
261
+ directory, after a `y/N` confirmation prompt. Refuses (without prompting)
262
+ if any job is still live (`running`/`pausing`/`paused` with an alive pid);
263
+ run `orch stop <slug>` first, then clean.
230
264
 
231
265
  Examples:
232
266
 
@@ -234,9 +268,16 @@ Examples:
234
268
  orch "fix the typo in the README" --agent claude
235
269
  orch "fix the bug described in task.md" --agent cursor -v
236
270
  orch "implement the local spec" --agent agn -v
271
+ orch "fix the typo in the README" --agent opencode
237
272
  orch --ask "where is the CLI entrypoint?" --agent claude
273
+ orch --ask "where is package.json?" --agent opencode
238
274
  orch --quick "fix the typo in the README" --agent claude
239
275
  orch "noop" --dry-run --agent cursor
276
+ orch "noop" --dry-run --agent opencode
277
+ orch config
278
+ orch config --agent claude
279
+ orch config --agent agn --local
280
+ orch config --agent opencode
240
281
  ```
241
282
 
242
283
  ## Headless runs
@@ -260,10 +301,11 @@ directory's `.orch/`:
260
301
  orch list # SLUG ROLE STATE PHASE AGENT STARTED DURATION PID
261
302
  orch status swift-lagoon-49ea # full record: state, phase, branch, worktree, exit code, ...
262
303
  orch pause swift-lagoon-49ea # request a pause at the next stage-boundary checkpoint
263
- orch resume swift-lagoon-49ea # resume a paused/pausing run
304
+ orch resume swift-lagoon-49ea # unpause, or recover failed/stopped/crashed
305
+ orch continue swift-lagoon-49ea "follow-up polish" # new work on a done run
264
306
  orch logs swift-lagoon-49ea -f # follow orch.log until the run finishes
265
307
  orch stop swift-lagoon-49ea # SIGTERM the run
266
- orch jobs clean # delete every tracked run under .orch/ (asks to confirm)
308
+ orch jobs clean # delete every tracked run under .orch/ (asks to confirm; refuses if live)
267
309
  ```
268
310
 
269
311
  Pausing is cooperative and happens at stage boundaries (before the first
@@ -394,6 +436,58 @@ Depth is capped at 1: every worker and the integration session run with
394
436
  variable is already present — a worker or integration session never fans out
395
437
  again.
396
438
 
439
+ ## Sequential multi-unit (`--seq`)
440
+
441
+ Big features fail in one context window. orch splits them into units agents
442
+ can finish. `--fan-out` runs independent units in parallel; `--seq` runs
443
+ ordered units one-by-one, merging each into `orch/<slug>` before adjusting
444
+ the next.
445
+
446
+ | | `--fan-out` | `--seq` |
447
+ |---|---|---|
448
+ | Split reason | Parallel independence | Finishable unit size + order |
449
+ | Boundaries | Yes | **No** |
450
+ | Unit shape | Workers + `dependsOn` / `owns` / layers | **Flat ordered list** |
451
+ | Schedule | Parallel (layers / concurrency) | **Strictly one unit at a time** |
452
+ | Base SHA | One shared base for all workers | **Advances after each merge** |
453
+ | Integrate | One session at the end | **Merge + verify after every unit** |
454
+ | Replan | Frozen after decompose | **Hybrid adjust** after each merge |
455
+
456
+ ```bash
457
+ orch "implement the billing module" --seq --agent claude
458
+ orch "implement X" --seq --max-units 6
459
+ ```
460
+
461
+ ```text
462
+ triage: complex — seq requested
463
+ decomposer: 5 units
464
+ [01-types …] done — merged into orch/wise-pine-e904
465
+ adjust: rewrote 02-api against tip; dropped 05-legacy-path
466
+ [02-api …] done — merged
467
+ [03-ui …] failed at code-loop / test-runner (round 3)
468
+ stopped: 2/5 merged; next: orch resume <unit-slug>
469
+ ```
470
+
471
+ The flow, in order:
472
+
473
+ 1. **Triage** runs once. If it routes to quick-fix, seq is skipped entirely —
474
+ triage never opts into `--seq` on its own.
475
+ 2. **`seq-decomposer`** (no boundaries agent) emits an ordered `units[]`
476
+ backlog, or declines → today's single-worktree pipeline with no `seq.json`.
477
+ 3. **Schedule** runs concurrency 1: spawn the first pending unit at the current
478
+ tip → wait → on failure stop the chain → on success merge into
479
+ `orch/<parent-slug>`, runner-first verify, advance tip, hybrid-adjust the
480
+ next 1–2 pending units (or drop obsolete ones), continue.
481
+ 4. **Continue / resume.** Fix a failed unit with `orch resume <unit-slug>`,
482
+ then `orch --seq-continue <parent>` (or `orch resume <parent>` when paused).
483
+ Use `orch continue <unit-slug>` only for new follow-up work on a done unit.
484
+ Do not `orch continue` the coordinator.
485
+
486
+ `--max-units` (default `8`) caps both the initial backlog and adjust growth.
487
+ Unit children set `ORCH_SEQ_DEPTH=1` and `ORCH_FANOUT_DEPTH=1` so they cannot
488
+ nest `--seq` or `--fan-out`. Deliverable stays on `orch/<parent-slug>` until
489
+ you merge it yourself.
490
+
397
491
  ## Project structure
398
492
 
399
493
  Complex runs create a run directory and a sibling worktree, reusing the
@@ -426,21 +520,23 @@ manually if that happens:
426
520
  pkill -f 'agent -p' # --agent cursor
427
521
  pkill -f 'claude ' # --agent claude, adjust to local argv
428
522
  pkill -f 'agn ' # --agent agn
523
+ pkill -f 'opencode run' # --agent opencode
429
524
  ```
430
525
 
431
526
  ## Agent compatibility
432
527
 
433
528
  | Backend | `--agent` value | Status | Notes |
434
529
  | --- | --- | --- | --- |
435
- | Cursor Agent CLI | `cursor` | Supported (default) | Command `agent` on `PATH`. |
530
+ | Cursor Agent CLI | `cursor` | Supported (builtin default) | Command `agent` on `PATH`. Override via `orch config` or `--agent`. |
436
531
  | Claude Code CLI | `claude` | Supported | Command `claude` on `PATH`. |
437
532
  | agn | `agn` | Supported | Requires `npm install -g @welluable/agn-cli` (`>= 0.0.12`) and `agn init`. |
533
+ | OpenCode CLI | `opencode` | Supported | Command `opencode` on `PATH` (`>= 1.17.18`). Install from https://opencode.ai; first-time setup: `opencode auth login`. Read-only/`--ask` uses `--agent plan` with edit/bash denied. |
438
534
 
439
535
  ## Requirements
440
536
 
441
537
  - A modern Node.js runtime.
442
538
  - One supported agent CLI on your `PATH`: `agent` (Cursor), `claude` (Claude
443
- Code), or `agn`.
539
+ Code), `agn`, or `opencode` (OpenCode `>= 1.17.18`).
444
540
  - Git, for any run that isn't `--ask` or `--quick` (worktrees and commits
445
541
  need it).
446
542
 
@@ -0,0 +1,66 @@
1
+ import { summaryTrailerInstructions } from './summary-footer.js';
2
+
3
+ function feedbackBlock(feedback) {
4
+ if (!Array.isArray(feedback) || feedback.length === 0) return '';
5
+ const violations = feedback.map((violation) => `- ${violation}`).join('\n');
6
+ return `
7
+
8
+ [Validation Feedback]
9
+ Your previous adjust result was rejected for these reasons. Fix them and
10
+ try again:
11
+ ${violations}
12
+ [/Validation Feedback]`;
13
+ }
14
+
15
+ function formatUnits(units) {
16
+ if (!Array.isArray(units) || units.length === 0) return '(none)';
17
+ return units
18
+ .map((u) => `- ${u.id}: ${u.title} — ${u.subtask}${u.sha ? ` (sha ${u.sha})` : ''}`)
19
+ .join('\n');
20
+ }
21
+
22
+ export function adjustAgentArgs({
23
+ originalTask,
24
+ doneUnits,
25
+ pendingUnits,
26
+ tip,
27
+ cwd,
28
+ maxUnits,
29
+ feedback,
30
+ }) {
31
+ return {
32
+ name: 'adjust',
33
+ instructions: `
34
+ You are an Adjust Agent for a sequential orch run.
35
+
36
+ * The original task is the hard fence — do not expand product scope past it:
37
+ ${originalTask}
38
+ * Current tip SHA of the integration branch: ${tip}
39
+ * Done units (do not rewrite or drop these):
40
+ ${formatUnits(doneUnits)}
41
+ * Pending units in schedule order:
42
+ ${formatUnits(pendingUnits)}
43
+ * You may rewrite at most the next two pending units' title/subtask texts
44
+ so they match the tip. You may drop obsolete pending units. Do not invent
45
+ new ids. Do not resurrect done/failed work as new ids. Respect maxUnits
46
+ ${maxUnits} (rewrite/drop only — do not grow the backlog).
47
+ * Your final message MUST be valid JSON only — no markdown, no prose
48
+ outside JSON:
49
+
50
+ {
51
+ "rewrites": [
52
+ { "id": "02-api", "title": "optional new title", "subtask": "optional new subtask" }
53
+ ],
54
+ "drops": ["04-obsolete"]
55
+ }
56
+
57
+ Use empty arrays when you have no rewrites or drops.
58
+ * Before the summary marker below, the JSON itself must stay exactly as
59
+ specified above.
60
+ ${summaryTrailerInstructions({ before: 'the JSON verdict only' })}
61
+ ${feedbackBlock(feedback)}
62
+ `,
63
+ prompt: originalTask,
64
+ options: { cwd },
65
+ };
66
+ }
package/agents/ask.js CHANGED
@@ -1,3 +1,5 @@
1
+ import { summaryTrailerInstructions } from './summary-footer.js';
2
+
1
3
  export function askAgentArgs({ prompt, cwd }) {
2
4
  return {
3
5
  name: 'ask',
@@ -10,11 +12,7 @@ export function askAgentArgs({ prompt, cwd }) {
10
12
  * Put the full answer in your final message.
11
13
  * Before the summary marker below, your message should only have the
12
14
  answer, no other text.
13
- * After the answer above, on a new line write the summary marker (three
14
- '<' characters, then SUMMARY, then three '>' characters, with no
15
- spaces), followed by one paragraph in natural, human-readable language
16
- explaining what you did in this step and what happened — no lists, no
17
- headers, just prose.
15
+ ${summaryTrailerInstructions({ before: 'the answer' })}
18
16
  `,
19
17
  prompt,
20
18
  options: { cwd, readOnly: true },
@@ -1,3 +1,5 @@
1
+ import { summaryTrailerInstructions } from './summary-footer.js';
2
+
1
3
  export function boundariesAgentArgs({ prompt, cwd, boundariesPath }) {
2
4
  return {
3
5
  name: 'boundaries',
@@ -15,10 +17,7 @@ export function boundariesAgentArgs({ prompt, cwd, boundariesPath }) {
15
17
  ${boundariesPath}
16
18
  * Before the summary marker below, your message must contain only the
17
19
  exact path: ${boundariesPath}
18
- * After the path above, on a new line write the exact marker
19
- \`<<<SUMMARY>>>\`, followed by one paragraph in natural, human-readable
20
- language explaining what you did in this step and what happened — no
21
- lists, no headers, just prose.
20
+ ${summaryTrailerInstructions({ before: `the exact path: ${boundariesPath}` })}
22
21
  `,
23
22
  prompt,
24
23
  options: { cwd },
@@ -1,3 +1,5 @@
1
+ import { summaryTrailerInstructions } from './summary-footer.js';
2
+
1
3
  export function codeWriterAgentArgs({
2
4
  prompt,
3
5
  cwd,
@@ -42,11 +44,7 @@ export function codeWriterAgentArgs({
42
44
  * Do not run \`git add\`, \`git commit\`, or any other git branch/commit
43
45
  command. Leave changes unstaged — orch commits after the pipeline finishes.
44
46
  * Once implementation is done, the task is complete.
45
- * After your final message above, on a new line write the summary
46
- marker (three '<' characters, then SUMMARY, then three '>'
47
- characters, with no spaces), followed by one paragraph in natural,
48
- human-readable language explaining what you did in this step and
49
- what happened — no lists, no headers, just prose.
47
+ ${summaryTrailerInstructions({ before: 'your final message' })}
50
48
  * If you changed any files, after that paragraph add a line reading
51
49
  exactly "Files:" followed by one line per changed file formatted
52
50
  as "<path>: <one-line description>" (e.g. "lib/agent.js: wired the
@@ -1,3 +1,5 @@
1
+ import { summaryTrailerInstructions } from './summary-footer.js';
2
+
1
3
  function feedbackBlock(feedback) {
2
4
  if (!Array.isArray(feedback) || feedback.length === 0) return '';
3
5
  const violations = feedback.map((violation) => `- ${violation}`).join('\n');
@@ -46,11 +48,9 @@ export function decomposerAgentArgs({ prompt, cwd, boundariesOutput, maxWorkers,
46
48
  "decomposable": false,
47
49
  "why": "short reason"
48
50
  }
49
- * After the JSON above, on a new line write the exact marker
50
- \`<<<SUMMARY>>>\`, followed by one paragraph in natural, human-readable
51
- language explaining what you did in this step and what happened — no
52
- lists, no headers, just prose. The JSON itself must stay exactly as
53
- specified above, before the summary marker.
51
+ * Before the summary marker below, the JSON itself must stay exactly as
52
+ specified above.
53
+ ${summaryTrailerInstructions({ before: 'the JSON verdict only' })}
54
54
 
55
55
  [Boundaries Agent Output]
56
56
  ${boundariesOutput}
package/agents/index.js CHANGED
@@ -10,3 +10,5 @@ export { testRunnerAgentArgs } from './test-runner.js';
10
10
  export { integratorAgentArgs } from './integrator.js';
11
11
  export { boundariesAgentArgs } from './boundaries.js';
12
12
  export { decomposerAgentArgs } from './decomposer.js';
13
+ export { seqDecomposerAgentArgs } from './seq-decomposer.js';
14
+ export { adjustAgentArgs } from './adjust.js';
@@ -1,3 +1,5 @@
1
+ import { summaryTrailerInstructions } from './summary-footer.js';
2
+
1
3
  /** Conflict-repair-only agent for the `--integrate` driver: resolves merge conflict
2
4
  * markers in the given files using the merge output and involved workers' subtask/area
3
5
  * as context. Never touches git itself — orch owns the merge commit. */
@@ -26,10 +28,7 @@ export function integratorAgentArgs({ prompt, cwd, conflictedFiles, mergeOutput,
26
28
  command. orch completes the merge commit itself; you only edit files.
27
29
  * Do not report a pass/fail judgment of any kind; orch checks whether
28
30
  the conflict markers are gone itself.
29
- * After you finish editing, on a new line write the exact marker
30
- \`<<<SUMMARY>>>\`, followed by one paragraph in natural,
31
- human-readable language explaining what you did in this step and
32
- what happened — no lists, no headers, just prose.
31
+ ${summaryTrailerInstructions({ before: 'your conflict-resolution work' })}
33
32
 
34
33
  [Merge Output]
35
34
  ${mergeOutput}
package/agents/planner.js CHANGED
@@ -1,3 +1,5 @@
1
+ import { summaryTrailerInstructions } from './summary-footer.js';
2
+
1
3
  export function plannerAgentArgs({ prompt, cwd, researchPath, taskPath, researchOutput }) {
2
4
  return {
3
5
  name: 'planner',
@@ -10,11 +12,7 @@ export function plannerAgentArgs({ prompt, cwd, researchPath, taskPath, research
10
12
  the exact path: ${taskPath}
11
13
  * Before the summary marker below, your message must contain only the
12
14
  exact path: ${taskPath}
13
- * After the path above, on a new line write the summary marker (three
14
- '<' characters, then SUMMARY, then three '>' characters, with no
15
- spaces), followed by one paragraph in natural, human-readable language
16
- explaining what you did in this step and what happened — no lists, no
17
- headers, just prose.
15
+ ${summaryTrailerInstructions({ before: `the exact path: ${taskPath}` })}
18
16
 
19
17
  [Research Agent Output]
20
18
  ${researchOutput}
@@ -1,3 +1,5 @@
1
+ import { summaryTrailerInstructions } from './summary-footer.js';
2
+
1
3
  export function quickFixAgentArgs({ prompt, cwd, fix_plan }) {
2
4
  const fixPlan = fix_plan
3
5
  ? `
@@ -17,11 +19,7 @@ export function quickFixAgentArgs({ prompt, cwd, fix_plan }) {
17
19
  * Apply changes in the current working tree.
18
20
  * Do not write research.md or task.md.
19
21
  * Do not create a git worktree.
20
- * After your final message above, on a new line write the summary
21
- marker (three '<' characters, then SUMMARY, then three '>'
22
- characters, with no spaces), followed by one paragraph in
23
- natural, human-readable language explaining what you did in this
24
- step and what happened — no lists, no headers, just prose.
22
+ ${summaryTrailerInstructions({ before: 'your final message' })}
25
23
  * If you changed any files, after that paragraph add a line reading
26
24
  exactly "Files:" followed by one line per changed file formatted
27
25
  as "<path>: <one-line description>" (e.g. "README.md: documented
@@ -1,3 +1,5 @@
1
+ import { summaryTrailerInstructions } from './summary-footer.js';
2
+
1
3
  export function researchAgentArgs({ prompt, cwd, researchPath }) {
2
4
  return {
3
5
  name: 'research',
@@ -11,11 +13,7 @@ export function researchAgentArgs({ prompt, cwd, researchPath }) {
11
13
  findings only to the exact path: ${researchPath}
12
14
  * Before the summary marker below, your message must contain only the
13
15
  exact path: ${researchPath}
14
- * After the path above, on a new line write the summary marker (three
15
- '<' characters, then SUMMARY, then three '>' characters, with no
16
- spaces), followed by one paragraph in natural, human-readable language
17
- explaining what you did in this step and what happened — no lists, no
18
- headers, just prose.
16
+ ${summaryTrailerInstructions({ before: `the exact path: ${researchPath}` })}
19
17
  `,
20
18
  prompt,
21
19
  options: { cwd },
@@ -0,0 +1,58 @@
1
+ import { summaryTrailerInstructions } from './summary-footer.js';
2
+
3
+ function feedbackBlock(feedback) {
4
+ if (!Array.isArray(feedback) || feedback.length === 0) return '';
5
+ const violations = feedback.map((violation) => `- ${violation}`).join('\n');
6
+ return `
7
+
8
+ [Validation Feedback]
9
+ Your previous decomposition was rejected for these reasons. Fix them and
10
+ try again:
11
+ ${violations}
12
+ [/Validation Feedback]`;
13
+ }
14
+
15
+ export function seqDecomposerAgentArgs({ prompt, cwd, maxUnits, feedback }) {
16
+ return {
17
+ name: 'seq-decomposer',
18
+ instructions: `
19
+ You are a Seq-Decomposer Agent.
20
+
21
+ * Read the original task and decide whether the work can be split into an
22
+ ordered backlog of finishable units. Order is the schedule — a flat
23
+ list only, not a parallel dependency graph.
24
+ * At most ${maxUnits} units — that is a hard ceiling, not a target.
25
+ * Each unit object must contain only id, title, and subtask.
26
+ * "decomposable": false (with a "why") is a valid answer when you cannot
27
+ find a useful ordered split — do not force a split. That is the
28
+ not decomposable / decomposable: false path.
29
+ * Your final message MUST be valid JSON only — no markdown, no prose
30
+ outside JSON:
31
+
32
+ {
33
+ "decomposable": true,
34
+ "why": "short reason",
35
+ "units": [
36
+ {
37
+ "id": "01-types",
38
+ "title": "short title",
39
+ "subtask": "what this unit implements"
40
+ }
41
+ ]
42
+ }
43
+
44
+ or, when declining:
45
+
46
+ {
47
+ "decomposable": false,
48
+ "why": "short reason"
49
+ }
50
+ * Before the summary marker below, the JSON itself must stay exactly as
51
+ specified above.
52
+ ${summaryTrailerInstructions({ before: 'the JSON verdict only' })}
53
+ ${feedbackBlock(feedback)}
54
+ `,
55
+ prompt,
56
+ options: { cwd },
57
+ };
58
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Shared `<<<SUMMARY>>>` trailer instructions for every stage agent.
3
+ * @param {{ before?: string }} [opts]
4
+ */
5
+ export function summaryTrailerInstructions({ before = 'required stage output' } = {}) {
6
+ return `
7
+ * Your final message MUST end with a summary trailer. After ${before}, on its
8
+ own line write exactly this marker (copy it verbatim — do not put it inside
9
+ a code fence):
10
+
11
+ \`<<<SUMMARY>>>\`
12
+
13
+ * On the next line, write one paragraph in natural, human-readable language
14
+ explaining what you did in this step and what happened — no lists, no headers.
15
+ * Shape:
16
+
17
+ <${before}>
18
+ <<<SUMMARY>>>
19
+ One short paragraph of what you did and what happened.
20
+
21
+ * Omitting the marker makes the reply invalid for this pipeline.
22
+ `;
23
+ }
@@ -1,3 +1,5 @@
1
+ import { summaryTrailerInstructions } from './summary-footer.js';
2
+
1
3
  export function testCriticAgentArgs({
2
4
  prompt,
3
5
  cwd,
@@ -23,12 +25,9 @@ export function testCriticAgentArgs({
23
25
  * Your final message MUST include a JSON verdict:
24
26
  {"passed": true|false, "summary": "short reason", "failures": ["optional"]}
25
27
  * Set passed: true only when verification is adequate to freeze for implementation.
26
- * After the JSON above, on a new line write the summary marker (three
27
- '<' characters, then SUMMARY, then three '>' characters, with no
28
- spaces), followed by one paragraph in natural, human-readable language
29
- explaining what you did in this step and what happened — no lists, no
30
- headers, just prose. The JSON itself must stay exactly as specified
31
- above, before the summary marker.
28
+ * Before the summary marker below, the JSON itself must stay exactly as
29
+ specified above.
30
+ ${summaryTrailerInstructions({ before: 'the JSON verdict only' })}
32
31
 
33
32
  [Test Writer Output]
34
33
  ${testWriterOutput}
@@ -1,3 +1,5 @@
1
+ import { summaryTrailerInstructions } from './summary-footer.js';
2
+
1
3
  export function testRunnerAgentArgs({
2
4
  prompt,
3
5
  cwd,
@@ -22,12 +24,9 @@ export function testRunnerAgentArgs({
22
24
  * Your final message MUST include a JSON verdict:
23
25
  {"passed": true|false, "summary": "short reason", "failures": ["optional"]}
24
26
  * Set passed: true only when the verification gate is green.
25
- * After the JSON above, on a new line write the summary marker (three
26
- '<' characters, then SUMMARY, then three '>' characters, with no
27
- spaces), followed by one paragraph in natural, human-readable language
28
- explaining what you did in this step and what happened — no lists, no
29
- headers, just prose. The JSON itself must stay exactly as specified
30
- above, before the summary marker.
27
+ * Before the summary marker below, the JSON itself must stay exactly as
28
+ specified above.
29
+ ${summaryTrailerInstructions({ before: 'the JSON verdict only' })}
31
30
 
32
31
  [Code Writer Output]
33
32
  ${codeWriterOutput}
@@ -1,3 +1,5 @@
1
+ import { summaryTrailerInstructions } from './summary-footer.js';
2
+
1
3
  export function testWriterAgentArgs({
2
4
  prompt,
3
5
  cwd,
@@ -37,11 +39,7 @@ export function testWriterAgentArgs({
37
39
  * Your final message must include test file paths / run command, if
38
40
  applicable, so it can be handed to the next stage.
39
41
  * Later rounds must address critic feedback; do not write production code.
40
- * After your final message above, on a new line write the summary
41
- marker (three '<' characters, then SUMMARY, then three '>'
42
- characters, with no spaces), followed by one paragraph in natural,
43
- human-readable language explaining what you did in this step and
44
- what happened — no lists, no headers, just prose.
42
+ ${summaryTrailerInstructions({ before: 'your final message' })}
45
43
  * If you changed any files, after that paragraph add a line reading
46
44
  exactly "Files:" followed by one line per changed file formatted
47
45
  as "<path>: <one-line description>" (e.g. "test/agent.test.js:
package/agents/triage.js CHANGED
@@ -1,3 +1,5 @@
1
+ import { summaryTrailerInstructions } from './summary-footer.js';
2
+
1
3
  export function triageAgentArgs({ prompt, cwd }) {
2
4
  return {
3
5
  name: 'triage',
@@ -18,12 +20,9 @@ export function triageAgentArgs({ prompt, cwd }) {
18
20
  Set "simple": true only when a quick fix in the current working tree is enough.
19
21
  Set "simple": false when research, planning, or a worktree is needed.
20
22
 
21
- After the JSON above, on a new line write the summary marker (three
22
- '<' characters, then SUMMARY, then three '>' characters, with no
23
- spaces), followed by one paragraph in natural, human-readable language
24
- explaining what you did in this step and what happened — no lists, no
25
- headers, just prose. The JSON itself must stay exactly as specified
26
- above, before the summary marker.
23
+ Before the summary marker below, the JSON itself must stay exactly as
24
+ specified above.
25
+ ${summaryTrailerInstructions({ before: 'the JSON verdict only' })}
27
26
  `,
28
27
  prompt,
29
28
  options: { cwd },
package/lib/agent-agn.js CHANGED
@@ -1,4 +1,4 @@
1
- import { Agent } from './agent.js';
1
+ import { Agent, appendVerbose } from './agent.js';
2
2
  import { normalizeAgnToolEvent } from './tool-status.js';
3
3
 
4
4
  export class AgentAgn extends Agent {
@@ -24,8 +24,10 @@ export class AgentAgn extends Agent {
24
24
  }
25
25
  case 'assistant': {
26
26
  if (event.subtype === 'delta') {
27
+ const text = event.text ?? '';
28
+ appendVerbose(text);
27
29
  if (verbose) {
28
- process.stderr.write(event.text ?? '');
30
+ process.stderr.write(text);
29
31
  } else if (this.activeTools.size === 0) {
30
32
  this.setStatus('thinking…');
31
33
  }