bareloop 0.7.0 → 0.8.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 +283 -0
- package/README.md +4 -2
- package/bareloop.context.md +156 -18
- package/package.json +1 -1
- package/src/exits.js +8 -2
- package/src/job.js +43 -6
- package/src/ledger.js +57 -9
- package/src/plan.js +31 -100
- package/src/planrun.js +318 -62
- package/src/ralph.js +8 -1
- package/src/reuse.js +67 -4
- package/src/run.js +21 -2
- package/src/validate.js +121 -0
- package/types/exits.d.ts +14 -0
- package/types/job.d.ts +13 -2
- package/types/plan.d.ts +23 -38
- package/types/planrun.d.ts +116 -16
- package/types/ralph.d.ts +16 -0
- package/types/reuse.d.ts +5 -1
- package/types/run.d.ts +14 -1
- package/types/validate.d.ts +50 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,289 @@ 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
|
+
## [0.8.0] — 2026-08-07
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
- **The variance meter REPORTS progress — and still decides nothing on it** (F85, A+B;
|
|
12
|
+
`src/planrun.js`). The meter stopped a step that was CONVERGING and then told the redrafting
|
|
13
|
+
planner that step's exits were *"unmoved"*. Two instruments over one run, and only one of them
|
|
14
|
+
right. Live on `u-msh70zla`: the close gaps went 30 → 24 → 15 → 14 with the ladder recording
|
|
15
|
+
`distinctGaps 3` / `strikes 0`, while the replan brief said nothing had moved and the materials
|
|
16
|
+
said *"0 step(s) completed"* — so the replanner, told it had achieved nothing, rebuilt a file
|
|
17
|
+
that was already at zero errors, wrote nothing twice, and the run died with $1.09 and 7 minutes
|
|
18
|
+
unspent. hamr's ruling, verbatim: *"meter is right but missing a piece … it should give heads up
|
|
19
|
+
on money/time + progress for llm to judge."*
|
|
20
|
+
- **The firing condition is byte-identical** — `moneyShare`/`timeShare >= 0.5`, both axes, the
|
|
21
|
+
same threshold, the same throw. A progress term in the TRIGGER would make a governance
|
|
22
|
+
instrument over an operator-owned allowance judge capability, so a converging step that is
|
|
23
|
+
eating the run is still stopped. What was missing was never a term in the condition; it was
|
|
24
|
+
the READING.
|
|
25
|
+
- **The reading is read off `src/trend.js` — the SAME instance the money halt reads.** Nothing
|
|
26
|
+
new is computed at the meter: no second reader, because two instruments answering one question
|
|
27
|
+
is the exact defect being fixed. Per-stage, stage names and numbers only, and `unknown` stays
|
|
28
|
+
`unknown` — a close that reports no number donates nothing (F6).
|
|
29
|
+
- **The hardcoded `with its exits unmoved` string is DELETED.** It printed on every step-variance
|
|
30
|
+
stop whatever had happened, and on `u-msh70zla` it was simply false. The escalation detail —
|
|
31
|
+
which a human reads and which the replan brief quotes — now states the meter's fact (*a share of
|
|
32
|
+
the run was consumed*) and then the trend instrument's measured reading, as two separate claims
|
|
33
|
+
rather than one fused verdict on the work. The materials `progress` line keeps its structural
|
|
34
|
+
sentence (*"step N of M did not finish; K step(s) completed before it"* — a plan's shape is what
|
|
35
|
+
the planner re-allocates) and gains the close trend beside it.
|
|
36
|
+
- **Spine change:** the `variance` record gains `trend` / `motion` / `reading` / `series` — the
|
|
37
|
+
same four names and the same shapes the money halt already emits, so one reader parses a
|
|
38
|
+
progress reading wherever it appears. ADDED fields, never repurposed ones (the spine is
|
|
39
|
+
append-only forever). Measured post-fix on `u-mshcpdg4`, iteration 3 on `finish-strict-typecheck`:
|
|
40
|
+
`moneyShare 0.497`, `timeShare 0.769`, `axis time`, `trend "converging"`,
|
|
41
|
+
`reading "still progressing — typecheck 30 → 15 → 15 → 1"`,
|
|
42
|
+
`series [{stage:"typecheck",values:[30,15,15,1]}]` — against `u-msh70zla`'s pre-fix record, which
|
|
43
|
+
carried the shares and nothing else.
|
|
44
|
+
- **`runTrend` was blind to everything a STEP achieves** (F85, the prerequisite found en route). The
|
|
45
|
+
run's trend reader was fed only the close PRECHECK and the outer fix loop, so a meter firing
|
|
46
|
+
mid-step had nothing to report about a step that had just gone 24 → 15 → 14. Two folds corrected:
|
|
47
|
+
a step's failing check is an arbiter-rendered close grade (same `runStages`, same chain, same stage
|
|
48
|
+
names) and is now recorded like one; and it is recorded on the RAW gap, because `evalExits` wraps
|
|
49
|
+
stage output as `check "<name>" red: …` and that wrapper line carries the word "red" with no number
|
|
50
|
+
on it — so every step grade used to donate a null. Measured on `u-msh70zla`'s own archived gaps:
|
|
51
|
+
raw reads `typecheck 24 → 15 → 14`, wrapped reads nothing at all. Plus a **preflight seed** so the
|
|
52
|
+
counter has a baseline: the precheck reds at the close's FIRST failing stage (first-red-wins) and
|
|
53
|
+
every shipped close opens with `changed-from-seed`, so the numeric stages had no baseline at all and
|
|
54
|
+
a run would read `unknown` on exactly the stop the readout exists for. Seeded ONCE PER STAGE, off the
|
|
55
|
+
reader's own books — the precheck and the preflight menu grade the same unchanged tree back to back,
|
|
56
|
+
and a repeated baseline in a series reads as an attempt that achieved nothing, which is the phantom
|
|
57
|
+
flat step this change exists to remove.
|
|
58
|
+
- **`tests/resume-u.test.js` is decoupled from the live job spec.** It read
|
|
59
|
+
`jobs/bareagent-u-types.json` while hardcoding $8 / 45min, so an operator budget edit on a live spec
|
|
60
|
+
turned the suite red. Every number now derives from the spec it reads, proven by passing at a third
|
|
61
|
+
budget value. Suite: **1023 tests** (1027 minus the 4 deleted with the `never wrote` feature below);
|
|
62
|
+
typecheck and `build:types` exit 0.
|
|
63
|
+
- **The per-step model tier menu narrows to `['sonnet']`** (`STEP_MODELS`, `src/plan.js` — a PUBLIC
|
|
64
|
+
surface: a plan declaring `model: "haiku"` is now REDDED by `validatePlan` with `invalid-value` at
|
|
65
|
+
`steps.N.model`, carrying the menu). hamr's order, verbatim: *"haiku should be dropped to see if it
|
|
66
|
+
passes, if it's agent poor work or harness, like always."* **This is a reversible ATTRIBUTION PROBE,
|
|
67
|
+
not a finding that haiku is incapable** — what the $0 archive read behind it shows is a CONFOUND, in
|
|
68
|
+
both directions. Over 186 spines / 71 accepted plans / 255 steps, only **20 steps ever declared a tier
|
|
69
|
+
at all**: haiku **12** — 9 of them on a REPLANNED plan, 6 the LAST step of their plan, round bounds
|
|
70
|
+
3–12, **2 ever green** — against explicitly-declared sonnet **8**, 2 on a replan, 3 last, bounds 14–32,
|
|
71
|
+
**5 green**. The planner reaches for haiku on steps it judges mechanical, and a replanned step sits on
|
|
72
|
+
a run already in trouble, so neither column is a controlled read; and all three bareagent-u runs of
|
|
73
|
+
2026-08-06 tiered their replanned step down (haiku/6, haiku/8, haiku/12) and all three failed. That
|
|
74
|
+
confound sat directly on top of the programme's most recent capability read (F86's tail). Removing the
|
|
75
|
+
tier is how it gets attributed: with one tier expressible, a still-failing replan is agent or harness,
|
|
76
|
+
never the tier.
|
|
77
|
+
- **The `model` field and its validator branch STAY.** A one-entry menu means a plan — or a stored
|
|
78
|
+
bridge — declaring `model: "sonnet"` still validates, and restoring haiku is a one-token edit. No
|
|
79
|
+
stored bridge declares haiku (verified).
|
|
80
|
+
- **The drafter prompt's `model` line is DELETED** rather than reduced to a one-item menu: it
|
|
81
|
+
advertised *"a cheaper tier for mechanical steps"* and there is no cheaper tier now, and reciting a
|
|
82
|
+
single legal value spends tokens to invite a no-op field (`attempts` is the existing
|
|
83
|
+
legal-but-not-offered precedent). Verified by RENDERING the real prompt — 0 lines match
|
|
84
|
+
`/model|tier|haiku|sonnet|cheaper/i` in either the fresh or the replan form.
|
|
85
|
+
- **Only what the AGENT may express narrows**, which is the safe direction. `--model haiku` remains
|
|
86
|
+
the OPERATOR's probe knob (PRD v1.36), untouched in `scripts/run-u.mjs`; two comments there called
|
|
87
|
+
the runner's tier map *"the same closed menu the planner uses"* and now say plainly that it is
|
|
88
|
+
deliberately wider.
|
|
89
|
+
- **No spec hash moved.** `jobSpecHash` covers the resolved spec, not `STEP_MODELS`; all 10 job specs
|
|
90
|
+
were re-hashed and none moved — bareagent-u and bareguard-u are still the hashes hamr signed.
|
|
91
|
+
- Measured on the first sonnet-only run against the last haiku-era one, same patient: **10.9 → 17.5
|
|
92
|
+
seconds per round** (1.6× slower), 120 → 87 rounds, and the failure CLASS changed — struck out at 2
|
|
93
|
+
remaining errors with money and clock left, versus still converging at 1 error when the wall
|
|
94
|
+
expired. Better rounds are slower rounds, and the binding constraint moved from the strike ladder to
|
|
95
|
+
the clock. That sonnet-only run was also the first to reach the outer close, where it was redded for
|
|
96
|
+
**silencing the type checker instead of typing the code** (F87's suppression genre) — the close
|
|
97
|
+
refused it, and the eventual green removed the suppressions.
|
|
98
|
+
- **`bareloop.context.md` named `sonnet | haiku` in two places and is corrected** — the shipped adopter
|
|
99
|
+
contract carried the old menu in the `steps[].model` schema row and in the `providerFor` (P) section,
|
|
100
|
+
so an integrating agent reading it would have drafted a plan that reds. Both now name the one-entry
|
|
101
|
+
menu and say why it is one entry.
|
|
102
|
+
|
|
103
|
+
### Fixed
|
|
104
|
+
- **A locked-VERDICT `request-red` no longer files as a bare-agent upstream ask** (`src/job.js`,
|
|
105
|
+
`src/ledger.js`). `classifyIncidents` hardcoded `lib: 'bare-agent'` for every `request-red`, but the
|
|
106
|
+
code covers two territories: a locked TOOL verb (bare-agent, the original case) and a locked VERDICT
|
|
107
|
+
type (`soft-green`/`hitl`) — demand against bareloop's OWN `VERDICT_TYPES` menu, live-misfiled for
|
|
108
|
+
every `LOCKED_VERDICTS` declaration (the BA-2 misattribution class). Per the typed-lib rule the
|
|
109
|
+
territory is now stamped at the EMIT site (`Red` gains `lib?`; `verdictType` → `bareloop`, `tools` →
|
|
110
|
+
`bare-agent`) and the ledger reads it, keeping the hardcode only as the pre-field legacy-spine
|
|
111
|
+
fallback (zero archived rows carry the code, verified). The `suggestedAsk` template renders the
|
|
112
|
+
stamped target, so a bareloop-catalogue refusal can never seed a bare-agent ask; D13's genre refusals
|
|
113
|
+
(close-authoring design record) route by stamping their own lib with no ledger change. 5 new
|
|
114
|
+
failing-first tests; `capability-gap` still hardcodes bare-agent — flagged, structurally unreachable
|
|
115
|
+
today (a validation red returns before any provider call; the module header says so).
|
|
116
|
+
- **…and `capability-gap` no longer hardcodes it either — the flag above is CLOSED** (`src/ledger.js`).
|
|
117
|
+
`capability-gap` is `request-red`'s cap-halted form and inherits both of its territories, but the fix
|
|
118
|
+
landed at one of the two sites and not the other: the synthesis loop still wrote `'bare-agent'`
|
|
119
|
+
literally, and so did its `suggestedAsk` template — the one field a human actually files from, where
|
|
120
|
+
fixing only the occurrence's `lib` would have left the misattribution fully intact. The territory now
|
|
121
|
+
comes from the OCCURRENCE (the `request-red` rows already carry the lib stamped at their emit site),
|
|
122
|
+
and the **dedup key becomes `(verb, lib)` rather than the verb alone**: the ledger's own occurrence
|
|
123
|
+
key carries `lib`, so a `Set` over verbs would merge two territories into one row and attribute it to
|
|
124
|
+
whichever the set happened to hold first — an arbitrary target, which is the defect itself. One gap
|
|
125
|
+
per (territory, verb) costs nothing and each carries an honest one. **Reachability is unchanged and
|
|
126
|
+
still DORMANT** — `validateJob` returns not-ok on any red and `run.js` returns on a validation red, so
|
|
127
|
+
a `request-red` and a `cap-halt` cannot share one spine today; this is a latent-consistency fix at a
|
|
128
|
+
seam the module header documents as dormant, and it claims no live bug. 3 new failing-first tests,
|
|
129
|
+
each labelled in-file as a FUNCTION-CONTRACT fixture (`classifyIncidents` is pure) so a later reader
|
|
130
|
+
cannot mistake the synthetic stream for something a run can produce. Closed now rather than left
|
|
131
|
+
flagged because a fix that lands at one of two identical sites is the class this repo has already
|
|
132
|
+
paid for once — the ripgrep install that went into `ci.yml` and not `publish.yml`, which then failed
|
|
133
|
+
on the identical cause.
|
|
134
|
+
- **The replan ceiling reset on EVERY resume leg — a kill refilled a bound the operator signed**
|
|
135
|
+
(`src/planrun.js`, `src/run.js`, `src/reuse.js`, `scripts/run-u.mjs`). `replanned`/`varianceGrantUsed`
|
|
136
|
+
were locals in `runPlan`, and a resume is another `runPlan` call. Confirmed by execution before any
|
|
137
|
+
remedy: leg 1 replanned and stopped; leg 2, driven with a `resumeSeed`, replanned again — a run total
|
|
138
|
+
of 2 with every record showing 1. Two became four by being killed once, which is precisely the creep
|
|
139
|
+
the latch exists to make impossible (*"unlimited replanning launders thrash as adaptation"*, PRD
|
|
140
|
+
v1.12).
|
|
141
|
+
- **Fixed on the MONEY-FOLD precedent, not the grade-seed one, and the choice is the fix.**
|
|
142
|
+
`readGradeSeed`'s documented known limit is that it reads ONE spine, so a resume of a resume
|
|
143
|
+
inherits leg 2's grades and not leg 1's — the chain shortens by a leg per kill. That is fail-safe
|
|
144
|
+
for a READOUT (a short chain can only under-claim a direction) and it is the DANGEROUS direction
|
|
145
|
+
for a CEILING, where an under-claimed ledger is a refilled allowance. So each leg DECLARES the
|
|
146
|
+
ledger it inherited on its own spine (`priorSpentUsd`'s mechanism) and the next reader adds only
|
|
147
|
+
its own window's `replan` records. Leg 3 inherits the whole chain, not leg 2's slice.
|
|
148
|
+
- **The counter-argument was answered, not stepped over.** `src/trend.js` deliberately refuses to
|
|
149
|
+
seed the fix loop's ITERATIONS from history, and that stays right: an attempt allowance is a LEG
|
|
150
|
+
bound, and a restarted leg buys its own attempts with its own money. A replan ceiling bounds how
|
|
151
|
+
many times the WORKFLOW may be redrawn, which is the run's span — the same span the halt readout
|
|
152
|
+
already covers. The two seeds disagree on purpose and both now say so in-source.
|
|
153
|
+
- **SPINE FIELDS — `job-start` gains `priorReplans` / `priorReplanGrantUsed`.** Emitted only when
|
|
154
|
+
there is a fold (a decorative `0` is indistinguishable from a run nobody folded), on the same
|
|
155
|
+
declaration `priorSpentUsd`/`priorWallMs` already ride. The grant latch travels BESIDE the count
|
|
156
|
+
rather than being derived from it: a leg can inherit a spent ordinary ceiling with the arbiter's
|
|
157
|
+
extra still unearned (`1`, `false`), and deriving one from the other would collapse two real states
|
|
158
|
+
into one. `readResume`'s `direct` mode reads the fold off these very fields, so a U-path run
|
|
159
|
+
resumed twice inherits leg 1.
|
|
160
|
+
- **SPINE MEANING CHANGE — `plan-executed.replans` counts the CHAIN, not the leg** (and `replanned`
|
|
161
|
+
with it). **An adopter parsing spines must re-read this field.** The change was made rather than
|
|
162
|
+
shadowed by a second field precisely because `replans` shipped only on this unreleased branch, so
|
|
163
|
+
no archived spine carries the leg reading for a reader to be confused by. The leg's own number is
|
|
164
|
+
not lost: a leg emits one `replan` record per replan it drafts, so `chain − records-in-this-window`
|
|
165
|
+
is the fold it inherited — the same arithmetic `readResume` runs — and a second field stating a
|
|
166
|
+
derivable number would be a second reader of one question.
|
|
167
|
+
- **New option: `runJob(spec, { resumeReplans })`** (and `runPlan`'s identically-named option),
|
|
168
|
+
`{count, grantUsed} | null`, defaulting to `null` = the cold path, byte-identical to a run nobody
|
|
169
|
+
resumed. Belted like every declared fold entering the arbiter's arithmetic: a garbage, negative or
|
|
170
|
+
non-finite seed reads as `0` rather than poisoning later comparisons as `NaN`, and a negative one
|
|
171
|
+
can never cancel a real replan and widen the ceiling. `scripts/run-u.mjs --resume` and `runReuse`
|
|
172
|
+
both pass it. **The disarm control holds** — a resumed leg with an UNSPENT ceiling still replans, so
|
|
173
|
+
the bound is bounded and not switched off.
|
|
174
|
+
- **The replan brief's `closeGapBlock` re-bounded an ALREADY-bounded gap and deleted the red names the
|
|
175
|
+
first envelope had rescued** (`src/planrun.js`, `src/exits.js`). `src/exits.js` states the rule for
|
|
176
|
+
this same artifact one seam earlier: the check gap arrives already enveloped by `runClose`'s
|
|
177
|
+
`boundGap` under the stage's own `gapKeep`, which deliberately rescues the `not ok`/`FAILED` names
|
|
178
|
+
out of the elided middle — the mechanical gap F46's conversion mechanism feeds the worker. A second
|
|
179
|
+
400/1500 envelope deleted exactly those a second time, which is F28 reintroduced; measured on a
|
|
180
|
+
120-red-line close output, the first bound rescued **78** names and the second dropped **12** of
|
|
181
|
+
them. `CHECK_GAP_MAX` (12,000) is now **exported** from `src/exits.js` and imported here rather than
|
|
182
|
+
respelled, so the two seams cannot drift on how big a check gap may be, and it is applied as a
|
|
183
|
+
**backstop, not an envelope**: under it the gap passes through VERBATIM. The headroom is measured,
|
|
184
|
+
which is why the ceiling is not smaller — `runClose`'s own envelope tops out at ~10.1KB (400 head +
|
|
185
|
+
8192 keep + 1500 tail + labels), and joined with a `tree-changed` detail that is ~10.2KB, leaving
|
|
186
|
+
~1.8KB to spare, so the shipped path is untouched and the backstop fires only where `src/exits.js`
|
|
187
|
+
says it should: a seam that returned something never bounded at all. Length is measured AFTER the
|
|
188
|
+
secret scrub, because a mask is wider than most literals it replaces and bounding the raw string
|
|
189
|
+
would let a gap cross the ceiling on its way through the one transformation that always runs.
|
|
190
|
+
- **The replan brief's block was LABELLED as the close's own output but could lead with ours**
|
|
191
|
+
(`src/planrun.js`). `res.gap` is `lastGap` — the join of EVERY failing exit's detail in exit order —
|
|
192
|
+
and with `MAX_EXITS_PER_STEP` at 2 plus the mandatory `tree-changed` pairing on a write step, a step
|
|
193
|
+
that wrote nothing opens the block with the EVALUATOR's own prose (`0 files changed under src/** …`)
|
|
194
|
+
under a label reading *"the verification's own output"*. Captured verbatim from a real replan prompt.
|
|
195
|
+
The **label** is corrected to *"What this step's exits reported on its last attempt (their own
|
|
196
|
+
output, verbatim)"*; the **payload is deliberately kept** — "the step wrote nothing" is exactly what
|
|
197
|
+
a replanner should be told, and stripping it to make an old label true would trade a real fact for a
|
|
198
|
+
tidy sentence. F86's thesis (the artifact travels as TEXT, never parsed) is restated, not weakened.
|
|
199
|
+
- **F49's ReDoS reject now covers the OPERATOR's two regex fields, not just the agent's**
|
|
200
|
+
(`src/job.js`, `src/validate.js`, `src/plan.js`). `hasNestedQuantifier` shipped wired at exactly ONE
|
|
201
|
+
site — the agent-authored `artifact-written.pattern`. Two operator-authored patterns reach an
|
|
202
|
+
equally untimed evaluator and were uncovered: `judged.pattern`, exec'd by `runClose` against the
|
|
203
|
+
close's whole stdout+stderr, and `gapKeep`, compiled by `boundGap` and run **once per line** of
|
|
204
|
+
close output. Both now red `invalid-value` at the validation gate, before any tokens burn.
|
|
205
|
+
- **Measured, not argued:** `boundGap(stream, '(a+)+$')` against a 40-character line does not finish
|
|
206
|
+
in 20 seconds (`timeout` exit 124).
|
|
207
|
+
- **Why this is worse than an operator self-DoS, and F67 is the reason.** On the agent side F49's
|
|
208
|
+
own scope note holds — the agent authors both the pattern and the artifact, so a hang burns only
|
|
209
|
+
its own wall-clock and compromises no arbiter. These two run in **bareloop's own process**, so the
|
|
210
|
+
hang blocks the MAIN EVENT LOOP — and the in-process stall fuse is a timer in that same loop, so
|
|
211
|
+
it cannot fire (*a guard living inside the process it guards shares that process's fate*). The run
|
|
212
|
+
then dies to the OUTSIDE watchdog and reads as a model stall: **a bad regex in a SIGNED spec
|
|
213
|
+
presents as a provider problem.** The cost is a misdiagnosis, not just a stop.
|
|
214
|
+
- **The detector MOVED to `src/validate.js` — one inventory, never a second copy.** `plan.js`
|
|
215
|
+
imports `job.js`, so `job.js` cannot import `plan.js`; housing the scan with the other shared
|
|
216
|
+
primitives is what lets both documents reject through the same function. `plan.js` re-exports the
|
|
217
|
+
name (the `WRITE_VERBS` precedent), so the F49 site is byte-identical in behaviour. A second copy
|
|
218
|
+
would be precisely the drift class `SECRET_PATTERNS` exists to prevent — a shape one validator
|
|
219
|
+
rejects and the other admits. `hasNestedQuantifier` is not exported from `src/index.js`, so the
|
|
220
|
+
move is internal and breaks no adopter.
|
|
221
|
+
- **Admissibility swept before the change and re-run after:** all **92** operator regexes across all
|
|
222
|
+
**10** signed specs in `jobs/` — **zero** newly red. No spec breaks, no spec-hash churn (no spec
|
|
223
|
+
file is touched). The shipped shapes are pinned in the suite as the control that can produce the
|
|
224
|
+
negative: if the reject ever reds one of them, it has broken the live fleet.
|
|
225
|
+
- **Known limit, PARKED not chased:** the scan misses the alternation-overlap class (`(a|aa)+$`,
|
|
226
|
+
`(x|xy)*$` — no inner quantifier exists to find; the blowup comes from overlapping branches under
|
|
227
|
+
a repeat). Widening it changes which SIGNED specs are admissible, which is arbiter-adjacent.
|
|
228
|
+
Docked to the close-authoring rung, where hand-authored operator regexes may cease to be a surface
|
|
229
|
+
at all — see **PRD v1.55**. Recorded there too, so it is not re-chased: `([^])*$` is NOT caught and
|
|
230
|
+
that is CORRECT (`[^]` is the any-char idiom, so the pattern is linear); the genuinely nested
|
|
231
|
+
`([^]*)*$` IS caught.
|
|
232
|
+
|
|
233
|
+
### Added
|
|
234
|
+
- **A SECOND variance stop can earn ONE more replan — granted by the ARBITER, on a mechanical
|
|
235
|
+
reading** (F85, C). A second `step-variance` after the one-replan ceiling was spent used to be a
|
|
236
|
+
hard stop, and on a run that was still converging that stop threw away work already paid for. Every
|
|
237
|
+
clause of the grant is a constraint: **ONE**, bounded by a LATCH rather than a comparison so the
|
|
238
|
+
ceiling cannot creep by arithmetic (unlimited replanning launders thrash as adaptation, v1.12) — a
|
|
239
|
+
third variance stop is the stop, however well it is going; **the arbiter's**, read mechanically off
|
|
240
|
+
the same `runTrend` instance the meter and the money halt read, with the agent given no channel to
|
|
241
|
+
it — it never asks, is never offered it, and cannot influence it (no self-adjusted budgets, ever);
|
|
242
|
+
**`converging` only**, the trend instrument's own category with no fresh number invented here
|
|
243
|
+
(threshold-setting is the operator's), so `flat` and `unknown` stop exactly as before and an unknown
|
|
244
|
+
is never rounded up into a reason to spend (F6); and **`step-variance` only** — an exhaustion or a
|
|
245
|
+
stall after the ceiling is unchanged. `remainingUsd() > MONEY_MIN` still gates it, as it gates the
|
|
246
|
+
ordinary replan. **Spine change:** `replan` gains `replan` (which replan this is) and — only when the
|
|
247
|
+
arbiter granted one past the ordinary ceiling — `granted` with the reading it was granted on, so a
|
|
248
|
+
reader can never mistake the ceiling for a grant; `plan-executed` gains a `replans` count riding
|
|
249
|
+
BESIDE the shipped `replanned` boolean, never repurposing it. Live on `u-mshcpdg4`: three
|
|
250
|
+
`plan-accepted` records — the initial draft and two replans, the second granted on `converging`.
|
|
251
|
+
- **`closeGapBlock(gap)` — the replan brief carries the close's OWN output** (F86; `src/planrun.js`).
|
|
252
|
+
`u-mshcpdg4` reached ONE remaining strict error and still died. The close gap named the work exactly
|
|
253
|
+
— `src/recurse.js(978,115)` — and the worker read it every attempt; the REPLANNER, which is the
|
|
254
|
+
component that chooses which files the next plan targets, saw only
|
|
255
|
+
*"still progressing — typecheck 30 → 15 → 15 → 1"*: a number with no address. It aimed the last plan
|
|
256
|
+
at `src/loop.js`, already at zero errors, wrote nothing twice, and took two strikes — with $1.10 and
|
|
257
|
+
82 seconds still on the clock at the moment that replan was granted (the run itself ended $0.8174 and
|
|
258
|
+
~6 seconds short of its caps). F85 gave the brief the trajectory; this gives it the artifact the trajectory
|
|
259
|
+
is a summary OF. Handed over as **TEXT**, with both boundaries reused rather than respelled: scrubbed
|
|
260
|
+
through the one secret inventory (`redactSecrets`/`SECRET_PATTERNS`) because a prompt is an egress
|
|
261
|
+
point and the next caller will not remember the upstream scrub, and bounded by ralph's own `boundGap`
|
|
262
|
+
— now **exported** from `src/ralph.js` for it, since a private slice would be a second truncation
|
|
263
|
+
scheme that drifts on exactly the property that matters (whether the red lines survive, and whether
|
|
264
|
+
the trim announces itself, F28); the close path still calls it with the stage's own `gapKeep`,
|
|
265
|
+
byte-identically. The red-set is `REPLAN_GAP_KEEP = '\S'` — every non-blank line — because a shipped
|
|
266
|
+
`gapKeep` is `^`-anchored (`^BAREAGENT `, `^red`, `^FAILED`) and the exit evaluator wraps stage output
|
|
267
|
+
in `check "x" red: …`, so the anchor no longer sits where the pattern expects it; a gap that reaches
|
|
268
|
+
here is ALREADY a red-set (`judge` builds it out of the failing exits only), so keeping every line is
|
|
269
|
+
the correct instrument, not a widened one. **No spine field** — `src/trend.js`'s rule is that no
|
|
270
|
+
record carries a close byte. An empty gap yields the empty string, so the brief renders
|
|
271
|
+
byte-identically to the pre-F86 one (a labelled empty section would invite the planner to explain an
|
|
272
|
+
absence the run never observed). Verified by execution, not by reading: sabotaging the wiring
|
|
273
|
+
(`gapBlock = ''`) turns the `u-mshcpdg4` regression test red (`not ok 121`); restored byte-clean.
|
|
274
|
+
|
|
275
|
+
### Removed
|
|
276
|
+
- **`gapFilesNeverWritten()` and the `never wrote` replan advisory** (F86) — an EXPORT of
|
|
277
|
+
`src/planrun.js` is gone (it was never re-exported through the package entry, whose `exports` map
|
|
278
|
+
admits only `.`, so no adopter import breaks). hamr's order, verbatim: *"delete it and rerun
|
|
279
|
+
bareagent"*. The measured reason, on `u-mshcpdg4`'s own gap: the advisory resolved to
|
|
280
|
+
`src/loop.js` — the file already at zero errors — and said so as a DIRECTIVE, beside the artifact
|
|
281
|
+
that said the opposite; this worker follows directive prose (positive-scope confinement). It also had
|
|
282
|
+
no evidence of ever converting anything: it fired by construction on `u-msdsmkid`, and that run still
|
|
283
|
+
step-redded. The general rule it broke is why `closeGapBlock` hands over text instead: **a parsed
|
|
284
|
+
file list cannot tell a summary line from a detail line**. Line 1 of that very gap reads
|
|
285
|
+
`reports 1 error(s) in src/recurse.js, src/loop.js` — every file in SCOPE — and only line 2 names the
|
|
286
|
+
culprit; and the shapes differ per close anyway (tsc `file:line`, pytest test ids, a count close that
|
|
287
|
+
names no file at all). A model reading the artifact can make that distinction; a regex reproduces the
|
|
288
|
+
bug. Two readers of one question with the parsed one wrong is the same failure the variance meter just
|
|
289
|
+
had. Its 4 tests went with it, and its sole caller was the brief — no speculative code left behind.
|
|
290
|
+
|
|
8
291
|
## [0.7.0] — 2026-08-05
|
|
9
292
|
|
|
10
293
|
### Changed
|
package/README.md
CHANGED
|
@@ -49,8 +49,10 @@ it and never disappears.
|
|
|
49
49
|
> kill-switch question). As of 0.7.0 N3's machinery **ships** — the bridge registry, the
|
|
50
50
|
> operator-signed reuse envelope, the selection call and step-level resume are all in the
|
|
51
51
|
> public API — but shipping is not acceptance: one execution green proves reuse safe and
|
|
52
|
-
> legal, never *beneficial*. The
|
|
53
|
-
>
|
|
52
|
+
> legal, never *beneficial*. The contrast that was to prove the lift is now retired (F88): a
|
|
53
|
+
> validation rule hands every cold run the plan shape a bridge was carrying, so the gap it
|
|
54
|
+
> would have measured is gone. Treat reuse as available and unproven — what a bridge should
|
|
55
|
+
> carry instead is open.
|
|
54
56
|
|
|
55
57
|
## Quick start
|
|
56
58
|
|
package/bareloop.context.md
CHANGED
|
@@ -96,7 +96,7 @@ the step plan at run time (gated by `validatePlan`); the human signs only:
|
|
|
96
96
|
| field | shape | notes |
|
|
97
97
|
|---|---|---|
|
|
98
98
|
| `goal` | non-empty text | what the agent plans against |
|
|
99
|
-
| `verdictType` | `green` \| `soft-green` \| `hitl` | declared radio, never inferred (`VERDICT_TYPES`, frozen). v1 ADMITS only `green`; declaring `soft-green`/`hitl` reds `request-red` with the type as a structured `verb` field (declared-but-locked — the tool-menu pattern) |
|
|
99
|
+
| `verdictType` | `green` \| `soft-green` \| `hitl` | declared radio, never inferred (`VERDICT_TYPES`, frozen). v1 ADMITS only `green`; declaring `soft-green`/`hitl` reds `request-red` with the type as a structured `verb` field (declared-but-locked — the tool-menu pattern). Every `request-red` also carries `lib` — the territory the demand lands against, stamped at the emit site (`verdictType` → `bareloop`, a locked tool verb → `bare-agent`): the ledger keys and its `suggestedAsk` seed on it, so a bareloop-catalogue refusal never files as an upstream ask |
|
|
100
100
|
| `close` | **an ORDERED LIST of named stages** `[{ name, cmd, expect, judged?, gapKeep?, offer?, needs? }, ...]` (PRD v1.28), or a close object (table below) **only** for the declared-but-locked verdict classes | the destination, the only thing hand-authored; the check menu DERIVES from it (below). The plan flow executes a staged close directly and adapts a bare `predicate` object into a one-stage list; a `gold`/`rubric`/`hitl` object close validates (the declared-but-locked verdict classes still parse) but the plan flow refuses it at runtime as `close-unsupported` — it names no command to run |
|
|
101
101
|
| `checks` | **RETIRED** (PRD v1.28/v1.32) | hand-authored checks are gone, not merely discouraged: declaring `checks` reds `checks-derived` by name. The check menu is DERIVED from the close's own stages instead — see **Staged close** below. The hazard this removes is measured, not theoretical: job #5's three hand-written checks were re-implementations of three stages the close already ran, and a hand-carved copy can drift LENIENT (the worker passes the operator's ruler and fails the real inspection) |
|
|
102
102
|
| `tools` | optional unique subset of `TOOL_MENU` (14 verbs, below) | the CEILING every plan step's grant must fit inside (omitting it means the full menu — and the hash is taken over that RESOLVED form, so a `TOOL_MENU` widening flips an omitted-`tools` spec's hash and forces a re-sign; see `jobSpecHash`, MED-1); `run` is `LOCKED_TOOLS` and reds `request-red` — locked-but-listed, and the red IS the admission evidence the ledger tallies (a typo stays `invalid-value`). A ceiling of write-class and store-class verbs ONLY reds `invalid-value`: the scout surveys read-only, so it would be handed an empty menu and survey blind |
|
|
@@ -174,6 +174,20 @@ reader for every close, deliberately (the F49 precedent). **Splitting a stage mo
|
|
|
174
174
|
hash** — stage names are in the signed spec — so it is a spec edit the operator re-signs, and
|
|
175
175
|
it changes what stored bridges match (below).
|
|
176
176
|
|
|
177
|
+
**The law for spec authors: your `goal` must state everything your close will judge.**
|
|
178
|
+
Nothing derives the goal from the close or the close from the goal, and nothing checks the
|
|
179
|
+
two against each other — the ONE derivation in the library is close stages → the agent's
|
|
180
|
+
`check-passes` menu, one hop, one direction. That separation is the arbiter rule (the agent
|
|
181
|
+
references the operator's ruler and never authors it) and it is not negotiable, so the drift
|
|
182
|
+
is yours to close: the planner and the worker read the `goal` text, not your stage list, and
|
|
183
|
+
they will spend their single check slot (above) on the constraint the goal sentence names.
|
|
184
|
+
A stage your goal never mentions is a **cost trap, not a safety hole** — the close still
|
|
185
|
+
refuses the tree and the run still cannot mint a green — but the run discovers that stage at
|
|
186
|
+
the tail, after paying for work aimed elsewhere, and may not have the money or wall left to
|
|
187
|
+
redo it. Write the goal from the close's own stage list, in the close's own words; keep it to
|
|
188
|
+
a few explicit requirements rather than prose. Preflight already names every stage on the
|
|
189
|
+
`check-menu` spine event — read it back and check your goal against it before you sign.
|
|
190
|
+
|
|
177
191
|
**Close types and the hierarchy — the OBJECT form, which survives only for the
|
|
178
192
|
declared-but-locked verdict classes** (a close is data, never code; verdict-class laundering
|
|
179
193
|
is a named red `close-hierarchy`). The go-forward shape is the staged list above; a
|
|
@@ -233,6 +247,19 @@ first) so a pathological close cannot rebuild the very bloat the bound exists to
|
|
|
233
247
|
when the cap trims matches it **says so** with an explicit marker (silent truncation is the
|
|
234
248
|
disease this cures, never a cure that truncates silently). Validated like `judged.pattern`:
|
|
235
249
|
a non-empty string that must **compile as a RegExp**, else a spec red before any tokens.
|
|
250
|
+
|
|
251
|
+
**Both operator patterns must also survive the ReDoS shape check** (`invalid-value`, F49 —
|
|
252
|
+
the same reject the agent's `artifact-written.pattern` gets): an unbounded quantifier over a
|
|
253
|
+
group that itself repeats unboundedly (`(a+)+`, `(\d*)*`, even wrapped: `((a+))+`) is
|
|
254
|
+
rejected at the gate. **This is stricter than an "it compiles" contract, and deliberately
|
|
255
|
+
so.** `judged.pattern` is exec'd by `runClose` over the close's whole stdout+stderr and
|
|
256
|
+
`gapKeep` is run by `boundGap` **once per line** — both untimed, both in bareloop's own
|
|
257
|
+
process. Measured: `boundGap(stream, "(a+)+$")` on a 40-character line does not finish in
|
|
258
|
+
20 seconds. Because the hang blocks the main event loop, the in-process stall fuse cannot
|
|
259
|
+
fire, the run dies to the outside watchdog, and **a bad regex in a signed spec presents as a
|
|
260
|
+
provider problem** — so this reds at signing time rather than being diagnosed later. All 92
|
|
261
|
+
operator regexes across the 10 in-repo specs pass unchanged. Known limit, parked: the scan
|
|
262
|
+
does not catch the alternation-overlap class (`(a|aa)+$`) — see PRD v1.55.
|
|
236
263
|
Omitting it is exactly today's bound. Like `judged`, it is **arbiter territory** — the
|
|
237
264
|
drafted workflow config cannot express it (unknown-field red). The gap path also combines
|
|
238
265
|
**both** streams (stdout + stderr), so a failure printed to stdout survives stderr noise
|
|
@@ -249,11 +276,23 @@ middle writes; `validatePlan` gates it against the SIGNED job spec before tokens
|
|
|
249
276
|
| `steps[].tools` | non-empty unique subset of the SPEC ceiling | a verb beyond the ceiling reds `verb-escape` with the verb as structured data (overreach, distinct from the operator-side `request-red`); `run` is `verb-escape` at every layer |
|
|
250
277
|
| `steps[].rounds` | int 1..shell cap (default 40) | the step's per-attempt tool-round bound (the Gate's `maxTurns` natively) |
|
|
251
278
|
| `steps[].target` | path inside the fence | v1.18 deliverable; REQUIRED on write-granted steps |
|
|
252
|
-
| `steps[].model` | optional; `sonnet`
|
|
279
|
+
| `steps[].model` | optional; `sonnet` (`STEP_MODELS`) | the step's model TIER — a menu, never a model string the agent spells (the tier→model mapping and any per-model effort params are the runner's). `opus` is deliberately absent: reserved for work the human assigns, never plan-selectable; `haiku` was removed 2026-08-06 as a reversible ATTRIBUTION PROBE (F87, see `STEP_MODELS`), so the menu is one entry deep and the field is no longer offered in the drafting prompt. The field and its validator branch STAY: `model: "sonnet"` still validates, a stored bridge declaring it still loads, and restoring haiku is a one-token edit — this is not a finding that haiku is incapable. **The narrowing binds only what the AGENT may express.** Which real model a tier maps to, and what the caller runs as its own default worker model, stay the OPERATOR's: `providerFor` is yours, and the in-repo runner's `--model haiku` probe knob is untouched (PRD v1.36) — the runner's tier map is deliberately WIDER than the plan menu. Off-menu reds `invalid-value`. A step naming a tier when the caller supplied no `providerFor` factory is an `interpreter-red` STOP, never a silent run on the default tier |
|
|
253
280
|
| `steps[].attempts` | optional; int `>= 1` | **RETIRED and INERT (F79) — it bounds nothing.** A step's iterations are governed by the STRIKE LADDER (below), which is shell-owned and inexpressible from the plan. The field is no longer offered in the drafting prompt and the runner ignores it; it is deliberately still ACCEPTED, because stored bridge plans carry it and a plan minted under one runner's number must not red under another's. The shape is still checked (a known field holding garbage stays a `bounds` red); the old `<= capRuns` ceiling is gone with the cap it named. It was previously TIGHTEN-only — which meant a step that needed MORE iterations could not ask, and drafters measurably tightened it instead (F77) |
|
|
254
281
|
| `steps[].scope` | optional; one value from the SAME menu `tree-changed` uses | narrows this step's live WRITE fence to a subset of the signed `writeScope` — chosen from the offered scope menu (below), never authored. Off-menu reds `invalid-value` carrying the menu |
|
|
255
282
|
| `steps[].exit` | 1..2 items (`MAX_EXITS_PER_STEP`), ALL must pass (AND-only, no OR/NOT) | closed menu (`EXIT_TYPES`): `artifact-written(path, pattern?)` · `tree-changed(scope)` · `json-valid(path)` · `check-passes(name)`. **`tree-changed.scope` is a MENU, not a glob the agent authors** — the runner enumerates the signed fence entries plus the real directories beneath them (shallowest-first, capped at `MAX_SCOPE_MENU`=24; `legalScopes` in `src/plan.js`), `planPrompt` lists them verbatim, and `validatePlan` accepts membership only — the SAME array on both sides, so what was offered is what is accepted; omitted, it derives from `writeScope` alone — never a free-text fallback. An off-menu value that escapes the fence still reds `scope-escape` (the ledger's attribution class), an in-fence unoffered value reds `invalid-value` carrying the menu. `runPlan` emits `scope-menu {offered, truncated, offerableCount?, cap?}` so a capped menu is never silently complete. `check-passes` must name a stage on the close's DERIVED menu (`check-unknown` red names the offered menu — no operator authors this list, it comes straight from `checkMenu(close)`); on a write-granted step it must be paired with `tree-changed` (`exit-illegal` — the seed tree is green, a lone check would pass untouched, F17/F46). **`check-passes` also requires a write-class verb (`write`/`edit`) on the SAME step** (`exit-illegal` — BREAKING for plan authors: a read-only "verify" step carrying a check used to validate and no longer does). A failing check's gap is re-delivered to that step's OWN worker, so a step holding a check must be able to act on it; a read-only one is a mailbox with no hands and stalls to cap on a byte-identical gap (measured on 4 of 4 drafted plans). The check belongs on the step that fixes; the run's final verification is the operator's close, which the agent never authors. The rule is stated identically in the drafter prompt and the validator, so the two can never disagree — and it is suppressed when `tools` failed to parse (the step's hands are unknowable then, and the real defect already redded: one defect, one red). `artifact-written.pattern` must compile AND survive a ReDoS shape check — an unbounded quantifier over a group that itself repeats unboundedly (`(a+)+`, `(\d*)*`, even wrapped: `((a+))+`) is an `invalid-value` red (F49, catastrophic-backtracking footgun; rewrite without a repeated group inside a repeat). Exits verify FORM, not truth — progress gates; the operator's close stays the one arbiter |
|
|
256
283
|
|
|
284
|
+
**A step has exactly ONE check slot — plan for it.** The three exit rules above compose into
|
|
285
|
+
a ceiling worth stating on its own, because it decides what a plan can enforce *during* a
|
|
286
|
+
run: `MAX_EXITS_PER_STEP` is **2**, the two are ANDed (no OR/NOT, `exit-illegal` past the
|
|
287
|
+
bound), and a `check-passes` on a write-granted step MUST be paired with a `tree-changed`
|
|
288
|
+
exit. Both slots are therefore spent the moment a step carries one check, so **a step cannot
|
|
289
|
+
carry two checks** — `check-passes(typecheck)` and `check-passes(no-suppressions)` together
|
|
290
|
+
on one step is inexpressible today, not merely discouraged. The consequence for an adopter:
|
|
291
|
+
if your close judges N stages, at most ONE of them can be enforced inside a step; the other
|
|
292
|
+
N−1 are enforced only at the close, at the end of the run. That is a COST shape, not a safety
|
|
293
|
+
hole — the close still refuses a tree that fails any stage, and nothing green is minted mid-run
|
|
294
|
+
— but a stage first tested at the tail is a stage the run pays full price to discover.
|
|
295
|
+
|
|
257
296
|
Red vocabulary (both validators): `parse-error`, `unknown-field`, `missing-required`,
|
|
258
297
|
`invalid-value`, `bounds`, `duplicate-id`, `close-type`, `close-hierarchy`,
|
|
259
298
|
`secret-literal`, `scope-escape`,
|
|
@@ -431,6 +470,17 @@ ONE inventory) at the emission boundary, before anything rides the append-only s
|
|
|
431
470
|
module itself does not scrub: a direct `evalExits` caller wiring results into its own log
|
|
432
471
|
must apply its own redaction.
|
|
433
472
|
|
|
473
|
+
A `check-passes` detail is sliced at `CHECK_GAP_MAX` (12,000 chars) — a defensive backstop
|
|
474
|
+
above the close path's own `boundGap` envelope, not a second trim of it: a normally-bounded
|
|
475
|
+
gap passes through intact and only a seam that returned something unbounded is ever cut. The
|
|
476
|
+
constant is now `export`ed from `src/exits.js` so the replan brief's own backstop imports it
|
|
477
|
+
rather than respelling it (two spellings would drift on the one property that matters —
|
|
478
|
+
whether the `not ok`/`FAILED` names survive). **It is NOT part of the adopter surface:** the
|
|
479
|
+
package `exports` map admits only `"."`, and `src/index.js` re-exports `snapshotScope` and
|
|
480
|
+
`evalExits` from this module and nothing else, so `CHECK_GAP_MAX` and `boundGap` are internal
|
|
481
|
+
by construction and a deep import cannot reach them. They are named here to explain the
|
|
482
|
+
behaviour you WILL see in a detail string, not as API to call.
|
|
483
|
+
|
|
434
484
|
### `jobSpecHash(job)` / `checkApproval(job, approvals)` — `src/job.js`
|
|
435
485
|
|
|
436
486
|
The pure half of **human-signs-always**: an agent may draft a job spec, but no job runs
|
|
@@ -454,10 +504,12 @@ Reserved spine vocabulary (V7, machinery-free until job #1 surfaces one):
|
|
|
454
504
|
`coordination-red` — a failure between units (scope contention, step order, store
|
|
455
505
|
races), never to be folded into worker/interpreter reds.
|
|
456
506
|
|
|
457
|
-
### `runJob(spec, { approvals, workdir, provider, nativeProvider?, providerFor?, emit, capRuns?, strikeLimit?, shellCapUsd?, closeTimeoutMs?, layerRoot?, bridge?, priorSpentUsd?, priorSpendComplete?, priorWallMs?, resumeSeed?, resumeGrades? })` → outcome — `src/run.js`
|
|
507
|
+
### `runJob(spec, { approvals, workdir, provider, nativeProvider?, providerFor?, emit, capRuns?, strikeLimit?, shellCapUsd?, closeTimeoutMs?, layerRoot?, bridge?, priorSpentUsd?, priorSpendComplete?, priorWallMs?, resumeSeed?, resumeGrades?, resumeReplans? })` → outcome — `src/run.js`
|
|
458
508
|
|
|
459
|
-
The last
|
|
460
|
-
|
|
509
|
+
The last six are the RESUME fold and are documented under *Resuming a killed run* below; they
|
|
510
|
+
default to `0` / `true` / `0` / `null` / `[]` / `null`, so a fresh run passes none of them.
|
|
511
|
+
Three of them are folds of a bound the operator SIGNED (money, wall, replans) and one is a
|
|
512
|
+
readout seed (grades) — the distinction matters and is spelled out there.
|
|
461
513
|
|
|
462
514
|
The runner — the shell's top layer, and the ONE entry. It composes everything below it and
|
|
463
515
|
interprets nothing itself. Sequence: **approval gate** (human-signs-always — refuses an
|
|
@@ -524,8 +576,9 @@ fed back, then `plan-red`) → **EXECUTE** (strictly sequential micro-loops: `ra
|
|
|
524
576
|
the exit-evaluator judge, each step under its own STRIKE LADDER — below; tree snapshots at
|
|
525
577
|
step start; the gap names every failing
|
|
526
578
|
wall — mechanical genre, F46's measured mechanism; artifacts feed forward labeled by
|
|
527
|
-
step id) → **ONE replan**, triggered by exhaustion OR variance (an instrument stop
|
|
528
|
-
replans)
|
|
579
|
+
step id) → **ONE replan**, triggered by exhaustion OR variance OR a stall (an instrument stop
|
|
580
|
+
never replans) — plus at most ONE arbiter-granted extra on a second variance stop that reads
|
|
581
|
+
`converging`, below → **the operator's close**, a red feeding ONE bounded fix loop judged by the
|
|
529
582
|
REAL close and governed by the close TREND rule below (`capRuns`, default 3, survives only as
|
|
530
583
|
its blind fallback). `plan-executed` (the plan-as-executed record, design law #2) lands on the
|
|
531
584
|
spine on every path that executed steps. Additional outcomes: `already-green |
|
|
@@ -557,8 +610,26 @@ escalation and wall-stop records carry `strikes`/`strikeLimit`/`distinctGaps` be
|
|
|
557
610
|
iteration count rather than a cap the step no longer has. **The replan brief
|
|
558
611
|
names the mechanism**: which shape ended the step (converging-cut / stalled-no-write /
|
|
559
612
|
repeated-gap, with the iteration numbers as evidence), the gap trajectory, and how much money
|
|
560
|
-
and wall the stop left unspent (time reported UNBOUNDED when no wall was set, never `0`).
|
|
561
|
-
|
|
613
|
+
and wall the stop left unspent (time reported UNBOUNDED when no wall was set, never `0`).
|
|
614
|
+
**It also carries that step's own last EXIT output, verbatim** (F86) — masked through the repo's
|
|
615
|
+
one secret inventory, and labelled as *the exits'* words rather than the close's, because the
|
|
616
|
+
payload is `lastGap`: the join of EVERY failing exit's detail in exit order. The close stage's
|
|
617
|
+
output is one of those details, never reliably all of them, so a step that wrote nothing opens
|
|
618
|
+
the block with the exit evaluator's own `0 files changed under …` prose. That line is kept (it is
|
|
619
|
+
exactly what a replanner should be told); only the label was wrong. The bound over it is a
|
|
620
|
+
**backstop, not a second envelope** — what arrives has already been bounded by `runClose` under
|
|
621
|
+
the stage's own `gapKeep`, which deliberately rescues the `not ok`/`FAILED` names out of the
|
|
622
|
+
elided middle, and re-enveloping that deletes them a second time (F28 reintroduced; measured, a
|
|
623
|
+
second 400/1500 pass dropped 12 of the 78 names the first had rescued). Under `CHECK_GAP_MAX` —
|
|
624
|
+
`src/exits.js`'s own ceiling, imported rather than respelled so the two seams cannot drift — the
|
|
625
|
+
gap passes through VERBATIM, with ~1.8KB of measured headroom over the largest thing the close
|
|
626
|
+
envelope can produce; the backstop fires only on a gap nothing upstream ever bounded. No gap, no
|
|
627
|
+
block, and the brief is byte-identical to one drafted before this existed.
|
|
628
|
+
Handed over as TEXT: the runner makes **no parsed file claim of its own** in this channel —
|
|
629
|
+
a close's summary line ("N error(s) in a.js, b.js" — every file in scope) and its detail line
|
|
630
|
+
(the one location actually failing) are indistinguishable to a regex, and the shapes differ per
|
|
631
|
+
close anyway. What the planner is told about WHERE the work is comes from the close's own
|
|
632
|
+
bytes, never from the runner's reading of them.
|
|
562
633
|
|
|
563
634
|
**The close TREND rule — how the CLOSE-FIX LOOP ends (PRD v1.46 §4).** `capRuns` retired as
|
|
564
635
|
that loop's governor once its own $0 replay over every archived fix loop came back clean (0
|
|
@@ -608,7 +679,9 @@ on money (the step loop, the close-fix loop, and the scout/draft relay), exactly
|
|
|
608
679
|
close rendered no number (F6). **The library only ever REPORTS**: `budgetUsd` is in the spec
|
|
609
680
|
hash, so a top-up is a spec edit a human signs — nothing in a run may widen its own budget.
|
|
610
681
|
|
|
611
|
-
**ONE progress instrument, read by both halts
|
|
682
|
+
**ONE progress instrument, read by both halts — and REPORTED by the variance meter.** The
|
|
683
|
+
`variance` record and its escalation carry the same four fields off the same instance (below);
|
|
684
|
+
the meter is the one consumer that never decides on the reading. The wall halt used to run its own: byte
|
|
612
685
|
equality of the last two close gaps, reading `stalled` / `moving` / `unknown` beside the money
|
|
613
686
|
halt's `flat` / `converging` / `unknown` on the same run. Both now read `runTrend`, the run's
|
|
614
687
|
own `src/trend.js` reader. Where a stage reported a comparable NUMBER it decides; where none
|
|
@@ -671,14 +744,57 @@ The planner (never the worker) receives a **materials** block at draft and at re
|
|
|
671
744
|
spread on verification-gap duration, so a per-round constant describes almost no real round
|
|
672
745
|
and a rate framing creates a rush-or-fake incentive. The replan trigger gains a second axis
|
|
673
746
|
(`step-variance`): a step consuming ≥ `VARIANCE_THRESHOLD` (0.5) of the run's REMAINING money
|
|
674
|
-
or time
|
|
747
|
+
or time — either axis — is pre-empted at the head of its next attempt so the planner
|
|
675
748
|
re-allocates instead. **Known and recorded (F63): that trigger fired 0 times across 18
|
|
676
749
|
archived spines / 54 steps** (near misses 0.35–0.45) — the threshold is arbiter territory,
|
|
677
|
-
deliberately not fitted to those points.
|
|
750
|
+
deliberately not fitted to those points. It is no longer inert: it fires on real runs (F85).
|
|
751
|
+
|
|
752
|
+
**The meter REPORTS progress; it never decides on it (F85).** The firing condition above is
|
|
753
|
+
the whole trigger — a progress term inside it would let a governance instrument over an
|
|
754
|
+
operator-owned allowance judge capability, and a converging step that is eating the run is
|
|
755
|
+
still stopped. What the meter says about the work is a MEASURED reading, not an assertion: the
|
|
756
|
+
`variance` spine record gains `trend` (`converging | flat | unknown`), `motion`, `reading` and
|
|
757
|
+
`series` beside its unchanged `step`/`iteration`/`threshold`/`moneyShare`/`timeShare`/`axis`,
|
|
758
|
+
and the escalation `detail` (which the replan brief quotes) ends with that same `reading` as a
|
|
759
|
+
second, separate claim — "it was stopped for eating the run" and "here is what it achieved" are
|
|
760
|
+
different statements. All of it is read off the SAME `src/trend.js` instance the money and wall
|
|
761
|
+
halts read — one reader, so the meter and the halts can never disagree, and `unknown` stays
|
|
762
|
+
`unknown` when the close reported no number (F6). This
|
|
763
|
+
replaces a fixed sentence ("with its exits unmoved") that used to print on every variance stop
|
|
764
|
+
whatever had happened; a consumer parsing that string will not find it.
|
|
765
|
+
|
|
766
|
+
**The replan ceiling is no longer flatly ONE.** A SECOND `step-variance` stop earns exactly one
|
|
767
|
+
more replan when the run's own reading is mechanically `converging`; it is granted by the
|
|
768
|
+
ARBITER off that same reader, bounded by a latch rather than a counter compared against a
|
|
769
|
+
limit, so it cannot creep — a third variance stop is the stop however well the run is going.
|
|
770
|
+
`flat` and `unknown` stop exactly as before, and an exhaustion or stall stop past the ceiling
|
|
771
|
+
is unchanged. The agent has no channel to it: it never asks, is never offered it, and cannot
|
|
772
|
+
influence it (no self-adjusted budgets, ever). Observable on the spine as ADDED fields, never
|
|
773
|
+
repurposed ones: `replan` records carry `replan: <n>` and — only on a granted one — `granted:
|
|
774
|
+
"converging"`, and `plan-executed` carries `replans` beside the unchanged `replanned` boolean.
|
|
775
|
+
|
|
776
|
+
**Both of those count the RUN, not the leg — the ceiling spans a resume chain.** `replanned`
|
|
777
|
+
and `replans` were locals in `runPlan`, and a resume is another `runPlan` call, so a kill used
|
|
778
|
+
to hand the next leg a fresh allowance of an allowance that is the RUN's by doctrine (measured:
|
|
779
|
+
leg 1 replanned and stopped, leg 2 replanned twice more — a run total of 2 with every record
|
|
780
|
+
showing 1). **`plan-executed.replans` therefore now means the CHAIN total**; if you parse spines,
|
|
781
|
+
re-read that field. Each leg DECLARES what it inherited on its own `job-start` — new fields
|
|
782
|
+
`priorReplans` and `priorReplanGrantUsed`, present only when there is a fold — and the next
|
|
783
|
+
reader adds only its own window's `replan` records, exactly the way `priorSpentUsd` folds money.
|
|
784
|
+
The grant latch travels beside the count rather than being derived from it (`1` + `false` is a
|
|
785
|
+
real state: an ordinary ceiling spent, the arbiter's extra still unearned). The leg's own number
|
|
786
|
+
is not lost — a leg emits one `replan` record per replan it drafts, so `chain −
|
|
787
|
+
records-in-this-window` is the fold it inherited. You seed it with `resumeReplans` (below);
|
|
788
|
+
omitted is the cold path and byte-identical to a run nobody resumed.
|
|
678
789
|
|
|
679
790
|
**Per-step model tiers — `providerFor` (P).** A plan step may declare a `model` TIER
|
|
680
|
-
(`sonnet
|
|
681
|
-
|
|
791
|
+
(`sonnet`, `STEP_MODELS` — `haiku` removed 2026-08-06 as a reversible attribution probe,
|
|
792
|
+
F87; the field stays legal and one-token reversible, and the narrowing binds only what the
|
|
793
|
+
AGENT may express, never what the operator may run).
|
|
794
|
+
The tier menu is signed into the schema; mapping a tier
|
|
795
|
+
onto a real model (and any per-model provider params) is the RUNNER's territory — nothing
|
|
796
|
+
forces the `sonnet` tier onto any particular model, and running haiku as your default worker
|
|
797
|
+
model stays an operator choice the plan menu never governed — so both
|
|
682
798
|
`runJob` and `runPlan` take `providerFor?: (tier: string) => provider`. It is called ONLY for
|
|
683
799
|
a step that declared a tier, once per such step — the scout, the plan drafter and every
|
|
684
800
|
untiered step stay on `opts.provider`, so a caller that never plans tiers can omit it
|
|
@@ -792,7 +908,7 @@ their next green, not repaired.
|
|
|
792
908
|
door**: before the clock, before the close precheck, before any token. On a pass, the
|
|
793
909
|
**newest** version's plan rides into the FIRST drafting prompt as a starting draft
|
|
794
910
|
(`bridge-loaded {name, versions, runid}` on the spine) and everything after it is the
|
|
795
|
-
ordinary shipped path — same validator, same redraft-on-reds, same
|
|
911
|
+
ordinary shipped path — same validator, same redraft-on-reds, the same replan ceiling, and a
|
|
796
912
|
**replan never re-injects the bridge** (it drafts from the run's own state). On a fail the
|
|
797
913
|
run returns the distinct terminal **`recipe-stale`** with `bridge-gate {outcome, name, reds}`
|
|
798
914
|
and a decision-ready escalation, having spent nothing. **Falling back to cold is the
|
|
@@ -950,7 +1066,8 @@ semantics are byte-unchanged (PRD v1.46 §3):
|
|
|
950
1066
|
implicit try opened at `job-start`, through the same window machinery rather than a second
|
|
951
1067
|
reader. Without it such a spine is honestly refused (`started: false`), which is what a
|
|
952
1068
|
reuse runner needs. `job-start` carries the DECLARED fold (`priorSpentUsd` with its
|
|
953
|
-
`priorSpendComplete`,
|
|
1069
|
+
`priorSpendComplete`, `priorWallMs`, and `priorReplans` with its `priorReplanGrantUsed`, each
|
|
1070
|
+
present only when there is one) exactly as
|
|
954
1071
|
`try-start` does, so a chain of resumes adds only each attempt's own new rounds instead of
|
|
955
1072
|
re-deriving and double-billing. **An inherited FLOOR is one** even when the money it qualifies
|
|
956
1073
|
is `$0`: a leg whose every round came back unpriced folds nothing countable and is still not
|
|
@@ -1018,6 +1135,23 @@ and is re-signed.
|
|
|
1018
1135
|
that re-grades an unchanged tree is flat on its own evidence while the RUN — what the
|
|
1019
1136
|
top-up decision is actually about — may have been converging when the money ran out. See
|
|
1020
1137
|
the trend section above for what the seed does and, deliberately, does not touch.
|
|
1138
|
+
- **Its REPLAN LEDGER comes with it too — and this one is a BOUND, not a readout.**
|
|
1139
|
+
`restart.replans` / `restart.replanGrantUsed` ride into `runJob`/`runPlan` as **`resumeReplans`**
|
|
1140
|
+
(`{count, grantUsed} | null`), seeding the replan ceiling instead of leaving it reborn per call.
|
|
1141
|
+
Without it a kill refilled an allowance PRD v1.12 makes the RUN's — measured, a leg that spent
|
|
1142
|
+
both replans and step-redded handed its checkpoint to a second leg that replanned twice more.
|
|
1143
|
+
**It deliberately does NOT use `resumeGrades`' mechanism.** The grade seed reads ONE spine, so a
|
|
1144
|
+
resume of a resume inherits the previous leg's grades and not the whole chain; that shortening is
|
|
1145
|
+
fail-safe for a READOUT (it can only under-claim a direction) and is the DANGEROUS direction for
|
|
1146
|
+
a ceiling, where an under-claimed ledger is a refilled allowance and every kill buys one. So this
|
|
1147
|
+
seed follows the MONEY fold: each leg declares `priorReplans`/`priorReplanGrantUsed` on its own
|
|
1148
|
+
spine and the next reader adds only its own window, so leg 3 inherits the whole chain. It is also
|
|
1149
|
+
not the same class as the trend's ITERATIONS, which `src/trend.js` refuses to seed on purpose: an
|
|
1150
|
+
attempt allowance is a LEG bound bought with the leg's own money, while a replan ceiling bounds
|
|
1151
|
+
how many times the WORKFLOW may be redrawn, which is the run's span. Belted like every declared
|
|
1152
|
+
fold — a garbage, non-finite or negative `count` reads as `0` rather than widening the ceiling.
|
|
1153
|
+
Null/omitted is the cold path. The disarm control is pinned: a resumed leg with an UNSPENT
|
|
1154
|
+
ceiling still replans, so the bound is bounded, not switched off.
|
|
1021
1155
|
- **Under the REMAINDER, never a fresh allotment.** The killed attempt's spend and wall
|
|
1022
1156
|
FOLD IN (`runJob`'s `priorSpentUsd`/`priorSpendComplete`/`priorWallMs`, `createClock`'s
|
|
1023
1157
|
`priorElapsedMs`), so
|
|
@@ -1080,9 +1214,13 @@ the same corpus), and `seq` continues monotonically across appends. Keys are
|
|
|
1080
1214
|
across runs dedupes and distinct bugs in one verb don't merge. Classes, worst-first
|
|
1081
1215
|
(`LEDGER_CLASSES`, frozen): `silent-degradation` (a failed `primitive-smoke` — the class
|
|
1082
1216
|
failures can't derive), `runtime-red`, `provider-red`, `pricing-red` (F6), possibly-dormant
|
|
1083
|
-
`capability-gap` (cap-halt + request-red in one spine
|
|
1217
|
+
`capability-gap` (cap-halt + request-red in one spine — one row per `(verb, lib)`, never per
|
|
1218
|
+
verb, so two territories asking for one verb string stay two rows rather than collapsing onto
|
|
1219
|
+
whichever territory was seen first), `broken-close` (consumer-attributed),
|
|
1084
1220
|
`request-red` (admission demand for a locked verb — keyed on the red's structured
|
|
1085
|
-
`verb` field, prose-quoted verb as legacy fallback
|
|
1221
|
+
`verb` field, prose-quoted verb as legacy fallback; both classes take their `lib` from the
|
|
1222
|
+
red's own stamp, so neither seeds an upstream ask for a bareloop-catalogue refusal),
|
|
1223
|
+
`retention-red`, `config-red`
|
|
1086
1224
|
(drafting friction — attributed to bareloop's own schema/prompt). Deliberate exclusions
|
|
1087
1225
|
(`EXCLUDED_ESCALATIONS`, a runtime set — anything outside classified ∪ excluded is
|
|
1088
1226
|
counted as unmapped, never dropped): `cap-halt`/`wall-halt` (budget stories, money and
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bareloop",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Workflows that earn their own design, with receipts — agent-authored workflows for repeated, long, verifiable jobs that improve across runs under an un-gameable gate.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.js",
|