bullswarm 0.13.2 → 0.14.1
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/CHANGELOG.md +80 -0
- package/docs/claude-dynamic-workflow-mechanics.md +17 -7
- package/docs/experiments/2026-08-29-dogfood-bullswarm-builds-bullswarm.md +171 -0
- package/docs/planner-prompt-audit-2026-08-29.md +157 -0
- package/package.json +1 -1
- package/skill/SKILL.md +22 -1
- package/src/workflow/cli.js +1 -1
- package/src/workflow/dashboard.js +49 -17
- package/src/workflow/decision.js +35 -9
- package/src/workflow/draft.js +2 -6
- package/src/workflow/fsjson.js +41 -0
- package/src/workflow/goal.js +30 -21
- package/src/workflow/runner.js +88 -12
- package/src/workflow/runs-cli.js +14 -6
- package/src/workflow/runtime.js +244 -90
- package/src/workflow/schema.js +80 -0
- package/src/workflow/short-id.js +3 -2
- package/src/workflow/template.js +4 -1
- package/src/workflow/validate.js +13 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,85 @@
|
|
|
1
1
|
# bullswarm changelog
|
|
2
2
|
|
|
3
|
+
## 0.14.1 — the TUI survives its writer; steering lands or expires truthfully
|
|
4
|
+
|
|
5
|
+
Proven on a goal-3 re-run (`d7xyg2`): 1 planner turn / 269 s (baseline 0.13.1: 1 / 294 s), planner context 6.2 k chars (from 32.7 k), auto-completed, deliverable verified, zero observation crashes — `docs/experiments/2026-08-29-dogfood-bullswarm-builds-bullswarm.md`.
|
|
6
|
+
|
|
7
|
+
- Workflow `state.json`/`report.json`/`workflow.json` writes are atomic
|
|
8
|
+
(temp + rename, new `src/workflow/fsjson.js`): a concurrent reader can never
|
|
9
|
+
observe a half-written file. Earned: `workflow tui` crashed with
|
|
10
|
+
"Unterminated string in JSON at position 138968" parsing `state.json`
|
|
11
|
+
mid-write (observed twice, 2026-08-29).
|
|
12
|
+
- Observation readers tolerate torn or missing JSON: the TUI keeps painting
|
|
13
|
+
the last good frame of the same run, and a render or key-handler error is
|
|
14
|
+
shown in the message line instead of killing the process and stranding the
|
|
15
|
+
terminal in alt-screen raw mode. Mutating commands (stop, approval) retry
|
|
16
|
+
the read once and then refuse loudly instead of silently dropping the
|
|
17
|
+
operator's command. `runs delete` treats an unreadable `state.json` as
|
|
18
|
+
ongoing (refuses without `--force`) rather than deleting a possibly-live run.
|
|
19
|
+
- An action being re-run (repair round, re-verify, schema retry) reads as
|
|
20
|
+
`running` and its phase as `active` even when its previous round recorded
|
|
21
|
+
`ok:false`; a failed mark now means failed-and-not-being-retried. (User
|
|
22
|
+
report: the TUI showed ✗ "2/2 complete" beside a live spinner.)
|
|
23
|
+
- Pending operator steering defers program self-completion: a clean program
|
|
24
|
+
with `completion: all-actions-ok` returns to the planner gate (event
|
|
25
|
+
`decision.completion_deferred`), which delivers the steer — instead of
|
|
26
|
+
auto-completing and silently discarding it (defect observed live:
|
|
27
|
+
0 `steering.delivered` events for a queued steer). Steering that can no
|
|
28
|
+
longer reach any gate is marked `expired_undelivered` with event
|
|
29
|
+
`steering.expired` at the terminal transition; interrupted runs keep their
|
|
30
|
+
queue for the resumed run's next gate.
|
|
31
|
+
- Resume re-runs an action the interruption cancelled mid-flight instead of
|
|
32
|
+
re-planning around a phantom failure: cancelled actions and the dependents
|
|
33
|
+
blocked only by them are reopened (event `action.reopened`) and the accepted
|
|
34
|
+
program continues from where it stopped. Observed on a SIGTERM-interrupted
|
|
35
|
+
run: 1 cancelled action → 4 "blocked" → a spurious planner turn.
|
|
36
|
+
- Planner contract: a verify with several `dependsOn` must set `review`
|
|
37
|
+
(rule 4); the program's last worker must be covered by a successful verify
|
|
38
|
+
(rule 8) — both were the causes of extra planner gates on the goal-3 proof
|
|
39
|
+
run. A corrective turn's `validationFeedback.rejectedResponseExcerpt` is
|
|
40
|
+
capped at 2 000 chars, and the rejected proposal is resent as a skeleton
|
|
41
|
+
(ids, shapes, dependsOn; prompts elided) — the planner's thread already
|
|
42
|
+
holds it verbatim.
|
|
43
|
+
- A verify without `review` is no longer grounds to reject a whole program:
|
|
44
|
+
it reviews its single (or last) dependency's artifact, or audits the
|
|
45
|
+
repository directly when it has no `dependsOn` (`reviewScope: repository`).
|
|
46
|
+
Observed on two proof runs: a 9-action program bounced for one field,
|
|
47
|
+
costing a 5-minute correction turn each time.
|
|
48
|
+
|
|
49
|
+
## 0.14.0 — structured worker output, compact planner contract
|
|
50
|
+
|
|
51
|
+
- A verify whose reply cannot be parsed as the verdict JSON gets ONE bounded
|
|
52
|
+
re-ask (event `verify.verdict_retry`) before its failure can reach a planner
|
|
53
|
+
boundary — observed on run `ejk9w2`: one unparseable verdict cost a full
|
|
54
|
+
planner turn plus ~8 minutes of re-proving a passing state.
|
|
55
|
+
- Planner contract amendments from the same run's observations: a verify is
|
|
56
|
+
scoped to what can be true at its point in the graph (later-scheduled work is
|
|
57
|
+
not a defect; cosmetic mismatches are concerns, never ok:false); when the
|
|
58
|
+
goal's acceptance checks pass the planner returns complete instead of adding
|
|
59
|
+
polish actions; restored the shared-working-tree, redundant-verification,
|
|
60
|
+
and operatorSteering guidance dropped by the contract merge.
|
|
61
|
+
- "Full" planner-context excerpts (scout, new-since-last-decision, failing
|
|
62
|
+
verifies) obey the per-excerpt and total budgets again; the compaction must
|
|
63
|
+
never rebuild the 163 k-char contexts it replaced.
|
|
64
|
+
- Planner context and contract compacted: complete emitted planner task text up to the durable-context marker, worktree-isolation suffix included **OBSERVED** `16,316 -> 5,208` characters, and a sample turn-2 durable context **COMPUTED** `163,000 -> 23,547` characters by replacing full attempt records with compact ledger rows and retaining full output excerpts only for new/scout or `ok:false` verify actions.
|
|
65
|
+
- Planner `run` actions and fan-out `stepTemplate`s may declare an optional
|
|
66
|
+
`outputSchema`, an object-typed JSON-Schema subset. The runtime tells the
|
|
67
|
+
worker to end its output with one matching JSON object, parses and validates
|
|
68
|
+
it, and persists a `run` result as `outputs.<id>.data` with `schemaOk: true`;
|
|
69
|
+
fan-out results store those fields inside each `outputs.<fanoutId>.items[]`
|
|
70
|
+
entry. Successful validation emits `action.output_validated`.
|
|
71
|
+
- Schema failures emit `action.output_schema_retry` and receive exactly one
|
|
72
|
+
bounded retry with the validation errors and the previous output tail. If
|
|
73
|
+
that retry also fails, the action remains `ok:false`, records
|
|
74
|
+
`schemaOk:false` and `schemaErrors`, and keeps the output text with the
|
|
75
|
+
reason `output did not match outputSchema: <errors>`. Resumed runs do not
|
|
76
|
+
re-dispatch actions already marked `schemaOk:true`.
|
|
77
|
+
- Dependent prompts can render `{{outputs.<id>.data.<field>}}`, and
|
|
78
|
+
`fanout.itemsFrom` accepts `outputs.<id>.data.items` without an extraction
|
|
79
|
+
agent when the array is already present. Planner decision validation rejects
|
|
80
|
+
`outputSchema` on a proposed `verify` because verify has a fixed verdict
|
|
81
|
+
shape.
|
|
82
|
+
|
|
3
83
|
## 0.13.2 — user text is never a template
|
|
4
84
|
|
|
5
85
|
- `workflow goal` failed before anything ran when the goal text quoted
|
|
@@ -234,7 +234,7 @@ from `docs/experiments/2026-08-29-ultracode-vs-bullswarm.md`, never projected.
|
|
|
234
234
|
| Phases | Labels for grouping; never synchronise | Forward-only kebab-case names per action; also just labels | None |
|
|
235
235
|
| Parallelism | `pipeline` default, `parallel` barrier; cap min(16, CPUs−2) | `executeActions` ran dependency-ready siblings **serially** (`runner.js:558`); only `fanout` items ran concurrently; goal default concurrency 3 | **Fixed in 0.11.0** — ready-set scheduler + default 8 |
|
|
236
236
|
| Planner bias | Script author is told to fan out and default to pipeline | Goal prompt said "return needs_more_work with the **smallest useful set** of bounded … actions" (`goal.js:18`) and planner prompt said "keep actions cohesive" | **Fixed in 0.11.0** — "propose the COMPLETE dependency graph", per-item fix→verify chains, file ownership, self-contained prompts |
|
|
237
|
-
| Per-agent prompt | Self-contained, plus JSON schema enforced at tool layer | Planner-authored prompt;
|
|
237
|
+
| Per-agent prompt | Self-contained, plus JSON schema enforced at tool layer | Planner-authored prompt; `outputSchema` validates structured worker data, while `verify` retains its fixed JSON verdict | Adopted for declared schemas; tool-layer enforcement remains a difference |
|
|
238
238
|
| Failure handling | Loops in code; `null` on agent death | Planner replans (costly); 0.10.9 added corrective turns for invalid decisions and 0.11.0 recovers mis-shaped `verify.review` before dispatch | Improved; retry-in-code per action still absent |
|
|
239
239
|
| Determinism / resume | Journal of return values; prefix cache | Durable `state.json` + `events.jsonl` + action ledger; resume skips durable outputs | Equivalent |
|
|
240
240
|
| Data-driven fan-out | `pipeline(discovered.items, …)` — count unknown when the script is written | Decision schema forced inline `items`; the planner spent a turn waiting for discovery | **Fixed in 0.12.0** — `itemsFrom` on proposed fan-outs + one bounded extraction retry |
|
|
@@ -288,8 +288,7 @@ author and the `Workflow` runtime.
|
|
|
288
288
|
if the output still has no array the runtime runs ONE bounded, read-only
|
|
289
289
|
extraction action over it (never re-running the producer, which may have
|
|
290
290
|
mutated files). That is the "schema retry" of Claude's `StructuredOutput`,
|
|
291
|
-
done as a second cheap agent instead of a tool-layer retry.
|
|
292
|
-
`outputSchema` on run actions is still open (§5).
|
|
291
|
+
done as a second cheap agent instead of a tool-layer retry.
|
|
293
292
|
3. **Pre-authored repair** — shipped. `repair: { prompt, maxRounds }` on a
|
|
294
293
|
verify: verify-fail → `<verifyId>-repair-<n>` (concerns verbatim) →
|
|
295
294
|
re-verify, inside the executor. Claude's fix-loop as code.
|
|
@@ -350,6 +349,21 @@ author and the `Workflow` runtime.
|
|
|
350
349
|
exist — the script's `while (!ok)` loop *is* the evidence — which is the
|
|
351
350
|
general lesson: every piece of control flow bullswarm moves from planner
|
|
352
351
|
into runtime needs its evidence rule moved with it.
|
|
352
|
+
11. **[SPEC] Schema-enforced worker output** — a planner `run` action or fan-out
|
|
353
|
+
`stepTemplate` may declare an object-typed `outputSchema` subset. The
|
|
354
|
+
runtime appends instructions for one trailing matching JSON object, with no
|
|
355
|
+
prose or markdown fences after it, then parses and validates the object.
|
|
356
|
+
A successful `run` persists `outputs.<id>.data` and `schemaOk: true`; a
|
|
357
|
+
fan-out stores those schema results inside each
|
|
358
|
+
`outputs.<fanoutId>.items[]` entry. Both emit `action.output_validated`. A mismatch emits
|
|
359
|
+
`action.output_schema_retry` and gets exactly one bounded retry carrying
|
|
360
|
+
the validation errors and the previous output tail; a second mismatch
|
|
361
|
+
fails the action while retaining its output text and recording
|
|
362
|
+
`schemaOk:false` and `schemaErrors`. Dependent prompts can render data
|
|
363
|
+
fields, and `fanout.itemsFrom` can consume `outputs.<id>.data.items` without
|
|
364
|
+
extraction when it is already an array. Planner decision validation rejects
|
|
365
|
+
`outputSchema` on a proposed `verify` because verify has a fixed verdict
|
|
366
|
+
shape.
|
|
353
367
|
|
|
354
368
|
**Honest limitation.** `itemsFrom` removes the planner *turn*, not the stage
|
|
355
369
|
*barrier*: a verify depending on a data-driven fan-out waits for all items,
|
|
@@ -361,10 +375,6 @@ them.
|
|
|
361
375
|
|
|
362
376
|
## 5. Not adopted (yet), and why
|
|
363
377
|
|
|
364
|
-
- **Schema-enforced worker output.** bullswarm's content verification and the
|
|
365
|
-
JSON `verify` verdict cover the failure mode today; adding per-action
|
|
366
|
-
`outputSchema` is the next step if planners keep re-asking workers for
|
|
367
|
-
structure.
|
|
368
378
|
- **Per-action worktree isolation.** File ownership declared by the planner is
|
|
369
379
|
cheaper and matches Claude's own guidance ("EXPENSIVE … use ONLY when agents
|
|
370
380
|
mutate files in parallel and would otherwise conflict").
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# Dogfood 2026-08-29 — bullswarm builds bullswarm (outputSchema + planner refactor)
|
|
2
|
+
|
|
3
|
+
Observation log of the two dogfood runs that produced 0.14.0, kept verbatim
|
|
4
|
+
from the driving session's notes; the post-run defects below produced 0.14.1.
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
Goal file: `goal4.txt` (7 deliverables, single-implementer constraint). Repo branch: `feat/output-schema`.
|
|
8
|
+
Runtime: local worktree `bullswarm-rt` pinned at main (user: redispatch with local runtime, keep committing; no npm wait).
|
|
9
|
+
Home: default `~/.bullswarm` (heterogeneous pools) — user can `bullswarm workflow tui <shortId>`.
|
|
10
|
+
|
|
11
|
+
## Attempt 1 — 01:43:37 Z, installed 0.13.1 — failed at validation, nothing ran
|
|
12
|
+
`autonomous workflow invalid (nothing ran): phases[0].steps[0](scout): template ref "{{outputs.x.data.field}}" cannot resolve`.
|
|
13
|
+
Cause: goal text spliced into the scout prompt; validator parsed a quoted ref in user text.
|
|
14
|
+
Fix (bullswarm defect #1): goal → declared `inputs.goal`, inserted at render time; unresolved grammar-valid refs are
|
|
15
|
+
left literal + `template.unresolved_ref` event instead of fatal. Commit `7badea3`, released 0.13.2 (`a0f0965`).
|
|
16
|
+
Verified: scout task file of attempt 3 contains `{{outputs.x.data.field}}` verbatim; only `{{inputs.goal}}` resolved.
|
|
17
|
+
|
|
18
|
+
## Attempt 2 — 01:47:37 Z — launcher bug (mine, not bullswarm): zsh does not word-split `$BS="node path"`. Fixed script.
|
|
19
|
+
|
|
20
|
+
## Attempt 3 — 01:48:19 Z, runtime 0.13.2 @ a0f0965 — run `wf-mtdq1l9v-ed22fe` / `75t4n2`
|
|
21
|
+
- 01:48:22 scout → pool `opencode2` model `kaihk/gpt-5.6-luna`. Routing: "most-behind capable pool (surplus 0)";
|
|
22
|
+
candidates opencode2 pace 0 (unmetered) > grok −7.5 > claude-code −9.4 > codex −49.1.
|
|
23
|
+
Observation: an unmetered pool reads as exactly on pace (0) and therefore outranks every metered pool that is
|
|
24
|
+
ahead of pace. Not a crash, but quota-unknown pools capture all work whenever metered pools are burning ahead.
|
|
25
|
+
- 01:48:22 → 01:49:54 scout ok (92 s, opencode2).
|
|
26
|
+
- 01:50:06 → 02:00:40 planner turn 1 (**~634 s** — roughly 2× the goal-2/3 turns; goal text is 6.5 k chars and the
|
|
27
|
+
program is 12 actions). Decision: `needs_more_work`, 12 actions, `completion: all-actions-ok` attached.
|
|
28
|
+
Program honours the single-implementer constraint: `impl-src` (all src) ∥ `docs`; `tests-schema`/`tests-adaptive`/
|
|
29
|
+
`tests-gaps` + `verify-impl(repair 2)` fan out after impl-src; per-writer verifies with repair; `final-report` →
|
|
30
|
+
`verify-suite(repair 2)`. Note: `verify-impl`'s repair may edit src while the test writers read it — accepted risk,
|
|
31
|
+
`verify-suite` runs the whole suite at the end.
|
|
32
|
+
- 02:00:31 `impl-src` and `docs` → opencode2 `kaihk/gpt-5.6-luna` (medium effort, "most-behind capable pool (surplus 0)").
|
|
33
|
+
All build work lands on the unmetered pool while claude-code/grok/codex are ahead of pace. Orchestrator stayed on
|
|
34
|
+
claude-code (pinned).
|
|
35
|
+
- 02:00:31 → 02:07:09 `impl-src` ok (398 s, opencode2); `docs` ok (140 s). 02:07:11 five actions started at once
|
|
36
|
+
(3 test writers + verify-impl + verify-docs).
|
|
37
|
+
- 02:08:20 `verify-impl` ok:false → `verify-impl-repair-1` (concerns were concrete and correct: schema JSON omitted from
|
|
38
|
+
the retry task text; fan-out item resume skipped schemaOk; no combined run+fanout skeleton). 02:08:36 `verify-docs`
|
|
39
|
+
ok:false → `verify-docs-repair-1` (doc claimed fan-out persists top-level data/schemaOk; doc claimed static validator
|
|
40
|
+
rejects outputSchema on verify but only decision.js did). Repairs 121 s / 124 s. Both are repair-loop live uses; if the
|
|
41
|
+
program then self-completes this is the first live exercise of the 0.13.1 fix path.
|
|
42
|
+
- 02:12 → 02:18 second round of repairs: `verify-impl-repair-2` (escalation could allow a 2nd schema retry — legit design
|
|
43
|
+
nit; "schema.js untracked so absent from git diff --stat" — verifier misreading), `verify-adaptive-tests-repair-1/2`
|
|
44
|
+
(rejected twice for the same reason: "diff is not purely additive: modifies an existing assertion" — the
|
|
45
|
+
programFeatures assertion HAD to change; an unrepairable process criterion). Both verifies ended ok:false after
|
|
46
|
+
maxRounds → `final-report`/`verify-suite` blocked (`failed_terminal: dynamic actions blocked by failed or unresolved
|
|
47
|
+
dependencies`) → program boundary → planner turn 2 at 02:18:52.
|
|
48
|
+
Observation (behaviour): a verifier that judges process criteria ("purely additive", "untracked file") instead of the
|
|
49
|
+
goal's acceptance checks produces rejections no repair can satisfy; two repair rounds (~10 min) were spent before the
|
|
50
|
+
boundary. Candidate for the prompt audit: verify doctrine "ok:false only for failed acceptance checks; process
|
|
51
|
+
observations are concerns" and/or runtime: identical concerns after a repair → boundary immediately.
|
|
52
|
+
- 02:18:52 → ~02:25:20 planner turn 2 (~390 s): `needs_more_work`, 3 sequential actions — `recheck-src` (verify, repair 2,
|
|
53
|
+
depends on verify-impl-repair-2) → `full-suite-report` (run) → `verify-final` (verify, repair 2). Reason correctly
|
|
54
|
+
notes no whole-suite evidence existed after the repairs (impl-src's npm test predated them).
|
|
55
|
+
- 02:25:52 → 02:32:36 follow-up program: `recheck-src` ok:false → `recheck-src-repair-1` → re-verify ok; `full-suite-report`
|
|
56
|
+
ok; `verify-final` ok → **`decision.auto_completed` (program-completion)** at 02:32:39.
|
|
57
|
+
## Result (attempt 3)
|
|
58
|
+
- Wall **44 min 17 s** (2 654 s), 28 dispatches (26 opencode2 + 2 planner on claude-code), max concurrent 5,
|
|
59
|
+
parallelism 1.5, planner 2 turns / 1 045 s (39 % — turn 1 alone 637 s), repairs 6 (2 repaired ok, 4 re-verify
|
|
60
|
+
rejected), tokens ≈ 108 k (estimate).
|
|
61
|
+
- Deliverable: 10 files changed + 2 new (489+/38−); `npm test` **318/318** (299 + 19) on my own run; validator module,
|
|
62
|
+
decision/validate/runtime/runner/template changes all present; docs + changelog written.
|
|
63
|
+
- My review: sound; one robustness flaw fixed by hand — `readTrailingObject` used a reverse brace/quote scanner whose
|
|
64
|
+
escape handling is wrong scanning backwards (a `\"` inside a string could derail it and waste the single retry);
|
|
65
|
+
replaced with the parse-candidates approach `hasStructuredAnswer` already uses. Double failure now reports the
|
|
66
|
+
retry's errors. Escalation concern from verify-impl judged mistaken (escalation follows failed dispatches only).
|
|
67
|
+
- 0.13.1 fix path: NOT exercised here either — the latest worker at completion was `full-suite-report`, verified by a
|
|
68
|
+
direct edge (`verify-final`).
|
|
69
|
+
## Adversarial review via `bullswarm run --lane analyze` (02:36 → 02:39 Z, 206 s, opencode2 kaihk/gpt-5.6-luna)
|
|
70
|
+
- Verdict "do not release" with **two confirmed, reproduced defects** — both in code I had reviewed and passed:
|
|
71
|
+
(1) my rewritten `readTrailingObject` returned the first schema-valid `{…}` from the right, so a nested object
|
|
72
|
+
could be recorded (`{"wrapper":{"ok":"inner"}}` → data `{"ok":"inner"}`); (2) `schema.js` used `in`, so
|
|
73
|
+
`toString`/`constructor`/`__proto__` counted as present/declared. Cleared: escaped quotes, fenced JSON, resume rules,
|
|
74
|
+
exactly-one retry, validator paths. Fixed + regression tests (320/320); my fix also had an infinite loop when output
|
|
75
|
+
starts with `{` (lastIndexOf clamps negative fromIndex) — caught by the suite hanging, fixed.
|
|
76
|
+
- Evidence for open item "adversarial verification by default": a 3-minute refute-framed review found what the
|
|
77
|
+
run's own verifies (6 rounds) and my manual review both missed.
|
|
78
|
+
## Goal 5 — planner context/contract refactor — run `wf-mtds7tzx-95ab05` / `ejk9w2`, 02:49:09 Z, runtime 0.13.2 local
|
|
79
|
+
- scout 72 s (opencode2); planner turn 1 02:50:55 → 02:58:56 (~480 s): 6 actions, completion attached; noticed the goal's
|
|
80
|
+
stale "318 passing" and used the real 320. Program: impl-src → verify-src(repair 2) → update-tests ∥ update-docs →
|
|
81
|
+
verify-tests(repair 2) → verify-suite(repair 1).
|
|
82
|
+
- 02:58:56 → 03:09:04 impl-src (~610 s). verify-src rejected twice, both times on SUBSTANTIVE spec points (obsolete
|
|
83
|
+
skeleton text left in a comment; 6 JSON examples instead of 2; `<item>` instead of `{{item}}` in examples; excerpt
|
|
84
|
+
policy). One misread to check in the final diff: it called the existing 3 000/36 000-char excerpt caps a violation of
|
|
85
|
+
"full excerpt" although the goal said "(existing budget logic)" — the repair may have removed the caps.
|
|
86
|
+
Ordering tension: verify-src runs before update-tests, so it necessarily sees 5 failing old assertions; the planner
|
|
87
|
+
should either fold assertion updates into impl-src or make verify-src judge src only.
|
|
88
|
+
- Goal 5 finished 04:00:40 Z (71.5 min, 3 planner turns + program-completion, 15 dispatches, plannerSec 1 105+175).
|
|
89
|
+
Where the time went: 18–20 min planner turns; 12.4 min verify-src repair loop enforcing my over-exacting spec and
|
|
90
|
+
judging intermediate state; ~8 min decision-3 nit round (`align-prefix-number` + `confirm-docs`) triggered by an
|
|
91
|
+
UNPARSEABLE final-check verdict; the queued steer (03:57:32, "converge now") was NEVER delivered — 0 steering.delivered events; the run auto-completed (program-completion, 04:00:40) and deliverSteering only runs at planner gates, so auto-completion silently discards pending operator steering. Known issue; convergence came from the runtime, not the steer.
|
|
92
|
+
Deliverable reviewed + committed `15f1534`: 10-rule contract (2.2 k chars) + 2 examples (1.9 k) replace 16.3 k prefix;
|
|
93
|
+
compact ledger rows; id-only failures; 200-char stale excerpts; `decision.context_built` size event; 323/323.
|
|
94
|
+
- My follow-up (commit after 15f1534): verify verdict parse failure → ONE bounded re-ask (`verify.verdict_retry`,
|
|
95
|
+
test with a garbled-once verifier, 324/324); contract amendments (verify scoping, converge-not-polish, restored
|
|
96
|
+
shared-tree/redundant-verification/operatorSteering lines the merge dropped); re-budgeted "full" excerpts (the
|
|
97
|
+
uncapped version could have rebuilt the 163 k contexts).
|
|
98
|
+
- Speed answer to the user: ~35 of 66 min (at question time) was real work; fixes target the rest — re-ask (−8 min),
|
|
99
|
+
verify scoping (−12 min), convergence rule (−nit rounds). Remaining lever: planner turn latency itself (Opus
|
|
100
|
+
high-effort per boundary; context compaction cuts cost ~7×, latency is model thinking time).
|
|
101
|
+
|
|
102
|
+
## Post-run defects → 0.14.1 (fixed directly, dogfooding paused by user direction)
|
|
103
|
+
- `workflow tui` crashed twice (`detailRow` dashboard.js:809 `JSON.parse` of
|
|
104
|
+
state.json mid-write; the throw escaped the repaint timer and killed the TUI,
|
|
105
|
+
stranding the terminal in alt-screen raw mode). Fix: atomic temp+rename
|
|
106
|
+
writes for state/report/workflow.json + torn-read-tolerant observation
|
|
107
|
+
readers + guarded paint/key handlers with last-good-frame fallback.
|
|
108
|
+
- TUI showed phase ✗ "2/2 complete" while a re-verify attempt was live. Fix:
|
|
109
|
+
an action with an active agent reads as running; phase precedence
|
|
110
|
+
active > failed > completed.
|
|
111
|
+
- The goal-5 steer was never delivered: auto-completion bypassed the planner
|
|
112
|
+
gate and silently discarded pending steering (0 steering.delivered events).
|
|
113
|
+
Fix: pending steering defers self-completion to the planner
|
|
114
|
+
(`decision.completion_deferred`); undeliverable steering is marked
|
|
115
|
+
`expired_undelivered` (`steering.expired`) at the terminal transition.
|
|
116
|
+
- Routing concentration on the unmetered pool (26/28 dispatches) confirmed as
|
|
117
|
+
design intent (quota protection outranks diversity) and documented in the
|
|
118
|
+
skill rather than changed.
|
|
119
|
+
|
|
120
|
+
## Proof run 1 — goal-3 re-run `d8pr8s` (wf-mtdvuk9m), 0.14.1-pre @ 0bbe78c
|
|
121
|
+
Baseline (0.13.1, same fixture/goal/pool/flags): 28m42s wall, 1 planner turn, 294 s planner.
|
|
122
|
+
- **completed + verified, deliverable exactly right** (csv+slugify guarded, existing tests byte-identical, 63/63),
|
|
123
|
+
zero crashes, 8 workers, no repairs.
|
|
124
|
+
- Wall **32m19s**, planner **5 dispatches / 463 s** (195+114+58+66+30). Per-turn latency DOWN (max 195 s vs 294 s);
|
|
125
|
+
context per turn 6.2k–48k chars (`decision.context_built` measuring itself) vs the old 16.3k prefix + up-to-178k contexts.
|
|
126
|
+
- The 3 extra gates, each diagnosed and fixed in `c0ff947`:
|
|
127
|
+
1. first proposal rejected — verify with several dependsOn lacked `review` → contract rule 4 now states it;
|
|
128
|
+
2. evidence-policy boundary + rejected `complete` — final-report left as last unverified worker → rule 8 now
|
|
129
|
+
states the LAST worker must be covered by a verify;
|
|
130
|
+
3. one deliberate operator steer — which **live-proved the 0.14.1 steering fix**: `decision.completion_deferred`
|
|
131
|
+
→ `steering.delivered` (first ever observed; the goal-5 defect showed 0) → planner turn honoured it.
|
|
132
|
+
- Also observed and fixed: a corrective turn re-inflated validationFeedback to 24k chars (raw response duplicated
|
|
133
|
+
the parsed proposal) → excerpt capped at 2k.
|
|
134
|
+
- 0.13.1 completion-evidence policy exercised live for the first time: it refused auto-completion twice, correctly.
|
|
135
|
+
|
|
136
|
+
## Proof run 2 — goal-3 re-run `djnjka` (wf-mtdx5htt), 0.14.1-pre @ c0ff947 — interrupted, then cancelled
|
|
137
|
+
- Turn-1 proposal **accepted first try** (no validation rejection, no correction turn): contract fix #1 confirmed.
|
|
138
|
+
Turn-1 context 6,201 chars.
|
|
139
|
+
- At 05:21 the driving session's background task was killed by the harness (not the user); SIGTERM reached the
|
|
140
|
+
runner, which persisted `interrupted` + resumable (1/3 steps, in-flight `triage` cancelled) — the 0.13 interruption
|
|
141
|
+
path working as designed.
|
|
142
|
+
- Resume at 05:54 exposed a **resume defect**: the cancelled `triage` was persisted `ok:false` ("workflow
|
|
143
|
+
cancellation requested"), so its 4 dependents were marked "blocked by failed or unresolved dependencies" and the
|
|
144
|
+
planner was asked to re-plan around a failure that never happened. Cancelled the run; fixed in `459c58c`
|
|
145
|
+
(cancelled actions + dependents blocked only by them are reopened on resume, event `action.reopened`; regression
|
|
146
|
+
test drives a cancel marker into a slow in-flight action and asserts exactly one further planner turn).
|
|
147
|
+
|
|
148
|
+
## Proof run 3 — goal-3 re-run `4t6m5a` (wf-mtdyyqkw), 0.14.1-pre @ 459c58c — cancelled after diagnosis
|
|
149
|
+
- Turn-1 proposal (9 actions, sound shape: 3 disjoint impl workers ∥ audit of untouched modules → per-module
|
|
150
|
+
verifies → final-report → verify-final, completion attached) was **rejected** for one field: a zero-dependsOn
|
|
151
|
+
audit verify carried no `review`. Correction turn cost ~5 min and re-inflated context to 47.8k chars
|
|
152
|
+
(validationFeedback 38k — the 2k cap on rejectedResponseExcerpt was insufficient because rejectedProposal
|
|
153
|
+
itself is 37k). Root cause is the validator's posture, not the planner: rejecting a whole program for a field the
|
|
154
|
+
runtime can default. Fixed in `548eabe`: review defaults to the single/last dependency's artifact, or
|
|
155
|
+
`reviewScope: repository` for a no-dependency audit; contract rule 4 reworded. Run cancelled to re-prove cleanly.
|
|
156
|
+
|
|
157
|
+
## Proof run 4 — goal-3 re-run `d7xyg2` (wf-mtdzhw88), 0.14.1-pre @ 548eabe — **PASS**
|
|
158
|
+
| metric | 0.13.1 baseline (x3x2a2-era, same fixture) | 0.14.1-pre run 4 |
|
|
159
|
+
| --- | ---: | ---: |
|
|
160
|
+
| outcome | completed, verified | completed, verified, **auto-completed** (program-completion) |
|
|
161
|
+
| wall | 28 min 42 s | 30 min 12 s |
|
|
162
|
+
| planner turns / plannerSec | 1 / 294 s | **1 / 269 s** |
|
|
163
|
+
| planner context (turn 1) | 32.7 k chars (measured on a sibling run) | **6.2 k chars** |
|
|
164
|
+
| dispatches (workers) | — | 8 (7) · max concurrent 9 |
|
|
165
|
+
| corrections / rejections / repairs / verdict re-asks | 0 / 0 / 0 / 0 | 0 / 0 / 0 / 0 |
|
|
166
|
+
| deliverable | csv + slugify guarded, 63/63 | csv + slugify guarded, **63/63**, existing test files byte-identical |
|
|
167
|
+
Program: scout → probe (all exports, wrong-type matrix) → fanout guards over the discovered modules → verify-guards →
|
|
168
|
+
report → verify-final, `completion: all-actions-ok`. Wall is within noise of baseline (+90 s, dominated by worker
|
|
169
|
+
model time: probe 4.7 min, guards 5.1 min, verify-guards 4.7 min); the planner side is faster and 5× smaller.
|
|
170
|
+
Zero observation crashes across four runs of TUI/watch/runs/result/static-tui polling and a 20 s stress loop
|
|
171
|
+
(1,681 paints against the live writer, 0 torn, 0 throws).
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# Planner prompt and context audit — 2026-08-29
|
|
2
|
+
|
|
3
|
+
Question from the user: after the 0.11 → 0.14 iterations, does the instruction
|
|
4
|
+
set given to the orchestrator/planner still make sense, or should it be
|
|
5
|
+
simplified or refactored?
|
|
6
|
+
|
|
7
|
+
Method: measure what the planner actually receives, not what the source files
|
|
8
|
+
look like. The two planner task files of dogfood run `wf-mtdq1l9v-ed22fe`
|
|
9
|
+
(`75t4n2`, bullswarm building `outputSchema` in its own repo, runtime 0.13.2)
|
|
10
|
+
are the specimens; sizes are characters of the task text
|
|
11
|
+
(`task-orchestrator-*.md`), tokens ≈ chars / 4.
|
|
12
|
+
|
|
13
|
+
## 1. What one planner turn receives
|
|
14
|
+
|
|
15
|
+
| section | source | turn 1 | turn 2 |
|
|
16
|
+
| --- | --- | ---: | ---: |
|
|
17
|
+
| orchestrator prompt + worktree line | `goal.js` `AUTONOMOUS_ORCHESTRATOR_PROMPT` | 5 000 | 5 000 |
|
|
18
|
+
| PLANNING DOCTRINE (11 bullets) | `runtime.js` runDecision | 3 605 | 3 605 |
|
|
19
|
+
| Action skeletons (6 shapes + verify semantics) | `runtime.js` | 1 342 | 1 342 |
|
|
20
|
+
| Program skeleton (discovery → fan-out → verify → suite) | `runtime.js` | 1 217 | 1 217 |
|
|
21
|
+
| Graph skeleton (two chains + suite) + fanout paragraph + runtime-owned line | `runtime.js` | 4 054 | 4 054 |
|
|
22
|
+
| durable context (JSON) | `runtime.js` plannerContext | 17 474 | 162 946 |
|
|
23
|
+
| **total** | | **32 730** (~8 k tokens) | **178 452** (~45 k tokens) |
|
|
24
|
+
|
|
25
|
+
The fixed prefix is 15.2 k chars on every turn. The durable context grew
|
|
26
|
+
**9×** between turn 1 and turn 2 of the same run.
|
|
27
|
+
|
|
28
|
+
### Where the 163 k of turn 2 went
|
|
29
|
+
|
|
30
|
+
| key | chars | what it is |
|
|
31
|
+
| --- | ---: | --- |
|
|
32
|
+
| `completedActions` (19 entries) | 66 700 | every finished action **with its full attempt records**: routing candidates and pace numbers, usage, pricing table, child pid, timings — ~3.5 k per action |
|
|
33
|
+
| `outputs` (19 entries) | 41 560 | `outputExcerpt` of ~3 k chars for *every* finished action, including ones that finished ok and were already verified in the previous program |
|
|
34
|
+
| `intent` | 6 657 | the goal text (6.5 k) + cwd + policy — needed, once |
|
|
35
|
+
| `failures` | 1 073 | the two blocked actions — duplicates of `completedActions` entries |
|
|
36
|
+
| `availablePools`, `budget`, `executionConstraints`, `closedPhases`, … | ~1 800 | fine |
|
|
37
|
+
|
|
38
|
+
## 2. What is said more than once
|
|
39
|
+
|
|
40
|
+
Reading the prefix as the planner does, the same rules appear two or three
|
|
41
|
+
times in different words:
|
|
42
|
+
|
|
43
|
+
| rule | orchestrator prompt | doctrine | skeletons |
|
|
44
|
+
| --- | --- | --- | --- |
|
|
45
|
+
| propose the whole program, one round trip costs minutes | item 2 | bullets 1, 2 | "all in ONE decision" ×2 |
|
|
46
|
+
| N items → N run + N verify + one suite verify | item 5 | "Per-item chains" | Graph skeleton |
|
|
47
|
+
| unknown items → discovery + `itemsFrom` fan-out | item 5 | "Unknown item count" | Program skeleton + fanout paragraph |
|
|
48
|
+
| every verify gets a `repair` policy | item 5 | "Verification failures" | verify skeleton |
|
|
49
|
+
| `completion: all-actions-ok` on a clean program | item 5 | "Self-completing programs" | — |
|
|
50
|
+
| self-contained worker prompts, file ownership | item 3 | "File ownership", "Self-contained prompts" | — |
|
|
51
|
+
| don't propose pool/addDir/taskFile | closing line | — | final line |
|
|
52
|
+
|
|
53
|
+
Item 5 of the orchestrator prompt alone is 1 050 chars and restates four
|
|
54
|
+
doctrine bullets. The two program skeletons both end in the same
|
|
55
|
+
`verify-items → verify-suite` tail.
|
|
56
|
+
|
|
57
|
+
## 3. Does it matter? Measured
|
|
58
|
+
|
|
59
|
+
- Turn 1 (32.7 k chars) took **637 s**; turn 2 (178 k chars) took **390 s**.
|
|
60
|
+
Latency is therefore dominated by the model's reasoning on the goal, not by
|
|
61
|
+
context size — the 6.5 k-char goal and a 12-action program cost more thinking
|
|
62
|
+
than reading 45 k tokens. Trimming context is a **cost** and **attention**
|
|
63
|
+
lever, not primarily a latency lever.
|
|
64
|
+
- Cost: turn 2 read ~45 k tokens to emit a ~1 k-token decision. At Opus
|
|
65
|
+
prices that is ~$0.25 per boundary; a run with four boundaries (goal 2 on
|
|
66
|
+
0.12.1) spends more on re-reading attempt metadata than on the decisions.
|
|
67
|
+
- Attention: the planner's turn-2 reason correctly diagnosed the blocked
|
|
68
|
+
graph, so quality did not visibly suffer here — but 64 k chars of pricing
|
|
69
|
+
tables and routing candidates are noise it must skip to find the two
|
|
70
|
+
`ok:false` concerns that matter.
|
|
71
|
+
- Behaviour observed in three runs (goal 2, goal 3, dogfood): every rule the
|
|
72
|
+
prefix repeats was followed on the first turn (whole program, per-item
|
|
73
|
+
chains, repair policies, `completion`). No observed decision needed a rule
|
|
74
|
+
to be stated twice.
|
|
75
|
+
|
|
76
|
+
## 4. Recommendation
|
|
77
|
+
|
|
78
|
+
Yes — refactor, in two independent pieces, both measurable:
|
|
79
|
+
|
|
80
|
+
**A. Compact the durable context (the 9× growth).** Planner-facing ledger rows
|
|
81
|
+
instead of raw ledger entries: `{ id, type, phase, status, pool, durationSec,
|
|
82
|
+
attempts, why }` (~150 chars; 19 actions → ~3 k instead of 66.7 k). Keep a
|
|
83
|
+
full `outputExcerpt` only for actions finished **since the last decision** and
|
|
84
|
+
for every `ok:false` verify; older ok actions get a one-line summary (id, ok,
|
|
85
|
+
first 200 chars). Replace `failures` with the ids of failing actions (their
|
|
86
|
+
full entry already sits in the ledger). Expected turn-2 context: ~25 k chars
|
|
87
|
+
instead of 163 k. Pure runtime change; no planner behaviour change intended.
|
|
88
|
+
|
|
89
|
+
**B. One contract instead of three overlapping texts.** Merge
|
|
90
|
+
`AUTONOMOUS_ORCHESTRATOR_PROMPT` and the doctrine bullets into a single ordered
|
|
91
|
+
list of ~10 rules (target ≤ 4 k chars, from 8.1 k), each stated once with its
|
|
92
|
+
reason; keep exactly two JSON examples — the action shapes list and one
|
|
93
|
+
complete program (discovery → data-driven fan-out → per-item verify with
|
|
94
|
+
repair → suite verify, with `completion`) — and delete the second program
|
|
95
|
+
skeleton (target ≤ 3 k, from 6.6 k). Total prefix ≤ 7 k chars, from 15.2 k.
|
|
96
|
+
|
|
97
|
+
Acceptance for both: unit tests on the context builder (row shape, excerpt
|
|
98
|
+
policy by decision sequence) and on the prompt (each rule appears once; the
|
|
99
|
+
skeleton assertions in `tests/workflow-adaptive.test.js` updated); then one
|
|
100
|
+
re-run of goal 3 on the same fixture (baseline 0.13.1: 28 min 42 s, 1 planner
|
|
101
|
+
turn, 294 s) to confirm the decision shape is unchanged and record the new
|
|
102
|
+
per-turn size.
|
|
103
|
+
|
|
104
|
+
Not recommended: cutting the goal text or the scout excerpt from the context —
|
|
105
|
+
both were used verbatim by every first-turn program observed.
|
|
106
|
+
|
|
107
|
+
## 5. Outcome
|
|
108
|
+
|
|
109
|
+
Measurements below were taken after the refactor from the current source and
|
|
110
|
+
from the committed source saved into `/tmp/goal-before.mjs` and
|
|
111
|
+
`/tmp/runtime-before.js`, using the same temporary measurement script. The
|
|
112
|
+
canonical prefix is the complete emitted planner task text counted from its
|
|
113
|
+
first character up to (not including) the durable-context marker, with the
|
|
114
|
+
worktree-isolation suffix included. The committed baseline predates the named
|
|
115
|
+
section exports, so its emitted prefix was reconstructed from the committed
|
|
116
|
+
`runtime.js` task-text assembly and the committed `AUTONOMOUS_ORCHESTRATOR_PROMPT`.
|
|
117
|
+
|
|
118
|
+
- Complete emitted planner task text up to the durable-context marker,
|
|
119
|
+
worktree-isolation suffix included: **OBSERVED**, `16,316` characters before
|
|
120
|
+
and `5,208` characters after. Command: `node /tmp/measure-planner.mjs`.
|
|
121
|
+
- Static planner task-prefix array through the durable-context boundary:
|
|
122
|
+
**OBSERVED**, `16,209` characters before and `5,101` characters after. The
|
|
123
|
+
after value is 107 characters shorter because it excludes the unchanged
|
|
124
|
+
worktree-isolation suffix; this is a secondary source-level measurement, not
|
|
125
|
+
the canonical emitted-prefix headline. Command: `node /tmp/measure-planner.mjs`.
|
|
126
|
+
- `PLANNER_RULES_SECTION`: **OBSERVED**, `2,202` characters after. Command:
|
|
127
|
+
`node /tmp/measure-planner.mjs`.
|
|
128
|
+
- `PLANNER_EXAMPLES_SECTION`: **OBSERVED**, `1,867` characters after. Command:
|
|
129
|
+
`node /tmp/measure-planner.mjs`.
|
|
130
|
+
- `AUTONOMOUS_ORCHESTRATOR_PROMPT`: **OBSERVED**, `4,670` characters after;
|
|
131
|
+
the committed before source had no separately exported rules or examples
|
|
132
|
+
sections, so separate before-section sizes are **NOT AVAILABLE**, not
|
|
133
|
+
inferred. Command: `node /tmp/measure-planner.mjs`.
|
|
134
|
+
- Sample durable context: **OBSERVED** baseline `163,000` characters in the
|
|
135
|
+
audit's rounded turn-2 durable-context total (the detailed table records
|
|
136
|
+
`162,946`; the full turn-2 task was `178,452`), and **COMPUTED** `23,547`
|
|
137
|
+
characters after. The computed sample applies 19
|
|
138
|
+
compact ledger rows at 150 characters each, keeps a 3,000-character scout
|
|
139
|
+
excerpt and two 3,000-character failing-verify excerpts, truncates the
|
|
140
|
+
other 16 action excerpts to 200 characters, represents two failures as
|
|
141
|
+
20-character IDs, and retains the audit's 6,657-character intent and
|
|
142
|
+
1,800-character other-context components. Command: `node /tmp/compute-context.mjs`.
|
|
143
|
+
|
|
144
|
+
Deliverables:
|
|
145
|
+
|
|
146
|
+
- Durable planner context shrank because completed actions are compact ledger
|
|
147
|
+
rows, failures are IDs, and stale successful output is truncated.
|
|
148
|
+
- Planner contract shrank because overlapping prompt/doctrine/skeleton text is
|
|
149
|
+
now one ordered rules section plus exactly two JSON examples, single-sourced
|
|
150
|
+
in `src/workflow/goal.js`.
|
|
151
|
+
- Runtime prompt construction shrank because `src/workflow/runtime.js` imports
|
|
152
|
+
the shared contract instead of carrying a duplicate doctrine and graph
|
|
153
|
+
skeleton.
|
|
154
|
+
- `skill/SKILL.md` was left unchanged: it documents the general durable
|
|
155
|
+
context and the separate run-state/TUI attempt view, but does not document a
|
|
156
|
+
renamed/dropped planner-context field shape such as the old attempt records
|
|
157
|
+
or an old `failures` representation.
|
package/package.json
CHANGED
package/skill/SKILL.md
CHANGED
|
@@ -246,7 +246,12 @@ bullswarm workflow steer <shortId> --message "guidance for the next planner chec
|
|
|
246
246
|
`capabilities` reports available pools, supported lanes, configured models,
|
|
247
247
|
meter readings, burst gates, quarantine state, retry limits, and the important
|
|
248
248
|
routing rule. Automatic routing chooses the highest time-adjusted quota surplus
|
|
249
|
-
among capable pools.
|
|
249
|
+
among capable pools. An unmetered pool reads as exactly on pace (surplus 0), so
|
|
250
|
+
whenever every metered pool is burning ahead of its window (negative
|
|
251
|
+
surplus) the unmetered pool wins ALL work — by design: quota protection
|
|
252
|
+
outranks provider diversity (observed 2026-08-29: 26 of 28 dispatches on
|
|
253
|
+
one unmetered pool). If that concentration is unwanted, meter the pool or
|
|
254
|
+
exclude its models via `strategy exclude-model`. For strategic model selection, first run:
|
|
250
255
|
|
|
251
256
|
```bash
|
|
252
257
|
bullswarm strategy refresh
|
|
@@ -361,6 +366,11 @@ that expressible without extra turns:
|
|
|
361
366
|
(`source: "program-completion"`, event `decision.auto_completed`) and the run
|
|
362
367
|
ends without another planner turn; a failing action emits
|
|
363
368
|
`decision.completion_predicate_unmet` and the boundary returns to the planner.
|
|
369
|
+
- `outputSchema` on a `run` or fan-out `stepTemplate` — declare it when a
|
|
370
|
+
downstream action needs reliable structured data, such as an object to render
|
|
371
|
+
into a dependent prompt or an `items` array for `fanout.itemsFrom`; leave it
|
|
372
|
+
off for ordinary prose; planner proposals must not put it on `verify`, whose
|
|
373
|
+
verdict shape is fixed.
|
|
364
374
|
|
|
365
375
|
Every fan-out records a summary artifact as `outputs.<id>.outFile` and a
|
|
366
376
|
boolean `ok` (item count in `succeeded`), so a verify may depend on a fan-out
|
|
@@ -401,6 +411,17 @@ Resume by shortId:
|
|
|
401
411
|
bullswarm workflow draft run my-audit --resume <shortId> --json --quiet
|
|
402
412
|
```
|
|
403
413
|
|
|
414
|
+
## Writing goals that converge
|
|
415
|
+
|
|
416
|
+
State outcomes, not measurements. A goal that fixes character counts, exact
|
|
417
|
+
event names, or cosmetic layout turns every verifier into a nit machine:
|
|
418
|
+
observed 2026-08-29 (run `ejk9w2`), a spec with hard numeric limits cost a
|
|
419
|
+
12-minute verify/repair loop enforcing them against an intermediate state.
|
|
420
|
+
Say what must be true at the end (`npm test` passes, the planner receives one
|
|
421
|
+
contract stated once, context stays bounded) and let workers pick the numbers;
|
|
422
|
+
put any hard limit in ONE final acceptance check, not on every intermediate
|
|
423
|
+
verify.
|
|
424
|
+
|
|
404
425
|
## Writing prompts that the verify gate will accept
|
|
405
426
|
|
|
406
427
|
The verify gate (`src/lib/verify.js`) flags outputs as `intent_only`
|
package/src/workflow/cli.js
CHANGED
|
@@ -340,7 +340,7 @@ async function wfGoal(opts) {
|
|
|
340
340
|
try {
|
|
341
341
|
doc = existsSync(workflowPath)
|
|
342
342
|
? JSON.parse(readFileSync(workflowPath, 'utf8'))
|
|
343
|
-
:
|
|
343
|
+
: readJsonForUpdate(statePath, 'workflow state')._doc;
|
|
344
344
|
} catch (err) {
|
|
345
345
|
console.error(`✗ cannot load durable workflow for ${resumeRunId}: ${err.message}`);
|
|
346
346
|
return 1;
|