bareloop 0.13.0 → 0.15.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/CHANGELOG.md +189 -0
- package/README.md +1 -1
- package/bareloop.context.md +375 -5
- package/package.json +3 -3
- package/src/behaviour.js +150 -0
- package/src/bridges.js +1 -1
- package/src/calibrate.js +1 -1
- package/src/cardauthor.js +1 -1
- package/src/codeversion.js +155 -0
- package/src/index.js +17 -0
- package/src/job.js +1 -1
- package/src/judged.js +1 -1
- package/src/ledger.js +2 -2
- package/src/planrun.js +140 -3
- package/src/promptregisters.js +93 -0
- package/src/readshim.js +39 -7
- package/src/replay.js +1212 -0
- package/src/reuse.js +24 -2
- package/src/root.js +1 -1
- package/src/run.js +29 -2
- package/src/selection.js +1 -1
- package/src/transport.js +71 -0
- package/types/behaviour.d.ts +46 -0
- package/types/codeversion.d.ts +18 -0
- package/types/index.d.ts +3 -0
- package/types/ledger.d.ts +14 -0
- package/types/promptregisters.d.ts +31 -0
- package/types/readshim.d.ts +17 -6
- package/types/replay.d.ts +195 -0
- package/types/reuse.d.ts +22 -0
- package/types/transport.d.ts +19 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,195 @@ All notable changes to bareloop are documented here. Format:
|
|
|
5
5
|
[SemVer](https://semver.org/spec/v2.0.0.html). Pre-1.0: **minor** = a ladder rung or
|
|
6
6
|
feature lands, **patch** = docs, fixes, scaffolding.
|
|
7
7
|
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.15.0] — 2026-08-27
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **Generic run replay** (PRD build-list #6): `replayRun`/`formatReplay` (`src/replay.js`)
|
|
15
|
+
reconstruct one run's whole story from its spine JSONL + gate-audit sidecar. The single-run
|
|
16
|
+
report is a signed layout (hamr, 2026-08-25): a header block (`goal:`/`shape:`/`signed:`/
|
|
17
|
+
`branch:`/`outcome:`/`spent:`, absent fields printing the literal `none recorded`, never
|
|
18
|
+
blank or a fabricated `0`), a `TIMELINE (steps)` or `TIMELINE (iterations — loop-shape run)`
|
|
19
|
+
section with its own column-header row and per-row time/cost (wall, `$` — a `null` `costUsd`
|
|
20
|
+
anywhere in a row's window makes that row's spend `unknown (N unpriced rounds)`, never `0` or
|
|
21
|
+
a partial sum stamped exact) and a `↳ tripped:`/`↳ close:` line under any row an escalation or
|
|
22
|
+
close fell inside, a `CLOSE` section (never omitted — `none — the run ended before any close
|
|
23
|
+
ran` when nothing ever closed; picks the run's REAL final close by seq, never by record type —
|
|
24
|
+
an early `outer-close` precheck reading red must not shadow a LATER fix-loop close-verdict that
|
|
25
|
+
went satisfied), `ENDING` (last record + 3 before it, plus an out-of-window escalation), the
|
|
26
|
+
`BEHAVIOUR` block, and a `MEMORY-CACHE` line always printed (`not armed on this run` when
|
|
27
|
+
absent — absence is never silent). Stop reason is `category — decision — detail` off the last
|
|
28
|
+
escalation, so a run's actual failure text (a TLS "bad record mac", a specific mypy error)
|
|
29
|
+
rides onto the report instead of just the generic per-category decision prose. A step id that
|
|
30
|
+
recurs after a replan is split per OCCURRENCE, never merged into one pooled row. `--all <dir>`
|
|
31
|
+
is name-agnostic (a spine is identified by a `job-start`/`run-start` record in its content,
|
|
32
|
+
never a filename convention) and prints a fixed-column, ALIGNED table (never CSV) — `id job
|
|
33
|
+
shape outcome spend wall steps reason`, `reason` built from `detail` only (never `decision`,
|
|
34
|
+
cut at 60 chars) — flagging any non-spine `.jsonl` as `<name> not-a-spine` rather than dropping
|
|
35
|
+
it. A RESUMED run's `job-end.spentUsd` is a CHAIN total (prior legs folded in) that can never
|
|
36
|
+
equal this one file's own rounds (measured: `u-msf70nei` header `$5.3389` vs its own 55 rounds
|
|
37
|
+
summing to `$1.2127`) — the header now prints both, each labelled, plus a `resumed:` line
|
|
38
|
+
(keyed on `job-start.priorSpentUsd`, never on the narrower `resume-seed` record alone — 4 real
|
|
39
|
+
archived runs carry a real fold with no resume-seed record at all). A non-resumed run's own
|
|
40
|
+
spend disagreeing with its summed rounds is a genuine finding and prints as a `MISMATCH`, never
|
|
41
|
+
hidden (measured: 0 of the archive's non-resumed runs trip it). `--all` marks a resumed row
|
|
42
|
+
with a trailing `*` and one footnote line when the listing holds any. Reuses `runBehaviour`/the
|
|
43
|
+
`memory-cache` readout rather than reimplementing either. Reads only, mints no verdict, writes
|
|
44
|
+
nothing to the spine. `job-start` (`src/run.js`) now also carries `verdictType` (the spec's
|
|
45
|
+
required verdict class) and `model` (when the shell-owned provider binding itself carries one)
|
|
46
|
+
— PRD TODO #20, blessed verbatim by hamr the same day. The header prints `class:`/`model:`
|
|
47
|
+
right after `shape:` (`not recorded (pre-F117 spine)` / `not recorded` on an older archive or
|
|
48
|
+
a provider with no `.model`), and `--all` gains `class`/`model` columns (`model` kept FULL,
|
|
49
|
+
never truncated).
|
|
50
|
+
- **Prompt-commit shape — a check, not convention** (PRD build-list #5, TODO #8, Q9 answered
|
|
51
|
+
by hamr 2026-08-25): a commit that changes a prompt register (a model-facing
|
|
52
|
+
system/strategy/instruction string a worker or judge reads) must now say, in its own
|
|
53
|
+
message, three labelled things — `Failure:` what caused the change, `Addresses:` what it
|
|
54
|
+
addresses, `Corrects:` what it corrects. `src/promptregisters.js` (`PROMPT_REGISTERS`,
|
|
55
|
+
`isPromptFile`, both exported from `bareloop`'s root) is the ONE inventory: 7 files carry
|
|
56
|
+
real prompt content (`src/authorscout.js`, `src/authorflow.js`, `src/judged.js`,
|
|
57
|
+
`src/cardauthor.js`, `src/readshim.js`, `src/tools.js`, `src/planrun.js`) — more than double
|
|
58
|
+
the 8 named consts a first grep found, once module-private consts and inline
|
|
59
|
+
template-literal prompt builders (`scoutPrompt()`, `cardCasesPrompt()`, `locatePrompt()`,
|
|
60
|
+
`strategyFor()`) are counted; the check is file-scoped precisely because a const-scoped one
|
|
61
|
+
would have silently missed those. Enforcement is LOCAL ONLY — wired into `npm test`
|
|
62
|
+
(`scripts/prompt-commit-check.mjs --range origin/main..HEAD`), never into any
|
|
63
|
+
`.github/workflows/*` file (ask-first, untouched); an unresolvable range (no `origin`, no
|
|
64
|
+
fetch) is a printed SKIP at exit 0, never a violation. Pure decision logic lives in
|
|
65
|
+
`scripts/promptcommitlib.mjs`, neither of which ships (`scripts/` is outside
|
|
66
|
+
`package.json`'s `files`). hamr's same-day addition: the `Failure:` line must also cite
|
|
67
|
+
the RUN that caused the change (`run <id>` / `run u-<id>`, `\brun\s+(u-)?[a-z0-9]{6,12}\b`
|
|
68
|
+
— alphabet/length measured against 231 real archived spine ids, every one exactly 8
|
|
69
|
+
lowercase-alnum chars), failing with a distinct message
|
|
70
|
+
(`FAILURE_NEEDS_RUN_REF`) when absent; format only, never checked against a real spine
|
|
71
|
+
file.
|
|
72
|
+
- **Run→code stamp** (F118, the parked half of the same day's prompt-commit build, closed
|
|
73
|
+
same day on hamr's go): the prompt-commit check closes commit→run; this closes run→code.
|
|
74
|
+
`src/codeversion.js`'s `codeVersion()` is a pure, $0, no-shell reader — `version` from
|
|
75
|
+
bareloop's own `package.json`, `sha` from `.git/HEAD` (loose ref, falling back to
|
|
76
|
+
`packed-refs`) ONLY when a `.git` directory exists at the package root (a dev checkout;
|
|
77
|
+
an npm install has none), `dirty` always `null` (uncommitted-changes state cannot be
|
|
78
|
+
known without a shell — reported as unknown, never faked as `false`). `job-start`
|
|
79
|
+
(`src/run.js`) now also carries `code: {version, sha}`. `replayRun`/`formatReplay`
|
|
80
|
+
(`src/replay.js`) read it: a `code:` line right after `model:` — `bareloop 0.14.0 @
|
|
81
|
+
43f2812` / `@ sha unknown` / `not recorded (pre-F118 spine)`; no new `--all` column
|
|
82
|
+
(hamr's index is already full). The library never grows a shell seam to answer this —
|
|
83
|
+
`run` stays the one locked verb.
|
|
84
|
+
- **Replay surfaces transport retries and floor reasons** (F119, live-proven the same day
|
|
85
|
+
a real run — `u-mt8yk53k` — fired the retry seam twice): `replayRun`/`formatReplay`
|
|
86
|
+
window `transport-retry` records into the step/iteration occurrence they fell inside
|
|
87
|
+
(same `seq` windowing as `worker-round`), printing `↳ transport retry ×N (recovered|not
|
|
88
|
+
recovered|partially recovered) — <error>` under that row; a retry outside every window
|
|
89
|
+
prints as `transport retries outside steps: N` under the `TIMELINE` header instead. The
|
|
90
|
+
header's `spent:` line, whenever `spendComplete:false`, now appends `· floor because:
|
|
91
|
+
<reason(s)>` — derived from the spine, never guessed: `transport retry ×N`, `unpriced
|
|
92
|
+
round(s)`, `cut mid-call`, `stall`, `prior leg floor`, every cause that shows real
|
|
93
|
+
evidence, or `floor (reason not in spine)` when none can be derived. `--all` adds no
|
|
94
|
+
column: a run carrying any retry gets ` ⟲N` appended to its outcome word (same posture
|
|
95
|
+
as the resumed `*`), with one footnote when the listing holds any. Report-only — reads
|
|
96
|
+
existing `transport-retry`/`wall-halt`/`stall`/`job-start` records, writes nothing.
|
|
97
|
+
|
|
98
|
+
### Fixed
|
|
99
|
+
|
|
100
|
+
- **Leaked tmp dirs from `npm test`** (same class that filled `/tmp` tmpfs on 2026-08-25 and
|
|
101
|
+
blocked a build): `tests/codeversion.test.js`'s real-worktree test tracked only the `wt`
|
|
102
|
+
subdirectory passed to `git worktree remove`, never the `mkdtemp`-created parent, so every run
|
|
103
|
+
left an empty `codeversion-worktree-*` dir behind; its `after()` now also prunes stale worktree
|
|
104
|
+
metadata when `remove` fails and `rmSync`s the tracked dir unconditionally, plus the mkdtemp
|
|
105
|
+
parent is now itself tracked and removed. `tests/ralph.test.js`'s cwd-threading test created a
|
|
106
|
+
`ralph-cwd-*` repo with no cleanup at all, and its cwd-relative-close test likewise leaked a
|
|
107
|
+
`close-cwd-*` and a `close-cwd-other-*` dir per run; added a per-test `after(() => rmSync(...))`
|
|
108
|
+
for each, matching the file's existing top-level cleanup pattern.
|
|
109
|
+
- **`codeversion.test.js` false-red on PR checkouts** (F118 addendum): the HEAD-sha test's own
|
|
110
|
+
precondition assumed a symbolic ref, but `actions/checkout` leaves a PR checkout's HEAD
|
|
111
|
+
DETACHED at the merge commit, so the precondition threw before the module under test ever
|
|
112
|
+
ran — `src/codeversion.js` itself already handled the detached case correctly. Test-only fix:
|
|
113
|
+
the precondition now resolves a bare 40-hex `.git/HEAD` the same way `codeVersion()` does, plus
|
|
114
|
+
a new deterministic fake-package-root test that exercises the detached case without depending
|
|
115
|
+
on the checkout's own HEAD state.
|
|
116
|
+
- **Review of PR #23 (2026-08-26), 7 items fixed**: (1) `replayRun`'s spend totals
|
|
117
|
+
(`src/replay.js`) counted only `worker-round`, silently dropping a close's own `judge-round`
|
|
118
|
+
spend and native `worker-turn` attribution out of every total — now filters on the canonical
|
|
119
|
+
`SPEND_RECORD_TYPES` (newly exported from `src/ledger.js`), including the per-step window,
|
|
120
|
+
which no longer requires a `.phase` match for records (like `judge-round`) that never carry
|
|
121
|
+
one. (2) `scripts/promptcommitlib.mjs`'s run-reference regex accepted plain prose ("run
|
|
122
|
+
failed" satisfied its `{6,12}` margin outright) — tightened to the measured exact shape
|
|
123
|
+
(`{8}`); a proposed additional per-id digit requirement was investigated and REJECTED (23 of
|
|
124
|
+
130 re-sampled real archived ids carry no digit, `msdsmkid` among them). (3)
|
|
125
|
+
`src/codeversion.js` read `sha: null` inside any real git worktree (`.git` there is a FILE,
|
|
126
|
+
`gitdir: <path>`, not a directory) — now resolves the pointer file and the worktree's
|
|
127
|
+
`commondir` to find HEAD/refs correctly. (4) `replayRun`'s `resumed` flag missed a genuine
|
|
128
|
+
$0-floor resume (`priorSpentUsd: 0` is still a real fold per `src/run.js:328`'s own doctrine)
|
|
129
|
+
— now keys on the field's presence, not `> 0`. (5) `scripts/run-replay.mjs --all` opened
|
|
130
|
+
every run's gate-audit sidecar despite never using the fields it feeds — no longer opens it
|
|
131
|
+
in `--all` mode (`--all` output on `aurora-u-bareloop` confirmed byte-identical before/after).
|
|
132
|
+
(6) the steps/iterations windowing logic in `replayRun` was duplicated inline twice — extracted
|
|
133
|
+
into one shared `buildOccurrenceMetrics` helper (replay output on `u-msdsmkid`, `u-msf70nei`,
|
|
134
|
+
`u-mt8yk53k` confirmed byte-identical before/after). (7) `npm test`'s prompt-commit check
|
|
135
|
+
(`origin/main..HEAD`) inspects NOTHING after a direct push to main, because the push itself
|
|
136
|
+
advances `origin/main` to match `HEAD` — `scripts/prompt-commit-check.mjs` now falls back to
|
|
137
|
+
`HEAD~1..HEAD` on that exact condition and prints that it did; a multi-commit direct push
|
|
138
|
+
still only re-covers the last commit (needs `github.event.before`, a `.github/` edit — out of
|
|
139
|
+
scope here, ask-first).
|
|
140
|
+
- **Release-gate findings (2026-08-27)**: `scripts/prompt-commit-check.mjs`'s `--range` value
|
|
141
|
+
was passed to `git rev-list` positionally with no way to disambiguate it from an option — an
|
|
142
|
+
option-shaped value (e.g. `--output=<path>`) was parsed by git as its own flag (verified: a
|
|
143
|
+
`--` separator does NOT fix `rev-list`, unlike `show`/`diff-tree`, since it treats everything
|
|
144
|
+
after it as a path filter, not a revision range); the script now rejects any `--range` value
|
|
145
|
+
starting with `-` up front, before any git call runs. Also corrected the fallback's printed
|
|
146
|
+
limitation and code comment: on a MERGE commit, `HEAD~1` is the first parent, so
|
|
147
|
+
`HEAD~1..HEAD` covers the merge PLUS every commit unique to the merged branch (measured on
|
|
148
|
+
this repo's own `4904d76`: 10 commits, not 1) — the prior wording ("only re-covers the last
|
|
149
|
+
commit") was only accurate for a LINEAR multi-commit push, which is now stated explicitly.
|
|
150
|
+
(`src/replay.js`'s phase-less step-window filter was also reviewed and confirmed correct by a
|
|
151
|
+
new adversarial synthetic test — no code change there.)
|
|
152
|
+
|
|
153
|
+
## [0.14.0] — 2026-08-25
|
|
154
|
+
|
|
155
|
+
### Added
|
|
156
|
+
|
|
157
|
+
- **One transport-class retry** (F115, hamr's ruling): the ONE worker-Loop seam in
|
|
158
|
+
`src/planrun.js` now retries exactly once on a transport-only throw (TLS fault,
|
|
159
|
+
`ECONNRESET`/`EPIPE`/`ETIMEDOUT`, `fetch failed` over a network cause — never an HTTP
|
|
160
|
+
4xx/5xx/429 response), classified by the new `src/transport.js` (`isTransportFailure`,
|
|
161
|
+
fixed `TRANSPORT_MAX_ATTEMPTS`, never operator-configurable). Emits a report-only
|
|
162
|
+
`transport-retry` spine record and forces `job-end.spendComplete: false` for the rest of the
|
|
163
|
+
run — the retried attempt's first throw may already have been billed — surfaced by
|
|
164
|
+
`scripts/run-u.mjs` as a `retries` line only when a retry occurred.
|
|
165
|
+
- **provider-red joins the resumable set** (F115, PRD v1.80 TODO #4): when the one transport
|
|
166
|
+
retry also fails and a `run-u` run ends `provider-red`, its tail now prints an honest
|
|
167
|
+
readout — spend so far as a floor, "died in step X of Y" (or "before a plan was accepted"),
|
|
168
|
+
and the exact `--resume` command — and `--resume` re-enters at the recorded step with the
|
|
169
|
+
accepted plan, folding prior spend in. No cost/step threshold gates the offer (the operator
|
|
170
|
+
decides, every time); verdict terminals (`escalated`/`step-red`/`plan-red`) still refuse.
|
|
171
|
+
Folded from a local `scripts/run-u.mjs` widening into the library's own canonical
|
|
172
|
+
`CHECKPOINT_OUTCOMES` (`src/reuse.js`) so the two lists cannot drift.
|
|
173
|
+
- **Run behaviour summary** (build-list item 2, agreed 2026-08-23): `runBehaviour(events, {
|
|
174
|
+
runId? })` and `formatBehaviour(summary)` in `src/behaviour.js`, plus
|
|
175
|
+
`scripts/behaviour-readout.mjs <gate-audit.jsonl> [run_id]`. A report-only text block —
|
|
176
|
+
"94 tool calls · 61 read, 28 grep, 5 recall" / "17 exact repeats (~18%)" — computed entirely
|
|
177
|
+
from a run's own gate-audit JSONL. Gates nothing, changes no verdict, writes nothing to the
|
|
178
|
+
spine. The exact-repeat key is the full recorded action (`type` + `path` + every `args` key),
|
|
179
|
+
never collapsed to `{type, path}` — sharper collapse would have merged different byte ranges
|
|
180
|
+
of the same file into false repeats.
|
|
181
|
+
- **`memory-cache` end-of-run readout** for the read shim: `runPlan` emits one spine record
|
|
182
|
+
(`{pointered, capped, bytesWithheld, approxTokens}`) summing what L1's pointer and L4's cap
|
|
183
|
+
withheld across the whole run, only when the shim is armed. `scripts/run-u.mjs` prints it as
|
|
184
|
+
`MEMORY-CACHE N re-reads answered from memory · M reads capped · X KB withheld (~Tk tokens
|
|
185
|
+
not re-sent)`; `scripts/behaviour-readout.mjs` prints the same line given the run's spine as
|
|
186
|
+
an optional third positional. No cost fields — report-only, not a spend record.
|
|
187
|
+
|
|
188
|
+
### Fixed
|
|
189
|
+
|
|
190
|
+
- **`scripts/readshim-battery.mjs`'s spend reader compared a resumed leg's own money against
|
|
191
|
+
the wrong job-end figure** (F116): `readSpend`'s `ledgerUsd` now reads
|
|
192
|
+
`job-end.engagementSpentUsd` (this leg's spend) when a spine carries one, falling back to
|
|
193
|
+
`job-end.spentUsd` only for a cold run — `spentUsd` on a resumed job-end is a CHAIN total,
|
|
194
|
+
not this spine's own spend. `chainUsd`/`priorUsd` are exposed as new, honest fields and never
|
|
195
|
+
folded into `accountedUsd`/`ledgerUsd`/`spendUsd`.
|
|
196
|
+
|
|
8
197
|
## [0.13.0] — 2026-08-23
|
|
9
198
|
|
|
10
199
|
### Fixed (2026-08-23 — the release round: a pointer that lied, a close with no home, a rule that charged for its own refusal)
|
package/README.md
CHANGED
|
@@ -162,7 +162,7 @@ package, and gaps get fixed upstream rather than shimmed.
|
|
|
162
162
|
bareloop is the productization of **[adaptlearn](https://github.com/hamr0/adaptlearn)**, a
|
|
163
163
|
closed experimental record on whether agent-authored workflows improve under an honest gate.
|
|
164
164
|
The design laws it inherited, and the open questions it has not answered yet, are in
|
|
165
|
-
[`docs/
|
|
165
|
+
[`docs/product/PRD.md`](docs/product/PRD.md).
|
|
166
166
|
|
|
167
167
|
## License
|
|
168
168
|
|
package/bareloop.context.md
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
> where `hitl` IS admitted, `human-confirms` IS live, and the three `hitl-*` terminals exist —
|
|
27
27
|
> integrate against what is written here, and expect the class-level rename when the next rung
|
|
28
28
|
> lands. Records: PRD Addendum v1.71 and
|
|
29
|
-
> `docs/
|
|
29
|
+
> `docs/product/2026-08-17-softgreen-review-door-design.md` (repo-only, not shipped);
|
|
30
30
|
> evidence F102–F105.
|
|
31
31
|
|
|
32
32
|
## What this is
|
|
@@ -1093,13 +1093,25 @@ raises it.
|
|
|
1093
1093
|
of PRICED rounds ONLY — never an estimate from token counts or averages (cap-not-estimate) —
|
|
1094
1094
|
and it is stated even on the pre-token reds, where the honest figure is a real `0`.
|
|
1095
1095
|
`spendComplete` says whether that figure is EXACT; `false` means `spentUsd` is a FLOOR ("at
|
|
1096
|
-
least $X", true total unknowable) and must not be read as a total.
|
|
1096
|
+
least $X", true total unknowable) and must not be read as a total. Five things set it, all
|
|
1097
1097
|
the same class — a call whose cost is unknowable from here: a round that came back unpriced
|
|
1098
1098
|
(F6); a run that ABSORBED a stall, because the fuse's silent reissue may pay twice for one
|
|
1099
1099
|
answer and the flag is one-way whatever the outcome; a `wall-halt` that cut a call
|
|
1100
|
-
mid-flight (`cutMidCall`);
|
|
1100
|
+
mid-flight (`cutMidCall`); a RESUME whose fold was itself a floor
|
|
1101
1101
|
(`priorSpendComplete: false` — an unknown does not heal by being carried forward, so every
|
|
1102
|
-
round of this attempt being priced repairs nothing about the one before it)
|
|
1102
|
+
round of this attempt being priced repairs nothing about the one before it); and a
|
|
1103
|
+
**transport retry** (F115, hamr's ruling: "one retry for transport layer failure and
|
|
1104
|
+
reporting with the rest end of gate"). Every worker Loop (scout, drafter, each step, the fix
|
|
1105
|
+
worker — the ONE seam in `planrun.js`) gets exactly ONE extra attempt, and only for a
|
|
1106
|
+
transport-class throw — fetch itself throwing with no HTTP response at all (a TLS fault,
|
|
1107
|
+
`ECONNRESET`/`EPIPE`/`ETIMEDOUT`, `fetch failed` wrapping a network cause); an HTTP response
|
|
1108
|
+
(4xx/5xx/429) is never retried here and rides bare-agent's own unchanged policy. The budget is
|
|
1109
|
+
fixed (`TRANSPORT_MAX_ATTEMPTS` in `src/transport.js`) — never a job-spec or CLI knob, tighten-only
|
|
1110
|
+
doctrine. Each retry emits a report-only `transport-retry` spine record (`{phase, attempt, error,
|
|
1111
|
+
recovered}`, no cost field — the ledger is unaffected); the FIRST attempt threw before any usage
|
|
1112
|
+
figure came back, so **any** transport retry floors `spendComplete` for the rest of the run even
|
|
1113
|
+
when the retry recovers and the run finishes green — the same one-way flag class as `stalled`
|
|
1114
|
+
and `cutMidCall`. A wall stop read BETWEEN iterations or steps has no call in flight,
|
|
1103
1115
|
so it stays exact. Both fields are present on all outcomes, so a consumer never branches on
|
|
1104
1116
|
field presence and never has to launder a missing `spentUsd` into `$0`.
|
|
1105
1117
|
|
|
@@ -2084,6 +2096,364 @@ $0). `VOUCHED_RATE_SOURCES` is the frozen allow-list they key on. **Reporting on
|
|
|
2084
2096
|
no class, no halt. CLI lands at N5; the
|
|
2085
2097
|
panel reads the same file at N6.
|
|
2086
2098
|
|
|
2099
|
+
### `runBehaviour(events, { runId? })` / `formatBehaviour(summary)` — `src/behaviour.js`
|
|
2100
|
+
|
|
2101
|
+
Build-list item 2 (agreed 2026-08-23): one report-only text block summarizing what a run
|
|
2102
|
+
DID with its tools, computed entirely from the run's own gate-audit JSONL. **Gates nothing,
|
|
2103
|
+
changes no verdict, writes NOTHING to the spine** — a pure function over records that already
|
|
2104
|
+
exist, same posture as `spendProvenance` above. `runBehaviour` counts and returns the summary
|
|
2105
|
+
object; `formatBehaviour` renders it as the printed block:
|
|
2106
|
+
|
|
2107
|
+
```
|
|
2108
|
+
94 tool calls · 61 read, 28 grep, 5 recall
|
|
2109
|
+
17 exact repeats (~18%)
|
|
2110
|
+
```
|
|
2111
|
+
|
|
2112
|
+
`llm` action rows are provider rounds, not tool calls, and are always excluded; only
|
|
2113
|
+
`decision: 'allow'` rows count toward the total (a `deny` is tallied separately as `N denied`,
|
|
2114
|
+
shown only when nonzero). **The exact-repeat key is the FULL recorded action — `type` + `path`
|
|
2115
|
+
+ every key of `args`** (sorted, stably serialized), never collapsed to `{type, path}`: if the
|
|
2116
|
+
gate ever starts recording byte ranges or offsets, two different slices of one file must not
|
|
2117
|
+
read as the same repeated call. Repeat detection is therefore exactly as sharp as what the gate
|
|
2118
|
+
records, no sharper — a tool that reads identical bytes through two differently-shaped argument
|
|
2119
|
+
objects will not be caught by this. `scripts/behaviour-readout.mjs <gate-audit.jsonl> [run_id]`
|
|
2120
|
+
prints the block for one run or every run_id found in the file, skipping malformed lines with
|
|
2121
|
+
a count rather than throwing.
|
|
2122
|
+
|
|
2123
|
+
```js
|
|
2124
|
+
import { runBehaviour, formatBehaviour } from 'bareloop';
|
|
2125
|
+
const events = fs.readFileSync(auditFile, 'utf8').trim().split('\n').map(JSON.parse);
|
|
2126
|
+
console.log(formatBehaviour(runBehaviour(events, { runId })));
|
|
2127
|
+
```
|
|
2128
|
+
|
|
2129
|
+
#### `memory-cache` — the read shim's own end-of-run readout (`src/readshim.js`, `src/planrun.js`)
|
|
2130
|
+
|
|
2131
|
+
A single spine record, emitted once by `runPlan` right before it returns (so it lands on the
|
|
2132
|
+
spine before the caller's `job-end`), reporting what the READ SHIM (L1 pointer + L4 cap) saved
|
|
2133
|
+
this run: `{pointered, capped, bytesWithheld, approxTokens}`. `pointered` counts re-reads of
|
|
2134
|
+
unchanged content answered with a pointer instead of the bytes; `capped` counts reads served as
|
|
2135
|
+
a bounded slice; `bytesWithheld` sums exactly the bytes NOT re-sent in both cases (never
|
|
2136
|
+
estimated) — the diff lever (L2) isn't counted, it's a different response shape. `approxTokens`
|
|
2137
|
+
is `Math.round(bytesWithheld / 4)`, a bytes→tokens ESTIMATE named accordingly so nothing reads
|
|
2138
|
+
it as measured. **Emitted only when the shim is armed** (any `readShim` arm but the off one) —
|
|
2139
|
+
an unarmed run emits no record at all, never a fabricated zero. It carries no cost fields, so it
|
|
2140
|
+
is not a spend record and no spend-slicing instrument needs to account for it.
|
|
2141
|
+
`scripts/run-u.mjs` prints it as a `MEMORY-CACHE` line right after `BEHAVIOUR` when armed
|
|
2142
|
+
(`no record` if the run ended before the summary could fire); `scripts/behaviour-readout.mjs`
|
|
2143
|
+
prints the same line when handed the run's spine file as its optional third positional.
|
|
2144
|
+
|
|
2145
|
+
|
|
2146
|
+
### `replayRun(spineEvents, auditEvents?, { runId? })` / `formatReplay(summary)` — `src/replay.js`
|
|
2147
|
+
|
|
2148
|
+
PRD build-list item 6: today no failed run can be reconstructed in under five minutes — every
|
|
2149
|
+
byte is on disk (the spine JSONL + its gate-audit sidecar) but only hand-slicing JSONL reads it.
|
|
2150
|
+
`replayRun` is a report-only reader over BOTH files: **reads only, mints no verdict, writes
|
|
2151
|
+
NOTHING to the spine**, same posture as `runBehaviour` above, which it reuses rather than
|
|
2152
|
+
reimplements for the tool-call breakdown. It returns one plain object:
|
|
2153
|
+
|
|
2154
|
+
```
|
|
2155
|
+
{ runId, job, goal, budgetUsd, specHash, branch, verdictType, model, code,
|
|
2156
|
+
outcome, stopReason, spentUsd, spendComplete, wallMs, chainClock,
|
|
2157
|
+
resumed, resumeSeed, thisFileSpend, spendMismatch,
|
|
2158
|
+
timelineKind: 'steps'|'iterations', replans, close,
|
|
2159
|
+
steps: [{ id, occurrence, outcome, rounds, toolCalls, checks: {passed, failed},
|
|
2160
|
+
treeChanged, wallMs, spentUsd, unpricedRounds, tripped }],
|
|
2161
|
+
iterations: [{ iteration, verdict, boundary, closeStage, rounds, toolCalls,
|
|
2162
|
+
wallMs, spentUsd, unpricedRounds, tripped }],
|
|
2163
|
+
ending: { record, before /* the 3 records before it */, escalation },
|
|
2164
|
+
lastEscalation, behaviour, memoryCache, skipped }
|
|
2165
|
+
```
|
|
2166
|
+
|
|
2167
|
+
**`verdictType`/`model`** (F117, PRD TODO #20 — hamr blessed the whole item verbatim, landed
|
|
2168
|
+
2026-08-25): `verdictType` reads `job-start.verdictType` (`src/run.js:303`) — a REQUIRED spec
|
|
2169
|
+
field (`src/job.js:552` reds `missing-required` before job-start ever fires), so `null` here
|
|
2170
|
+
means only "this spine predates the field" (measured: `u-msdsmkid`, an Aug-3 archived run),
|
|
2171
|
+
never "unset". `model` reads `job-start.model`, present only when the shell-owned provider
|
|
2172
|
+
binding itself carried a `.model` string at job-start time — `src/planrun.js:76`'s own comment
|
|
2173
|
+
documents bare-agent's Loop reading `baseProvider.model` directly off that same object, so it is
|
|
2174
|
+
a real, citable, synchronously-available property, not a guess; a native/clipipe call path (or
|
|
2175
|
+
any provider double with no such field — measured on `tests/helpers.js`'s `scriptedProvider`)
|
|
2176
|
+
legitimately has neither, and this reader reports that honestly rather than defaulting one in.
|
|
2177
|
+
`formatReplay` prints both right after `shape:` — `class: softgreen` / `class: not recorded
|
|
2178
|
+
(pre-F117 spine)`, `model: claude-sonnet-5` / `model: not recorded`
|
|
2179
|
+
(the two "not recorded" wordings are deliberately different: a missing `verdictType` is always
|
|
2180
|
+
an archive-age gap, a missing `model` can happen on any spine, old or new). `--all` gains a
|
|
2181
|
+
`class` column right after `shape` and a `model` column right after `class` (the verdict class /
|
|
2182
|
+
the model string, or `-`) — `model` is kept FULL, never truncated, unlike the compact `reason`
|
|
2183
|
+
column: a wrong or uncertain model reading is exactly the thing a directory-wide scan needs in
|
|
2184
|
+
full, not cut.
|
|
2185
|
+
|
|
2186
|
+
**`code`** (F118, the run→code direction — the commit→run direction is the prompt-commit
|
|
2187
|
+
check's `Failure:` run-reference rule; the two are companion halves of one loop, built the
|
|
2188
|
+
same day). `codeVersion()` (`src/codeversion.js`) is a pure, $0, no-shell reader: `version`
|
|
2189
|
+
is bareloop's own `package.json` version (resolved off `import.meta.url`, never a hardcoded
|
|
2190
|
+
path — works whether this module is imported from `src/` or from an installed
|
|
2191
|
+
`node_modules/bareloop`); `sha` is read straight off `.git/HEAD` (following a symbolic ref
|
|
2192
|
+
to its loose ref file, falling back to `packed-refs` when the branch has been packed) **only
|
|
2193
|
+
when `.git` is a real directory at the package root** — a dev checkout has one, an
|
|
2194
|
+
npm-installed copy does not, and that absence reads as `sha: null`, honestly, never a guess.
|
|
2195
|
+
`dirty` is always `null`: whether the tree has uncommitted changes cannot be known without a
|
|
2196
|
+
shell, and this module NEVER shells out — `run` is the one locked verb (hamr's law targets
|
|
2197
|
+
what the agent can DO, not how it's written), and a report-only field is not a license for
|
|
2198
|
+
the library to grow a shell seam. `null` is reported here rather than a defaulted `false`,
|
|
2199
|
+
because a faked "clean" reading is a worse lie than an honest "don't know."
|
|
2200
|
+
|
|
2201
|
+
`job-start` (`src/run.js:303`) carries `code: {version, sha}` alongside F117's
|
|
2202
|
+
`verdictType`/`model` (`dirty` is intentionally omitted from the spine — it is always
|
|
2203
|
+
`null`, and a spine field that can only ever hold one value is not worth a byte).
|
|
2204
|
+
`replayRun` reads it as `{version, sha}` or `null` (absent field, an archive older than this
|
|
2205
|
+
landing); `formatReplay` prints a `code:` line right after `model:` — `code: bareloop
|
|
2206
|
+
0.14.0 @ 43f2812` on a spine with a real sha, `code: bareloop 0.14.0 @ sha unknown` when
|
|
2207
|
+
`sha` is null (an npm-installed run), `code: not recorded (pre-F118 spine)` when the
|
|
2208
|
+
whole field is absent. `--all` gains no new column for this — the row is already at hamr's
|
|
2209
|
+
named limit.
|
|
2210
|
+
|
|
2211
|
+
**Unknown money/time is always `null`/`unknown` — never `0`, and a partial sum is never stamped
|
|
2212
|
+
exact** (F6's rule, carried through every level this module reports at): `spentUsd`/`wallMs` are
|
|
2213
|
+
`null` when the run's own `job-end` is absent or carries no figure; `spendComplete` reads `false`
|
|
2214
|
+
whenever `spentUsd` is `null`, regardless of what the record's own flag says. The SAME rule
|
|
2215
|
+
governs one occurrence/iteration's own `spentUsd`: it sums `worker-round.costUsd`
|
|
2216
|
+
(`src/planrun.js:2288-2297`) over records strictly inside that occurrence's own `seq` window —
|
|
2217
|
+
a single `null` `costUsd` anywhere in the window makes the WHOLE occurrence's `spentUsd` `null`
|
|
2218
|
+
(`unpricedRounds` names how many), never a partial sum. Zero rounds in the window is a real `$0`,
|
|
2219
|
+
not an unknown one.
|
|
2220
|
+
|
|
2221
|
+
`stopReason` is `null` on a green/already-green outcome; otherwise `category — decision —
|
|
2222
|
+
detail` from the last `escalation` record, every part present that record actually carries
|
|
2223
|
+
(`category` dropped when it duplicates the outcome already shown beside it; `detail` trimmed at
|
|
2224
|
+
400 chars with an explicit `…[+N chars]` marker, never silent) — `decision` alone is the generic
|
|
2225
|
+
one-line prose every category shares ("the provider path failed mid-run…" fires for every
|
|
2226
|
+
transport casualty); **`detail` is what actually killed THIS run** (a TLS "bad record mac", a
|
|
2227
|
+
specific mypy error) and is the reason a stop is explained, never the decision prose alone.
|
|
2228
|
+
Falls back to the bare outcome string for a pre-escalation red (`plan-red`/`job-red`/
|
|
2229
|
+
`smoke-red`). `ending.escalation` carries that same last escalation record whenever it falls
|
|
2230
|
+
outside the fixed 3-before window (common on a run that keeps emitting bookkeeping records after
|
|
2231
|
+
its real failure) — never silently dropped. `lastEscalation` is the same raw record exposed
|
|
2232
|
+
unconditionally (not gated on the window check) — `summarizeForAllLine`'s `reason` column reads
|
|
2233
|
+
it directly rather than re-deriving from `ending`.
|
|
2234
|
+
|
|
2235
|
+
**A step id can recur** (a replan re-executes the same step): each `step-start`..`step-end` pair
|
|
2236
|
+
is its own **occurrence** (`occurrence: 1, 2, …`), and every field on it — `rounds`, `toolCalls`,
|
|
2237
|
+
`checks`, `treeChanged`, `wallMs`, `spentUsd` — is scoped to records strictly between THAT pair's
|
|
2238
|
+
own `seq` (the spine's monotonic per-record counter, never `ts` — every record in one spine
|
|
2239
|
+
shares the same seq counter, so it is an exact ordering where two same-millisecond `ts` stamps
|
|
2240
|
+
are not), never pooled across every occurrence sharing the id. `rounds` counts real
|
|
2241
|
+
`worker-round` records (`phase === 'step:<id>'`), never the declared cap; `toolCalls` is the one
|
|
2242
|
+
thing still windowed by `ts` (the gate-audit carries no `step` or shared `seq`). `tripped` is the
|
|
2243
|
+
last `escalation` whose `seq` falls inside the occurrence's window — `{category, detail}`, with
|
|
2244
|
+
`detail` falling back to `decision` when the escalation site carries no `detail` at all (a
|
|
2245
|
+
ladder-exhaustion `cap-halt`, `src/ralph.js`, is the measured example — without the fallback the
|
|
2246
|
+
tripped line would print a bare category and hide the real reason).
|
|
2247
|
+
|
|
2248
|
+
**A run whose spine carries no `step-start` at all** — the plan's only step was
|
|
2249
|
+
already-green-skipped on a resumed leg, or a genuinely loop-only shape (measured: `u-msf70nei`,
|
|
2250
|
+
a resumed leg whose one step was already proven and only the CLOSE-FIX loop ran) — has
|
|
2251
|
+
`timelineKind: 'iterations'` and `iterations[]` populated from `iteration-start`
|
|
2252
|
+
(`src/ralph.js:583`) instead: each row runs from one `iteration-start` to the next
|
|
2253
|
+
`close-verdict`/`run-end`/`escalation` (`src/ralph.js:674,577/657/676/698`), whichever comes
|
|
2254
|
+
first by `seq`. `steps`/`iterations` are never both populated — exactly one is non-empty. Each
|
|
2255
|
+
iteration's own `closeStage` (`{verdict, stages}`) is set when a `close-verdict` ended it, distinct
|
|
2256
|
+
from the run's FINAL `close` (below).
|
|
2257
|
+
|
|
2258
|
+
**`close`** is the run's real, final close — resolved by picking the LAST (by `seq`) record
|
|
2259
|
+
among every `close-verdict` that carries an ARRAY `stages` field and every `outer-close`
|
|
2260
|
+
(`src/planrun.js:3296`). A `close-verdict` with NO `stages` came from a PLAN STEP's own
|
|
2261
|
+
micro-loop, judged by the exit evaluator instead of a command (`src/planrun.js:2907`) — never
|
|
2262
|
+
"the close". Picking by `seq` rather than preferring `outer-close`'s TYPE matters: `outer-close`
|
|
2263
|
+
is the PRECHECK against the real close, fired once before the fix loop even starts, so when it
|
|
2264
|
+
reads red the fix loop's own staged close-verdicts run strictly AFTER it — measured on
|
|
2265
|
+
`u-msf70nei`, whose `outer-close` (seq 14) reads `needs_revision` but whose fix loop's own final
|
|
2266
|
+
close-verdict (seq 97, LATER) reads `satisfied`, which is what the run actually ended on
|
|
2267
|
+
(`job-end.outcome:'green'`). A run that never reached ANY close (e.g. `u-msdsmkid`, stopped by a
|
|
2268
|
+
step-variance meter before `judgeClose()` ever fired) resolves `close: null`, printed as `none —
|
|
2269
|
+
the run ended before any close ran` — never omitted.
|
|
2270
|
+
|
|
2271
|
+
`replans` prefers `plan-executed.replans` (`src/planrun.js:2688`) and falls back to counting
|
|
2272
|
+
`materials` records stamped `phase:'replan'` (`src/planrun.js:2520`) when that field is absent —
|
|
2273
|
+
an older archived spine can predate the field (measured on `u-msdsmkid`, an Aug-3 run). `branch`
|
|
2274
|
+
reads the last `work-branch.branch` (`src/planrun.js:1589`) or `null` when absent — also measured
|
|
2275
|
+
missing on the same era of archive, before the work-branch rung landed.
|
|
2276
|
+
|
|
2277
|
+
`wallMs` is THIS FILE's own `job-start`→`job-end` span, labeled `(this file)` — on a RESUMED leg
|
|
2278
|
+
that is only the latest engagement, not the whole signed chain. `chainClock` is the other true
|
|
2279
|
+
number: the SIGNED wall read off the run's own clock record — `wall-halt` when present (the
|
|
2280
|
+
TERMINAL reading, taken at the moment the wall stopped the run; `src/planrun.js:1269`'s
|
|
2281
|
+
`emitWallHalt`), else the single `wall-clock` record every run emits near its start
|
|
2282
|
+
(`src/planrun.js:1203`) — both spread from `clock.report()` (`src/clock.js:235-247`: `bounded,
|
|
2283
|
+
requestedMs, closeStages, enforcedMs, elapsedMs, remainingMs`). `elapsedMs` on either is
|
|
2284
|
+
chain-scoped by construction (`createClock`'s `priorElapsedMs` fold, `src/clock.js:174`), so it
|
|
2285
|
+
can legitimately exceed this file's own `wallMs` on a resumed run. `wall-halt`'s reading is
|
|
2286
|
+
end-of-run-honest, so `formatReplay` states it plainly: `clock: 60m04s of 60m00s signed (chain,
|
|
2287
|
+
from wall-halt)`. `wall-clock`'s reading is taken at the TOP of the engagement (chain time
|
|
2288
|
+
already inherited, not this run's own duration) — printing that under the same template read as
|
|
2289
|
+
"this run took 0 seconds" on a cold run, so it gets its own honest wording instead: `clock: 30m00s
|
|
2290
|
+
signed · 0.0s inherited from prior legs at start (from wall-clock; no end-of-run reading in this
|
|
2291
|
+
file)`.
|
|
2292
|
+
|
|
2293
|
+
**Money carries the identical split** (hamr, 2026-08-25): a RESUMED run's `job-end.spentUsd` is
|
|
2294
|
+
the CHAIN total (prior legs folded in — `src/run.js:227`'s `chainFoldUsd`, seeded from
|
|
2295
|
+
`job-start.priorSpentUsd`), which can never equal this ONE file's own rounds — measured on
|
|
2296
|
+
`u-msf70nei`: header `spentUsd` reads `$5.3389` while its own 55 `worker-round`s sum to exactly
|
|
2297
|
+
`$1.2127`. `resumed` reads `job-start.priorSpentUsd > 0` — the SAME condition `src/run.js` itself
|
|
2298
|
+
folds on — deliberately NOT `resume-seed`'s presence: `resume-seed` (`src/planrun.js:2622`) is a
|
|
2299
|
+
NARROWER record, fired only when a plan is actually reloaded mid-flight, and its absence does not
|
|
2300
|
+
mean "not resumed" — measured directly on 4 real archived runs (`litectx-maintainer-bareloop`'s
|
|
2301
|
+
`msx7xoe0`/`msx87qqs`/`msxf9129`/`msxf2mwi`) that carry a real `priorSpentUsd` fold with NO
|
|
2302
|
+
resume-seed record at all. `resumeSeed` (when present) supplies the DETAIL —
|
|
2303
|
+
`{phase, completed, skipping, divergence}` — printed as `resumed: yes — phase close, 1 of 1
|
|
2304
|
+
completed step(s) skipped`; a resumed leg with no resume-seed record still reads `resumed: yes
|
|
2305
|
+
(no resume-seed detail recorded)`, honestly, rather than falling back to `no`. `thisFileSpend`
|
|
2306
|
+
prefers `job-end.engagementSpentUsd` (F103, `src/run.js:284` — this engagement's own fold-out
|
|
2307
|
+
spend) when the record carries it, falling back to summing this file's own `worker-round.costUsd`
|
|
2308
|
+
when it does not (measured absent on `u-msf70nei` itself); an unpriced round anywhere in THIS
|
|
2309
|
+
FILE makes the figure `null` regardless of source (F6, never a partial sum stamped exact) — both
|
|
2310
|
+
figures print on the `spent:` line, each labeled with its source, never one silently standing in
|
|
2311
|
+
for the other. `spendMismatch` is the same-class check for a NON-resumed run: there is no fold to
|
|
2312
|
+
explain a gap, so `job-end.spentUsd` disagreeing with this file's own summed rounds by more than
|
|
2313
|
+
$0.0001 is a genuine finding, printed as `MISMATCH vs job-end`, never hidden — measured across
|
|
2314
|
+
every non-resumed run in the available archive (aurora-u, bareagent-u, litectx-maintainer): zero
|
|
2315
|
+
trip it. `--all`'s spend column stays the chain total (what a directory scan asks first) but
|
|
2316
|
+
marks a resumed row with a trailing `*` on its id, with one footnote line
|
|
2317
|
+
(`* resumed run — spend is the chain total, see detail`) printed once, only when the listing
|
|
2318
|
+
holds at least one such row.
|
|
2319
|
+
|
|
2320
|
+
`formatReplay` renders the full page: a header block (`goal:`/`shape:`/`signed:`/`branch:`/
|
|
2321
|
+
`resumed:`/`outcome:`/`spent:`, every absent field printing the literal `none recorded` — never
|
|
2322
|
+
blank, never a fabricated `0`), a `TIMELINE (steps)` or `TIMELINE (iterations — loop-shape run)` section with
|
|
2323
|
+
its own column-header row (`# step result rounds tools checks tree wall spend`, or the
|
|
2324
|
+
iteration equivalent without `checks`/`tree`) and a `↳ tripped:`/`↳ close:` line under any row an
|
|
2325
|
+
escalation or close fell inside, a `CLOSE` section (never omitted — `none — the run ended before
|
|
2326
|
+
any close ran` when nothing ever closed; the failing stage, if any, prints FIRST with `✗`, the
|
|
2327
|
+
rest `name OK`), `ENDING` (last record + 3 before it, plus an out-of-window escalation), the
|
|
2328
|
+
`BEHAVIOUR` block, and a `MEMORY-CACHE` line **always printed** — the existing wording when the
|
|
2329
|
+
run carried a record, `not armed on this run` otherwise. An absent record is never silent.
|
|
2330
|
+
`RESULT` words are `GREEN`/`RED`/`UNKNOWN` — `satisfied`/`green`/`already-green` read GREEN,
|
|
2331
|
+
every other non-null value reads RED.
|
|
2332
|
+
|
|
2333
|
+
`summarizeForAllLine(summary)` builds one `--all` row's raw fields (`id, job, shape, outcome,
|
|
2334
|
+
spend, wall, steps, reason`) — `shape` is `plan`/`loop` (from `timelineKind`, never the
|
|
2335
|
+
always-literal-`'plan'` `job-start.shape` field); `steps` is a bare count for a plan-shape run,
|
|
2336
|
+
`"N it"` for a loop-shape one; `reason` is `category — <first clause of detail, cut at 60 chars,
|
|
2337
|
+
plain "…", no [+N] marker>` off `lastEscalation` — **`detail` only, never `decision`** for this
|
|
2338
|
+
column specifically (unlike the full report's `↳ tripped:` line, which does fall back to
|
|
2339
|
+
`decision` — this compact row has no room for both and the coordinator's own ruling keeps
|
|
2340
|
+
`decision`'s generic per-category prose out of it); `-` on a green row; the distinct, literal
|
|
2341
|
+
`no job-end (killed mid-run)` when the spine never reached a `job-end` at all (`outcome` itself
|
|
2342
|
+
is `null`) — not folded into the ordinary escalation case. `formatAllLines(entries)` renders every
|
|
2343
|
+
row as a FIXED-COLUMN, ALIGNED table with a header row (never CSV — columns are padded, not
|
|
2344
|
+
delimited; the header's own label lengths count toward the column widths) plus any
|
|
2345
|
+
`<name> not-a-spine` lines at their original position (unpadded — a filename carries no
|
|
2346
|
+
outcome/spend/wall of its own). No header prints when there are zero real spines in the listing.
|
|
2347
|
+
|
|
2348
|
+
`scripts/run-replay.mjs <spine.jsonl>` resolves the sibling `-gate-audit.jsonl` automatically and
|
|
2349
|
+
prints the full report; `--all <dir>` is **name-agnostic** — the patient corpus does not agree on
|
|
2350
|
+
one filename convention (`u-<id>.jsonl`, `battery-A1-<id>.jsonl`, `l2accept-L1-<id>.jsonl`,
|
|
2351
|
+
`reuse-<id>.jsonl`, `types-screen-C-<id>.jsonl`, …), so a spine is identified by CONTENT: any
|
|
2352
|
+
`.jsonl` that is not a sidecar by name (`*-gate-audit.jsonl`, `*.lag.jsonl`) and whose records
|
|
2353
|
+
include a `job-start` (present on every real spine checked, nested wrappers included) or
|
|
2354
|
+
`run-start` (present on most, absent on a run that died before ralph's first iteration). A
|
|
2355
|
+
`.jsonl` matching neither is printed as `<name> not-a-spine` — never silently dropped or
|
|
2356
|
+
misread as an empty run. Both modes mirror `scripts/behaviour-readout.mjs`'s own malformed-line
|
|
2357
|
+
handling (skip-and-count, never throw).
|
|
2358
|
+
|
|
2359
|
+
**F117's PRD TODO #20 is now landed in full**: both the verdict CLASS and the worker MODEL
|
|
2360
|
+
name are surfaced (see `verdictType`/`model` above) — hamr blessed the item verbatim
|
|
2361
|
+
("1") on 2026-08-25, so the spine-WRITER change to `job-start` (`src/run.js`) that a
|
|
2362
|
+
report-only reader would not otherwise make on its own authority went in with his explicit
|
|
2363
|
+
word. `judgedCount` on a close-verdict was never a reliable stand-in for the class either way
|
|
2364
|
+
(it appears on plain green closes too, not only softgreen ones) — this reads the real
|
|
2365
|
+
declared field instead.
|
|
2366
|
+
|
|
2367
|
+
**Transport retries and floor reasons (F119, live-proven 2026-08-25)**: every
|
|
2368
|
+
`transport-retry` record (`src/planrun.js:126`) whose `seq` falls inside a step's or
|
|
2369
|
+
iteration's own window prints a `↳ transport retry ×N (recovered|not recovered|partially
|
|
2370
|
+
recovered) — <first 80 chars of the error>` line under that row — the same open-interval
|
|
2371
|
+
`seq` windowing `windowSpend` already uses for rounds. A retry whose `seq` falls outside
|
|
2372
|
+
every occurrence window (e.g. during the scout/plan phase) is counted instead under the
|
|
2373
|
+
`TIMELINE` header as `transport retries outside steps: N`, printed only when that count is
|
|
2374
|
+
non-zero. The header's `spent:` line, whenever `spendComplete` is `false`, appends
|
|
2375
|
+
`· floor because: <reason(s)>` — derived from the spine, never guessed: `transport retry
|
|
2376
|
+
×N` (any `transport-retry` record), `unpriced round(s)` (a `worker-round`/`judge-round`
|
|
2377
|
+
with non-finite `costUsd`), `cut mid-call` (a `wall-halt` record with `cutMidCall:true`),
|
|
2378
|
+
`stall` (a `stall` record), `prior leg floor` (`job-start.priorSpendComplete === false`) —
|
|
2379
|
+
every cause that actually shows evidence prints, never just one; a floor with no derivable
|
|
2380
|
+
evidence on this spine (an older archive, or a resumed leg's floor inherited from a file not
|
|
2381
|
+
in hand) prints `· floor (reason not in spine)` instead. `--all` adds no new column: a run
|
|
2382
|
+
that carried any transport retry gets ` ⟲N` appended directly after its outcome word (same
|
|
2383
|
+
posture as the resumed `*` on `id`), with one footnote
|
|
2384
|
+
(`⟲N transport retry(ies), recovered inline — see the full replay for detail`) printed once
|
|
2385
|
+
when the listing holds at least one such row. Live-proven on `u-mt8yk53k` (F119): two
|
|
2386
|
+
recovered TLS `bad record mac` retries, both inside step `fix-mypy-strict`'s window —
|
|
2387
|
+
`floor because: transport retry ×2` and `green ⟲2` in `--all`, on a run that finished green.
|
|
2388
|
+
|
|
2389
|
+
```js
|
|
2390
|
+
import { replayRun, formatReplay } from 'bareloop';
|
|
2391
|
+
const spine = fs.readFileSync(spineFile, 'utf8').trim().split('\n').map(JSON.parse);
|
|
2392
|
+
const audit = fs.readFileSync(auditFile, 'utf8').trim().split('\n').map(JSON.parse);
|
|
2393
|
+
console.log(formatReplay(replayRun(spine, audit, { runId: 'mszcthk1' })));
|
|
2394
|
+
```
|
|
2395
|
+
|
|
2396
|
+
### `PROMPT_REGISTERS` / `isPromptFile(path)` — `src/promptregisters.js`, and the prompt-commit rule
|
|
2397
|
+
|
|
2398
|
+
PRD build-list item 5 (TODO #8), Q9 answered (hamr, 2026-08-25): "a check". A commit that
|
|
2399
|
+
changes a **prompt register** — one of the model-facing system/strategy/instruction
|
|
2400
|
+
strings a worker or judge actually reads (`SCOUT_SYSTEM`, `AUTHOR_SYSTEM`,
|
|
2401
|
+
`PERSONA_TOOLS`, `READ_SHIM_STRATEGY`, and their neighbors — the full, verified list is
|
|
2402
|
+
`PROMPT_REGISTERS` in `src/promptregisters.js`) — must say, in its own message, three
|
|
2403
|
+
things: what failure caused the change, what it addresses, and what it corrects.
|
|
2404
|
+
Convention-only was rejected: doctrine already on record is "a frozen rule without a
|
|
2405
|
+
wired detector is prose." `isPromptFile(path)` is the pure predicate the check runs
|
|
2406
|
+
against; both are exported from `bareloop`'s root so the inventory has exactly one home.
|
|
2407
|
+
|
|
2408
|
+
hamr's addition (2026-08-25, same day): the `Failure:` line must ALSO cite the **run**
|
|
2409
|
+
that caused the change — `run <id>` or `run u-<id>` (case-insensitive at the `run`
|
|
2410
|
+
keyword, multiple refs fine), matched by `\brun\s+(u-)?[a-z0-9]{8}\b` (tightened from an
|
|
2411
|
+
initial `{6,12}` margin after a PR #23 review found the margin loose enough to accept
|
|
2412
|
+
plain prose — "run failed" is 6 lowercase letters, satisfying `{6,12}` outright — to the
|
|
2413
|
+
measured exact shape: every real archived id is exactly 8 characters. A per-id digit
|
|
2414
|
+
requirement was proposed in that same review and rejected: 23 of 130 re-sampled real ids
|
|
2415
|
+
carry no digit at all, `msdsmkid` among them — a real archived run cited by name
|
|
2416
|
+
elsewhere in this repo — so requiring a digit would reject genuine citations). The commit
|
|
2417
|
+
points at the run; `replayRun`/`formatReplay` (above) close the loop the other way,
|
|
2418
|
+
turning that run id back into the whole story. A `Failure:` line with no run reference
|
|
2419
|
+
fails with a distinct message: `Failure: must cite the run that caused the change (e.g.
|
|
2420
|
+
"Failure: run mszcthk1 — ...")`. FORMAT ONLY — the check never verifies the cited run's
|
|
2421
|
+
spine file actually exists (a run's patient lives outside this repo entirely, so there is
|
|
2422
|
+
nothing here to check it against).
|
|
2423
|
+
|
|
2424
|
+
Enforcement is **local only** — wired into `npm test` (see `package.json`'s `test`
|
|
2425
|
+
script), never into `.github/workflows/*` (CI already runs `npm test`, so the rule is
|
|
2426
|
+
enforced there without an ask-first CI edit). The check itself lives outside the
|
|
2427
|
+
published package, at `scripts/prompt-commit-check.mjs` (pure decision logic in
|
|
2428
|
+
`scripts/promptcommitlib.mjs`, `scripts/` is not in `package.json`'s `files`, so none of
|
|
2429
|
+
this ships):
|
|
2430
|
+
|
|
2431
|
+
```
|
|
2432
|
+
node scripts/prompt-commit-check.mjs --range origin/main..HEAD # validates every commit
|
|
2433
|
+
# in the range that
|
|
2434
|
+
# touches a prompt file
|
|
2435
|
+
node scripts/prompt-commit-check.mjs --message-file .git/COMMIT_EDITMSG
|
|
2436
|
+
# commit-msg hook mode:
|
|
2437
|
+
# validates one message
|
|
2438
|
+
# against staged files
|
|
2439
|
+
```
|
|
2440
|
+
|
|
2441
|
+
A compliant message:
|
|
2442
|
+
|
|
2443
|
+
```
|
|
2444
|
+
fix: tighten PERSONA_TOOLS
|
|
2445
|
+
|
|
2446
|
+
Failure: run mszcthk1 — worker read the arbiter spine after being told it was denied
|
|
2447
|
+
Addresses: PERSONA_TOOLS did not name the spine file explicitly
|
|
2448
|
+
Corrects: spells the denied paths from ARBITER_BOOK_STORES
|
|
2449
|
+
```
|
|
2450
|
+
|
|
2451
|
+
A commit that touches no prompt-register file is passed without inspecting its message.
|
|
2452
|
+
An unresolvable `--range` (no such ref — a fresh clone with no `origin`, a detached
|
|
2453
|
+
checkout that never fetched) is a **skip**, printed and exited 0: a missing baseline is
|
|
2454
|
+
not a rule violation, never a red.
|
|
2455
|
+
|
|
2456
|
+
|
|
2087
2457
|
## Architecture
|
|
2088
2458
|
|
|
2089
2459
|
Three layers. An **outer shell** (dumb, permanent): per-run budget cap via bareguard,
|
|
@@ -2115,7 +2485,7 @@ is disclosed to the authoring agent; only admitted verbs are callable per job.
|
|
|
2115
2485
|
## Gotchas
|
|
2116
2486
|
|
|
2117
2487
|
*TBD from real adopter friction; recorded here as they're found (repo-side friction goes
|
|
2118
|
-
to `docs/FINDINGS.md`).*
|
|
2488
|
+
to `docs/logs/FINDINGS.md`).*
|
|
2119
2489
|
|
|
2120
2490
|
## Constraints
|
|
2121
2491
|
|