@ai-dossier/cli 0.20.2 → 0.21.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 +123 -18
- package/dist/commands/runstate.d.ts +11 -1
- package/dist/commands/runstate.d.ts.map +1 -1
- package/dist/commands/runstate.js +346 -7
- package/dist/commands/runstate.js.map +1 -1
- package/dist/commands/sched.d.ts.map +1 -1
- package/dist/commands/sched.js +24 -1
- package/dist/commands/sched.js.map +1 -1
- package/dist/runstate.d.ts +114 -1
- package/dist/runstate.d.ts.map +1 -1
- package/dist/runstate.js +210 -8
- package/dist/runstate.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -360,6 +360,11 @@ ai-dossier runstate last --issue 440 --json
|
|
|
360
360
|
|
|
361
361
|
# Ask where a run should resume from
|
|
362
362
|
ai-dossier runstate verify --issue 440
|
|
363
|
+
|
|
364
|
+
# Supersede a stalled run before redispatching it, then work as the takeover
|
|
365
|
+
ai-dossier runstate fence --issue 440 --run r-440-ab56 \
|
|
366
|
+
--phase implement --takeover slot-2-r1 # -> gen=1
|
|
367
|
+
ai-dossier runstate check --issue 440 --run r-440-ab56 --gen 1 # exit 3 = superseded
|
|
363
368
|
```
|
|
364
369
|
|
|
365
370
|
### Subcommands
|
|
@@ -369,19 +374,23 @@ ai-dossier runstate verify --issue 440
|
|
|
369
374
|
| `post` | Validates and posts one milestone comment via `gh issue comment` | yes |
|
|
370
375
|
| `last` | Prints the most recent milestone on the issue, parsed | no |
|
|
371
376
|
| `verify` | Runs the gate's resume verification and prints `resume_from` + `resume_context` | no |
|
|
377
|
+
| `fence` | Supersedes a run: posts the takeover record and prints the generation it installed (#504) | yes |
|
|
378
|
+
| `check` | Reports whether a run generation has been superseded; exits `3` when it has (#504) | only with `--comment` |
|
|
372
379
|
| `mint` | Prints a fresh run id (`r-<issue>-<hex>`) | no |
|
|
373
380
|
| `stats` | Reports per-phase durations derived from the trail's `at=` stamps; aggregates across a `--issues` selection | no |
|
|
374
381
|
|
|
375
|
-
`last`, `verify`, and `stats` are strictly read-only — they only run `gh issue view`,
|
|
382
|
+
`last`, `verify`, and `stats` are strictly read-only (as is `check`, unless `--comment` is passed) — they only run `gh issue view`,
|
|
376
383
|
`gh pr view`, `git ls-remote`/`rev-parse`, and `stat`, so they work fine without push
|
|
377
384
|
access to the repository.
|
|
378
385
|
|
|
379
386
|
`--issue <n>` (a positive integer — the number only, not a URL or a `#`-prefixed string)
|
|
380
|
-
is required by `post`, `last`, `verify`, and `mint`; `stats` takes
|
|
381
|
-
`--issues <list>`, exactly one of the two. `post`, `last`,
|
|
382
|
-
additionally take `--repo <owner/name>` (a bare
|
|
383
|
-
repository `gh` resolves for the current directory) and
|
|
384
|
-
|
|
387
|
+
is required by `post`, `last`, `verify`, `fence`, `check`, and `mint`; `stats` takes
|
|
388
|
+
either `--issue <n>` or `--issues <list>`, exactly one of the two. `post`, `last`,
|
|
389
|
+
`verify`, `fence`, `check`, and `stats` additionally take `--repo <owner/name>` (a bare
|
|
390
|
+
slug, not a URL; defaults to the repository `gh` resolves for the current directory) and
|
|
391
|
+
`--json`; `fence` also takes `--dry-run`. `fence` requires `--run`, `--phase` and
|
|
392
|
+
`--takeover`; `check` requires `--run` and takes `--gen` and `--comment`. `mint` takes
|
|
393
|
+
`--issue` and nothing else.
|
|
385
394
|
|
|
386
395
|
`last` prints the milestone's own `key=value` lines, so the output is already in the
|
|
387
396
|
shape a shell or a dossier reads:
|
|
@@ -408,10 +417,11 @@ prints `No runstate milestones on issue #440.` (or `null` under `--json`) and ex
|
|
|
408
417
|
|---|---|
|
|
409
418
|
| `--issue <n>` | Issue to comment on (required) |
|
|
410
419
|
| `--phase <p>` | `classify`, `gate`, `setup`, `plan`, `implement`, `review`, `ship`, `report`, or a batch phase — `batch-setup`, `batch-validate`, `batch-review`, `batch-ship`, `batch-report` (required) |
|
|
411
|
-
| `--status <s>` | `done`, `partial`, `blocked`, `awaiting-merge` (required) |
|
|
420
|
+
| `--status <s>` | `done`, `partial`, `blocked`, `awaiting-merge` (required). `superseded` is rejected here — a fence is written by [`runstate fence`](#run-fencing-504) |
|
|
412
421
|
| `--run <id>` | Run id (`r-<issue>-<hex>`) — mint one with `runstate mint`; full-cycle runs mint it at the gate phase (required) |
|
|
413
422
|
| `--kv <key=value...>` | Phase-specific key, repeatable (and variadic: `--kv a=1 b=2` works too) |
|
|
414
423
|
| `--next <phase>` | Override the computed `next=` line — a phase name or `done` |
|
|
424
|
+
| `--gen <n>` | Run generation this agent owns (default 0). A post below the trail's fenced generation is refused — see [Run fencing](#run-fencing-504) |
|
|
415
425
|
| `--repo <owner/name>` | Target repository (defaults to the current one) |
|
|
416
426
|
| `--dry-run` | Print the comment body instead of posting it |
|
|
417
427
|
| `--json` | Machine-readable output |
|
|
@@ -496,6 +506,72 @@ batch-report → done.
|
|
|
496
506
|
> but its `verify` derives a bogus `resume_from` from a *blocked* classify/batch
|
|
497
507
|
> milestone — don't point an old CLI at batch anchor issues.
|
|
498
508
|
|
|
509
|
+
### Run fencing (#504)
|
|
510
|
+
|
|
511
|
+
The stall-recovery ladder redispatches the *same* run, so a takeover inherits the run id
|
|
512
|
+
and its trail. Nothing used to tell the run it replaced that it had been replaced — and a
|
|
513
|
+
scheduler can only kill a process it can see. In the #472 race the original agent was
|
|
514
|
+
alive the whole time (throttled, cwd outside the worktree); both runs implemented the
|
|
515
|
+
issue in full and both kept posting to one trail.
|
|
516
|
+
|
|
517
|
+
A **generation** fences the trail. `runstate fence` posts a `status=superseded` milestone —
|
|
518
|
+
valid on every phase, carrying `gen=<n>` and `takeover=<label>` — and prints the generation
|
|
519
|
+
it installed:
|
|
520
|
+
|
|
521
|
+
```bash
|
|
522
|
+
$ ai-dossier runstate fence --issue 440 --run r-440-ab56 --phase implement --takeover slot-2-r1
|
|
523
|
+
✅ fenced r-440-ab56 gen=1 takeover=slot-2-r1 → https://github.com/o/r/issues/440#issuecomment-9
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
From then on `post` refuses anything from a lower generation, **before** writing to the
|
|
527
|
+
issue:
|
|
528
|
+
|
|
529
|
+
```bash
|
|
530
|
+
$ ai-dossier runstate post --issue 440 --phase implement --status done --run r-440-ab56 …
|
|
531
|
+
❌ Run r-440-ab56 was SUPERSEDED — refusing to post this implement/done milestone from generation 0.
|
|
532
|
+
It was fenced at generation 1 (takeover 'slot-2-r1', fenced at implement on 2026-08-30T11:00:00Z).
|
|
533
|
+
…
|
|
534
|
+
Fix: stop working on this issue and exit. … If you ARE the takeover, pass --gen 1.
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
Generation 0 is the default, which is what makes this work on a fleet that has not been
|
|
538
|
+
upgraded: an agent that knows nothing about fencing posts at 0 and is locked out the
|
|
539
|
+
moment generation 1 exists. `>` not `>=` — the takeover posting at exactly its own
|
|
540
|
+
generation is live, and a recovery-of-recovery at generation 2 fences the first takeover
|
|
541
|
+
in turn.
|
|
542
|
+
|
|
543
|
+
`runstate check` is the same question without a write, for a workflow to run before
|
|
544
|
+
implement, review, and ship:
|
|
545
|
+
|
|
546
|
+
```bash
|
|
547
|
+
$ ai-dossier runstate check --issue 440 --run r-440-ab56 --gen 1
|
|
548
|
+
fenced=false
|
|
549
|
+
gen=1
|
|
550
|
+
current_gen=1
|
|
551
|
+
```
|
|
552
|
+
|
|
553
|
+
It exits **3** when the caller has been superseded (distinct from `1`, so a script can
|
|
554
|
+
tell "you were replaced" from "you called this wrong"), and a refused `post` uses the same
|
|
555
|
+
code — one exit code means one thing everywhere. `--comment` additionally leaves one short,
|
|
556
|
+
deduplicated abort comment on the issue, so a run that stops mid-cycle is explained rather
|
|
557
|
+
than merely absent. `verify` reports the owning generation as `generation=`.
|
|
558
|
+
|
|
559
|
+
Both commands harden the read side, because a milestone is an issue comment and anyone who
|
|
560
|
+
can comment can author one: only comments from an account with **write access** count as a
|
|
561
|
+
fence (a stranger must not be able to halt a run), a `takeover=` label that does not match
|
|
562
|
+
the write-path grammar is dropped rather than echoed into an agent's instruction stream,
|
|
563
|
+
and a generation past `1000` is treated as forged rather than honoured.
|
|
564
|
+
|
|
565
|
+
> **Compatibility.** Posting and reading fences requires CLI ≥ 0.21.0. An older CLI rejects
|
|
566
|
+
> `--status superseded` outright on `post`, has no concept of generations (so it never
|
|
567
|
+
> refuses a superseded post), and its `verify` reads a fence milestone as ordinary progress
|
|
568
|
+
> — with no `head=` to confirm, it derives a bogus `resume_from`, typically all the way back
|
|
569
|
+
> to `setup`. Upgrade the fleet before relying on fencing.
|
|
570
|
+
|
|
571
|
+
Both `post` and `check` **fail open** if the trail cannot be read: a milestone is a
|
|
572
|
+
phase's only durable record, and losing one to a transient `gh` outage is a worse failure
|
|
573
|
+
than the race this prevents. The degradation is warned about on stderr, never silent.
|
|
574
|
+
|
|
499
575
|
### mode=slot and batch= on plan/implement/review
|
|
500
576
|
|
|
501
577
|
Slot-cycle members post the ordinary `plan`/`implement`/`review` milestones with two
|
|
@@ -519,11 +595,16 @@ carries the key nowhere are bucketed as `unknown`.
|
|
|
519
595
|
The `Statuses` column is a closed set: a status not listed for a phase is rejected, so
|
|
520
596
|
`report` cannot be `blocked` and only the ship phases (`ship`, `batch-ship`) may be
|
|
521
597
|
`awaiting-merge`. Any phase that *can* report `status=blocked` must also carry
|
|
522
|
-
`reason=<short-slug>` when it does.
|
|
598
|
+
`reason=<short-slug>` when it does. One status sits outside the table: `superseded` is
|
|
599
|
+
valid on **every** phase — a takeover can happen anywhere — and requires `gen=` and
|
|
600
|
+
`takeover=`. It is written by [`runstate fence`](#run-fencing-504), never by hand, and
|
|
601
|
+
`post` refuses it.
|
|
523
602
|
|
|
524
603
|
`next=` is computed for you: the linear order `gate → setup → plan → implement → review →
|
|
525
|
-
ship → report → done`, except that `blocked` ends the run (`next=done`) and the
|
|
526
|
-
non-terminal statuses stay in their own phase — `
|
|
604
|
+
ship → report → done`, except that `blocked` ends the run (`next=done`) and the three
|
|
605
|
+
non-terminal statuses stay in their own phase — `superseded` points back at its own phase,
|
|
606
|
+
so a takeover that dies leaves a resumable trail rather than a terminal one;
|
|
607
|
+
`ship`/`awaiting-merge` is followed by a
|
|
527
608
|
second `ship` milestone, and a `partial` review still has agents to run. Use `--next` to
|
|
528
609
|
override.
|
|
529
610
|
|
|
@@ -556,6 +637,8 @@ Every `--kv` pair is checked before anything is posted:
|
|
|
556
637
|
| `test_scope` | one of `focused`, `broad`, `unknown` |
|
|
557
638
|
| `est_files` | non-negative integer, e.g. `3` |
|
|
558
639
|
| `est_diff` | non-negative integer (lines), e.g. `400` |
|
|
640
|
+
| `gen` | non-negative integer run generation, e.g. `1` — see [Run fencing](#run-fencing-504) |
|
|
641
|
+
| `takeover` | a single flag-safe token naming what took the run over — letters/digits then `. _ : @ / -`, e.g. `slot-2-r1` or `r-504-fc02` |
|
|
559
642
|
| `confidence` | decimal `0`–`1`, e.g. `0.85` (RFC-0001 E.2 compares it to 0.6) |
|
|
560
643
|
| `areas` | comma-separated lowercase slugs, e.g. `cli,docs` |
|
|
561
644
|
| `deps` | `none`, or comma-separated issue numbers, e.g. `474,480` |
|
|
@@ -568,8 +651,11 @@ Every `--kv` pair is checked before anything is posted:
|
|
|
568
651
|
### When `gh` or `git` fails
|
|
569
652
|
|
|
570
653
|
Every subcommand exits **1** on failure with the cause *and its fix* on stderr, so a
|
|
571
|
-
calling dossier can branch on the exit code and an agent knows what to do next. The
|
|
572
|
-
|
|
654
|
+
calling dossier can branch on the exit code and an agent knows what to do next. The one
|
|
655
|
+
exception is supersession: `check` — and a `post` refused as superseded — exit **3**, a
|
|
656
|
+
distinct code so a script can tell "you were replaced" from "you called this wrong" (see
|
|
657
|
+
[Run fencing](#run-fencing-504)). The three causes that look identical from the outside
|
|
658
|
+
are reported as three different things:
|
|
573
659
|
|
|
574
660
|
```
|
|
575
661
|
$ ai-dossier runstate last --issue 440
|
|
@@ -611,6 +697,7 @@ worktree (#499):
|
|
|
611
697
|
$ ai-dossier runstate verify --issue 440
|
|
612
698
|
resume_from=implement
|
|
613
699
|
run_id=r-440-ab56
|
|
700
|
+
generation=0
|
|
614
701
|
verified=branch,head
|
|
615
702
|
local_worktree=absent
|
|
616
703
|
resume_context={"branch":"feature/440-runstate","worktree":"/repo/worktrees/feature-440-runstate",...}
|
|
@@ -625,6 +712,13 @@ resume_context={"branch":"feature/440-runstate","worktree":"/repo/worktrees/feat
|
|
|
625
712
|
| `ship-teardown` | The PR is already merged; re-enter `ship` at post-merge cleanup |
|
|
626
713
|
| `done` | The `report` milestone is posted and the issue is closed (`note=already complete`) |
|
|
627
714
|
|
|
715
|
+
A `superseded` milestone resumes at its **own** phase, with
|
|
716
|
+
`note=fenced by '<takeover>' at generation N …`: the takeover was announced but has posted
|
|
717
|
+
nothing yet, and it may never post, so the state is resumable rather than terminal. The
|
|
718
|
+
note tells a resumer that is *not* that takeover to install generation N+1 with
|
|
719
|
+
`runstate fence` before posting — adopting N would put a second writer alongside a takeover
|
|
720
|
+
that may be alive and working.
|
|
721
|
+
|
|
628
722
|
A failed check sends the resume *backwards*, never forwards: if the branch is gone from
|
|
629
723
|
the remote, a `plan`/`implement`/`review` milestone still yields `resume_from=setup`. If
|
|
630
724
|
the branch verifies but the milestone's own `head=` cannot be confirmed on
|
|
@@ -820,7 +914,14 @@ against ground truth.
|
|
|
820
914
|
output journaled to `runs/<unit>.log`. On every ~60s tick it reconciles: an agent that
|
|
821
915
|
exited is **not** complete until `ai-dossier runstate last` / `gh` ground truth confirms
|
|
822
916
|
it (unverified exits and stalls are redispatched one tier stronger — mechanical → mid →
|
|
823
|
-
strong, cap 2 — then the unit fails and its transitive dependents are blocked)
|
|
917
|
+
strong, cap 2 — then the unit fails and its transitive dependents are blocked). Since
|
|
918
|
+
#504 that redispatch is **fenced**: before the replacement spawns, the engine posts a
|
|
919
|
+
`status=superseded` milestone (`runstate fence`) so the agent it replaced — which may
|
|
920
|
+
still be alive and unreachable — is refused by `runstate post`, and the replacement is
|
|
921
|
+
told its generation in its prompt. A fence that cannot be written journals
|
|
922
|
+
`fence-failed` with its cause and the redispatch proceeds unfenced, so an unprotected
|
|
923
|
+
redispatch is degraded but never silent; `sched status` shows each slot's `gen` and
|
|
924
|
+
`fenced` columns.
|
|
824
925
|
externally-advanced state and orphaned pids after a restart are detected; a freed slot
|
|
825
926
|
is refilled in the same tick. Since #468 the default dispatch prompt is
|
|
826
927
|
detached-ship (the agent parks its PR on `auto-merge` and stops): a parked
|
|
@@ -870,14 +971,18 @@ against ground truth.
|
|
|
870
971
|
State is written atomically (tmp + fsync + rename), so a process killed between writes
|
|
871
972
|
always leaves the previous complete state, and a scheduler restart resumes identically
|
|
872
973
|
from `state.json` (pre-#464/#468/#472/#500/#505 state files — schema
|
|
873
|
-
1.0.0/1.1.0/1.2.0/1.3.0/1.4.0 — migrate to 1.
|
|
974
|
+
1.0.0/1.1.0/1.2.0/1.3.0/1.4.0/1.5.0 — migrate to 1.6.0 on load). A corrupt state file is a loud
|
|
874
975
|
error naming the file — never a silent queue reset. Concurrency is serialized by a
|
|
875
976
|
`.sched-lock` directory mutex (stolen from dead holders). `config.json` holds
|
|
876
977
|
`max_slots` (default 3, bounds concurrently-live units), `stall_timeout_ms` (default
|
|
877
|
-
1 800 000
|
|
878
|
-
|
|
879
|
-
(default
|
|
880
|
-
|
|
978
|
+
1 800 000 — but the `implement` phase defaults to 5 400 000, overridable per phase via
|
|
979
|
+
`dispatch.phase_stall_timeout_ms: {"<phase>": <ms>}`, #495), `reconcile_interval_ms`
|
|
980
|
+
(default 60 000), and the optional `dispatch` section (including `report_prompt` for
|
|
981
|
+
the #468 report agent and `fence_takeover_timeout_ms`, default 900 000 — the short stall
|
|
982
|
+
allowance for a freshly-fenced takeover that has posted nothing, #504; `dispatch.prompt`
|
|
983
|
+
substitutes `{issue}` and `{gen}`); `pr_poll_interval_ms` (default 150 000) sets the
|
|
984
|
+
parked-PR poll cadence; an issue with an unmerged dependency — or a batch behind an unmerged batch —
|
|
985
|
+
is never runnable.
|
|
881
986
|
|
|
882
987
|
Library consumers: see [`@ai-dossier/sched`](../packages/sched/README.md).
|
|
883
988
|
|
|
@@ -7,6 +7,16 @@
|
|
|
7
7
|
* hoped for.
|
|
8
8
|
*/
|
|
9
9
|
import type { Command } from 'commander';
|
|
10
|
-
/**
|
|
10
|
+
/**
|
|
11
|
+
* Exit code the fence guards use for "this run has been superseded".
|
|
12
|
+
*
|
|
13
|
+
* Distinct from `fail`'s 1 on purpose: a fenced run is a correct, expected answer that a
|
|
14
|
+
* caller should act on by aborting quietly, not a usage error to be retried. A shell
|
|
15
|
+
* `if ! ai-dossier runstate check …` can tell the two apart — as can a supervisor
|
|
16
|
+
* distinguishing "you were replaced" from "you called this wrong". Both `check` and a
|
|
17
|
+
* refused `post` use it, so one exit code means one thing everywhere.
|
|
18
|
+
*/
|
|
19
|
+
export declare const FENCED_EXIT_CODE = 3;
|
|
20
|
+
/** Registers the `runstate` command tree (post, last, verify, fence, check, mint, stats). */
|
|
11
21
|
export declare function registerRunstateCommand(program: Command): void;
|
|
12
22
|
//# sourceMappingURL=runstate.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runstate.d.ts","sourceRoot":"","sources":["../../src/commands/runstate.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"runstate.d.ts","sourceRoot":"","sources":["../../src/commands/runstate.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAoZzC;;;;;;;;GAQG;AACH,eAAO,MAAM,gBAAgB,IAAI,CAAC;AAwzBlC,6FAA6F;AAC7F,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAY9D"}
|