@ai-dossier/sched 0.7.1 → 0.9.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 +154 -15
- package/dist/batch-dispatch.d.ts +121 -0
- package/dist/batch-dispatch.d.ts.map +1 -0
- package/dist/batch-dispatch.js +1114 -0
- package/dist/batch-dispatch.js.map +1 -0
- package/dist/dispatch.d.ts +99 -14
- package/dist/dispatch.d.ts.map +1 -1
- package/dist/dispatch.js +204 -21
- package/dist/dispatch.js.map +1 -1
- package/dist/engine.d.ts +43 -2
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +181 -30
- package/dist/engine.js.map +1 -1
- package/dist/enqueue.d.ts.map +1 -1
- package/dist/enqueue.js +17 -2
- package/dist/enqueue.js.map +1 -1
- package/dist/fence.d.ts +78 -0
- package/dist/fence.d.ts.map +1 -0
- package/dist/fence.js +107 -0
- package/dist/fence.js.map +1 -0
- package/dist/groundtruth.d.ts +23 -0
- package/dist/groundtruth.d.ts.map +1 -1
- package/dist/groundtruth.js +41 -0
- package/dist/groundtruth.js.map +1 -1
- package/dist/index.d.ts +5 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +23 -3
- package/dist/index.js.map +1 -1
- package/dist/journal.d.ts +2 -0
- package/dist/journal.d.ts.map +1 -1
- package/dist/journal.js +8 -0
- package/dist/journal.js.map +1 -1
- package/dist/persist.js +25 -1
- package/dist/persist.js.map +1 -1
- package/dist/state.d.ts +7 -1
- package/dist/state.d.ts.map +1 -1
- package/dist/state.js +56 -2
- package/dist/state.js.map +1 -1
- package/dist/types.d.ts +84 -6
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +15 -4
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -97,9 +97,10 @@ where every mechanical supervision decision is code, not remembered prose:
|
|
|
97
97
|
5. **Immediate refill (AC5)** — a slot freed by a terminal state is refilled in the SAME
|
|
98
98
|
tick; a runnable unit never waits while a slot is idle (pinned by a regression test).
|
|
99
99
|
6. **Journal (AC6)** — every event (assigned, spawned, exit-detected, external-advance,
|
|
100
|
-
progress, stalled, redispatched,
|
|
101
|
-
dispatch-unhealthy, …) is appended to
|
|
102
|
-
|
|
100
|
+
progress, stalled, redispatched, fence-written, fence-failed, unit-failed,
|
|
101
|
+
dependents-blocked, suspect-dispatch, dispatch-unhealthy, …) is appended to
|
|
102
|
+
`events.jsonl`; `sched status` shows the live phase per unit, plus each slot's `gen`
|
|
103
|
+
and `fenced` state (#504). `label-blocked`/`label-check-failed` (#507) are the one pair journaled
|
|
103
104
|
OUTSIDE the engine — `sched enqueue` appends them at enqueue time, before dispatch.
|
|
104
105
|
7. **Dispatch-health pause (#505)** — an unverified exit within `SUSPECT_DISPATCH_WINDOW_MS`
|
|
105
106
|
(60s) of a slot's last progress is `suspect-dispatch`: real work rarely produces zero
|
|
@@ -132,17 +133,63 @@ Two engine-safety policies were explicit product decisions on #464:
|
|
|
132
133
|
an outage holds in `verifying` until truth returns. Each pause is journaled as
|
|
133
134
|
`ground-truth-unreachable`.
|
|
134
135
|
|
|
135
|
-
|
|
136
|
-
|
|
136
|
+
This applies to `issue:<n>` unit dispatch (`dispatchAssignments`). `batch:<id>` units run
|
|
137
|
+
through a separate pass with its own claim/reconcile logic — see
|
|
138
|
+
[Batch dispatch (#523)](#batch-dispatch-523) below.
|
|
139
|
+
|
|
140
|
+
### Zombie-run fencing (#504)
|
|
141
|
+
|
|
142
|
+
The ladder redispatches the SAME run, so a takeover inherits the run id and its milestone
|
|
143
|
+
trail. In the #472 race that turned out to be a hole: `enterRecovery` kills the pid it
|
|
144
|
+
knows about, but an agent it cannot see or signal — throttled, cwd outside the worktree —
|
|
145
|
+
survives, and nothing on the trail tells that agent it was replaced. Both runs implemented
|
|
146
|
+
the same issue, and both kept posting milestones on one trail. The doctrine, one step past
|
|
147
|
+
"an agent exiting is not proof of merge": **no visible process is not proof of death.**
|
|
148
|
+
|
|
149
|
+
A **generation** now fences the trail:
|
|
150
|
+
|
|
151
|
+
- Before the takeover is spawned, the engine calls `ai-dossier runstate fence`, which
|
|
152
|
+
posts a `status=superseded` milestone carrying `gen=<n>` and `takeover=<label>`.
|
|
153
|
+
Written first, on purpose, so it survives the takeover dying too.
|
|
154
|
+
- The takeover is told its generation in its prompt and passes `--gen <n>` to every
|
|
155
|
+
`runstate post`. **The CLI refuses any post below the trail's fenced generation**, so
|
|
156
|
+
the superseded agent cannot extend the trail even though it never checks — and an agent
|
|
157
|
+
running an older dossier implicitly sits at generation 0, fenced out the moment
|
|
158
|
+
generation 1 exists.
|
|
159
|
+
- `ai-dossier runstate check --issue <n> --run <id> --gen <g>` exits `3` when the caller
|
|
160
|
+
has been superseded: the checkpoint a workflow runs before implement, review, and ship.
|
|
161
|
+
- A takeover that posts NOTHING is watched on the **shorter** of
|
|
162
|
+
`fence_takeover_timeout_ms` (default 15 min) and the phase's own stall allowance — the
|
|
163
|
+
fence window can only ever bring recovery forward, never delay it — so a takeover that
|
|
164
|
+
dies at birth re-enters the ladder in minutes and the next fence supersedes it in turn.
|
|
165
|
+
The first progress signal disarms the short window. `ESCALATION_CAP` still bounds the
|
|
166
|
+
whole ladder.
|
|
167
|
+
- Report agents ride the same rail: a fenced report slot is told its generation too, or
|
|
168
|
+
its `report done` milestone would be refused and it would recover to the cap on a PR
|
|
169
|
+
that already merged.
|
|
170
|
+
- The read side is hardened, because a milestone is an issue comment: only comments from
|
|
171
|
+
an account with **write access** count as a fence, a forged `takeover=` label is dropped
|
|
172
|
+
rather than echoed into an agent's prompt, and the engine refuses to fence a run id that
|
|
173
|
+
does not belong to the issue it is working on (that would journal success while the real
|
|
174
|
+
zombie stayed free to write).
|
|
175
|
+
|
|
176
|
+
Fencing is defense-in-depth, not a precondition: if the fence cannot be written (no run id
|
|
177
|
+
on the trail yet, gh unreachable, no fencer configured) the redispatch proceeds unfenced
|
|
178
|
+
and journals `fence-failed`. Stranding a stalled unit forever would be the worse failure —
|
|
179
|
+
but the unprotected redispatch is never silent.
|
|
137
180
|
|
|
138
181
|
## Batch failure recovery (#472)
|
|
139
182
|
|
|
140
183
|
What happens when a batch's aggregate suite goes red, or its PR will not merge
|
|
141
184
|
(RFC-0001 §F.2/F.8/F.9).
|
|
142
185
|
|
|
143
|
-
**
|
|
144
|
-
|
|
145
|
-
|
|
186
|
+
**Wired into `sched start` since #523** — the `validating → attributing → fixing/evicting`
|
|
187
|
+
rail below is called directly from `batch-dispatch.ts`'s `runValidate`/`evictOffender`
|
|
188
|
+
(a red AGGREGATE suite, after every member individually went green). A member that never
|
|
189
|
+
went green in the first place (its own gate failed) evicts through a separate, simpler
|
|
190
|
+
rail that never touches this module — see
|
|
191
|
+
[Batch dispatch (#523)](#batch-dispatch-523). These modules remain independently tested
|
|
192
|
+
against real scratch repos.
|
|
146
193
|
|
|
147
194
|
```
|
|
148
195
|
validating → attributing → fixing (ONE bounded attempt) → validating
|
|
@@ -215,6 +262,12 @@ a report slot in the first place) with the persisted `phase` as a fallback when
|
|
|
215
262
|
matching entry exists. A backfilled role is a best-effort inference, not a guarantee —
|
|
216
263
|
see `validateState` in `state.ts` for the exact rule.
|
|
217
264
|
|
|
265
|
+
Schema 1.6.0: `SlotEntry` gains `gen` (number — the runstate generation the slot's agent
|
|
266
|
+
owns, 0 for a first dispatch) and `fenced_at` (ISO string or null — set when a takeover is
|
|
267
|
+
fenced in, cleared by its first progress signal; #504 above). 1.5.0 states migrate on
|
|
268
|
+
load: nothing was fenced before fencing existed, so `0`/`null` is the exact backfill, not
|
|
269
|
+
a guess. Both reset with the slot on release (`CLEARED_SLOT_FIELDS`).
|
|
270
|
+
|
|
218
271
|
Schema 1.5.0: `SchedState` gains `consecutive_suspect_dispatches` (number) and
|
|
219
272
|
`last_suspect_dispatch_unit` (string or null) — the dispatch-health pause's cross-unit
|
|
220
273
|
suspect-dispatch streak (#505 above). The two fields are a single fact and must agree
|
|
@@ -222,6 +275,64 @@ suspect-dispatch streak (#505 above). The two fields are a single fact and must
|
|
|
222
275
|
on load: no suspect dispatches were ever tracked under them, so `0`/`null` is the exact
|
|
223
276
|
backfill, not a guess.
|
|
224
277
|
|
|
278
|
+
## Batch dispatch (#523)
|
|
279
|
+
|
|
280
|
+
`batch-dispatch.ts`'s `runBatchTick` — called from `tick()` after the issue-level pass,
|
|
281
|
+
only when `batchExec`/`runBatchSuite` are both configured on `EngineDeps` — drives every
|
|
282
|
+
`batch:<id>` unit through:
|
|
283
|
+
|
|
284
|
+
```
|
|
285
|
+
ready → executing(member i/N) ⟲ → validating → reviewing → shipping
|
|
286
|
+
→ awaiting-merge → merged → deployed → reported → done
|
|
287
|
+
failure rails: executing → dissolving (a member self-reports blocked)
|
|
288
|
+
validating → attributing → (fixing | evicting) → validating → dissolving
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
- **One shared worktree/branch per batch**, claimed once by a deterministic (no LLM)
|
|
292
|
+
`batch-setup` step: `git branch`/`push`/`worktree add` off `base_branch`, named
|
|
293
|
+
`batch/<id>-<date>`, plus a fresh `ai-dossier runstate mint` against the anchor issue.
|
|
294
|
+
- **Members run serially, one fresh `slot-cycle` agent at a time**, in the shared
|
|
295
|
+
worktree. A member's completion signal is `phase=review status=done mode=slot` on its
|
|
296
|
+
OWN issue (`slot-cycle` posts no phase of its own past `review` — ship is batch-owned);
|
|
297
|
+
its commit range on the batch branch is recomputed (`git log`) after every member and
|
|
298
|
+
kept on `BatchEntry.ranges` for eviction. An incremental gate (`ai-dossier cap run
|
|
299
|
+
typecheck.run` / `test.focused`, when the repo has a manifest) runs after each member
|
|
300
|
+
before advancing — a second, independent check that the member's self-reported "done"
|
|
301
|
+
is real.
|
|
302
|
+
- **The batch's single slot is claimed FRESH for each live step** (a member, the tail
|
|
303
|
+
agent, the report agent, a bounded fix agent) — never held across a wait. The aggregate
|
|
304
|
+
suite itself runs with NO slot claimed at all (deterministic engine work, not an LLM
|
|
305
|
+
step).
|
|
306
|
+
- **Two failure rails.** A member that never went green evicts directly (nothing to
|
|
307
|
+
attribute — see the #472 section above for what "directly" skips). A red AGGREGATE
|
|
308
|
+
suite (every member individually green, but integration-level conflict) routes through
|
|
309
|
+
the #472 attribution/fix/evict library.
|
|
310
|
+
- **The tail**, after the last member: the aggregate suite runs deterministically; green
|
|
311
|
+
spawns ONE bounded strong-tier agent that runs `review-issue` aggregate mode then
|
|
312
|
+
`ship-issue` batch mode (rebase-merge, a `Closes` list) and parks the PR exactly like a
|
|
313
|
+
detached full-cycle run; the engine's own PR watcher (a batch-granularity mirror of the
|
|
314
|
+
per-issue one) accepts the merge and dispatches a cheap mechanical-tier agent for
|
|
315
|
+
`report-issue`'s batch variant.
|
|
316
|
+
- **Scope cuts, recorded rather than discovered later:** no `git bisect` stage for an
|
|
317
|
+
ambiguous aggregate failure (an unattributable red suite dissolves instead); no
|
|
318
|
+
worktree-pool integration for batch-setup (cold `git worktree add` only); no per-phase
|
|
319
|
+
stall/escalation ladder for batch sub-agents (a dead-without-verification agent is
|
|
320
|
+
treated as blocked, not redispatched stronger).
|
|
321
|
+
|
|
322
|
+
Schema 1.7.0: `BatchEntry` gains `worktree` (absolute path of the shared batch worktree,
|
|
323
|
+
null until batch-setup lands), `ranges` (`MemberRange[]` — each member's commit range,
|
|
324
|
+
recomputed after every member completes) and `pr` (the batch PR parked on auto-merge,
|
|
325
|
+
persisted so a restart mid-watch still knows what to poll). 1.6.0 states migrate on load:
|
|
326
|
+
no batch was ever dispatched under them, so `null`/`[]`/`null` is the exact backfill, not
|
|
327
|
+
a guess. Config schema moves to 1.3.0: `dispatch` gains `member_prompt`,
|
|
328
|
+
`batch_tail_prompt` and `batch_report_prompt` (the three new agent prompt templates).
|
|
329
|
+
|
|
330
|
+
New journal events: `batch-setup-done`, `batch-setup-failed`, `member-advanced`. Member/
|
|
331
|
+
tail/report/fix-agent spawn, progress, completion and park events reuse the existing
|
|
332
|
+
unit-generic names (`assigned`/`spawned`/`unit-failed`/`external-advance`/`pr-parked`/
|
|
333
|
+
`merge-accepted`/`report-dispatched`/`teardown-done`/`teardown-failed`) with
|
|
334
|
+
`unit = batch:<id>`.
|
|
335
|
+
|
|
225
336
|
## API surface
|
|
226
337
|
|
|
227
338
|
```ts
|
|
@@ -236,17 +347,29 @@ import {
|
|
|
236
347
|
runLoop, // the sched start loop (tick, sleep, repeat)
|
|
237
348
|
type TickResult, // what one tick did (spawned/parked/merge-accepted/stale-reconciled/
|
|
238
349
|
// dependents-unblocked/report-dispatched/teardown/completed/
|
|
239
|
-
// redispatched/failed/blocked)
|
|
350
|
+
// redispatched/failed/blocked) — since #523 also carries
|
|
351
|
+
// `batch:<id>` unit ids (issue numbers for `blocked`)
|
|
240
352
|
type EngineDeps, // inject everything the engine touches (store/journal/spawn/ground
|
|
241
|
-
// truth/clock/repoDir/teardownExec
|
|
353
|
+
// truth/clock/repoDir/teardownExec/fencer/batchExec/runBatchSuite/
|
|
354
|
+
// runBatchCapability — #523)
|
|
242
355
|
createSpawnDeps, // real detached-spawn process I/O
|
|
243
356
|
createExecGroundTruth, // runstate/gh/git ground truth via subprocesses (injectable exec);
|
|
244
357
|
// since #468 also gh pr view PR state + setup info from comments
|
|
245
358
|
resolveDispatch, // config → resolved command/prompt/report-prompt/tier-models/timers
|
|
246
359
|
stallTimeoutForPhase, // the stall allowance for the phase now in flight (#495 per-phase
|
|
247
360
|
// map → global, hardened against a prototype-name phase)
|
|
361
|
+
stallTimeoutForSlot, // #504: that allowance, shortened to fenceTakeoverTimeoutMs while
|
|
362
|
+
// a takeover has posted nothing (Math.min — never longer)
|
|
363
|
+
takeoverInstruction, // the TAKEOVER prompt suffix appended for gen > 0
|
|
364
|
+
SUPERSESSION_CHECKPOINT_INSTRUCTION, // the check-before-implement/review/ship clause
|
|
365
|
+
// every dispatch prompt carries
|
|
366
|
+
createExecRunFencer, // default fencer: shells `ai-dossier runstate fence --json`
|
|
367
|
+
parseFenceGeneration, // fence stdout → the installed generation (null = unfenced)
|
|
368
|
+
type RunFencer, // inject the takeover-record writer: (issue, run, phase, takeover)
|
|
369
|
+
type FenceOutcome, // {ok, gen} | {ok: false, reason} — a failure carries its cause
|
|
370
|
+
FENCE_TIMEOUT_MS, // fence subprocess timeout (60 s — two gh round trips)
|
|
248
371
|
DEFAULT_PHASE_STALL_TIMEOUT_MS, // built-in per-phase stall allowances (implement: 90 min)
|
|
249
|
-
buildReportPrompt, // report-agent prompt ({issue}/{pr}/{cleanup} substituted)
|
|
372
|
+
buildReportPrompt, // report-agent prompt ({issue}/{pr}/{cleanup}/{gen} substituted)
|
|
250
373
|
reportTierFor, // report (re)dispatch tier after N escalations
|
|
251
374
|
isParkedMilestone, // ship-phase awaiting-merge + pr= → the park signal
|
|
252
375
|
prOfMilestone, // a milestone's pr= key as a positive integer
|
|
@@ -279,9 +402,24 @@ import {
|
|
|
279
402
|
transitionIssue, transitionBatch, transitionSlot, // typed §D transitions
|
|
280
403
|
TRANSITIONS, // the transition tables themselves (for previews)
|
|
281
404
|
buildStatusReport, // machine-readable status incl. blocked/failed sets
|
|
282
|
-
validateState, // strict persisted-state validation (1.0.0-1.
|
|
405
|
+
validateState, // strict persisted-state validation (1.0.0-1.6.0 files migrate)
|
|
283
406
|
IllegalTransitionError, EnqueueError, CorruptStateError, LockTimeoutError,
|
|
284
407
|
SchedNotFoundError,
|
|
408
|
+
runBatchTick, // #523: one batch reconcile+refill pass; called by tick() after
|
|
409
|
+
// the issue pass — loads/saves state itself, holds no lock
|
|
410
|
+
// across the call
|
|
411
|
+
type BatchDispatchDeps, // inject store/journal/groundTruth/spawnDeps/exec/runSuite/
|
|
412
|
+
// runCapability(optional)/fsExists(optional)
|
|
413
|
+
type BatchTickResult, // spawned/completed/parked/mergeAccepted/failed (batch:<id> ids)
|
|
414
|
+
// + blocked (issue numbers, dissolve-requeued)
|
|
415
|
+
type CapOutcome, // ok | task-failed | automation-broken | capability-unavailable
|
|
416
|
+
buildMemberPrompt, buildBatchTailPrompt, buildBatchReportPrompt, // #523 prompt builders
|
|
417
|
+
DEFAULT_MEMBER_PROMPT_TEMPLATE, DEFAULT_BATCH_TAIL_PROMPT_TEMPLATE,
|
|
418
|
+
DEFAULT_BATCH_REPORT_PROMPT_TEMPLATE,
|
|
419
|
+
isMemberComplete, isMemberBlocked, // member milestone predicates (mode=slot gated)
|
|
420
|
+
isBatchTailParked, // batch-ship awaiting-merge + pr= — the batch park signal
|
|
421
|
+
isBatchPhaseDone, // <phase> done on the anchor (batch-review/batch-report)
|
|
422
|
+
batchOfUnit, // batch:<id> → <id>; null for issue units or malformed ids
|
|
285
423
|
} from '@ai-dossier/sched';
|
|
286
424
|
```
|
|
287
425
|
|
|
@@ -297,7 +435,8 @@ fake agents and stub ground truth; no LLM calls anywhere.
|
|
|
297
435
|
~/.dossier/sched/<project>/
|
|
298
436
|
├── state.json # hot operational truth — atomic tmp+fsync+rename writes;
|
|
299
437
|
├── config.json # durable intent: max_slots, stall_timeout_ms, reconcile_interval_ms,
|
|
300
|
-
│ # pr_poll_interval_ms, dispatch (incl. report_prompt,
|
|
438
|
+
│ # pr_poll_interval_ms, dispatch (incl. report_prompt,
|
|
439
|
+
│ # phase_stall_timeout_ms, fence_takeover_timeout_ms)
|
|
301
440
|
├── events.jsonl # append-only event journal (the operator's flight recorder)
|
|
302
441
|
├── runs/ # per-unit agent output logs (issue-<n>.log)
|
|
303
442
|
└── .sched-lock/ # cross-process directory mutex (pid; stolen from dead holders)
|
|
@@ -312,13 +451,13 @@ fake agents and stub ground truth; no LLM calls anywhere.
|
|
|
312
451
|
never a silent queue reset. `state.json` is deletable and rebuildable from GitHub,
|
|
313
452
|
which remains the system of record.
|
|
314
453
|
- **Schema**: state/config files from #460 (schema 1.0.0), #464 (1.1.0), #468 (1.2.0),
|
|
315
|
-
#472 (1.3.0)
|
|
454
|
+
#472 (1.3.0), #500 (1.4.0) and #505 (1.5.0) load and migrate to 1.6.0 automatically (slot
|
|
316
455
|
`branch`/`last_head`/`pid_start`, slot `role` (inferred from the unit's queue entry,
|
|
317
456
|
with the persisted `phase` as a fallback — #500), entry `pr`/`cleanup`/
|
|
318
457
|
`failure_evidence`, batch `anchor`/`branch`/`run_id`/`eviction_groups`/`evictions`/
|
|
319
458
|
`fix_attempts`/`rebase_attempts`, state-level `last_pr_poll_at` backfill to null, and
|
|
320
459
|
state-level `consecutive_suspect_dispatches`/`last_suspect_dispatch_unit` backfill to
|
|
321
|
-
`0`/`null` — #505).
|
|
460
|
+
`0`/`null` — #505, and slot `gen`/`fenced_at` backfill to `0`/`null` — #504).
|
|
322
461
|
- **`max_slots`** bounds live units (`assigned | running | recovering`); dependency
|
|
323
462
|
edges gate readiness — an issue with an unmerged dependency, and a batch behind an
|
|
324
463
|
unmerged batch, are never runnable.
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Batch dispatch (#523, RFC-0001 §C.4/D.2/D.3): the missing driver that
|
|
3
|
+
* executes `batch:<id>` units. #498 landed the batch failure-recovery library
|
|
4
|
+
* (attribution/bisect/eviction/dissolve, `recovery.ts`) and the batch state
|
|
5
|
+
* machine (`state.ts`); readiness/placement already treat a `ready` batch as
|
|
6
|
+
* a runnable unit (`readiness.ts`, `scheduler.ts`). Nothing dispatched one
|
|
7
|
+
* until now.
|
|
8
|
+
*
|
|
9
|
+
* Shape, mirroring `engine.ts`'s per-issue dispatch: claim a slot → spawn an
|
|
10
|
+
* agent → poll ground truth → verify → transition. Generalized to `BatchEntry`
|
|
11
|
+
* at batch-phase granularity instead of per-issue-phase granularity:
|
|
12
|
+
*
|
|
13
|
+
* ```
|
|
14
|
+
* ready → executing(member i/N) ⟲ → validating → reviewing → shipping
|
|
15
|
+
* → awaiting-merge → merged → deployed → reported → done
|
|
16
|
+
* failure rails (RFC F.2/F.8/F.9):
|
|
17
|
+
* executing → dissolving (a member self-reports blocked, RFC F.1)
|
|
18
|
+
* validating → attributing → (fixing | evicting) → validating
|
|
19
|
+
* → dissolving
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* NO batch claim — not the first (`ready → executing`) nor any continuation
|
|
23
|
+
* (a later member, the tail agent, the report agent, the fix agent) — ever
|
|
24
|
+
* goes through `computeAssignments`/`runnableUnits`. Every one is a bespoke
|
|
25
|
+
* free-capacity-gated assignment, the same shape `engine.ts`'s
|
|
26
|
+
* `dispatchReportAgents` already uses (`runnableUnits` only ever offers a
|
|
27
|
+
* `status === 'ready'` batch, i.e. the moment BEFORE any claim). Between
|
|
28
|
+
* steps — a suite run, a PR merge wait — the slot is released to `idle` and
|
|
29
|
+
* holds no capacity (AC5): only a live member/tail/report/fix agent holds a
|
|
30
|
+
* slot.
|
|
31
|
+
*
|
|
32
|
+
* The aggregate suite itself is deterministic engine work, not an LLM step —
|
|
33
|
+
* it runs with no slot claimed at all, matching AC5's "member or batch-LLM-step"
|
|
34
|
+
* wording precisely.
|
|
35
|
+
*
|
|
36
|
+
* Two distinct failure rails, deliberately different:
|
|
37
|
+
* - A member's OWN agent reports itself blocked (its own gate never went
|
|
38
|
+
* green) — evicted directly, no attribution needed: the offender is already
|
|
39
|
+
* known, and either it has no commits yet (blocked before implementing) or
|
|
40
|
+
* its commits are exactly what gets reverted.
|
|
41
|
+
* - The AGGREGATE suite (run by the engine after every member individually
|
|
42
|
+
* went green) comes back red — an integration-level conflict no member's own
|
|
43
|
+
* gate caught. THIS is what `recovery.ts`'s attribution/fix/evict pipeline
|
|
44
|
+
* exists for (RFC F.2).
|
|
45
|
+
*
|
|
46
|
+
* Scope decisions recorded here, not silently cut: no `git bisect` stage for
|
|
47
|
+
* an ambiguous aggregate failure (bisect needs a per-project "run only these
|
|
48
|
+
* tests" command this module has no generic way to construct) — an
|
|
49
|
+
* unattributable red aggregate suite dissolves the batch rather than
|
|
50
|
+
* bisecting, which `attributing → dissolving` already models. No worktree-pool
|
|
51
|
+
* integration for batch-setup — cold `git worktree add` only, mirroring
|
|
52
|
+
* `teardown.ts`'s cold path. No per-phase stall/escalation ladder for batch
|
|
53
|
+
* sub-agents — a dead-without-verification agent is treated as blocked and
|
|
54
|
+
* evicted/reported rather than redispatched stronger. Both are documented
|
|
55
|
+
* follow-ups, not gaps discovered later.
|
|
56
|
+
*/
|
|
57
|
+
import { type ResolvedDispatch, type SpawnDeps } from './dispatch';
|
|
58
|
+
import { type GroundTruth } from './groundtruth';
|
|
59
|
+
import { type Journal } from './journal';
|
|
60
|
+
import type { SchedStore } from './persist';
|
|
61
|
+
import type { ExecFn } from './project';
|
|
62
|
+
import { type SuiteResult } from './recovery';
|
|
63
|
+
import { type FsExists } from './teardown';
|
|
64
|
+
import type { SchedConfig } from './types';
|
|
65
|
+
/**
|
|
66
|
+
* The four `ai-dossier cap run` outcomes (docs/reference/capabilities.md):
|
|
67
|
+
* `ok` = the capability ran and passed; `task-failed` = it ran and the TASK
|
|
68
|
+
* itself failed (trust the result); `automation-broken` = do not trust the
|
|
69
|
+
* machinery (missing tool, timeout, bad manifest); `capability-unavailable` =
|
|
70
|
+
* no manifest / no such id / `lifecycle: shadow` — no fast path here.
|
|
71
|
+
*/
|
|
72
|
+
export type CapOutcome = 'ok' | 'task-failed' | 'automation-broken' | 'capability-unavailable';
|
|
73
|
+
/** Everything batch dispatch needs from the outside world. */
|
|
74
|
+
export interface BatchDispatchDeps {
|
|
75
|
+
store: SchedStore;
|
|
76
|
+
journal: Journal;
|
|
77
|
+
groundTruth: GroundTruth;
|
|
78
|
+
spawnDeps: SpawnDeps;
|
|
79
|
+
now: () => Date;
|
|
80
|
+
/** Repo root — cwd for `git`/`ai-dossier` calls that are not batch-worktree-scoped. */
|
|
81
|
+
repoDir: string;
|
|
82
|
+
/** Exec for batch git/milestone-CLI operations (never throws — the `ExecFn` contract). */
|
|
83
|
+
exec: ExecFn;
|
|
84
|
+
/** Runs the aggregate suite inside a batch worktree; batches never leave `validating` without one. */
|
|
85
|
+
runSuite: (worktree: string) => SuiteResult;
|
|
86
|
+
/**
|
|
87
|
+
* Runs one `ai-dossier cap run <capabilityId>` in a batch worktree for the
|
|
88
|
+
* per-member incremental gate (#523 AC2 — "typecheck + focused tests via
|
|
89
|
+
* `cap run test.focused` when available"). Optional and degrade-not-crash,
|
|
90
|
+
* like `batchExec`/`runBatchSuite`: without it, or on `automation-broken`/
|
|
91
|
+
* `capability-unavailable`, the gate is skipped — the member's own
|
|
92
|
+
* `slot-cycle` run already attempted this fast path (with its own reasoning
|
|
93
|
+
* fallback) before ever posting `review done`, so a repo with no manifest
|
|
94
|
+
* loses nothing but the engine's independent re-check.
|
|
95
|
+
*/
|
|
96
|
+
runCapability?: (worktree: string, capabilityId: string) => CapOutcome;
|
|
97
|
+
fsExists?: FsExists;
|
|
98
|
+
}
|
|
99
|
+
/** What one `runBatchTick` call did, merged into `engine.ts`'s `TickResult` by the caller. */
|
|
100
|
+
export interface BatchTickResult {
|
|
101
|
+
spawned: string[];
|
|
102
|
+
completed: string[];
|
|
103
|
+
parked: string[];
|
|
104
|
+
mergeAccepted: string[];
|
|
105
|
+
failed: string[];
|
|
106
|
+
/** Issue numbers requeued full-cycle by a dissolve — matches `TickResult.blocked`'s shape. */
|
|
107
|
+
blocked: number[];
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* One batch reconcile+refill pass. Called from `engine.ts`'s `tick()` after
|
|
111
|
+
* the issue-level pass — batches never compete with issues for a slot within
|
|
112
|
+
* the same tick because this pass runs strictly after `dispatchAssignments`
|
|
113
|
+
* already filled every slot it could (see the module doc: batch claims never
|
|
114
|
+
* go through `computeAssignments`/`runnableUnits` at all). Loads and saves
|
|
115
|
+
* state itself via `deps.store.withLock` — the caller holds no lock across
|
|
116
|
+
* this call. `deps.exec` and `deps.runSuite` are mandatory; `deps.
|
|
117
|
+
* runCapability` is independently optional (AC2's incremental gate is itself
|
|
118
|
+
* a "when available" fast path).
|
|
119
|
+
*/
|
|
120
|
+
export declare function runBatchTick(deps: BatchDispatchDeps, config: SchedConfig, dispatch: ResolvedDispatch): BatchTickResult;
|
|
121
|
+
//# sourceMappingURL=batch-dispatch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"batch-dispatch.d.ts","sourceRoot":"","sources":["../src/batch-dispatch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuDG;AAWH,OAAO,EAKL,KAAK,gBAAgB,EACrB,KAAK,SAAS,EAEf,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,KAAK,WAAW,EAOjB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,KAAK,OAAO,EAAa,MAAM,WAAW,CAAC;AACpD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAC5C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EASL,KAAK,WAAW,EACjB,MAAM,YAAY,CAAC;AAWpB,OAAO,EAAE,KAAK,QAAQ,EAA+B,MAAM,YAAY,CAAC;AACxE,OAAO,KAAK,EAKV,WAAW,EAIZ,MAAM,SAAS,CAAC;AAEjB;;;;;;GAMG;AACH,MAAM,MAAM,UAAU,GAAG,IAAI,GAAG,aAAa,GAAG,mBAAmB,GAAG,wBAAwB,CAAC;AAE/F,8DAA8D;AAC9D,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,UAAU,CAAC;IAClB,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,EAAE,WAAW,CAAC;IACzB,SAAS,EAAE,SAAS,CAAC;IACrB,GAAG,EAAE,MAAM,IAAI,CAAC;IAChB,uFAAuF;IACvF,OAAO,EAAE,MAAM,CAAC;IAChB,0FAA0F;IAC1F,IAAI,EAAE,MAAM,CAAC;IACb,sGAAsG;IACtG,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,WAAW,CAAC;IAC5C;;;;;;;;;OASG;IACH,aAAa,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,KAAK,UAAU,CAAC;IACvE,QAAQ,CAAC,EAAE,QAAQ,CAAC;CACrB;AAED,8FAA8F;AAC9F,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,8FAA8F;IAC9F,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAyuCD;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,CAC1B,IAAI,EAAE,iBAAiB,EACvB,MAAM,EAAE,WAAW,EACnB,QAAQ,EAAE,gBAAgB,GACzB,eAAe,CAsEjB"}
|