mandrel 1.67.0 → 1.69.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/.agents/docs/agentrc-reference.json +1 -2
- package/.agents/docs/configuration.md +2 -4
- package/.agents/schemas/agentrc.schema.json +1 -5
- package/.agents/schemas/lifecycle/epic.automerge.end.schema.json +2 -1
- package/.agents/scripts/epic-deliver-preflight.js +30 -13
- package/.agents/scripts/epic-deliver-prepare.js +40 -53
- package/.agents/scripts/epic-execute-record-wave.js +119 -133
- package/.agents/scripts/lib/baselines/refresh-service.js +13 -1
- package/.agents/scripts/lib/config/explain.js +0 -2
- package/.agents/scripts/lib/config/limits.js +19 -8
- package/.agents/scripts/lib/config-settings-schema-delivery.js +17 -0
- package/.agents/scripts/lib/config-settings-schema.js +12 -2
- package/.agents/scripts/lib/maintainability-utils.js +32 -9
- package/.agents/scripts/lib/orchestration/epic-cleanup.js +11 -7
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/cli.js +6 -6
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/context.js +11 -5
- package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/run-spec-phase.js +32 -1
- package/.agents/scripts/lib/orchestration/epic-run-state-store.js +203 -110
- package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/composition.js +38 -78
- package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/transport.js +16 -13
- package/.agents/scripts/lib/orchestration/epic-runner/sub-agent-return.js +10 -7
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/automerge-predicate.js +37 -24
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/watcher.js +13 -8
- package/.agents/scripts/lib/orchestration/manifest-builder.js +6 -0
- package/.agents/scripts/lib/orchestration/planning-risk.js +45 -6
- package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +6 -2
- package/.agents/scripts/lib/orchestration/wave-record-io.js +18 -77
- package/.agents/scripts/lib/orchestration/wave-record-notifications.js +78 -122
- package/.agents/scripts/lib/orchestration/wave-record-projection.js +21 -226
- package/.agents/scripts/lib/presentation/dispatch-manifest-render.js +18 -1
- package/.agents/scripts/lib/presentation/manifest-render-waves.js +77 -4
- package/.agents/scripts/lib/story-adjacency.js +14 -10
- package/.agents/scripts/lib/story-body/story-body.js +36 -4
- package/.agents/scripts/lib/templates/decomposer-prompts.js +23 -3
- package/.agents/scripts/lib/wave-runner/ready-set.js +295 -0
- package/.agents/scripts/lib/wave-runner/tick.js +312 -206
- package/.agents/scripts/lib/wave-runner/wave-runner-error.js +2 -1
- package/.agents/scripts/lint-label-vocabulary.js +1 -1
- package/.agents/scripts/stories-wave-tick.js +262 -161
- package/.agents/skills/core/epic-plan-consolidate/SKILL.md +6 -0
- package/.agents/skills/core/epic-plan-decompose-author/SKILL.md +108 -101
- package/.agents/skills/skills.index.json +2 -2
- package/.agents/workflows/deliver.md +12 -9
- package/.agents/workflows/helpers/deliver-epic.md +126 -90
- package/.agents/workflows/helpers/deliver-stories.md +131 -85
- package/.agents/workflows/helpers/plan-epic.md +13 -10
- package/.agents/workflows/plan.md +1 -1
- package/docs/CHANGELOG.md +26 -0
- package/package.json +1 -1
- package/.agents/scripts/lib/wave-runner/wave-checkpoint.js +0 -91
|
@@ -26,7 +26,7 @@ back to the operator-merges-button path.
|
|
|
26
26
|
```text
|
|
27
27
|
/deliver <epicId>
|
|
28
28
|
→ Phase 1 — prepare (epic-deliver-prepare.js)
|
|
29
|
-
→ Phase 2 —
|
|
29
|
+
→ Phase 2 — ready-set loop (wave-tick.js → dispatch ready set → observe → re-tick)
|
|
30
30
|
→ Phase 3 — close-validation (lint + test + ratchets on epic/<id>)
|
|
31
31
|
→ Phase 4 — epic-audit (helpers/epic-audit.md — change-set audits via selectAudits)
|
|
32
32
|
→ Phase 5 — code-review (helpers/code-review.md with scope: epic)
|
|
@@ -124,9 +124,9 @@ Every other runtime modifier is sourced from the Epic's labels or from
|
|
|
124
124
|
|
|
125
125
|
### Phase 1 prelude — Delivery preflight (Story #2899 / F13)
|
|
126
126
|
|
|
127
|
-
Before `epic-deliver-prepare.js` seeds the
|
|
127
|
+
Before `epic-deliver-prepare.js` seeds the checkpoint, run
|
|
128
128
|
`epic-deliver-preflight.js` so the operator (and any reviewer reading the
|
|
129
|
-
Epic ticket) sees the estimated Story count, install cost,
|
|
129
|
+
Epic ticket) sees the estimated Story count, install cost, dependency depth,
|
|
130
130
|
GitHub API request volume, and Claude Max quota burn for the run that is
|
|
131
131
|
about to fan out. **Preflight always runs before Story fan-out.**
|
|
132
132
|
|
|
@@ -137,9 +137,10 @@ node .agents/scripts/epic-deliver-preflight.js --epic <epicId> --post
|
|
|
137
137
|
The CLI upserts a `delivery-preflight` structured comment on the Epic
|
|
138
138
|
(idempotent across re-runs) and prints a JSON envelope on stdout with
|
|
139
139
|
the canonical metric keys `storyCount`, `installCostSeconds`,
|
|
140
|
-
`
|
|
141
|
-
|
|
142
|
-
|
|
140
|
+
`dependencyDepth` (the longest dependency chain — the ready-set wall-clock
|
|
141
|
+
floor, replacing the retired wave count), `githubApiRequests`,
|
|
142
|
+
`claudeQuotaTokens`, plus a `breaches` array describing any
|
|
143
|
+
`delivery.preflight.max*` thresholds the estimate exceeds.
|
|
143
144
|
|
|
144
145
|
**Breach handling.** When `breaches` is non-empty, the workflow MUST
|
|
145
146
|
flip the Epic to `agent::blocked`, surface the envelope in chat for the
|
|
@@ -162,11 +163,14 @@ node .agents/scripts/epic-deliver-prepare.js --epic <epicId> [--steal] [--as <ha
|
|
|
162
163
|
```
|
|
163
164
|
|
|
164
165
|
Validates `type::epic`, enumerates `type::story` descendants, parses
|
|
165
|
-
`blocked by #N` plus explicit `dependencies`,
|
|
166
|
-
and upserts the `epic-run-state`
|
|
167
|
-
|
|
168
|
-
`
|
|
169
|
-
`
|
|
166
|
+
`blocked by #N` plus explicit `dependencies`, computes the dependency DAG
|
|
167
|
+
(to enumerate the open Story set), and upserts the `epic-run-state`
|
|
168
|
+
checkpoint in the per-Story-status shape (a flat `stories` map seeded at
|
|
169
|
+
`pending`, plus the global `concurrencyCap`). Treat the printed JSON as
|
|
170
|
+
`state`: `{ epicId, storyCount, concurrencyCap, stories, checkpointInitializedAt }`.
|
|
171
|
+
`stories` is the flat dispatch hint (`{ storyId, worktree, title }` per open
|
|
172
|
+
Story); the ready-set `tick` (Phase 2) decides which to dispatch on each
|
|
173
|
+
beat. Flip the Epic to `agent::executing` (idempotent) after the CLI returns.
|
|
170
174
|
|
|
171
175
|
> **Preflight guards (Story #3482 / F-workflow-guards).** Before the
|
|
172
176
|
> snapshot phase runs — and before any worktree is created — prepare runs
|
|
@@ -217,14 +221,28 @@ Once the preflight guards pass, the snapshot phase applies one more gate:
|
|
|
217
221
|
|
|
218
222
|
---
|
|
219
223
|
|
|
220
|
-
## Phase 2 —
|
|
224
|
+
## Phase 2 — Ready-set loop
|
|
221
225
|
|
|
222
|
-
The
|
|
223
|
-
[`lib/wave-runner/tick.js`](../../scripts/lib/wave-runner/tick.js) —
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
226
|
+
The scheduler lives in
|
|
227
|
+
[`lib/wave-runner/tick.js`](../../scripts/lib/wave-runner/tick.js) — a thin
|
|
228
|
+
**Epic adapter over the ready-set core**
|
|
229
|
+
([`lib/wave-runner/ready-set.js`](../../scripts/lib/wave-runner/ready-set.js)).
|
|
230
|
+
One stateless `tick({ epic })` call re-derives readiness from the **live**
|
|
231
|
+
Story bodies + labels on every beat and returns one `WaveTickResult`
|
|
232
|
+
describing the next action. There is **no wave barrier** (Story #4155): a
|
|
233
|
+
Story whose own dependencies are all done is dispatched the instant a slot is
|
|
234
|
+
free under the GLOBAL in-flight cap, even while an unrelated sibling Story is
|
|
235
|
+
still `agent::executing`. The loop is simply:
|
|
236
|
+
|
|
237
|
+
```text
|
|
238
|
+
tick → dispatch the ready set → observe → re-tick → … → epic-complete
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
The slash command's job each beat is to call `tick()` via its CLI shim,
|
|
242
|
+
dispatch the Stories in `nextAction.stories` via the Agent tool, record each
|
|
243
|
+
returned Story's terminal status, and re-tick until terminal. There is no
|
|
244
|
+
`record-wave` / `currentWave` step — the checkpoint carries only a flat
|
|
245
|
+
per-Story status map (for resume + the operator rollup) and the global cap.
|
|
228
246
|
|
|
229
247
|
### 2a. Tick — plan the next action
|
|
230
248
|
|
|
@@ -237,55 +255,59 @@ Stdout is one `WaveTickResult` envelope:
|
|
|
237
255
|
```json
|
|
238
256
|
{
|
|
239
257
|
"nextAction":
|
|
240
|
-
{ "kind": "dispatch",
|
|
241
|
-
| { "kind": "observe",
|
|
242
|
-
| { "kind": "
|
|
243
|
-
| { "kind": "epic-complete" },
|
|
258
|
+
{ "kind": "dispatch", "stories": [{ "id": <n>, "title": "…" }, ...], "in-flight": [<storyId>, ...] }
|
|
259
|
+
| { "kind": "observe", "waitingOn": [<storyId>, ...], "in-flight": [<storyId>, ...] }
|
|
260
|
+
| { "kind": "halt", "reason": "dependency-cycle" | "unsatisfiable-dependency", "stuckStories": [<storyId>, ...], "cycle"?: [<storyId>, ...], "in-flight": [<storyId>, ...] }
|
|
261
|
+
| { "kind": "epic-complete", "in-flight": [<storyId>, ...] },
|
|
244
262
|
"blockedStories": [{ "storyId": <n>, "reason": "…", "detail"?: "…" }, ...],
|
|
245
263
|
"gateFailures": [{ "storyId": <n>, "gate": "…", "detail"?: "…" }, ...],
|
|
246
|
-
"
|
|
247
|
-
"
|
|
264
|
+
"readyCount": <n>,
|
|
265
|
+
"inFlight": [<storyId>, ...]
|
|
248
266
|
}
|
|
249
267
|
```
|
|
250
268
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
`wave-
|
|
257
|
-
`waveParallelism` report (and `wave-start` anchors span-tree Story
|
|
258
|
-
|
|
259
|
-
`
|
|
260
|
-
|
|
261
|
-
|
|
269
|
+
`nextAction.stories` is the **ready set** for this beat — the
|
|
270
|
+
dependency-satisfied, overlap-free subset of open Stories, capped at
|
|
271
|
+
`globalCap − inFlight`. The CLI is a planner: it dispatches nothing and
|
|
272
|
+
persists nothing. It emits only the two wave-window forensics signals that
|
|
273
|
+
have a live consumer — `wave-start` (on the run's first dispatch) and
|
|
274
|
+
`wave-complete` (when the run finishes), which the perf-aggregator brackets
|
|
275
|
+
into the `waveParallelism` report (and `wave-start` anchors span-tree Story
|
|
276
|
+
spans). The [`signals` helper](signals.md)
|
|
277
|
+
(`node .agents/scripts/signals-view.js`) renders the forensics signals in the
|
|
278
|
+
span-tree view.
|
|
279
|
+
|
|
280
|
+
> **Fail-closed on an old-shape checkpoint.** If the Epic still carries a
|
|
281
|
+
> pre-ready-set (`plan` / `currentWave` / `totalWaves`) `epic-run-state`
|
|
282
|
+
> checkpoint, the tick **refuses to run** and throws an explicit operator
|
|
283
|
+
> message — re-run `epic-deliver-prepare.js --epic <id>` to re-seed the
|
|
284
|
+
> checkpoint in the per-Story-status shape, then re-run `/deliver`.
|
|
262
285
|
|
|
263
286
|
### 2b. Dispatch — fan out per-Story Agent calls
|
|
264
287
|
|
|
265
|
-
*You* (the LLM running this skill) are the
|
|
266
|
-
|
|
288
|
+
*You* (the LLM running this skill) are the dispatcher; you never invoke
|
|
289
|
+
`helpers/epic-deliver-story` yourself. Emit **one `Agent` tool call per
|
|
267
290
|
Story** in `nextAction.stories` (even when `length === 1` — the
|
|
268
291
|
parent-child boundary keeps the return-parser uniform). The *children*
|
|
269
292
|
run [`helpers/epic-deliver-story`](epic-deliver-story.md). Use
|
|
270
293
|
`subagent_type: general-purpose`.
|
|
271
294
|
|
|
272
295
|
Emit **one assistant turn** with **N parallel `Agent` calls** where
|
|
273
|
-
`N ===
|
|
274
|
-
|
|
275
|
-
as background calls (`run_in_background: true`) and
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
> **Throughput tradeoff.** The default `concurrencyCap` of 3 is
|
|
280
|
-
> intentionally conservative — it keeps host-quota
|
|
281
|
-
>
|
|
282
|
-
>
|
|
283
|
-
>
|
|
284
|
-
>
|
|
285
|
-
>
|
|
286
|
-
>
|
|
287
|
-
>
|
|
288
|
-
> the configuration surface.
|
|
296
|
+
`N === nextAction.stories.length` (the ready set is already capped at
|
|
297
|
+
`globalCap − inFlight` by the tick, so it never exceeds available slots).
|
|
298
|
+
Dispatch the ready set as background calls (`run_in_background: true`) and,
|
|
299
|
+
as each child returns, record it (§ 2c) and **re-tick** (§ 2a) to pull the
|
|
300
|
+
next ready set — never wait for the whole set before refilling.
|
|
301
|
+
|
|
302
|
+
> **Throughput tradeoff.** The default `concurrencyCap` of 3 is the GLOBAL
|
|
303
|
+
> in-flight cap, intentionally conservative — it keeps host-quota
|
|
304
|
+
> consumption low and avoids flooding the GitHub API. For Epics with wide
|
|
305
|
+
> dependency-free fronts where the host has adequate parallel-agent quota,
|
|
306
|
+
> raising `delivery.deliverRunner.concurrencyCap` in `.agentrc.json` reduces
|
|
307
|
+
> wall-clock time proportionally to the extra concurrency. The safe default
|
|
308
|
+
> is left in place; this is a deliberate operator-tuning knob, not a hidden
|
|
309
|
+
> performance ceiling. See `agentrc-reference.json`
|
|
310
|
+
> `delivery.deliverRunner.concurrencyCap` for the configuration surface.
|
|
289
311
|
|
|
290
312
|
**Ledger the dispatch BEFORE the Agent call.** Immediately before each
|
|
291
313
|
per-Story `Agent` tool call (one shell-out per Story, every attempt —
|
|
@@ -294,19 +316,21 @@ including retries from a refill), invoke
|
|
|
294
316
|
so the lifecycle ledger durably records the dispatch attempt. The
|
|
295
317
|
emit must happen **before** the Agent call fires — never after — so
|
|
296
318
|
that a host-process crash mid-Agent leaves a `story.dispatch.start`
|
|
297
|
-
record that `wave-tick.js` (see § 2a)
|
|
298
|
-
`nextAction['in-flight']
|
|
319
|
+
record that `wave-tick.js` (see § 2a) excludes from the next beat's ready
|
|
320
|
+
set and surfaces under `nextAction['in-flight']`:
|
|
299
321
|
|
|
300
322
|
```bash
|
|
301
323
|
node .agents/scripts/lifecycle-emit-story-dispatch.js \
|
|
302
324
|
--epic <epicId> --story <storyId> \
|
|
303
|
-
--wave
|
|
325
|
+
--wave 0 --attempt <attempt>
|
|
304
326
|
```
|
|
305
327
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
328
|
+
Pass `--wave 0` — the ready-set runtime has a single continuous front, so
|
|
329
|
+
the ledger's `waveIndex` is a fixed `0` (it is metadata for the start/end
|
|
330
|
+
pairing math, not a scheduling input). `<attempt>` starts at 1 for the
|
|
331
|
+
Story's first dispatch and increments on each retry/refill. The CLI appends
|
|
332
|
+
exactly one NDJSON line to `temp/epic-<epicId>/lifecycle.ndjson`; the
|
|
333
|
+
matching `story.dispatch.end` record is appended later by
|
|
310
334
|
`epic-execute-record-wave.js` (via `emit-story-dispatch-end.js`, Story #3900)
|
|
311
335
|
after the Agent return is recorded in § 2c.
|
|
312
336
|
|
|
@@ -329,7 +353,7 @@ There is **no per-child JSON return-parsing ceremony** for the parent
|
|
|
329
353
|
to enforce. GitHub state is the contract: `epic-execute-record-wave.js`
|
|
330
354
|
(§ 2c, mode B) treats each child's raw return text as a best-effort
|
|
331
355
|
hint and reconciles any unparseable, empty, or missing return directly
|
|
332
|
-
from the Story's live labels and comments
|
|
356
|
+
from the Story's live labels and comments.
|
|
333
357
|
|
|
334
358
|
**Sub-agent dispatch.** `Agent` calls emit no `model:` argument by
|
|
335
359
|
default — children inherit from the `general-purpose` sub-agent
|
|
@@ -338,53 +362,66 @@ definition and the parent's worktree context. No
|
|
|
338
362
|
specific call needs to override the inherited model, pass `model:` as a
|
|
339
363
|
per-call literal at the `Agent(...)` site.
|
|
340
364
|
|
|
341
|
-
### 2c. Record the
|
|
365
|
+
### 2c. Record the Story outcomes
|
|
342
366
|
|
|
343
|
-
|
|
344
|
-
|
|
367
|
+
As dispatched Stories return (record them as they land — you need not wait
|
|
368
|
+
for the whole ready set), persist each Story's terminal status via
|
|
369
|
+
`epic-execute-record-wave.js`. There is **no `--wave` flag and no
|
|
370
|
+
`currentWave`** — the recorder splices each Story's status into the
|
|
371
|
+
checkpoint's flat per-Story map and re-renders the rollup:
|
|
345
372
|
|
|
346
373
|
```bash
|
|
347
374
|
# Mode A — host LLM already parsed each child return.
|
|
348
375
|
node .agents/scripts/epic-execute-record-wave.js \
|
|
349
|
-
--epic <epicId> --
|
|
350
|
-
--results @<file>|<inline-json>
|
|
376
|
+
--epic <epicId> --results @<file>|<inline-json>
|
|
351
377
|
|
|
352
378
|
# Mode B — pipe the raw per-Story sub-agent return texts directly.
|
|
353
379
|
node .agents/scripts/epic-execute-record-wave.js \
|
|
354
|
-
--epic <epicId> --
|
|
355
|
-
--returns @<file>|<inline-json>
|
|
380
|
+
--epic <epicId> --returns @<file>|<inline-json>
|
|
356
381
|
# `<inline-json>` shape: [{ "storyId": <n>, "returnText": "<raw text>" }]
|
|
357
382
|
```
|
|
358
383
|
|
|
359
384
|
**Mode B is the default path** — pipe the raw return texts through
|
|
360
|
-
without inspecting them. The CLI reconciles parse failures from
|
|
361
|
-
|
|
362
|
-
re-renders
|
|
363
|
-
`{ status, nextAction, renderedBody, ... }`.
|
|
364
|
-
verbatim, then optionally append a short **Notable**
|
|
365
|
-
bullets on newly blocked / failed / slow Stories, friction,
|
|
385
|
+
without inspecting them. The CLI reconciles parse failures from GitHub,
|
|
386
|
+
records each Story's terminal status, emits one `story.dispatch.end` per
|
|
387
|
+
recorded Story (closing the ledger pairing), re-renders
|
|
388
|
+
`epic-run-progress`, and prints `{ status, nextAction, renderedBody, ... }`.
|
|
389
|
+
Print `renderedBody` verbatim, then optionally append a short **Notable**
|
|
390
|
+
section (0–5 bullets on newly blocked / failed / slow Stories, friction,
|
|
366
391
|
elapsed-time surprises).
|
|
367
392
|
|
|
368
|
-
> **Crash recovery
|
|
369
|
-
>
|
|
370
|
-
>
|
|
371
|
-
>
|
|
372
|
-
> `
|
|
373
|
-
>
|
|
374
|
-
>
|
|
375
|
-
>
|
|
376
|
-
> `wave-complete` for the same index forever.
|
|
393
|
+
> **Crash recovery.** If the host crashed *after* a child finished but
|
|
394
|
+
> *before* its return was recorded, the next `tick` re-derives that Story's
|
|
395
|
+
> state directly from its live label (the tick reads labels every beat), so a
|
|
396
|
+
> done-but-unrecorded Story is recognised as done and never re-dispatched —
|
|
397
|
+
> there is no falsely-`complete` empty wave to recover from. If you want to
|
|
398
|
+
> reconcile a known-completed Story whose return text was lost, re-record it
|
|
399
|
+
> from its live state by passing `--results '[{"storyId":<n>,"status":"done"}]'`
|
|
400
|
+
> (verification re-checks the live label before recording `done`).
|
|
377
401
|
|
|
378
402
|
### 2d. Loop on `nextAction`
|
|
379
403
|
|
|
380
404
|
After `2c`, re-run `wave-tick.js`. Branch on the new envelope:
|
|
381
405
|
|
|
382
|
-
- `dispatch` → repeat 2b/2c for the
|
|
406
|
+
- `dispatch` → repeat 2b/2c for the new ready set (the next beat's
|
|
407
|
+
dependency-satisfied Stories), then re-tick.
|
|
383
408
|
- `observe` → poll the Epic (children may still be in flight, or some
|
|
384
409
|
are `agent::blocked`). If `blockedStories` is non-empty, post a
|
|
385
410
|
friction comment, flip Epic to `agent::blocked`, park.
|
|
386
|
-
- `
|
|
387
|
-
|
|
411
|
+
- `halt` → the run is stuck: no Story is dispatchable, nothing is in
|
|
412
|
+
flight, yet not every Story is done. `reason` distinguishes the two
|
|
413
|
+
causes — `dependency-cycle` (the in-scope Stories form a `blocked by`
|
|
414
|
+
cycle; `cycle` lists the offending Story ids) or
|
|
415
|
+
`unsatisfiable-dependency` (a Story is gated on a dependency that can
|
|
416
|
+
never satisfy). `stuckStories` names the Story id(s) that stranded the
|
|
417
|
+
run. Post a friction comment quoting `reason` + `stuckStories`, flip the
|
|
418
|
+
Epic to `agent::blocked`, and park for the operator. **Never** treat a
|
|
419
|
+
`halt` as completion — proceeding to Phase 3 would silently drop the
|
|
420
|
+
stuck Story.
|
|
421
|
+
- `epic-complete` → **every** in-scope Story is done and nothing is in
|
|
422
|
+
flight; proceed to Phase 3. (The tick returns `epic-complete` only when
|
|
423
|
+
the done count equals the in-scope Story count — a stuck Story surfaces
|
|
424
|
+
as `halt`, not a false `epic-complete`.)
|
|
388
425
|
|
|
389
426
|
### 2e. Idle Watchdog
|
|
390
427
|
|
|
@@ -394,7 +431,7 @@ A Story's implementation loop can run for many minutes between
|
|
|
394
431
|
sub-agent that has gone silent (host crash, mid-Story stall, lost
|
|
395
432
|
return). The Idle Watchdog closes that gap.
|
|
396
433
|
|
|
397
|
-
**Cadence.** While any
|
|
434
|
+
**Cadence.** While any Story is in flight (i.e. `nextAction.kind` is
|
|
398
435
|
`observe` or the most recent dispatch's `in-flight` list is non-empty),
|
|
399
436
|
re-tick every **30 minutes** with the watchdog flag:
|
|
400
437
|
|
|
@@ -447,9 +484,8 @@ incrementing the `--attempt` counter; if the child is alive but
|
|
|
447
484
|
genuinely blocked, flip the Story to `agent::blocked` and proceed per
|
|
448
485
|
§ 2d's `observe` branch.
|
|
449
486
|
|
|
450
|
-
Stop the watchdog cadence once `wave-tick.js` returns
|
|
451
|
-
|
|
452
|
-
left to monitor.
|
|
487
|
+
Stop the watchdog cadence once `wave-tick.js` returns `epic-complete` —
|
|
488
|
+
there are no in-flight Stories left to monitor.
|
|
453
489
|
|
|
454
490
|
---
|
|
455
491
|
|