bareloop 0.2.0 → 0.4.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 +426 -0
- package/README.md +15 -6
- package/bareloop.context.md +295 -14
- package/package.json +3 -3
- package/src/extract.js +14 -6
- package/src/index.js +13 -3
- package/src/interpret.js +652 -29
- package/src/job.js +119 -3
- package/src/ledger.js +266 -0
- package/src/ralph.js +245 -15
- package/src/root.js +305 -0
- package/src/run.js +507 -0
- package/src/text.js +65 -8
- package/src/validate.js +42 -1
- package/types/extract.d.ts +4 -2
- package/types/index.d.ts +5 -3
- package/types/interpret.d.ts +47 -6
- package/types/job.d.ts +32 -1
- package/types/ledger.d.ts +76 -0
- package/types/ralph.d.ts +111 -17
- package/types/root.d.ts +96 -0
- package/types/run.d.ts +71 -0
- package/types/text.d.ts +35 -1
- package/types/validate.d.ts +13 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,432 @@ 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.4.0] — 2026-07-21
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **Layer R — the root, the within-run ratchet (`src/root.js`; design record
|
|
15
|
+
`docs/plans/2026-07-19-layer-r-design.md`, interview-locked 2026-07-19).** The shell
|
|
16
|
+
mechanically detects fixation — consecutive attempts rewriting the same file(s)
|
|
17
|
+
without moving the close's kept-failure set — and injects an escalating note into the
|
|
18
|
+
next attempt's prompt: a capped summary first, then the worker's own prior failed
|
|
19
|
+
edit content verbatim (the BA-14 rejected-edit-buffer shape, rewritten for our
|
|
20
|
+
attempt loop). Fixation-gated (inert when not stuck — RSI §3.3: the lift is a
|
|
21
|
+
fixation phenomenon, an honest null on a strong unstuck model), shell-authored (the
|
|
22
|
+
worker authors nothing, gains no verb), within-run only (state dies with the run;
|
|
23
|
+
inheritance stays verdict-gated). Red-set comparison strips the spec-reporter's
|
|
24
|
+
per-run duration stamps (POC-measured: kept lines are never byte-stable) —
|
|
25
|
+
comparison-only, the delivered gap is untouched. Spine event `root-injected` carries
|
|
26
|
+
stage/mode/streak/paths, never content (append-only law). Ships **OFF by default**
|
|
27
|
+
(`layerRoot: false` on `runJob`/`interpret`; pass `true` for the ON/experimental arm) —
|
|
28
|
+
decided 2026-07-21: fixation is extinct on every current job (F41), so ON has never won
|
|
29
|
+
its own A/B; the default flip to `true` defers to the first Layer 2 job that produces
|
|
30
|
+
natural fixation (see `docs/01-product/LAYERS.md` Layer R note).
|
|
31
|
+
|
|
32
|
+
### Fixed
|
|
33
|
+
|
|
34
|
+
- **F44 — fresh whole-branch review: three correctness bugs + cleanups, all validated and
|
|
35
|
+
mutation-proven.** (1) A casualty job-end laundered unknown spend as complete: `1b0720c`
|
|
36
|
+
put `spentUsd` on every path, so a provider-red TRANSPORT THROW reported
|
|
37
|
+
`spendComplete: true` (F6 violation — the failed call's cost is unknown) and the battery
|
|
38
|
+
scripts' `spentUsd == null` casualty-STOP never fired. Fixed both sides: the transport
|
|
39
|
+
provider-red now reports `spendComplete: false` (the metered draft-truncation keeps
|
|
40
|
+
`true`), and the frozen battery/probe scripts key their STOP on
|
|
41
|
+
`spendComplete === false || spentUsd == null`. (2) Layer R's outcome probe mislabeled a
|
|
42
|
+
missed-anchor edit as "landed" when its `newText` appeared elsewhere (substring
|
|
43
|
+
false-positive) — now discriminated by the tool result, exact-equality for writes, one
|
|
44
|
+
read not two. (3) Layer R false-fired on a progressing text-mode worker with no gapKeep
|
|
45
|
+
(write-overlap is constant-true when the single target is rewritten every attempt) — a
|
|
46
|
+
`writesInformative` flag now requires a known-unmoved red-set when writes carry no
|
|
47
|
+
information. Cleanups: double redaction, unbounded `attempts` retention, a dead ternary.
|
|
48
|
+
(2) and (3) are latent behind the OFF-by-default `layerRoot`.
|
|
49
|
+
|
|
50
|
+
- **Layer R settled its note on the gate's allow, which is written BEFORE the tool runs
|
|
51
|
+
(F43).** An `allow` record states INTENT, never that bytes reached a file:
|
|
52
|
+
`shell_edit` returns an anchor miss as a refusal *result* and a byte-cap overflow as a
|
|
53
|
+
throw, both leaving the file untouched with the allow already on the audit. The
|
|
54
|
+
verbatim note could therefore present content to the worker as "your own previous
|
|
55
|
+
changes — they landed" while that text was provably absent from the file it named
|
|
56
|
+
(reproduced end-to-end: 3 allowed edits, 0 bytes changed). Fixed by splitting the two
|
|
57
|
+
axes rather than merging them — the DETECTOR keeps reading intent (an edit that never
|
|
58
|
+
applied is still repetition; a tree-diff detector measured blind to it on every
|
|
59
|
+
attempt), while the NOTE settles on the observed file through `Loop`'s
|
|
60
|
+
`onToolResult` seam. An unapplied repeat now names the missed anchor instead, which is
|
|
61
|
+
the mechanical gap genre (F38). `commitWrite` is replaced by `settleWrite(landed)`.
|
|
62
|
+
- **Guarded the `maxTurns` LLM-round invariant (F43 follow-up).** bareguard's `maxTurns`
|
|
63
|
+
ticks on every `gate.record`; our cap means "LLM rounds" only because the sole record
|
|
64
|
+
path is the LLM one (tool calls take `gate.check`, which does not tick). That was
|
|
65
|
+
correct but unguarded — wiring tool results into `gate.record` would silently halve the
|
|
66
|
+
LLM budget (the F37 lower-silent-ceiling class). Added a guard test pinning "every
|
|
67
|
+
`gate.record` is `type:llm`" (mutation-proven: wiring tool records turns it red) and a
|
|
68
|
+
config comment marking the invariant load-bearing. No behavior change; no upstream ask —
|
|
69
|
+
bareguard already offers the correct counters.
|
|
70
|
+
- **F41 — the disposition: armed-and-inert, field read deferred.** Before any ON/OFF
|
|
71
|
+
battery spent money, two cheap reads measured the disease's base rate: the archive
|
|
72
|
+
sweep (`poc/layer-r-base-rate.mjs`, $0 — every surviving spine is OFF-arm by
|
|
73
|
+
construction) read 0 fixated in 10 pairs on jobs #2/#4; two frozen probes on a
|
|
74
|
+
rebuilt job #1 patient (`scripts/run-probe-layer-r.mjs`, $10.12 total) read 0
|
|
75
|
+
fixated in 4 pairs — including against a three-plant tree (three subsystems, one fix
|
|
76
|
+
cannot green) that forced a full 8→4→green ladder across three judged attempts.
|
|
77
|
+
Every pair was healthy navigation. F21's fixation was a broken-loop symptom, cured
|
|
78
|
+
by the F20/F21/F30/BA-13 fixes. Because ON has therefore never won its own A/B,
|
|
79
|
+
Layer R ships **OFF by default** (decided 2026-07-21; `layerRoot: true` is the ON
|
|
80
|
+
arm) — measured cost-free when enabled and healthy (zero injections across all
|
|
81
|
+
probe runs); the repetition-drop ON/OFF read, and the default-flip decision, defer
|
|
82
|
+
to the first run whose spine records `root-injected`. No learning claim is minted.
|
|
83
|
+
|
|
84
|
+
### Changed
|
|
85
|
+
|
|
86
|
+
- **`jobs/litectx-maintainer.json` `budgetUsd` 1.5 → 4.5** (probe 2, re-signed): $1.50
|
|
87
|
+
funded exactly ONE judged attempt on the real patient — a run could red at attempt 1
|
|
88
|
+
and die at the gate mid-attempt-2, structurally unable to produce across-attempt
|
|
89
|
+
evidence. The advertised budget must fund the attempts the cap promises.
|
|
90
|
+
|
|
91
|
+
## [0.3.0] — 2026-07-19
|
|
92
|
+
|
|
93
|
+
### Fixed
|
|
94
|
+
|
|
95
|
+
- **A `close.judged` pattern with more than one capture group now reds at validation
|
|
96
|
+
(F40).** `runClose` reads capture group 1 only, so an alternation carrying the count
|
|
97
|
+
in another branch left group 1 `undefined` → `NaN` → `judgedCount` null → an exit-0
|
|
98
|
+
**green stamped `crashed`** (the mirror of the fake green the floor exists to catch);
|
|
99
|
+
at precheck that escalates `close-crashed` before the worker runs. The validator's
|
|
100
|
+
message already promised ONE group and only enforced "not zero". Alternation stays
|
|
101
|
+
expressible with non-capturing branches: `(?:a|b) (\d+)`.
|
|
102
|
+
- **Upstream-ledger attribution reads a typed `lib` field instead of sniffing error
|
|
103
|
+
prose (F40).** `interpret` prefixes every worker-loop error with `worker loop:`, and
|
|
104
|
+
the verb sniff ran first — so a bare-agent transport failure whose text merely
|
|
105
|
+
contained "recall" was billed to litectx. The throw site now stamps the owner it
|
|
106
|
+
knows, `ralph` relays it, and prose remains the fallback for older spines (the
|
|
107
|
+
`request-red` contract).
|
|
108
|
+
- **An unrecognised escalation category is counted, not silently dropped (F40).** The
|
|
109
|
+
dispatch keyed on four bare literals with no default, so a new or renamed category
|
|
110
|
+
vanished exactly like a deliberate exclusion. Exclusions are now an executable set;
|
|
111
|
+
anything outside {classified} ∪ {excluded} is charged to bareloop as a stale mapping.
|
|
112
|
+
- **The drafter is offered a PER-STEP share of the budget, not the whole pot (F40).** The
|
|
113
|
+
config is drafted once and re-validated at every step against what is left, so a ceiling
|
|
114
|
+
sized to the whole budget went stale the moment step 1 spent: step 2 then red `bounds` on
|
|
115
|
+
an unchanged config with money still in the pot. The advertised ceiling is now
|
|
116
|
+
`(budgetUsd − drafting reserve) ÷ predicate steps`, which still fits after earlier steps
|
|
117
|
+
have spent (the shares sum to the reserve-less budget). A drafting bound, not an
|
|
118
|
+
enforcement one — cap-not-estimate is unchanged and still tested, and a step needing more
|
|
119
|
+
than its share cap-halts cleanly rather than starving the steps after it. **Every shipped
|
|
120
|
+
job has one predicate step and is arithmetically identical.**
|
|
121
|
+
- **`jobs/aurora-fix.json` counts tests EXECUTED, not tests PASSED (F40).** A passed-count
|
|
122
|
+
floor conflated "did the close judge?" with "did the tests pass?", so the red tree the job
|
|
123
|
+
exists to fix fell under the floor and escalated as an instrument crash at precheck,
|
|
124
|
+
before the worker ever ran. Pattern is now `collected (\d+) items`; the patient's
|
|
125
|
+
`close.sh` swaps `-q` for `-ra` (`-q` prints no executed-count line at all, and `-ra` is
|
|
126
|
+
required or the job's `gapKeep "^FAILED "` loses every line it carries to the worker).
|
|
127
|
+
- **Revisor rounds no longer spend the worker's per-attempt bound (F40).**
|
|
128
|
+
`roundsThisAttempt` resets once, *before* the revisor phase, and revisor turns share
|
|
129
|
+
the worker's metered handler — so R revisor rounds silently left the worker 40−R while
|
|
130
|
+
the prompt still advertised 40, and a revisor burning the bound stopped the worker loop
|
|
131
|
+
before its first tool call. Money still meters on the run's axis (F12); only the round
|
|
132
|
+
charge moved. Restores "the advertised bound and the enforced bound stay the same
|
|
133
|
+
numbers on both axes", and matches the carve-out the summarizer fold already had.
|
|
134
|
+
- **Two F6 cost launderings closed (F40).** `run-job1`/`run-job2` printed
|
|
135
|
+
`spent: $0.0000` for provider-red/pricing-red runs (whose `job-end` carries no
|
|
136
|
+
`spentUsd`, and which can end after real priced spend) — now `UNKNOWN`, the
|
|
137
|
+
`run-battery` spelling. The `revision-red`/`revision-accepted` spine events recorded
|
|
138
|
+
`costUsd: 0` for an unpriced revisor — now the honest null.
|
|
139
|
+
|
|
140
|
+
### Added
|
|
141
|
+
|
|
142
|
+
- **`scanSecrets(raw)` — the ONE spelling of the raw-text secret scan (F40).** The scan
|
|
143
|
+
was hand-rolled at seven call sites off `SECRET_PATTERNS`; detection and redaction
|
|
144
|
+
must never disagree about what a secret looks like. Exported beside the inventory,
|
|
145
|
+
`sweepSecretLiterals`'s text-side twin.
|
|
146
|
+
|
|
147
|
+
### Changed
|
|
148
|
+
|
|
149
|
+
- **Tool-mode attempt bound raised 24→40 rounds (F37).** The TESTGEN calibration curve
|
|
150
|
+
measured that no prompt condition (bound undisclosed / disclosed / disclosed+pacing
|
|
151
|
+
strategy) produced a graded one-shot at 24 rounds — the worker's read-first prelude
|
|
152
|
+
eats the window — while one run proved a form-passing suite fits when writing starts
|
|
153
|
+
by mid-attempt. The per-attempt cutoff and the Gate's run-wide `maxTurns` now derive
|
|
154
|
+
from ONE hoisted `TURNS_PER_ATTEMPT` constant (they must agree or the enforced bound
|
|
155
|
+
drifts from the advertised one).
|
|
156
|
+
|
|
157
|
+
### Added
|
|
158
|
+
|
|
159
|
+
- **F32 — worker-crash attribution: a close crash the worker caused is a gap, not a stop.**
|
|
160
|
+
Battery pass 1 (F31) measured the gap: 4 of 7 rows whole-file-rewrote an orchestrator,
|
|
161
|
+
broke imports, the close crashed under the judged floor, and the run **escalated** — the
|
|
162
|
+
worker was never told "your edit crashed the suite", so no plant that needed a second
|
|
163
|
+
attempt ever got one. F17's forbidden zone was built against instrument crashes and could
|
|
164
|
+
not see worker-attributable ones. Now: a `crashed` verdict with worker writes on record
|
|
165
|
+
routes as the DISTINCT verdict **`worker-crash`** (spine event with the file list), the
|
|
166
|
+
gap tells the worker which files it wrote and to fix or revert, and the loop continues
|
|
167
|
+
under the same caps. Attribution instrument: the gate audit's allow-decision write/edit
|
|
168
|
+
lines, run_id-scoped, read through an injected `workerWrites` seam (`ralph` stays
|
|
169
|
+
stdlib-only and dumb). Escalation is UNCHANGED for true instrument crashes: crash at
|
|
170
|
+
precheck (structurally pre-worker) or crash with zero writes stays `close-crashed`, never
|
|
171
|
+
retried; an unreadable audit attributes nothing (fail toward the old behavior). Validated
|
|
172
|
+
against the real instrument (P3 rerun, sonnet, $0.77): all three crashes routed and fed
|
|
173
|
+
back, zero escalations-eaten rows, honest `cap-halt` stop — pass 1's same plant died at
|
|
174
|
+
attempt 1 with the worker never told. TDD, suite 292 → 299.
|
|
175
|
+
- **BA-13 consumed — the anchored edit verb (`bare-agent` 0.27.0 → 0.29.0).** `TOOL_MENU`
|
|
176
|
+
gains **`edit`** (job-spec grantable; `run` stays locked), `TOOL_BY_VERB` maps it to
|
|
177
|
+
`shell_edit` (anchored exact-once replace: BA-4 param guards, atomic rename, anchor-miss
|
|
178
|
+
as a refusal RESULT so the worker re-anchors). Judged by the SAME `writeScope` fence as
|
|
179
|
+
`write` (bareguard action type `'edit'`, already in its FS vocabulary); the F32
|
|
180
|
+
attribution instrument counts edit actions as worker writes; the tool-mode persona
|
|
181
|
+
carries the strategy (prefer the edit verb — F31: 4 of 5 big-file whole-writes broke the
|
|
182
|
+
tree). The frozen battery spec pins its grant explicitly, so the menu widening does NOT
|
|
183
|
+
change what the signed hash buys — granting `edit` to the battery is a new spec version.
|
|
184
|
+
Suite 299 → 303.
|
|
185
|
+
|
|
186
|
+
### Fixed
|
|
187
|
+
|
|
188
|
+
- **F33 — two verb-blind reporting instruments (battery pass 2's audit).** Pass 2 ran
|
|
189
|
+
7/7 attempt-1 green at $0.94 under the newly signed `edit`-granting spec — and the
|
|
190
|
+
battery's printed table said `writes=0` on a pass whose every fix was one anchored edit:
|
|
191
|
+
the collector counted only `write` actions, built before the `edit` verb existed and
|
|
192
|
+
never re-audited when the menu widened (F32's lesson, re-learned at the reporting layer
|
|
193
|
+
in the same session it was minted). `culpritRead` was equally blind to the retrieval
|
|
194
|
+
channels: P5 greened with the culprit's body handed to it by the drafted config's
|
|
195
|
+
`before-attempt` recall hook (`body: true` + litectx recency boost ranking the
|
|
196
|
+
just-planted chunk into the hits) — invisible to an instrument that only saw gated
|
|
197
|
+
reads. Fixed: the collector counts write AND edit as write-class; `culpritRead` sees
|
|
198
|
+
every read-class channel; both recall emit sites (`ctx-tool`, `hook-op`) now carry
|
|
199
|
+
`paths` so downstream instruments can see what reached the worker's context. The
|
|
200
|
+
archived pass-2 results JSON keeps its wrong zeros; FINDINGS F33 is the corrected read.
|
|
201
|
+
|
|
202
|
+
- **F28 — the gap bound cut every failure line out of the worker's feedback.** The first
|
|
203
|
+
real end-to-end firing of the N2 loop delivered a 1,927-char gap containing **zero**
|
|
204
|
+
`not ok` lines: `ralph`'s `boundGap` keeps a head sample + elided middle + tail, and a
|
|
205
|
+
large TAP suite (`npm test`, ~67KB) prints its failing tests in the *middle* — so the
|
|
206
|
+
worker was told "5 fail" and never *which*, three attempts running, and never navigated to
|
|
207
|
+
the culprit file. New optional **`close.gapKeep`** (job-spec, `predicate` only): a regex
|
|
208
|
+
**source** whose matching close-output lines are preserved in a capped, clearly-delimited
|
|
209
|
+
kept-failures block between head and tail — the failing-test NAMES reach the worker
|
|
210
|
+
regardless of where they print. Hard-capped (50 lines / 8192 bytes, whichever binds) so a
|
|
211
|
+
pathological close cannot rebuild the bloat; a trimmed block announces the trim (no silent
|
|
212
|
+
truncation). Validated like `judged.pattern` (must compile as a RegExp, else a spec red
|
|
213
|
+
before any tokens); **arbiter territory** — the drafted workflow config cannot express it.
|
|
214
|
+
Also fixed the adjacent hazard: the gap now combines **both** streams (stdout + stderr), so
|
|
215
|
+
a failure on stdout survives stderr noise (the old `err || out` returned stderr alone and
|
|
216
|
+
lost it). `jobs/mailproof-fix.json` gains `"gapKeep": "^not ok"` (spec re-signed). Threaded
|
|
217
|
+
spec → `runJob` → `interpret` → `ralph` parallel to `judged`. TDD, suite 281 → 291.
|
|
218
|
+
|
|
219
|
+
### Changed
|
|
220
|
+
|
|
221
|
+
- **Consume `bare-agent@0.27.0` — the N2 build gate (BA-4) is cleared.** 0.27.0 ("Provider
|
|
222
|
+
Fidelity & Honest Termination") shipped the entire upstream ask queue this rung filed:
|
|
223
|
+
BA-4, BA-5, BA-3, BA-6, BA-7, BA-1 (+ BA-10/BA-12). **BA-4** (`shell_write` truncating a
|
|
224
|
+
file to zero bytes on absent `content`) was the hard N2-exit blocker — its four acceptance
|
|
225
|
+
criteria are re-verified locally against the published tarball, so a write-granting tool
|
|
226
|
+
mode can ship honestly. The tool-mode middle (`src/interpret.js`) is updated to the new
|
|
227
|
+
contract: a **`truncated:max_tokens`** round (BA-6 — a round the API cut off, previously
|
|
228
|
+
laundered into a clean `error:null` finish, F25) now escalates as **provider-red** (retry,
|
|
229
|
+
the F11 transport class) instead of being scored as an empty attempt; **`loop.stop()`**'s
|
|
230
|
+
new `error:null`+text return (BA-3/BA-5) let the `stoppedByBound` shim be **deleted** (it
|
|
231
|
+
was dead under the new contract and could have swallowed a genuine halt); and
|
|
232
|
+
**`cacheMessages: true`** (BA-1) is wired on the worker loop — the transcript cache the
|
|
233
|
+
job #1 cost wall (F18: 754k full-price tokens, died at cap) needed. Regression test for
|
|
234
|
+
the truncation path added (mutation-checked). Suite 280/280, typecheck clean.
|
|
235
|
+
|
|
236
|
+
- **Agent/IDE scratch gitignored and de-tracked.** `.gitignore` now default-denies every dot-directory (`.*/`), re-admitting only what ships (`.github/`). Per-machine agent/IDE state (`.claude/`, `.litectx/`, `.idea/`, …) regenerates locally and only added noise and churn; any already-committed copies are removed from tracking (local files kept on disk). Repo hygiene only.
|
|
237
|
+
|
|
238
|
+
### Added
|
|
239
|
+
- **N2 — the headless single-job loop (rung 3 of the ladder), modules 1–4 + 2b.**
|
|
240
|
+
- **`runJob(spec, opts)`** (`src/run.js`): the runner — approval gate (human-signs-always,
|
|
241
|
+
refuses before ANY token: `unapproved-spec`) → litectx known-answer smoke before tokens
|
|
242
|
+
(`smoke-red`, adaptlearn A3) → config drafting through the PRICED path, one sealed shot
|
|
243
|
+
+ one redraft with reds fed back (`config-red` on a second red, zero grinding) →
|
|
244
|
+
sequential per-step interpret loops under the ONE cumulative ledger (a step-red stops
|
|
245
|
+
the job with attribution: `step-red:<id>`) → the hitl step opens a **draft PR
|
|
246
|
+
deterministically** (branch → stage the job fence ONLY → commit → push →
|
|
247
|
+
`gh pr create --draft`, through an injectable shell-owned exec seam — model tools never
|
|
248
|
+
touch git; a failure is `pr-red` + the escalation still fires) and ends `escalated` by
|
|
249
|
+
design, the PR URL riding the decision-ready escalation. New spine vocabulary:
|
|
250
|
+
`job-start/end`, `step-start/end`, `primitive-smoke`, `draft-result`, `pr-opened`,
|
|
251
|
+
`pr-red`.
|
|
252
|
+
- **Tool-mode middle** (module 2b): `job-v1` steps gain `mode: "text"|"tools"` and
|
|
253
|
+
`tools` (unique subset of `read|grep|write` — `TOOL_MENU`, frozen; requesting `run`
|
|
254
|
+
reds: locked-but-listed, admission waits on request-red evidence). The SPEC owns the
|
|
255
|
+
grant; the drafted config cannot express either. In tool mode the worker drives
|
|
256
|
+
bare-agent's shell tools with every call policy-checked against the same fence
|
|
257
|
+
(`actionTranslator` maps tool calls onto write/read actions; paths resolve exactly as
|
|
258
|
+
the tools resolve them), reads pinned to the workdir (`readScope`), a denial streak
|
|
259
|
+
stopping as `gate-red`. `STEP_MODES`/`TOOL_MENU` exported.
|
|
260
|
+
- **`artifact-red` + fence-robust extraction** (module 3): ONE parser (`extractArtifact`)
|
|
261
|
+
for every model-output parse — prose-wrapped and mid-text fences extract clean; a
|
|
262
|
+
non-artifact response reds on its OWN axis, writes nothing, and the retry is told why
|
|
263
|
+
(non-terminal, under ralph's cap). Text mode only — in tool mode the tools write
|
|
264
|
+
directly and the close judges the tree (there is no response artifact to red).
|
|
265
|
+
- **ralph options** (module 1): `closeTimeoutMs` (close wall-clock cap, was hardcoded
|
|
266
|
+
120s) and the tail-biased gap bound (400 head + 1500 tail — the assertion diff lives
|
|
267
|
+
at the end; head-only truncation fed the worker pure preamble).
|
|
268
|
+
- **The upstream ledger** (module 4): `updateLedger({ledgerFile, spineFiles})`
|
|
269
|
+
(`src/ledger.js`) folds run spines into ONE append-only incident JSONL — the
|
|
270
|
+
A1/A2/A3 upstream-ask flow, mechanized: evidence in, human judgment out
|
|
271
|
+
(`suggestedAsk` is a template seed, never an auto-file; status rows
|
|
272
|
+
`filed → fixed → consumed` stay human-appended). Keys `lib:verb:class:sig` dedupe
|
|
273
|
+
the same bug across runs (short hash of the path/number-normalized detail); rows
|
|
274
|
+
are cumulative deltas, the fold is current state, and the collector is idempotent
|
|
275
|
+
over the same corpus — the ledger is derived and reconstructible, spines stay
|
|
276
|
+
ground truth. Classes worst-first (`LEDGER_CLASSES`, frozen): `silent-degradation`,
|
|
277
|
+
`runtime-red`, `provider-red`, `pricing-red` (added vs the design doc — F6),
|
|
278
|
+
`capability-gap` (ships dormant until in-loop admission), `broken-close`,
|
|
279
|
+
`request-red`, `retention-red`, `config-red` (attributed to bareloop's own
|
|
280
|
+
drafting schema). Excluded by doctrine: bare `cap-halt` (budget story),
|
|
281
|
+
`close-verdict`/`artifact-red` (worker stories), `gate-red` (governance working
|
|
282
|
+
as intended), `pr-red` (operator environment). Pure pieces exported:
|
|
283
|
+
`classifyIncidents`, `foldLedger`, `ledgerDeltas`. Design record:
|
|
284
|
+
`docs/plans/2026-07-11-upstream-ledger-design.md` + 2026-07-13 addendum (the
|
|
285
|
+
bareloop event mapping). CLI lands at N5; the panel reads the same file at N6.
|
|
286
|
+
- **Resume-to-cap: close-first skip** (module 4.5): every predicate step runs its
|
|
287
|
+
close BEFORE any tokens (`close-precheck` on the spine, output scrubbed at capture
|
|
288
|
+
like every close). Already-green skips the step for zero tokens as a DISTINCT
|
|
289
|
+
record (`step-end` outcome `already-green`, never plain `green` — nothing was done,
|
|
290
|
+
so it mints no learning credit and runs no on-green retention); a close that cannot
|
|
291
|
+
RUN stops `broken-close` before any provider call. Config drafting is deferred to
|
|
292
|
+
the first step that actually needs a worker (still one sealed shot + one redraft,
|
|
293
|
+
always drafted fresh per run) — so the resume story is: a `cap-halt` stop is the
|
|
294
|
+
checkpoint (the workdir + the closes), the human raises `budgetUsd` (new spec hash,
|
|
295
|
+
re-sign) and reruns, finished steps skip in seconds, and a clean cadenced rerun
|
|
296
|
+
costs ZERO provider calls. Design record: the 2026-07-13 addendum on
|
|
297
|
+
`docs/plans/2026-07-12-n2-headless-loop-design.md`.
|
|
298
|
+
- **The cadenced no-op is silent (F7).** A `hitl` step now checks the fence
|
|
299
|
+
(`git status --porcelain -- <fence>`) before touching git: an affirmatively-clean
|
|
300
|
+
fence emits `pr-skipped` + `step-end: already-green` and the job ends **green** —
|
|
301
|
+
no PR, no escalation. (Before: a green-repo cadence opened a branch, and `git
|
|
302
|
+
commit` correctly failed "nothing added to commit" → a broken-PR escalation every
|
|
303
|
+
single day. A hitl close is a human decision point; with no changes there is no
|
|
304
|
+
decision.) A FAILED check — not a repo, broken git — falls through to the PR path
|
|
305
|
+
and reds honestly: an unknown fence state is never a green.
|
|
306
|
+
- **The PR step hands the checkout back (F7).** The starting branch is read before
|
|
307
|
+
anything moves and restored on every path, success or failure; a restore that fails
|
|
308
|
+
is a loud `workdir-red` naming the stranded branch, and never un-opens a real PR.
|
|
309
|
+
(Before: the workdir was left on `bareloop/<job>-<id>`, so the next cadenced run
|
|
310
|
+
branched off the previous run's unmerged branch and judged its close against that
|
|
311
|
+
state.) New spine vocabulary: `close-precheck`, `pr-skipped`, `workdir-red`;
|
|
312
|
+
`step-end` gains the `already-green` outcome.
|
|
313
|
+
|
|
314
|
+
- **Nine defects found by the first REAL-MODEL runs of job #1 (F8–F16)** — all fixed
|
|
315
|
+
TDD-first, all invisible to a stubbed seam:
|
|
316
|
+
- **`cwd` (F8):** `runClose` spawned the close with NO cwd, so a cwd-relative close
|
|
317
|
+
(`npm test`) ran in the RUNNER's directory — the arbiter judged the wrong repository.
|
|
318
|
+
`cwd` now threads runner → `ralph` → `spawnSync` (`ralph({cwd})`, `runClose(…, {cwd})`).
|
|
319
|
+
- **Drafting ceiling (F9):** the prompt advertised the JOB budget while the validator
|
|
320
|
+
enforced budget − drafting-spend — a bound the drafter was never told, so every real run
|
|
321
|
+
(the model claims the ceiling it is given) deadlocked `config-red`. The shell now reserves
|
|
322
|
+
its own drafting allowance and advertises `budget − reserve`: one number, advertised and
|
|
323
|
+
enforced.
|
|
324
|
+
- **Repository root (F10):** tool mode now tells the worker the absolute workdir. bare-agent's
|
|
325
|
+
shell tools resolve relative paths against the PROCESS cwd, so a worker with no root is
|
|
326
|
+
blind — the real one groped `/home/…`, the runner's dir, then `/`, and the fence denied it.
|
|
327
|
+
- **`provider-red` in the worker path (F11):** a transport throw out of `loop.run()` (the real
|
|
328
|
+
run: `read ENETUNREACH`) was filed `interpreter-red` ("fix the middle"). It is a provider
|
|
329
|
+
failure — retry, don't debug.
|
|
330
|
+
- **Per-round metering (F12):** the ledger accounted `worker-result`, emitted only after
|
|
331
|
+
`loop.run()` RETURNS — so an attempt that HALTS reported nothing: the real run spent $1.4375
|
|
332
|
+
and the ledger said $0.0048. Money is now metered per ROUND (`worker-round`, at
|
|
333
|
+
`onLlmResult`), including the round that trips the cap. Unpriced is never free (F6).
|
|
334
|
+
- **The close's current output (F13):** the precheck's gap now reaches the first attempt as the
|
|
335
|
+
tree's state (never as "your previous attempt"). The `run` verb is locked, so without it the
|
|
336
|
+
worker cannot see the failure it was hired to fix.
|
|
337
|
+
- **The arbiter's books (F14):** tool mode denies reads of `gate-audit.jsonl`, `.smoke` and
|
|
338
|
+
`.litectx` — the real worker read its own gate audit and spine. The agent does not author its
|
|
339
|
+
arbiter, and does not read its records.
|
|
340
|
+
- **The loop contract (F16):** the tool persona now tells the worker it is ONE attempt inside
|
|
341
|
+
`while close-red and under-cap` and will be re-run with the close's verdict. Without it a
|
|
342
|
+
model one-shots — the real run read for 12 rounds, never wrote, and ate the whole budget.
|
|
343
|
+
- **Job #1's close (F15):** `node --test --test-reporter=dot`, not `npm test` — the tail-biased
|
|
344
|
+
gap bound buries mid-stream failures in a 391-test TAP stream, so the worker was told "3
|
|
345
|
+
failed" and nothing else. A close's output format is part of its contract with the worker.
|
|
346
|
+
|
|
347
|
+
### Changed
|
|
348
|
+
- **`job-v1`: requesting a locked tool is now a DISTINCT red.** `tools` containing
|
|
349
|
+
`run` reds with code `request-red` (was a generic `invalid-value`) so the ledger can
|
|
350
|
+
tally admission demand — a generic code buried the evidence as a typo. An unknown
|
|
351
|
+
tool name stays `invalid-value`. `LOCKED_TOOLS` (frozen, `['run']`) exported.
|
|
352
|
+
- **`interpret` opts:** `target` is now optional (required in text mode only); new
|
|
353
|
+
`mode`/`tools` opts thread the spec's grant. Additive — existing callers unchanged.
|
|
354
|
+
- **Cost contract:** `extractRules` returns `costUsd: number|null` — null is the honest
|
|
355
|
+
"spend unknown" (F6); callers must not coerce it to 0.
|
|
356
|
+
|
|
357
|
+
### Fixed
|
|
358
|
+
- **Review round 2026-07-13 (8 confirmed findings, all execution-verified):**
|
|
359
|
+
- **extractArtifact wrapper-vs-content gate:** a fence counts as the artifact's
|
|
360
|
+
wrapper only when it opens within the first 5 lines of the response; a fence
|
|
361
|
+
buried deeper inside an unfenced reply is the artifact's OWN content and the
|
|
362
|
+
whole reply is the artifact, verbatim. Before: an unfenced doc-generator module
|
|
363
|
+
containing a ```js example``` was silently truncated to the 2-line fragment
|
|
364
|
+
with `red: null`, corrupting the close signal. Trade-off pinned in tests:
|
|
365
|
+
past the window, prose + fence is treated as the artifact (rare under the
|
|
366
|
+
no-fences persona); fence-heavy artifacts belong in tool mode.
|
|
367
|
+
- **Secret-leak channel closed:** `openDraftPr` now scrubs git/gh subprocess output
|
|
368
|
+
with the ONE shape inventory (`SECRET_PATTERNS`) at capture — a credentialed
|
|
369
|
+
remote URL echoed by a failed `git push` never reaches `pr-red`/the escalation's
|
|
370
|
+
`pr.error` on the append-only spine (same doctrine as the close path).
|
|
371
|
+
- **Plan-shape spend is metered:** the job ledger accounts `worker-plan` events too
|
|
372
|
+
(a separate `loop.run` whose metrics never fold into the implement call's) —
|
|
373
|
+
plan calls now drain `spentUsd` and an unpriced plan call halts `pricing-red`.
|
|
374
|
+
- **The spine never dangles:** a provider transport throw during drafting is a
|
|
375
|
+
decision-ready **`provider-red`** terminal (new outcome + escalation category,
|
|
376
|
+
classified by the ledger); an interpreter throw outside the loop (e.g. a broken
|
|
377
|
+
gate audit path) escalates `interpreter-red` with a terminal `job-end`.
|
|
378
|
+
- **Reds-before-tokens for the call, not just the spec:** a text-mode job invoked
|
|
379
|
+
without `opts.target` is a `job-red` before ANY provider call (`interpret`
|
|
380
|
+
throws a loud TypeError for direct callers); previously it burned a draft + a
|
|
381
|
+
worker call, the gate default-allowed the absent path, and `writeFileSync`
|
|
382
|
+
crashed as a misfiled interpreter-red.
|
|
383
|
+
- **Whitespace-padded `close.cmd` reds** (`invalid-value`) — a leading space made
|
|
384
|
+
`spawnSync('')` throw synchronously past every belt; the runner also trims
|
|
385
|
+
before splitting (defense in depth). **`cap-halt` job outcome:** drafting spend
|
|
386
|
+
that consumes the whole budget stops honestly (no paid redraft over a blown
|
|
387
|
+
budget, no config-red blaming the drafter).
|
|
388
|
+
- **One instrument for the F6 cost read:** `priceOf(result)` (`src/text.js`)
|
|
389
|
+
replaces four hand-copied `metrics ? costUsd : (cost ?? null)` spellings;
|
|
390
|
+
`REMEMBER_KINDS` is exported from the validator so the drafting prompt
|
|
391
|
+
advertises the menu the validator enforces (no drift). `request-red` reds carry
|
|
392
|
+
the locked verb as a **structured `verb` field** — the ledger keys on it (prose
|
|
393
|
+
stays a legacy fallback). Stale "clamped by validation" JSDoc corrected (the
|
|
394
|
+
validator REDS bounds; it never clamps).
|
|
395
|
+
- **Three silent $0 cost launderings (the F6 class) in shipped code:** `interpret`'s
|
|
396
|
+
worker cost emit (`?? cost` chain), `extract.js`'s rules-path cost (`?? cost ?? 0`),
|
|
397
|
+
and `extract.js`'s transport-throw path reporting unmeasured spend as `$0`. All now
|
|
398
|
+
carry the honest null + `unpricedRounds`; `runJob` halts `pricing-red` on either
|
|
399
|
+
signal (unpriced is never free — F6, PRD v1.8).
|
|
400
|
+
|
|
401
|
+
### Fixed (release review, 2026-07-19 — fresh full gates over the whole branch)
|
|
402
|
+
|
|
403
|
+
- **The plan-only call no longer carries the tool menu.** In tool mode with a drafted
|
|
404
|
+
`loop.shape: 'plan'`, the decompose call ("Plan only, no code") was offered the full
|
|
405
|
+
granted menu — a model calling `shell_write` during the plan round mutates the tree
|
|
406
|
+
before the implement round exists. The menu IS the grant (2b): the plan call now gets
|
|
407
|
+
an empty menu. Reachable by any tool-mode job on any run (the drafter picks the shape);
|
|
408
|
+
the combination was unexercised by any test until now.
|
|
409
|
+
- **`extractRules` consumes the parser's own red.** It took `extractArtifact(...).code`
|
|
410
|
+
and dropped `.red`, so an empty model response surfaced as a generic JSON
|
|
411
|
+
`parse-error` instead of the already-computed `'empty response'` — the ONE-parser
|
|
412
|
+
doctrine requires both callers to consume the red field (`interpret` already did).
|
|
413
|
+
Now a distinct `artifact-red`.
|
|
414
|
+
- **`run-job1` couples `shellCapUsd` to `spec.budgetUsd`** like every sibling runner —
|
|
415
|
+
the library default cap of $2 was a second, silent ceiling: a signed resume top-up
|
|
416
|
+
above $2 would red `bounds` on a budget the human explicitly approved (the advertised
|
|
417
|
+
budget must BE the enforced one).
|
|
418
|
+
- **`ctx_get`'s repo-relative conversion is boundary-aware** (`workdir + sep`): a bare
|
|
419
|
+
prefix match would garble a sibling path like `<workdir>-backup/x`. Defense in depth —
|
|
420
|
+
the gate independently denies such paths before the tool executes.
|
|
421
|
+
- **`jobs/litectx-maintainer.json`: `gapKeep "^✖ "` + the `edit` grant** — the two
|
|
422
|
+
omissions vs every sibling spec. The keep pattern is derived from the real
|
|
423
|
+
`--test-reporter=spec` red output (failing tests repeat unindented in the summary
|
|
424
|
+
block, so each failing-test NAME survives the gap bound exactly once — F28). Spec
|
|
425
|
+
edit = new hash; `run-job1` refuses until re-approved.
|
|
426
|
+
- **`scanSecrets` + `CLOSE_FAULTS` are exported from the package root.**
|
|
427
|
+
`bareloop.context.md` documented both as public API; the exports map admits only
|
|
428
|
+
`"."`, so neither was actually reachable by an adopter. The contract is now true.
|
|
429
|
+
- **Recorded, parked (arbiter territory):** symlink write-through is bareloop's
|
|
430
|
+
caller-contract debt — bareguard documents that its fence resolves lexical traversal
|
|
431
|
+
only and callers must canonicalise. No granted verb can create a symlink; the vector
|
|
432
|
+
needs a pre-existing one inside the patient's writeScope. UPSTREAM-ASKS "OUR SIDE" §7.
|
|
433
|
+
|
|
8
434
|
## [0.2.0] — 2026-07-12
|
|
9
435
|
|
|
10
436
|
### Added
|
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
<p align="center">
|
|
14
14
|
<img src="https://img.shields.io/github/package-json/v/hamr0/bareloop?label=version&color=2a4f8c" alt="version (auto from package.json)">
|
|
15
15
|
<img src="https://img.shields.io/badge/license-Apache%202.0-2a4f8c" alt="license: Apache 2.0">
|
|
16
|
-
<img src="https://img.shields.io/badge/status-
|
|
16
|
+
<img src="https://img.shields.io/badge/status-Layer%20R%20%C2%B7%20ratchet%20armed-2a6d3a" alt="status: Layer R, ratchet armed">
|
|
17
17
|
</p>
|
|
18
18
|
|
|
19
19
|
**"Automate this job — I don't know the best workflow."** For tasks that are **repeated,
|
|
@@ -25,8 +25,15 @@ inheritance with ledger-counted attribution.
|
|
|
25
25
|
The pitch in one line: **workflows that earn their own design, with receipts** — every
|
|
26
26
|
inherited rule carries the green that minted it and the contrast that attributed it.
|
|
27
27
|
|
|
28
|
-
> **Status:
|
|
29
|
-
>
|
|
28
|
+
> **Status: Layer R — the within-run ratchet is built and armed.** `runJob()` takes a
|
|
29
|
+
> signed job spec through draft → per-step worker loops → close verdicts → draft PR,
|
|
30
|
+
> under one budget the agent can only tighten; the shell now also detects a worker
|
|
31
|
+
> stuck repeating itself and feeds back its own failed attempts (escalating
|
|
32
|
+
> summary→verbatim). Two frozen probes found the disease in remission under the current
|
|
33
|
+
> loop (F41: 0 fixated pairs in 14, jobs #1/#2/#4), so the ratchet ships inert-by-
|
|
34
|
+
> measurement with its field read deferred to the first run that trips it. Next is
|
|
35
|
+
> Layer 2 (plan-v1 micro-wheels), then executed inheritance (N3) — the ladder's
|
|
36
|
+
> kill-switch question.
|
|
30
37
|
|
|
31
38
|
## Quick start
|
|
32
39
|
|
|
@@ -93,9 +100,11 @@ ladder, and the stop is a result.
|
|
|
93
100
|
|
|
94
101
|
| Rung | What lands |
|
|
95
102
|
|---|---|
|
|
96
|
-
| **N0** | Port + outer shell + spine (token-free) |
|
|
97
|
-
| **N1** | Job/close schema + validator |
|
|
98
|
-
| **N2** | Single-job headless loop — job #1 minimal (review→fix→PR, hard greens only) |
|
|
103
|
+
| **N0** ✅ | Port + outer shell + spine (token-free) — v0.1.0 |
|
|
104
|
+
| **N1** ✅ | Job/close schema + validator — v0.2.0 |
|
|
105
|
+
| **N2** ✅ | Single-job headless loop — job #1 minimal (review→fix→PR, hard greens only) — v0.3.0 |
|
|
106
|
+
| **Layer R** ✅ | Within-run ratchet: shell-side fixation detector (intent) + rejected-edit feedback (outcome), **shipped OFF by default** (F41: fixation in remission, so ON has never won its A/B; F43 split the two axes; field read + default decision defer to Layer 2) — v0.4.0 |
|
|
107
|
+
| **Layer 2** | plan-v1 micro-wheels — the road: bounded steps with in-run verifiable exits (the F39-measured semantic converter) |
|
|
99
108
|
| **N3** | Executed inheritance + contrast-bit extractor — **kill-switch: rules must transmit across non-identical runs** |
|
|
100
109
|
| **N4** | Verdict classes complete (soft/HITL minting) |
|
|
101
110
|
| **N5** | Scheduler + budget ops |
|