@wildorder/nightshift 0.16.0 → 0.18.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 +289 -1
- package/dist/agent-runner.d.ts +95 -5
- package/dist/agent-runner.d.ts.map +1 -1
- package/dist/agent-runner.js +442 -32
- package/dist/agent-runner.js.map +1 -1
- package/dist/agent-session.d.ts +123 -0
- package/dist/agent-session.d.ts.map +1 -0
- package/dist/agent-session.js +114 -0
- package/dist/agent-session.js.map +1 -0
- package/dist/atomic-write.d.ts +18 -0
- package/dist/atomic-write.d.ts.map +1 -0
- package/dist/atomic-write.js +74 -0
- package/dist/atomic-write.js.map +1 -0
- package/dist/author.d.ts +118 -4
- package/dist/author.d.ts.map +1 -1
- package/dist/author.js +650 -163
- package/dist/author.js.map +1 -1
- package/dist/causal-analysis.d.ts +216 -0
- package/dist/causal-analysis.d.ts.map +1 -0
- package/dist/causal-analysis.js +786 -0
- package/dist/causal-analysis.js.map +1 -0
- package/dist/cli.js +22 -2
- package/dist/cli.js.map +1 -1
- package/dist/concurrency.d.ts +24 -0
- package/dist/concurrency.d.ts.map +1 -0
- package/dist/concurrency.js +28 -0
- package/dist/concurrency.js.map +1 -0
- package/dist/decider-review.d.ts +13 -1
- package/dist/decider-review.d.ts.map +1 -1
- package/dist/decider-review.js +21 -7
- package/dist/decider-review.js.map +1 -1
- package/dist/decision-ledger.d.ts +7 -0
- package/dist/decision-ledger.d.ts.map +1 -1
- package/dist/decision-ledger.js +25 -10
- package/dist/decision-ledger.js.map +1 -1
- package/dist/detect-package-manager.d.ts +10 -0
- package/dist/detect-package-manager.d.ts.map +1 -1
- package/dist/detect-package-manager.js +12 -0
- package/dist/detect-package-manager.js.map +1 -1
- package/dist/index.d.ts +8 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -1
- package/dist/index.js.map +1 -1
- package/dist/manifest.d.ts +24 -5
- package/dist/manifest.d.ts.map +1 -1
- package/dist/manifest.js +254 -108
- package/dist/manifest.js.map +1 -1
- package/dist/permits.d.ts +18 -0
- package/dist/permits.d.ts.map +1 -1
- package/dist/permits.js +155 -0
- package/dist/permits.js.map +1 -1
- package/dist/preflight.d.ts +3 -0
- package/dist/preflight.d.ts.map +1 -1
- package/dist/preflight.js +73 -57
- package/dist/preflight.js.map +1 -1
- package/dist/prompt-telemetry.d.ts +64 -0
- package/dist/prompt-telemetry.d.ts.map +1 -0
- package/dist/prompt-telemetry.js +112 -0
- package/dist/prompt-telemetry.js.map +1 -0
- package/dist/provider-telemetry.d.ts +106 -0
- package/dist/provider-telemetry.d.ts.map +1 -0
- package/dist/provider-telemetry.js +423 -0
- package/dist/provider-telemetry.js.map +1 -0
- package/dist/review-pass.d.ts +52 -10
- package/dist/review-pass.d.ts.map +1 -1
- package/dist/review-pass.js +114 -30
- package/dist/review-pass.js.map +1 -1
- package/dist/run-analytics-report.d.ts +286 -0
- package/dist/run-analytics-report.d.ts.map +1 -0
- package/dist/run-analytics-report.js +1242 -0
- package/dist/run-analytics-report.js.map +1 -0
- package/dist/run-analytics.d.ts +844 -0
- package/dist/run-analytics.d.ts.map +1 -0
- package/dist/run-analytics.js +777 -0
- package/dist/run-analytics.js.map +1 -0
- package/dist/run-log.d.ts +16 -0
- package/dist/run-log.d.ts.map +1 -0
- package/dist/run-log.js +32 -0
- package/dist/run-log.js.map +1 -0
- package/dist/run-program.d.ts +268 -4
- package/dist/run-program.d.ts.map +1 -1
- package/dist/run-program.js +2508 -762
- package/dist/run-program.js.map +1 -1
- package/dist/scheduler.d.ts +75 -0
- package/dist/scheduler.d.ts.map +1 -0
- package/dist/scheduler.js +126 -0
- package/dist/scheduler.js.map +1 -0
- package/dist/verify-cache.d.ts +64 -0
- package/dist/verify-cache.d.ts.map +1 -0
- package/dist/verify-cache.js +56 -0
- package/dist/verify-cache.js.map +1 -0
- package/dist/whole-program-review.d.ts +3 -0
- package/dist/whole-program-review.d.ts.map +1 -1
- package/dist/whole-program-review.js +8 -1
- package/dist/whole-program-review.js.map +1 -1
- package/dist/worktree.d.ts +124 -0
- package/dist/worktree.d.ts.map +1 -0
- package/dist/worktree.js +397 -0
- package/dist/worktree.js.map +1 -0
- package/dist/write-queue.d.ts +25 -0
- package/dist/write-queue.d.ts.map +1 -0
- package/dist/write-queue.js +52 -0
- package/dist/write-queue.js.map +1 -0
- package/package.json +2 -2
- package/skills/plan-program/SKILL.md +37 -5
- package/dist/worktree-guard.d.ts +0 -40
- package/dist/worktree-guard.d.ts.map +0 -1
- package/dist/worktree-guard.js +0 -91
- package/dist/worktree-guard.js.map +0 -1
package/README.md
CHANGED
|
@@ -136,6 +136,20 @@ the first agent spawns and refuses to start if they are red, so the first
|
|
|
136
136
|
failure you debug is the environment's, named as such, not eight
|
|
137
137
|
workstreams' worth of budget spent on it.
|
|
138
138
|
|
|
139
|
+
Within one run, the runner may skip a verify pass when the tree it is about
|
|
140
|
+
to check is byte-identical to one it already proved green earlier in the same
|
|
141
|
+
run — the common case is a fix pass that changed nothing after a decider
|
|
142
|
+
declined every finding. The comparison ignores six files the runner writes
|
|
143
|
+
for itself under `docs/programs/<program-id>-*` (the manifest, decision
|
|
144
|
+
ledger, memory, run report, and analytics snapshot); every other file,
|
|
145
|
+
including another program's own artifacts, is part of the comparison. Two
|
|
146
|
+
things follow for your own `verify` commands: if any of them read those six
|
|
147
|
+
files (a lint over `docs/**/*.json`, a test that snapshots the ledger), a
|
|
148
|
+
green skip could stand in for a check that would have gone red — narrow
|
|
149
|
+
enough that it rarely matters, but worth knowing. And if any of them rewrite
|
|
150
|
+
tracked files (a formatter with `--write`, a codegen step), that pass simply
|
|
151
|
+
never caches — verification still runs in full, every time.
|
|
152
|
+
|
|
139
153
|
**3. Plan a program** — in your agent tool (e.g. Claude Code), run:
|
|
140
154
|
|
|
141
155
|
```text
|
|
@@ -192,11 +206,26 @@ every run.
|
|
|
192
206
|
```text
|
|
193
207
|
/plan-program HUMAN design, scope, decompose (interactive)
|
|
194
208
|
nightshift run ┬ author one clean agent per workstream, by dependency level
|
|
195
|
-
├ review critic→
|
|
209
|
+
├ review critic → triage → one fix, fail-open into the ledger
|
|
196
210
|
├ build implement, verify, commit; decisions journaled
|
|
197
211
|
└ report run report + decision ledger on the draft PR
|
|
198
212
|
```
|
|
199
213
|
|
|
214
|
+
Each critique loop — spec and test alike — is one reviewer read, one decider
|
|
215
|
+
triage, and one fix pass per cycle: no reviewer re-reads the fix. Test
|
|
216
|
+
critique runs one cycle for every workstream size; spec critique runs one
|
|
217
|
+
cycle for S and M workstreams and two for L. Findings the fixer declines, and
|
|
218
|
+
findings it fixes, both reach the decision ledger — the run report says which
|
|
219
|
+
by name rather than implying every raised finding was checked twice.
|
|
220
|
+
|
|
221
|
+
A spec revision resumes the author's own session, and a fix pass resumes the
|
|
222
|
+
implementer's, so the seat that wrote the artifact applies the fix instead of
|
|
223
|
+
rediscovering the codebase from a fresh spawn. Reviewer, decider, and
|
|
224
|
+
recovery seats never resume — that is what keeps no-self-grading intact. When
|
|
225
|
+
no session was captured, or the seat's configuration changed since it wrote
|
|
226
|
+
the artifact, the run falls back to today's fresh spawn, and the run report
|
|
227
|
+
says which happened.
|
|
228
|
+
|
|
200
229
|
Five agent roles, five jobs (`nightshift.config.json`):
|
|
201
230
|
|
|
202
231
|
| Block | Job |
|
|
@@ -222,6 +251,245 @@ Core rules, inherited from hard experience:
|
|
|
222
251
|
- **Every artifact is written for a human first.** Specs are narrative
|
|
223
252
|
documents; machine data lives in the manifest.
|
|
224
253
|
|
|
254
|
+
## Choosing models and effort per seat
|
|
255
|
+
|
|
256
|
+
The five agent roles above are ordinary configuration: `args` in
|
|
257
|
+
`nightshift.config.json` is passed to each spawn untouched (`src/config.ts`),
|
|
258
|
+
and nightshift neither validates nor interprets it. Anything the provider's
|
|
259
|
+
CLI accepts headlessly — a model name, an effort level, a reasoning-effort
|
|
260
|
+
config key — is available to set per seat. There is no `effort` key to look
|
|
261
|
+
for in nightshift itself; the knob lives on the CLI you configured.
|
|
262
|
+
|
|
263
|
+
On claude, effort is `--effort <level>`, with levels `low`, `medium`, `high`,
|
|
264
|
+
`xhigh`, and `max` (verified on claude 2.1.241 — check `claude --help`). On
|
|
265
|
+
codex there is no top-level flag; express it as a `-c` config override, e.g.
|
|
266
|
+
`-c model_reasoning_effort="low"` (verified on codex 0.149.0 — check
|
|
267
|
+
`codex exec --help`). **Use the `-c` form, never `--profile`**: session-resume
|
|
268
|
+
derivation passes `-c`/`--config` through unchanged on a fix pass, but refuses
|
|
269
|
+
`-p`/`--profile` outright and falls back to a fresh spawn — a profile-based
|
|
270
|
+
effort setting would silently cost a full rediscovery on every fix instead of
|
|
271
|
+
resuming the session that wrote the code.
|
|
272
|
+
|
|
273
|
+
Two facts worth stating plainly because they are not guessable:
|
|
274
|
+
|
|
275
|
+
- **`--effort` and `--model` do not change the derived permission posture.**
|
|
276
|
+
`src/permits.ts` only ever detects `--permission-mode`/`--allowedTools`/
|
|
277
|
+
`--disallowedTools` (and siblings) on claude and `--sandbox` on codex —
|
|
278
|
+
adding an effort or model flag does not flip a role to
|
|
279
|
+
`operator-configured`, and nightshift's derived posture still applies on
|
|
280
|
+
top of it.
|
|
281
|
+
- **Mixed configurations affect session resume.** When two seats differ in
|
|
282
|
+
model or effort, a fix pass that would have resumed the seat that produced
|
|
283
|
+
the commit falls back to a fresh spawn instead — correct, since resuming
|
|
284
|
+
under a posture the seat was not configured for would be worse — and the
|
|
285
|
+
run report says which happened.
|
|
286
|
+
|
|
287
|
+
**The recommendation**, from a measured program where roughly half of every
|
|
288
|
+
agent's output tokens were thinking tokens:
|
|
289
|
+
|
|
290
|
+
| Seat | Reason |
|
|
291
|
+
| --- | --- |
|
|
292
|
+
| `agent` | The largest share of a run's tokens, and checked hardest downstream — runner verification, test critique, decider ratification, whole-program review. The first seat to try a lower effort, and the first to look at when quality slips. |
|
|
293
|
+
| `authorAgent` | Writing a spec is design reasoning; a sparse spec costs a whole workstream. The last seat to economise on. |
|
|
294
|
+
| `reviewerAgent` / `deciderAgent` | Small token volume, high leverage per token. Lowering effort here saves little and risks the second opinion no-self-grading exists to protect. |
|
|
295
|
+
| `recoveryAgent` | Runs rarely, on the hardest input in the run — a red tree with a diagnosis. Economising here pays for the good case with the bad one. |
|
|
296
|
+
|
|
297
|
+
An illustrative example — not this repository's own configuration above —
|
|
298
|
+
showing the implementer seat at a lower effort, in both provider forms:
|
|
299
|
+
|
|
300
|
+
```json
|
|
301
|
+
{ "agent": { "command": "claude", "args": ["-p", "--model", "sonnet", "--effort", "low"], "promptMode": "stdin" } }
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
```json
|
|
305
|
+
{ "agent": { "command": "codex", "args": ["exec", "-c", "model_reasoning_effort=\"low\""], "promptMode": "stdin" } }
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
**A fast tier is a different knob, not a smaller effort.** Where a provider
|
|
309
|
+
exposes one headlessly, a fast tier is a latency setting — the same model,
|
|
310
|
+
emitted faster — not a smaller model and not a lower effort, so it carries no
|
|
311
|
+
quality bet and needs none of the one-change-at-a-time discipline the effort
|
|
312
|
+
recommendation above does. It is worth configuring exactly where output
|
|
313
|
+
latency sits on the critical path: `authorAgent`, because every pipelined
|
|
314
|
+
chain begins with a spec, and `agent`, because builds are the longest legs.
|
|
315
|
+
It buys almost nothing on `reviewerAgent`, `deciderAgent`, or
|
|
316
|
+
`recoveryAgent` — short, rare, or both. Check for one with `<cli> --help`
|
|
317
|
+
(look for a tier or fast flag), then confirm the provider accepts it
|
|
318
|
+
headlessly in the exact argv nightshift will spawn with
|
|
319
|
+
`nightshift doctor --agents`. As of the versions checked here — claude
|
|
320
|
+
2.1.241 and codex 0.149.0 — **neither exposes a fast tier headlessly**:
|
|
321
|
+
claude's fast mode is an interactive `/fast` toggle with no `--help` flag
|
|
322
|
+
behind it, and codex documents no fast-tier flag or config key at all.
|
|
323
|
+
Re-run both `--help` commands before trusting this — if a fast flag has
|
|
324
|
+
appeared since, put it on `authorAgent` and `agent` first.
|
|
325
|
+
|
|
326
|
+
**Verify the roster before trusting it.** `nightshift doctor --agents` spawns
|
|
327
|
+
each distinct agent once with the same derived posture a run would use and
|
|
328
|
+
prints the exact argv — effort flag included — the permission label, and
|
|
329
|
+
whether the seat can write and run the verify commands. A tier or effort
|
|
330
|
+
level the provider refuses headlessly shows up as one failed line here,
|
|
331
|
+
before a run has spent anything on it.
|
|
332
|
+
|
|
333
|
+
**Judging whether a lower effort hurt** uses signals already in the run
|
|
334
|
+
report: attempt counts and durations by disposition (a retried or failed
|
|
335
|
+
implementer attempt is the loudest signal), critique findings — more of
|
|
336
|
+
them, or more routed `fix-now`, means the first pass was worse — and parked
|
|
337
|
+
workstreams or recovery attempts, the expensive tail. Change one seat at a
|
|
338
|
+
time, compare runs of comparable programs, and never change effort and
|
|
339
|
+
`--max-parallel` in the same run — the report cannot tell you which one
|
|
340
|
+
moved the number.
|
|
341
|
+
|
|
342
|
+
## Running programs concurrently
|
|
343
|
+
|
|
344
|
+
The runner builds every workstream whose dependencies have landed and whose
|
|
345
|
+
spec is finished, concurrently, bounded by `--max-parallel`. A workstream's
|
|
346
|
+
build starts as soon as its own spec is finished and its dependencies are
|
|
347
|
+
integrated — it does not wait for every other workstream to finish
|
|
348
|
+
authoring. That second half is what pays even on a program with no width at
|
|
349
|
+
all: a chain of workstreams overlaps each spec's authoring with the previous
|
|
350
|
+
workstream's build, so pipelining pays on chains, not only on programs with
|
|
351
|
+
independent branches.
|
|
352
|
+
|
|
353
|
+
What did not change: the runner still owns verification and commits, the
|
|
354
|
+
program branch is still linear with one green runner-owned commit per
|
|
355
|
+
workstream, replay still works, a failed workstream still parks its
|
|
356
|
+
downstream cone while everything else keeps building, and a run still
|
|
357
|
+
always terminates.
|
|
358
|
+
|
|
359
|
+
Every line a workstream's flow logs is prefixed `[WS-xx]`; scheduler lines
|
|
360
|
+
are prefixed `[run]`. An interleaved log of several workstreams building at
|
|
361
|
+
once is readable because of this prefix — look for it first when a
|
|
362
|
+
concurrent run's log looks tangled.
|
|
363
|
+
|
|
364
|
+
### Worktrees
|
|
365
|
+
|
|
366
|
+
Every workstream builds in its own git worktree, outside your tracked
|
|
367
|
+
checkout, under `.nightshift-worktrees` next to the repository (falling back
|
|
368
|
+
to the OS temp directory when the repository's parent is not writable; the
|
|
369
|
+
run logs the chosen base once at start). One worktree exists per in-flight
|
|
370
|
+
workstream, and each one installs dependencies through the detected package
|
|
371
|
+
manager when a lockfile is present — n concurrent workstreams means n
|
|
372
|
+
dependency trees on disk, worth checking before raising `--max-parallel` on
|
|
373
|
+
a laptop.
|
|
374
|
+
|
|
375
|
+
**Uncommitted work in your root checkout is not picked up by any build** —
|
|
376
|
+
every build starts from the program branch's tip, not your working tree. If
|
|
377
|
+
you resume a run with uncommitted changes sitting in the root, the runner
|
|
378
|
+
warns you; commit or stash first.
|
|
379
|
+
|
|
380
|
+
A worktree is removed after its workstream integrates successfully. When a
|
|
381
|
+
workstream does not integrate, its worktree is retained and its path is
|
|
382
|
+
named in the run report, so the failure can be inspected — the next run
|
|
383
|
+
reaps every worktree and branch a program left behind before it does
|
|
384
|
+
anything else, so retained state is forensic, never an input a later run
|
|
385
|
+
builds on. Reaping never destroys the only copy of an attempt: before a
|
|
386
|
+
leftover worktree is removed, whatever it holds beyond the program branch
|
|
387
|
+
(committed or not) is committed and pinned under
|
|
388
|
+
`refs/nightshift/<program-id>/<workstream-id>-wip`, and the run's console
|
|
389
|
+
names every ref it salvaged. After a complete run, `git worktree list`
|
|
390
|
+
should show nothing belonging to the program; that one command is the check.
|
|
391
|
+
|
|
392
|
+
### Integration, failure, and salvage
|
|
393
|
+
|
|
394
|
+
A green worktree is squashed onto the program branch as one runner-owned
|
|
395
|
+
commit, and the integrated tree is re-verified — skipped only when the
|
|
396
|
+
program-branch tip has not moved since the workstream started, because then
|
|
397
|
+
the two trees are identical.
|
|
398
|
+
|
|
399
|
+
A merge conflict or a red integration verify is a bounded, journaled
|
|
400
|
+
recovery event, never a silent discard: the verified commit is retained
|
|
401
|
+
under `refs/nightshift/<program-id>/<workstream-id>`, one recovery attempt
|
|
402
|
+
runs in a fresh worktree from the current tip briefed with both commits and
|
|
403
|
+
the conflict or verify output, and — if that also fails — the workstream
|
|
404
|
+
fails with a diagnosis naming both commits, the retained ref, and the
|
|
405
|
+
retained worktree path. Its downstream cone parks exactly as it would for
|
|
406
|
+
any other build failure.
|
|
407
|
+
|
|
408
|
+
Salvage a retained commit by hand from the root checkout:
|
|
409
|
+
|
|
410
|
+
```sh
|
|
411
|
+
git log --oneline refs/nightshift/<program-id>/<workstream-id>
|
|
412
|
+
git diff <program-branch>...refs/nightshift/<program-id>/<workstream-id>
|
|
413
|
+
git cherry-pick refs/nightshift/<program-id>/<workstream-id> # or merge, or re-run
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
That commit **verified green against the tip it was built from** — it is a
|
|
417
|
+
real, tested change that could not be reconciled with a branch that moved
|
|
418
|
+
underneath it, not broken work. The ref survives until you delete it:
|
|
419
|
+
reaping removes worktrees and their branches, never `refs/nightshift/`.
|
|
420
|
+
|
|
421
|
+
### `--max-parallel`
|
|
422
|
+
|
|
423
|
+
```sh
|
|
424
|
+
nightshift run <program-id> --max-parallel <n>
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
Default: unbounded — the dependency graph is the only serializer. `1`
|
|
428
|
+
reproduces the pre-concurrency serial runner's *execution*: the same
|
|
429
|
+
dispatch order, one workstream at a time, one runner-owned commit each, the
|
|
430
|
+
same report sections — it is also what turns pipelining off. It reproduces
|
|
431
|
+
the runner's behaviour, not the agents': running the same program twice,
|
|
432
|
+
serially, will not produce the same diffs, because the agents are not
|
|
433
|
+
deterministic.
|
|
434
|
+
|
|
435
|
+
One budget covers both stages — authoring and building share the same cap,
|
|
436
|
+
not one each. A bad value is refused at the CLI, loudly, as a
|
|
437
|
+
could-not-start (exit 3), never silently reinterpreted as unbounded. The
|
|
438
|
+
report's resume command carries whatever value you ran with, so a run
|
|
439
|
+
bounded to 2 resumes bounded to 2.
|
|
440
|
+
|
|
441
|
+
Verify commands are CPU-heavy, so on a small machine several concurrent
|
|
442
|
+
suites can slow each other down or trip test timeouts. The run report's
|
|
443
|
+
wait-by-cause table — `spec-pending`, `author-concurrency-cap`,
|
|
444
|
+
`concurrency-cap`, `integration-mutex`, and the derived `dependency-landing`
|
|
445
|
+
— is the evidence for choosing a value: a workstream waiting a long time on
|
|
446
|
+
`concurrency-cap` or `author-concurrency-cap` is the case for raising it; a
|
|
447
|
+
verify pass that fails and then passes on retry is the case for lowering it.
|
|
448
|
+
See [docs/run-analytics.md](docs/run-analytics.md#reading-a-concurrent-run)
|
|
449
|
+
for what each figure means and how to act on it.
|
|
450
|
+
|
|
451
|
+
### Your first concurrent run
|
|
452
|
+
|
|
453
|
+
**Before.** Commit or stash work in the root checkout — builds start from
|
|
454
|
+
the program branch's tip and will not pick it up. Check disk space for one
|
|
455
|
+
dependency tree per concurrent workstream. Run `nightshift doctor --agents`
|
|
456
|
+
if the agent roster changed. Start with a deliberate, small
|
|
457
|
+
`--max-parallel` on a laptop rather than unbounded, so the first concurrent
|
|
458
|
+
run is one you can watch.
|
|
459
|
+
|
|
460
|
+
**During.** The `[WS-xx]` and `[run]` prefixes make an interleaved log
|
|
461
|
+
readable. `git worktree list` is the live inventory of what is building.
|
|
462
|
+
|
|
463
|
+
**After.** Read "Where the time went" first: the parallelism ratio says
|
|
464
|
+
whether concurrency happened at all, the critical path says what determined
|
|
465
|
+
the run's length, and the waits by cause say what to change. Confirm
|
|
466
|
+
`git worktree list` shows nothing belonging to the program — any retained
|
|
467
|
+
worktree path or `refs/nightshift/` ref named in the report belongs to a
|
|
468
|
+
workstream that did not finish.
|
|
469
|
+
|
|
470
|
+
**When something looks wrong**, re-run with `--max-parallel 1`. This is a
|
|
471
|
+
*diagnostic* step, not merely a retreat to "give up and go slow" — it makes
|
|
472
|
+
concurrency the leading suspect, but it does not replay the failed run:
|
|
473
|
+
`--max-parallel 1` removes concurrency from the run's *execution* (the same
|
|
474
|
+
dispatch order, one workstream at a time, no overlap between authoring and
|
|
475
|
+
building); it does not reproduce the agents' behaviour, and two agentic runs
|
|
476
|
+
of the same program can differ for reasons that have nothing to do with
|
|
477
|
+
scheduling — nondeterministic model output, a transient provider or network
|
|
478
|
+
failure, a flaky test, state outside the repository that moved between runs.
|
|
479
|
+
|
|
480
|
+
- **A serial run that is also red is the informative outcome.** It largely
|
|
481
|
+
rules concurrency out and points at the change itself, the spec, or the
|
|
482
|
+
environment.
|
|
483
|
+
- **A serial run that is green is evidence, not proof.** It makes
|
|
484
|
+
concurrency the leading suspect and worth investigating as a concurrency
|
|
485
|
+
bug — it does not isolate the cause, since the green run may simply be a
|
|
486
|
+
different roll of the same dice. Repeat it if it's cheap, and look for a
|
|
487
|
+
mechanism in the report (an integration conflict, a verify pass that
|
|
488
|
+
failed and passed on retry, a retained worktree) rather than resting on
|
|
489
|
+
the pair of outcomes alone.
|
|
490
|
+
|
|
491
|
+
Attach both run reports to the bug report either way.
|
|
492
|
+
|
|
225
493
|
## Authoring specs
|
|
226
494
|
|
|
227
495
|
`nightshift run` authors every missing workstream spec before it builds
|
|
@@ -277,6 +545,26 @@ ref and never loses a ledger event. It ends by telling you the one command
|
|
|
277
545
|
to re-run — the rebuilt work honors your ruling, since the next run's
|
|
278
546
|
implementer brief carries it as binding.
|
|
279
547
|
|
|
548
|
+
## Where the time went
|
|
549
|
+
|
|
550
|
+
Every run report ends with a "Where the time went" section: total elapsed
|
|
551
|
+
time, reconciled bucket durations (preflight, authoring, implementation,
|
|
552
|
+
verification, review, recovery/fixes, git/persistence, whole-program review,
|
|
553
|
+
reporting, and an explicit unattributed residual), the largest measured
|
|
554
|
+
contributors, attempt and rerun counts by trigger, and observability gaps.
|
|
555
|
+
It is descriptive arithmetic over the run's own recorded observations, never
|
|
556
|
+
a score. See [docs/run-analytics.md](docs/run-analytics.md) for the full
|
|
557
|
+
contract, including how to read each figure and the limits of what it
|
|
558
|
+
claims.
|
|
559
|
+
|
|
560
|
+
Right after it, a report may carry `## Why the time went there`: an
|
|
561
|
+
optional, independent, read-only pass that traces those numbers toward
|
|
562
|
+
planner-actionable causes, citing retained evidence for every step and
|
|
563
|
+
saying plainly when it cannot close a chain. It never grades the run, never
|
|
564
|
+
gates anything, and needs no configuration beyond `reviewerAgent`. See
|
|
565
|
+
[docs/run-analytics.md](docs/run-analytics.md#reading-the-causal-analysis)
|
|
566
|
+
for the four layers and what the evidence gate does and does not prove.
|
|
567
|
+
|
|
280
568
|
## Development
|
|
281
569
|
|
|
282
570
|
Requires Node.js 22+.
|
package/dist/agent-runner.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import type { AgentConfig, NightshiftConfig } from "./config.js";
|
|
2
|
-
import type
|
|
2
|
+
import { type AgentRole, type PermitsContext } from "./permits.js";
|
|
3
|
+
import { type NormalizedTelemetry } from "./provider-telemetry.js";
|
|
4
|
+
import { type Dimensions, type SpanScope } from "./run-analytics.js";
|
|
5
|
+
import { type AgentSessionHandle, type SessionContinuity, type SessionContinuityRequest } from "./agent-session.js";
|
|
3
6
|
/** A Bash command a Claude spawn actually ran, observed from its stream-json `tool_use` block. */
|
|
4
7
|
export interface TranscriptCommand {
|
|
5
8
|
command: string;
|
|
@@ -30,6 +33,16 @@ export interface AgentTranscript {
|
|
|
30
33
|
* never be silently blessed as fully authoritative alongside `observed: true`.
|
|
31
34
|
*/
|
|
32
35
|
corrupted?: boolean;
|
|
36
|
+
/**
|
|
37
|
+
* WS-03's provider-neutral normalization of this spawn's stream, when one
|
|
38
|
+
* was demultiplexed. Absent only for a spawn that never asked for
|
|
39
|
+
* stream-json output in the first place (`AgentTranscript` itself is
|
|
40
|
+
* absent then, so this field never needs to distinguish that case).
|
|
41
|
+
*/
|
|
42
|
+
telemetry?: NormalizedTelemetry;
|
|
43
|
+
/** The session id observed on this spawn's stream (Claude only — a codex
|
|
44
|
+
* spawn produces no transcript at all). WS-08. */
|
|
45
|
+
sessionId?: string;
|
|
33
46
|
}
|
|
34
47
|
export interface CommandResult {
|
|
35
48
|
exitCode: number;
|
|
@@ -38,6 +51,31 @@ export interface CommandResult {
|
|
|
38
51
|
inputError?: string;
|
|
39
52
|
/** Present only for a Claude spawn whose args requested stream-json output. */
|
|
40
53
|
transcript?: AgentTranscript;
|
|
54
|
+
/** The provider session id this spawn reported, when it reported one
|
|
55
|
+
* (WS-08). Set by the `AgentRunner` — the raw observation. */
|
|
56
|
+
sessionId?: string;
|
|
57
|
+
/** The same id assembled into a resumable handle. Set by `invokeAgent`,
|
|
58
|
+
* never by the runner, because only `invokeAgent` knows the derived
|
|
59
|
+
* invocation and the cwd that go with it (WS-08). This is the field
|
|
60
|
+
* every caller reads to advance its lineage's handle. */
|
|
61
|
+
session?: AgentSessionHandle;
|
|
62
|
+
/** How this spawn's session continuity resolved (WS-08). Present **only**
|
|
63
|
+
* when the caller passed a `SessionContinuityRequest` — absent entirely
|
|
64
|
+
* for the seats that never resume, which is what makes "no request" and
|
|
65
|
+
* "requested but nothing captured" distinguishable downstream. */
|
|
66
|
+
continuity?: SessionContinuity;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Retains a spawn's raw stdout, gitignored, for SC-08's raw-source-event
|
|
70
|
+
* requirement — never throws; a failure is routed to `onError` and the
|
|
71
|
+
* caller omits the raw evidence reference rather than losing the spawn.
|
|
72
|
+
*/
|
|
73
|
+
export interface RawTranscriptSink {
|
|
74
|
+
/** Appends one raw stdout chunk, in arrival order. */
|
|
75
|
+
write(chunk: string): void;
|
|
76
|
+
/** Flushes and closes; resolves once the bytes are durable (or the sink gave up). */
|
|
77
|
+
close(): Promise<void>;
|
|
78
|
+
onError(err: unknown): void;
|
|
41
79
|
}
|
|
42
80
|
export interface AgentInvocation {
|
|
43
81
|
command: string;
|
|
@@ -46,6 +84,8 @@ export interface AgentInvocation {
|
|
|
46
84
|
promptMode: "stdin" | "argument";
|
|
47
85
|
cwd: string;
|
|
48
86
|
onOutput?: (chunk: string) => void;
|
|
87
|
+
/** Teed a copy of this spawn's raw stdout, when the caller wants one retained (WS-03). */
|
|
88
|
+
rawTranscript?: RawTranscriptSink;
|
|
49
89
|
}
|
|
50
90
|
export type AgentRunner = (invocation: AgentInvocation) => Promise<CommandResult>;
|
|
51
91
|
export type VerifyRunner = (command: string, cwd: string) => Promise<CommandResult>;
|
|
@@ -73,8 +113,21 @@ export declare function runProcess(command: string, args: string[], options: {
|
|
|
73
113
|
* fallback even though stdout was itself well-formed NDJSON.
|
|
74
114
|
*/
|
|
75
115
|
onStdout?: (chunk: string) => void;
|
|
116
|
+
/**
|
|
117
|
+
* Stderr-only chunks, symmetric with `onStdout` (WS-08). Codex writes its
|
|
118
|
+
* startup header — including the `session id:` line — and its entire
|
|
119
|
+
* running transcript to stderr, writing only the agent's final message to
|
|
120
|
+
* stdout; a scanner fed from `onOutput` would also see the agent's own
|
|
121
|
+
* prose, which can contain any text at all.
|
|
122
|
+
*/
|
|
123
|
+
onStderr?: (chunk: string) => void;
|
|
76
124
|
env?: NodeJS.ProcessEnv;
|
|
77
125
|
}): Promise<CommandResult>;
|
|
126
|
+
/** The demultiplexer's telemetry inputs — the derived invocation's argv and command, for model precedence and the provider fallback (SC-05). */
|
|
127
|
+
export interface TelemetryInputs {
|
|
128
|
+
configuredArgs: readonly string[];
|
|
129
|
+
command: string;
|
|
130
|
+
}
|
|
78
131
|
/**
|
|
79
132
|
* Demultiplexes a whole captured stream-json string in one call — what
|
|
80
133
|
* fixture-replay tests exercise. `undefined` means the input did not parse as
|
|
@@ -84,6 +137,13 @@ export declare function demultiplexClaudeStreamJson(raw: string): {
|
|
|
84
137
|
output: string;
|
|
85
138
|
transcript: AgentTranscript;
|
|
86
139
|
} | undefined;
|
|
140
|
+
/** The session id from one codex stderr line, when it is the anchored
|
|
141
|
+
* `session id: <uuid>` header line — an embedded or prefixed occurrence
|
|
142
|
+
* inside ordinary prose does not match. */
|
|
143
|
+
export declare function captureCodexSessionIdFromStderr(line: string): string | undefined;
|
|
144
|
+
/** The `thread_id` from one codex `--json`-mode stdout line, when it is a
|
|
145
|
+
* well-formed `thread.started` event. */
|
|
146
|
+
export declare function captureCodexSessionIdFromStdout(line: string): string | undefined;
|
|
87
147
|
export declare const defaultAgentRunner: AgentRunner;
|
|
88
148
|
export declare const defaultVerifyRunner: VerifyRunner;
|
|
89
149
|
export declare function tail(output: string, limit?: number): string;
|
|
@@ -106,9 +166,15 @@ export interface TranscriptSink {
|
|
|
106
166
|
* invocation and so cannot go through `invokeAgent` itself). A no-op when the
|
|
107
167
|
* result carries no transcript at all (a non-Claude spawn, an operator-
|
|
108
168
|
* configured or operator-`--output-format` role — never asked to be
|
|
109
|
-
* observed).
|
|
169
|
+
* observed). Returns the repo-relative path actually written, so a caller
|
|
170
|
+
* (WS-03) can cite it as evidence; `undefined` when nothing was written.
|
|
171
|
+
*
|
|
172
|
+
* `nonce`, when given, names the file so it shares its stem with a raw
|
|
173
|
+
* sidecar `invokeAgent` allocated up front (WS-03); the probe has no such
|
|
174
|
+
* sidecar and lets this mint its own, exactly as it did before this
|
|
175
|
+
* parameter existed.
|
|
110
176
|
*/
|
|
111
|
-
export declare function recordAgentTranscript(observe: TranscriptSink, role: string, result: CommandResult): Promise<
|
|
177
|
+
export declare function recordAgentTranscript(observe: TranscriptSink, role: string, result: CommandResult, nonce?: string): Promise<string | undefined>;
|
|
112
178
|
/**
|
|
113
179
|
* Spawns a configured agent role with a prompt; shared by every brief
|
|
114
180
|
* spawner. `permits` derives the posture for this role/agent/host before the
|
|
@@ -119,9 +185,33 @@ export declare function recordAgentTranscript(observe: TranscriptSink, role: str
|
|
|
119
185
|
*
|
|
120
186
|
* `observe`, when given, writes the spawn's transcript (if one was observed)
|
|
121
187
|
* as JSONL under `build-logs/<programId>/` and logs the path — the one place
|
|
122
|
-
* this happens, rather than duplicated across every spawning call site.
|
|
188
|
+
* this happens, rather than duplicated across every spawning call site. It
|
|
189
|
+
* also names a raw stdout sidecar (WS-03, SC-08), teed during the spawn and
|
|
190
|
+
* `close()`-awaited before any evidence reference resolves to it.
|
|
191
|
+
*
|
|
192
|
+
* `recorder`, when given, opens a span around the spawn (WS-01) and — after
|
|
193
|
+
* the spawn returns — receives one `provider-telemetry` point per
|
|
194
|
+
* SC-08-enumerated metric (WS-03), correlated by the `role` dimension and
|
|
195
|
+
* the normalized/raw transcript paths as evidence. A caller with no
|
|
196
|
+
* run-scoped recorder to hand gets a shared, permanently disarmed default
|
|
197
|
+
* that buffers harmlessly and performs no I/O — telemetry emission is then
|
|
198
|
+
* simply a no-op, never a thrown error.
|
|
199
|
+
*
|
|
200
|
+
* `spanDims`, when given, overrides the role-derived default stage/dimensions
|
|
201
|
+
* (WS-02) — the call site is the only party that knows which of a reused
|
|
202
|
+
* role's several stages (e.g. `reviewerAgent` driving `test-critique`,
|
|
203
|
+
* `failure-diagnosis`, or `whole-program-review`) this particular spawn is,
|
|
204
|
+
* plus the workstream/attempt dimensions. A caller that passes nothing keeps
|
|
205
|
+
* today's `STAGE_FOR_ROLE` default, so every pre-WS-02 call site (the doctor
|
|
206
|
+
* probe, standalone tests) is unaffected.
|
|
123
207
|
*/
|
|
124
|
-
export declare function invokeAgent(runner: AgentRunner, agent: AgentConfig, prompt: string, cwd: string, permits: PermitsContext, role: AgentRole, observe?: TranscriptSink
|
|
208
|
+
export declare function invokeAgent(runner: AgentRunner, agent: AgentConfig, prompt: string, cwd: string, permits: PermitsContext, role: AgentRole, observe?: TranscriptSink, recorder?: SpanScope, spanDims?: Partial<Dimensions>,
|
|
209
|
+
/** Present only for the three seats that may continue a session (SC-13,
|
|
210
|
+
* WS-08); its presence is the permission and its `handle` is the
|
|
211
|
+
* candidate. Omitted everywhere else, which is how reviewer, decider,
|
|
212
|
+
* recovery and informed-retry seats are structurally incapable of
|
|
213
|
+
* resuming — not by a runtime check they could forget. */
|
|
214
|
+
continuityRequest?: SessionContinuityRequest): Promise<CommandResult>;
|
|
125
215
|
/**
|
|
126
216
|
* The implementer: the `agent` block, or the environment fallback for hosts
|
|
127
217
|
* that have no config file yet.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-runner.d.ts","sourceRoot":"","sources":["../src/agent-runner.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"agent-runner.d.ts","sourceRoot":"","sources":["../src/agent-runner.ts"],"names":[],"mappings":"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"}
|