bareloop 0.7.0 → 0.9.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 +704 -0
- package/README.md +13 -4
- package/bareloop.context.md +351 -32
- package/package.json +1 -1
- package/src/authorflow.js +1280 -0
- package/src/authoring.js +1296 -0
- package/src/authorjob.js +793 -0
- package/src/authorscout.js +706 -0
- package/src/bridges.js +2 -2
- package/src/declaredclose.js +519 -0
- package/src/exits.js +8 -2
- package/src/index.js +72 -0
- package/src/job.js +91 -10
- package/src/kinds.js +1354 -0
- package/src/ledger.js +70 -9
- package/src/plan.js +57 -101
- package/src/planrun.js +554 -75
- package/src/ralph.js +27 -5
- package/src/reuse.js +135 -11
- package/src/root.js +8 -1
- package/src/run.js +29 -3
- package/src/text.js +120 -0
- package/src/validate.js +121 -0
- package/src/workbranch.js +261 -0
- package/types/authorflow.d.ts +720 -0
- package/types/authoring.d.ts +447 -0
- package/types/authorjob.d.ts +241 -0
- package/types/authorscout.d.ts +307 -0
- package/types/declaredclose.d.ts +174 -0
- package/types/exits.d.ts +14 -0
- package/types/index.d.ts +9 -1
- package/types/job.d.ts +14 -2
- package/types/kinds.d.ts +343 -0
- package/types/plan.d.ts +41 -38
- package/types/planrun.d.ts +125 -18
- package/types/ralph.d.ts +17 -0
- package/types/reuse.d.ts +5 -1
- package/types/run.d.ts +23 -2
- package/types/text.d.ts +84 -0
- package/types/validate.d.ts +50 -0
- package/types/workbranch.d.ts +102 -0
package/README.md
CHANGED
|
@@ -43,14 +43,23 @@ it and never disappears.
|
|
|
43
43
|
> under an un-gameable outer close and one budget it can only tighten. On the real-model
|
|
44
44
|
> acceptance battery (F47) the emergent flow converted a job the same worker failed 4/4
|
|
45
45
|
> before — 3/3 runs, clearing the owned bar every time, composing its own check exits. The
|
|
46
|
-
> within-run ratchet (Layer R) ships armed-and-inert by measurement (F41). **
|
|
47
|
-
>
|
|
46
|
+
> within-run ratchet (Layer R) ships armed-and-inert by measurement (F41). **Then N3 —
|
|
47
|
+
> executed inheritance**, where a workflow that greened is kept, offered to the next
|
|
48
48
|
> similar job, and improved as executed instead of drafted from scratch (the ladder's
|
|
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. **The rung in flight is close authoring** — the last layer where a
|
|
56
|
+
> human still wrote code. You answer a short interview about what *done* means; an LLM then
|
|
57
|
+
> composes a close **declaration** over a catalogue of stage kinds bareloop implements — never
|
|
58
|
+
> code, never prose parsed back — which is validated, run once against your untouched repo so
|
|
59
|
+
> every ruler is seen measuring something real, and gated before you sign its hash. It is
|
|
60
|
+
> proven live on both a JavaScript and a Python patient, and its designed failure mode is an
|
|
61
|
+
> honest refusal: a close that would grade nothing, or a stage whose population does not exist
|
|
62
|
+
> in your tree, comes back decision-ready instead of green.
|
|
54
63
|
|
|
55
64
|
## Quick start
|
|
56
65
|
|
package/bareloop.context.md
CHANGED
|
@@ -96,8 +96,9 @@ 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
|
+
| `closeDecl` | **the AUTHORED close** `{ genre: "TYPES", lang: "js"\|"python", stages: [{ name, kind, params }], notes? }` | the ALTERNATIVE to `close`, and the point of the close-authoring rung: the user answers an interview and an LLM composes a DECLARATION over kinds whose implementations we own — never a script, never a shell fragment, never a new kind. **`close` and `closeDecl` are alternatives**: declaring both reds `close-duplicated` (two closes are two arbiters). It is HARD-class by construction (`CLASS_BY_CLOSE.declared`), so a locked verdict on one reds `close-hierarchy` as well as `request-red`. The declaration is validated by `validateCloseDecl`; the TREE-GROUNDED half of that gate (the path rule, and the scoped-job derivation that arms the F84 one-population law) is DEFERRED at spec-validation time — a job spec is validated with no repository in hand — and the runner re-runs it GROUNDED against the real seed before any stage and before any token. It stores the counting RULE and never a number (D12): there is no seed field, and `baseline: "seed"` is measured at each run's own HEAD |
|
|
101
102
|
| `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
103
|
| `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 |
|
|
103
104
|
|
|
@@ -174,6 +175,20 @@ reader for every close, deliberately (the F49 precedent). **Splitting a stage mo
|
|
|
174
175
|
hash** — stage names are in the signed spec — so it is a spec edit the operator re-signs, and
|
|
175
176
|
it changes what stored bridges match (below).
|
|
176
177
|
|
|
178
|
+
**The law for spec authors: your `goal` must state everything your close will judge.**
|
|
179
|
+
Nothing derives the goal from the close or the close from the goal, and nothing checks the
|
|
180
|
+
two against each other — the ONE derivation in the library is close stages → the agent's
|
|
181
|
+
`check-passes` menu, one hop, one direction. That separation is the arbiter rule (the agent
|
|
182
|
+
references the operator's ruler and never authors it) and it is not negotiable, so the drift
|
|
183
|
+
is yours to close: the planner and the worker read the `goal` text, not your stage list, and
|
|
184
|
+
they will spend their single check slot (above) on the constraint the goal sentence names.
|
|
185
|
+
A stage your goal never mentions is a **cost trap, not a safety hole** — the close still
|
|
186
|
+
refuses the tree and the run still cannot mint a green — but the run discovers that stage at
|
|
187
|
+
the tail, after paying for work aimed elsewhere, and may not have the money or wall left to
|
|
188
|
+
redo it. Write the goal from the close's own stage list, in the close's own words; keep it to
|
|
189
|
+
a few explicit requirements rather than prose. Preflight already names every stage on the
|
|
190
|
+
`check-menu` spine event — read it back and check your goal against it before you sign.
|
|
191
|
+
|
|
177
192
|
**Close types and the hierarchy — the OBJECT form, which survives only for the
|
|
178
193
|
declared-but-locked verdict classes** (a close is data, never code; verdict-class laundering
|
|
179
194
|
is a named red `close-hierarchy`). The go-forward shape is the staged list above; a
|
|
@@ -233,6 +248,19 @@ first) so a pathological close cannot rebuild the very bloat the bound exists to
|
|
|
233
248
|
when the cap trims matches it **says so** with an explicit marker (silent truncation is the
|
|
234
249
|
disease this cures, never a cure that truncates silently). Validated like `judged.pattern`:
|
|
235
250
|
a non-empty string that must **compile as a RegExp**, else a spec red before any tokens.
|
|
251
|
+
|
|
252
|
+
**Both operator patterns must also survive the ReDoS shape check** (`invalid-value`, F49 —
|
|
253
|
+
the same reject the agent's `artifact-written.pattern` gets): an unbounded quantifier over a
|
|
254
|
+
group that itself repeats unboundedly (`(a+)+`, `(\d*)*`, even wrapped: `((a+))+`) is
|
|
255
|
+
rejected at the gate. **This is stricter than an "it compiles" contract, and deliberately
|
|
256
|
+
so.** `judged.pattern` is exec'd by `runClose` over the close's whole stdout+stderr and
|
|
257
|
+
`gapKeep` is run by `boundGap` **once per line** — both untimed, both in bareloop's own
|
|
258
|
+
process. Measured: `boundGap(stream, "(a+)+$")` on a 40-character line does not finish in
|
|
259
|
+
20 seconds. Because the hang blocks the main event loop, the in-process stall fuse cannot
|
|
260
|
+
fire, the run dies to the outside watchdog, and **a bad regex in a signed spec presents as a
|
|
261
|
+
provider problem** — so this reds at signing time rather than being diagnosed later. All 92
|
|
262
|
+
operator regexes across the 10 in-repo specs pass unchanged. Known limit, parked: the scan
|
|
263
|
+
does not catch the alternation-overlap class (`(a|aa)+$`) — see PRD v1.55.
|
|
236
264
|
Omitting it is exactly today's bound. Like `judged`, it is **arbiter territory** — the
|
|
237
265
|
drafted workflow config cannot express it (unknown-field red). The gap path also combines
|
|
238
266
|
**both** streams (stdout + stderr), so a failure printed to stdout survives stderr noise
|
|
@@ -249,11 +277,23 @@ middle writes; `validatePlan` gates it against the SIGNED job spec before tokens
|
|
|
249
277
|
| `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
278
|
| `steps[].rounds` | int 1..shell cap (default 40) | the step's per-attempt tool-round bound (the Gate's `maxTurns` natively) |
|
|
251
279
|
| `steps[].target` | path inside the fence | v1.18 deliverable; REQUIRED on write-granted steps |
|
|
252
|
-
| `steps[].model` | optional; `sonnet`
|
|
280
|
+
| `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
281
|
| `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
282
|
| `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
283
|
| `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
284
|
|
|
285
|
+
**A step has exactly ONE check slot — plan for it.** The three exit rules above compose into
|
|
286
|
+
a ceiling worth stating on its own, because it decides what a plan can enforce *during* a
|
|
287
|
+
run: `MAX_EXITS_PER_STEP` is **2**, the two are ANDed (no OR/NOT, `exit-illegal` past the
|
|
288
|
+
bound), and a `check-passes` on a write-granted step MUST be paired with a `tree-changed`
|
|
289
|
+
exit. Both slots are therefore spent the moment a step carries one check, so **a step cannot
|
|
290
|
+
carry two checks** — `check-passes(typecheck)` and `check-passes(no-suppressions)` together
|
|
291
|
+
on one step is inexpressible today, not merely discouraged. The consequence for an adopter:
|
|
292
|
+
if your close judges N stages, at most ONE of them can be enforced inside a step; the other
|
|
293
|
+
N−1 are enforced only at the close, at the end of the run. That is a COST shape, not a safety
|
|
294
|
+
hole — the close still refuses a tree that fails any stage, and nothing green is minted mid-run
|
|
295
|
+
— but a stage first tested at the tail is a stage the run pays full price to discover.
|
|
296
|
+
|
|
257
297
|
Red vocabulary (both validators): `parse-error`, `unknown-field`, `missing-required`,
|
|
258
298
|
`invalid-value`, `bounds`, `duplicate-id`, `close-type`, `close-hierarchy`,
|
|
259
299
|
`secret-literal`, `scope-escape`,
|
|
@@ -267,9 +307,12 @@ on refusals). It binds WRITES, not observations: on the exit side it fires only
|
|
|
267
307
|
the narrow one does), and `artifact-written`/`json-valid` paths carry no step-scope red at
|
|
268
308
|
all — the evaluator asks nothing about who wrote the file, so naming a prior step's artifact
|
|
269
309
|
is legal and satisfiable. `job-invalid` — a plan validated against a
|
|
270
|
-
missing or non-plan-shape job fails CLOSED). `
|
|
271
|
-
every check-menu consumer shares
|
|
272
|
-
|
|
310
|
+
missing or non-plan-shape job fails CLOSED). `closeStagesOf(job)` is the ONE staging
|
|
311
|
+
every check-menu consumer shares — it reads whichever close field the spec carries:
|
|
312
|
+
`closeDecl` → its declared stages (each enriched with the arbiter's own `gapKeep`), else
|
|
313
|
+
`stageClose(close)` (array → itself; legacy object predicate → its one-stage list, named
|
|
314
|
+
`close`; gold/rubric/hitl → null). A consumer reading `close` alone would see a declared
|
|
315
|
+
job as closeless and offer the drafter an empty check menu. The `secret-literal` sweep is
|
|
273
316
|
defense-in-depth against known token shapes — env-only loading remains the law, not the
|
|
274
317
|
sweep.
|
|
275
318
|
|
|
@@ -410,7 +453,117 @@ retired-but-accepted, so a plan carries no iteration bound to check; `scopes` is
|
|
|
410
453
|
`tree-changed` scope menu — the array of scope strings the drafter prompt listed, which
|
|
411
454
|
`runJob`/`runPlan` build internally from the signed fence plus the real directories beneath
|
|
412
455
|
it (a direct `validatePlan` caller may pass its own array); omitted, it derives from the
|
|
413
|
-
signed `writeScope` alone, never a free-text fallback. Menus exported: `EXIT_TYPES`, `MAX_EXITS_PER_STEP`, `MAX_PLAN_STEPS`, `WRITE_VERBS` — plus `stageClose`.
|
|
456
|
+
signed `writeScope` alone, never a free-text fallback. Menus exported: `EXIT_TYPES`, `MAX_EXITS_PER_STEP`, `MAX_PLAN_STEPS`, `WRITE_VERBS` — plus `stageClose` and `closeStagesOf`.
|
|
457
|
+
|
|
458
|
+
### Close authoring — the user declares what done means (`src/kinds.js`, `src/authoring.js`, `src/authorscout.js`, `src/authorflow.js`, `src/declaredclose.js`, `src/authorjob.js`)
|
|
459
|
+
|
|
460
|
+
The one layer where a human still wrote code. A close used to be a hand-written script
|
|
461
|
+
per patient; now the user PICKS A VERDICT CLASS, answers that class's questions, and an LLM
|
|
462
|
+
composes a DECLARATION over kinds whose implementations bareloop owns. **v1 admits exactly
|
|
463
|
+
ONE genre** (`TYPES`: a type checker stops complaining without breaking the tests) and
|
|
464
|
+
exactly one verdict class (`green`) — everything else refuses honestly, and every refusal
|
|
465
|
+
is COUNTED.
|
|
466
|
+
|
|
467
|
+
**The verdict class is the USER's answer (PRD v1.57 §1), and it DRIVES the authoring.**
|
|
468
|
+
`verdictType` is a declared radio the preflight validates, never inferred:
|
|
469
|
+
`VERDICT_CLASSES` = `green` | `soft-green` | `hitl`, with `LOCKED_CLASSES` =
|
|
470
|
+
`soft-green` | `hitl` declared-but-locked. Picking a locked one returns a counted
|
|
471
|
+
`request-red` refusal at ADMISSION — before that class's questions are ever asked. The
|
|
472
|
+
pick is also a PROMISE: every catalogue kind carries the `verdictClass` it can honestly
|
|
473
|
+
render, `closeCeiling(declaration)` reports the highest class a declaration demands, and a
|
|
474
|
+
declaration ABOVE the pick is a `class-ceiling` red naming the kind that raised it (inert
|
|
475
|
+
in v1 by construction — every live kind is mechanical).
|
|
476
|
+
|
|
477
|
+
**The pipeline, in call order.** Each piece is exported so an adopter can drive it, cache
|
|
478
|
+
a step, or test it without a provider.
|
|
479
|
+
|
|
480
|
+
| step | call | what it is |
|
|
481
|
+
|---|---|---|
|
|
482
|
+
| interview | `runInterview({ verdictType, answers, repoPath })` → `{ ok, answers, verdictType, refusal, reds }` | PURE — no model, no repo, no clock. THREE frozen question sets keyed by verdict class (`QUESTION_SETS`; `questionsFor(cls)` / `requiredAnswersFor(cls)`), and the interview asks NOTHING about a genre. The green set is six questions, answers keyed by number; the soft-green and hitl sets are named but LOCKED (`questions: null`, absent rather than empty) and their selection refuses before they run. `verdictType` and `repoPath` are STRUCTURED input, never parsed out of prose. Answers are scrubbed at INGEST |
|
|
483
|
+
| survey | `runAuthorScout({ workdir, provider, attempts? })` → `{ state, facts, reason, meta, raw, raws, calls }` | a bounded READ-ONLY LLM survey. Read-only by MENU CONSTRUCTION (`AUTHOR_SCOUT_VERBS` = the full menu minus write-class and store-class verbs), 8 rounds, F59's reserved toolless final round. **`state: 'ABSENT'` means the scout did not complete — never "no special facts are needed"**, and a parsed `{}` is one of its five ABSENT routes. Up to `SCOUT_ATTEMPTS` (**3**, hardcoded — PRD v1.58) attempts: `attempts` is TIGHTEN-ONLY and CLAMPS rather than throws (floor **1**, because a scout that never ran is an ABSENT nobody can act on), the direction every cap in this system runs in. **A retry fires on the typed MALFORMED class alone** (`SCOUT_RETRY_CAUSES` = `empty` \| `unparseable`); what is excluded is excluded for a reason — `call-failed` covers transport and `truncated:max_tokens`, both provider-red with NO redraft; `short` is F59's cut-off population and already has its own instrument INSIDE the attempt; `non-object` and `empty-object` are valid JSON with wrong or vacuous content, which is a SEMANTIC failure, and F38/F39 measured what re-asking one buys (the same distribution, sampled twice) — that is the self-healing line, and it is not crossed here. The re-ask is TOOLLESS over the conversation the survey already produced (the repository was already read; only the emission was unreadable) and names the mechanical parse error and nothing else. **There is no JSON repair behind it and there never will be** — a repairer decides what the model MEANT and writes it down as though the model had said it, in the one artefact whose whole job is to be honest about what a repository contains. `meta.attempts` / `meta.attemptsAllowed` record what was paid for; every attempt is metered under its own label (`author-scout`, `author-scout#2`, …) and leaves its raw behind |
|
|
484
|
+
| listing | `buildSeedListing({ workdir, seedRef, sourcePaths, testPaths })` | mechanical, `$0`, no model. `files` is the WHOLE tree (what the validator judges paths against); `block` is scoped to the survey's own paths and capped in ANNOUNCED tiers (what the prompt carries). Handing the validator the scoped half would make a job scoped to `src/` read as whole-tree and silently disarm the one-population law |
|
|
485
|
+
| authoring | `authorClose({ workdir, seedRef, lang, verdictType, answers, scout, listing, generate })` | the grounded loop: author → validate → run EVERY stage at the seed → feed the MEASURED results back → revise, bounded at `MAX_REVISIONS` (2) — a passed `maxRevisions` is TIGHTEN-ONLY, so a caller may LOWER that ceiling and never buy more revise rounds than the constant (floor **0**: one authoring call with no revise round is a legal ask, and unlimited revising would launder thrash as adaptation) — early-stop on an unchanged declaration. The declaration is emitted through a SCHEMA-FORCED TOOL CALL (`declare_close`), never parsed out of prose; the feedback is EXECUTION OUTPUT only — no model ever reviews another model's close. The return carries **`raws`**: what every model call actually SAID, the scout's attempts absorbed together with the declaration's, in the cost book's own order and under the same labels it meters. Each is `{ label, attempt, bytes, trimmed, text, cause, reason }` through the ONE persist helper (`scrubRaw` — redacted over the ONE `SECRET_PATTERNS` inventory, since a raw is the record most likely in this system to carry a live credential; bounded at `RAW_PERSIST_MAX` (8000) with the trim ANNOUNCING its own full size, and the cut walked back off a continuation byte so a multi-byte character is never split). `raws` is present on EVERY path **including the `$0` preflight refusals** — the path that spends nothing more is exactly the one whose evidence used to vanish with the process. `iterations` records what each turn MEANT (declaration, validation, seed read); `raws` records what it SAID, and a malformation is only ever visible in the second |
|
|
486
|
+
| everything above, composed | `authorCloseForJob({ verdictType, answers, repoPath, lang, generate, ... })` → `{ ok, closeDecl, verdictType, refusal, cost, ... }` | refuses at the cheapest gate that can refuse: an interview refusal costs **zero**. THE GENRE REFUSAL LIVES HERE (not in the interview): a language the catalogue owns no data for, and a LOCKED KIND the model reached for, both come back as counted `request-red` demand |
|
|
487
|
+
| assembly | `assembleSpec(specDraft, { closeDecl, verdictType })` | folds the authored half into the OPERATOR's half. Budgets, the fence, cadence, escalation and the provider are never authored by anything here. The GOAL is passed through, not generated. **A draft that ALREADY carries the authored half is REFUSED — it THROWS, never merges and never overwrites.** `AUTHORED_SPEC_FIELDS` (`close`, `closeDecl`, `verdictType`) names the fields this fold WRITES, as data, so the refusal and the fold cannot disagree about which half of the spec is which; the rule is `job.js`'s one step earlier (two closes are two arbiters, and picking one silently is how a signed artefact stops meaning what the signer read). `scripts/run-author.mjs` asks the same question at **$0 BEFORE the scout**, so the answer never arrives after the model has been paid |
|
|
488
|
+
| the three gates | `prepareSigning({ spec, workdir, seedRef? })` → `{ ok, specHash, gates, work, guards, refusal }` | D9, and it NEVER signs |
|
|
489
|
+
|
|
490
|
+
**D5 — the mandatory guards are SHOWN and FIXED, and the battery keys off the VERDICT
|
|
491
|
+
CLASS** (PRD v1.57 §2 — what a battery is FOR is the class of dishonesty that class of
|
|
492
|
+
verdict admits). `CLASS_BATTERIES` is the attachment point (structure, un-removability,
|
|
493
|
+
which slot is the model's); the tool-specific CONTENTS — which suppression patterns, which
|
|
494
|
+
extensions — resolve at COMPOSITION from the language the declaration names. The green
|
|
495
|
+
battery injects `changed-from-seed`
|
|
496
|
+
and `no-suppressions` FULLY PARAMETERISED (`classGuards({ verdictType, lang })`, which
|
|
497
|
+
THROWS rather than ever hand back an empty battery for a known class and language); the
|
|
498
|
+
model fills exactly one
|
|
499
|
+
slot (the target prefixes) and cannot change, drop, rename, re-kind or NARROW them —
|
|
500
|
+
`validateDeclaration` reds `guard-weakened` on any of it, including on an added `scope` that
|
|
501
|
+
would shrink what the suppression scan covers. `genreEnv(lang, { sourcePrefixes })` is the
|
|
502
|
+
same idea for environment: `MYPYPATH` on a Python patient is a fact no user can supply and
|
|
503
|
+
no model found, it moves no seed number, and a declaration that authors it itself reds
|
|
504
|
+
`genre-owned-env`.
|
|
505
|
+
|
|
506
|
+
**The kind catalogue** (`KIND_CATALOGUE`, and it IS the whole vocabulary): `command-exit`,
|
|
507
|
+
`count-not-worse`, `pattern-absent-in-diff`, `files-changed` are live; `judged-floor` and
|
|
508
|
+
`human-confirms` are NAMED BUT LOCKED (and carry `verdictClass` `soft-green`/`hitl`), so
|
|
509
|
+
declaring one is a counted `locked-kind` red rather than an unknown-kind typo; `harness-loop` (TESTGEN) is ABSENT from v1 entirely. In
|
|
510
|
+
tool mode a locked kind is INEXPRESSIBLE — the schema carries one branch per live kind — so
|
|
511
|
+
that demand arrives through the interview layer instead (`refuseLockedKind(kind)`).
|
|
512
|
+
|
|
513
|
+
**The three gates, and the signature (D9).** Nothing LLM-judges a close.
|
|
514
|
+
1. `validateCloseDecl(closeDecl, { verdictType, ... })` — schema, kinds, params, the F84
|
|
515
|
+
one-population law, F49's static nested-quantifier reject, the D5 guard equality, the
|
|
516
|
+
class ceiling, and the listing rule (a declared path SELECTS from the seed tree or it
|
|
517
|
+
does not exist). `verdictType` is REQUIRED and has no default: the guard battery hangs
|
|
518
|
+
off it, and a validator with no class in hand reds `class-absent` rather than reporting
|
|
519
|
+
a declaration it never checked D5 against.
|
|
520
|
+
2. the CLOSE PRECHECK — every stage runs against the real patient. A stage that cannot run
|
|
521
|
+
is `broken-close`: a CASUALTY, never a red.
|
|
522
|
+
3. the SEED-VERDICT READ — every stage, offered or not, at the seed. Which are RED (that is
|
|
523
|
+
the work) and which are GREEN (those are the guards) is handed back for the user to read.
|
|
524
|
+
**A close with no WORK stage red at seed is refused decision-ready** — it grades nothing,
|
|
525
|
+
and an instrument that scans nothing reads clean exactly like one that measures correctly.
|
|
526
|
+
|
|
527
|
+
Then the human signs `specHash`, unchanged: the approvals array and the human's word are
|
|
528
|
+
the arbiter relocating to the user, not disappearing. **Re-authoring is a spec edit (D6)** —
|
|
529
|
+
a different declaration is different bytes, a different resolved hash, and a new signature.
|
|
530
|
+
The guards are stored ENUMERATED and every short-form parser is expanded, so no
|
|
531
|
+
omittable-with-a-default field can change what runs without changing what was signed.
|
|
532
|
+
|
|
533
|
+
**Refusals are COUNTED (D13).** `refusalEvents(refusal)` returns the spine events: a
|
|
534
|
+
`job-red` carrying `{ code: 'request-red', verb, lib: 'bareloop' }` — the `lib` stamped at
|
|
535
|
+
the EMIT SITE, so `classifyIncidents` files it against bareloop's own catalogue and its
|
|
536
|
+
`suggestedAsk` reads `bareloop: …` rather than seeding an upstream ask — plus one
|
|
537
|
+
decision-ready `escalation` under `close-unauthorable`, which is in the ledger's excluded
|
|
538
|
+
set precisely because the demand is already counted once. A refusal that is a STOP rather
|
|
539
|
+
than demand (a broken instrument, a close with nothing to do) emits the escalation ALONE.
|
|
540
|
+
**Known limit, stated:** v1's derivation cannot tell `soft-green` from `hitl` and does not
|
|
541
|
+
try — both refuse under one verb (`non-green-verdict`), because naming which locked class a
|
|
542
|
+
job belongs to would be a guess about prose.
|
|
543
|
+
|
|
544
|
+
**At run time** the declared close is executed by the kind executor, never compiled down to
|
|
545
|
+
shell (that would turn owned kinds back into authored strings — the exact thing D3 makes
|
|
546
|
+
inexpressible). `runPlan` picks the executor from which field the signed spec carries and
|
|
547
|
+
everything downstream is the same code: first-red-wins, the `CLOSE_FAULTS` forbidden zone
|
|
548
|
+
(routed by a TYPED fault, so a timeout still offers "raise the close timeout"), the same
|
|
549
|
+
`close stage "<name>" failed:` gap header the trend reader parses, the same scrub at the
|
|
550
|
+
emission boundary, and the check menu still derived from stage names one hop. The seed is
|
|
551
|
+
READ at run start (`seedAtHead`) and recorded on a `close-decl` spine event together with
|
|
552
|
+
the grounded re-validation's result. Direct executor access: `runDeclaredStages(stages,
|
|
553
|
+
redact, { cwd, seedRef, timeoutMs })` returns `runClose`'s verdict shape **plus one added
|
|
554
|
+
field, `notes`** — the DIAGNOSTIC channel, per stage and on the summary, ABSENT when empty
|
|
555
|
+
(never `''`). M1's kinds announce some things whichever way a stage lands (a declared env var
|
|
556
|
+
that was dropped, the scope filter's own arithmetic), and a RED carries those out inside its
|
|
557
|
+
`gap` while a GREEN and an instrument stop had nowhere to put them — checking the GREENS is
|
|
558
|
+
doctrine, and an audit trail that only survives a red cannot do it. It is deliberately NOT
|
|
559
|
+
`gap`: every consumer guards with `if (gap)`, so a green carrying one would read as
|
|
560
|
+
revision-worthy and a stop carrying one would look like a verdict it never rendered. **Nothing
|
|
561
|
+
routes on it, nothing bounds on it, and no verdict moves because of it** — it rides the records
|
|
562
|
+
that already spread the whole verdict (`close-precheck`, `outer-close`, ralph's
|
|
563
|
+
`close-verdict`). `runDeclaredClose`
|
|
564
|
+
and `seedRead` are the kind executor's own two entries (`runDeclaredClose` is named apart
|
|
565
|
+
from ralph's shipped `runClose` because one runs a DECLARATION and the other an argv);
|
|
566
|
+
`closeStagesOf(job)` is the ONE staging every close consumer reads, widened to both fields.
|
|
414
567
|
|
|
415
568
|
### `snapshotScope(dir, scope)` / `evalExits(exits, { dir, snapshot?, runCheck? })` — `src/exits.js`
|
|
416
569
|
|
|
@@ -431,6 +584,17 @@ ONE inventory) at the emission boundary, before anything rides the append-only s
|
|
|
431
584
|
module itself does not scrub: a direct `evalExits` caller wiring results into its own log
|
|
432
585
|
must apply its own redaction.
|
|
433
586
|
|
|
587
|
+
A `check-passes` detail is sliced at `CHECK_GAP_MAX` (12,000 chars) — a defensive backstop
|
|
588
|
+
above the close path's own `boundGap` envelope, not a second trim of it: a normally-bounded
|
|
589
|
+
gap passes through intact and only a seam that returned something unbounded is ever cut. The
|
|
590
|
+
constant is now `export`ed from `src/exits.js` so the replan brief's own backstop imports it
|
|
591
|
+
rather than respelling it (two spellings would drift on the one property that matters —
|
|
592
|
+
whether the `not ok`/`FAILED` names survive). **It is NOT part of the adopter surface:** the
|
|
593
|
+
package `exports` map admits only `"."`, and `src/index.js` re-exports `snapshotScope` and
|
|
594
|
+
`evalExits` from this module and nothing else, so `CHECK_GAP_MAX` and `boundGap` are internal
|
|
595
|
+
by construction and a deep import cannot reach them. They are named here to explain the
|
|
596
|
+
behaviour you WILL see in a detail string, not as API to call.
|
|
597
|
+
|
|
434
598
|
### `jobSpecHash(job)` / `checkApproval(job, approvals)` — `src/job.js`
|
|
435
599
|
|
|
436
600
|
The pure half of **human-signs-always**: an agent may draft a job spec, but no job runs
|
|
@@ -454,10 +618,12 @@ Reserved spine vocabulary (V7, machinery-free until job #1 surfaces one):
|
|
|
454
618
|
`coordination-red` — a failure between units (scope contention, step order, store
|
|
455
619
|
races), never to be folded into worker/interpreter reds.
|
|
456
620
|
|
|
457
|
-
### `runJob(spec, { approvals, workdir, provider, nativeProvider?, providerFor?, emit, capRuns?, strikeLimit?, shellCapUsd?, closeTimeoutMs?, layerRoot?, bridge?, priorSpentUsd?, priorSpendComplete?, priorWallMs?, resumeSeed?, resumeGrades? })` → outcome — `src/run.js`
|
|
621
|
+
### `runJob(spec, { approvals, workdir, provider, nativeProvider?, providerFor?, emit, capRuns?, strikeLimit?, shellCapUsd?, closeTimeoutMs?, layerRoot?, bridge?, priorSpentUsd?, priorSpendComplete?, priorWallMs?, resumeSeed?, resumeGrades?, resumeReplans?, resumeBranch? })` → outcome — `src/run.js`
|
|
458
622
|
|
|
459
|
-
The last
|
|
460
|
-
|
|
623
|
+
The last seven are the RESUME fold and are documented under *Resuming a killed run* below; they
|
|
624
|
+
default to `0` / `true` / `0` / `null` / `[]` / `null` / `null`, so a fresh run passes none of them.
|
|
625
|
+
Three of them are folds of a bound the operator SIGNED (money, wall, replans) and one is a
|
|
626
|
+
readout seed (grades) — the distinction matters and is spelled out there.
|
|
461
627
|
|
|
462
628
|
The runner — the shell's top layer, and the ONE entry. It composes everything below it and
|
|
463
629
|
interprets nothing itself. Sequence: **approval gate** (human-signs-always — refuses an
|
|
@@ -467,8 +633,11 @@ known-answer round-trip before tokens: `smoke-red` — a silent degradation thro
|
|
|
467
633
|
`min(job budget − spent, shell cap)`.
|
|
468
634
|
|
|
469
635
|
Outcomes: `green | already-green | escalated | unapproved-spec | job-red | smoke-red |
|
|
470
|
-
plan-red | check-red | close-red | close-unsupported | recipe-stale | pricing-red |
|
|
471
|
-
provider-red | interpreter-red | cap-halt | wall-halt | step-stalled | step-red:<id>`.
|
|
636
|
+
plan-red | check-red | close-red | close-unsupported | recipe-stale | branch-red | pricing-red |
|
|
637
|
+
provider-red | interpreter-red | cap-halt | wall-halt | step-stalled | step-red:<id>`.
|
|
638
|
+
`branch-red` is the WORK BRANCH refusing (below): the patient is not a git checkout, its
|
|
639
|
+
branch namespace has no free name, or a resume's recorded branch is gone. Zero tokens, and
|
|
640
|
+
never a fallback to working on the branch the run was handed. `provider-red` is a
|
|
472
641
|
transport throw or a worker round the API cut off mid-generation (`truncated:max_tokens`,
|
|
473
642
|
BA-6 — before which it laundered into a clean finish, F25): no verdict exists and the failed
|
|
474
643
|
round's spend is only partly known (F6). `cap-halt` is the wallet; `wall-halt` is the clock
|
|
@@ -516,7 +685,9 @@ their own ledger): **close precheck** (`already-green` is a
|
|
|
516
685
|
DISTINCT zero-token outcome; a forbidden-zone verdict escalates before spend) → **check-menu
|
|
517
686
|
preflight** (`check-menu` names the derived menu, then every OFFERED stage's chain runs once
|
|
518
687
|
at $0 — an unrunnable stage is a `check-red` stop before tokens, not a fault mid-plan) →
|
|
519
|
-
**
|
|
688
|
+
**THE WORK BRANCH** (below — created before the first paid call, `branch-red` if it cannot
|
|
689
|
+
be; on a RESUME it runs FIRST, ahead of the precheck and the preflight, because the recorded
|
|
690
|
+
branch IS the tree those instruments must measure) → **SCOUT** (read-only by construction: neither the
|
|
520
691
|
write-class nor the store-class verbs are in its menu; hard-bounded rounds) → **PLAN** (the decompose call —
|
|
521
692
|
the planner never sees the repo, only the scout blob; drafted against a schema
|
|
522
693
|
description with check NAMES only; `validatePlan` gates it, one redraft with the reds
|
|
@@ -524,8 +695,9 @@ fed back, then `plan-red`) → **EXECUTE** (strictly sequential micro-loops: `ra
|
|
|
524
695
|
the exit-evaluator judge, each step under its own STRIKE LADDER — below; tree snapshots at
|
|
525
696
|
step start; the gap names every failing
|
|
526
697
|
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)
|
|
698
|
+
step id) → **ONE replan**, triggered by exhaustion OR variance OR a stall (an instrument stop
|
|
699
|
+
never replans) — plus at most ONE arbiter-granted extra on a second variance stop that reads
|
|
700
|
+
`converging`, below → **the operator's close**, a red feeding ONE bounded fix loop judged by the
|
|
529
701
|
REAL close and governed by the close TREND rule below (`capRuns`, default 3, survives only as
|
|
530
702
|
its blind fallback). `plan-executed` (the plan-as-executed record, design law #2) lands on the
|
|
531
703
|
spine on every path that executed steps. Additional outcomes: `already-green |
|
|
@@ -533,6 +705,56 @@ plan-red | check-red | close-red | wall-halt`. Worker prompts hold the v1.12 §5
|
|
|
533
705
|
(mutation-proven): the absolute repo root, the step's action/target, prior artifacts,
|
|
534
706
|
the gap — NEVER the budget, the close command, a check's command, or the arbiter's books.
|
|
535
707
|
|
|
708
|
+
**THE WORK BRANCH — a hard rule, and a REQUIREMENT on the patient (PRD v1.57 §3).** hamr's
|
|
709
|
+
ruling, verbatim: *"The agent creates a NEW BRANCH before it touches any code — a HARD RULE,
|
|
710
|
+
no exceptions. Not a default, not a preference: no job edits the branch it was handed, and
|
|
711
|
+
none edits `main`."* So **the patient must be a git repository with at least one commit** —
|
|
712
|
+
that is new, and it is the honest consequence of the rule rather than an option. And the
|
|
713
|
+
patient itself is **ALWAYS a separate COPY of the repo being treated, never the original**
|
|
714
|
+
(hamr, 2026-08-09): the branch bounds where writes land, but workers and close stages
|
|
715
|
+
still mutate the patient tree (branches per try, caches, worktrees) — the copy is the
|
|
716
|
+
blast radius; the original never carries it. What the run does, before its first paid
|
|
717
|
+
call:
|
|
718
|
+
|
|
719
|
+
- **The name is DERIVED from the SIGNED spec**, never model-authored (a branch name is
|
|
720
|
+
arbiter bookkeeping): `bareloop-` + the spec's own `job` slug — `bareloop-typecheck-fix`.
|
|
721
|
+
`workBranchName(spec)` is exported so a runner can show it at its approval gate. The goal
|
|
722
|
+
prose is deliberately NOT an input: it is edited between runs without changing what the
|
|
723
|
+
job is, and the branch name would move with it.
|
|
724
|
+
- **A name already taken is SUFFIXED** `-2`, `-3`… — a second run of the same spec is a
|
|
725
|
+
second blast radius and never reuses the first one's branch.
|
|
726
|
+
- **A RESUME returns to ITS OWN branch.** Pass `resumeBranch` (read off the dead spine by
|
|
727
|
+
`readResume` as `restart.branch`); without it the deterministic name would collide with the
|
|
728
|
+
killed leg's own branch and mint a `-2` beside the work the resume exists to keep. The
|
|
729
|
+
recorded branch is the ONLY branch a resume can land on — it is validated and checked out,
|
|
730
|
+
never re-derived from the spec and never suffixed. A recorded branch that no longer exists
|
|
731
|
+
is a `branch-red` STOP, never a fresh start.
|
|
732
|
+
- **On a RESUME this step runs FIRST**, ahead of the close precheck and the check preflight
|
|
733
|
+
(cold, it runs after them — see the already-green bullet). Both are $0 readings OF A TREE,
|
|
734
|
+
and on a resume the tree they must read is the recorded branch's: standing on whatever ref
|
|
735
|
+
the operator handed back, the precheck could read `already-green` off a tree that is not
|
|
736
|
+
this run's and every `baseline: "seed"` stage would baseline against it. Nothing is minted
|
|
737
|
+
by moving it — the resume arm creates nothing — and a resume whose branch is gone or foreign
|
|
738
|
+
now stops `branch-red` before the arbiter grades anything at all.
|
|
739
|
+
- **`work-branch { branch, created, resumed, from, base, repo, collided? }`** lands on the
|
|
740
|
+
spine at creation — the run's books say where its work went.
|
|
741
|
+
- **Every failure is `branch-red`**, escalated decision-ready with zero spend. There is no
|
|
742
|
+
fallback to the handed branch anywhere; and `mkWorker`, the ONE seam that grants
|
|
743
|
+
write-class verbs, refuses to build a write-capable worker with no branch prepared, so the
|
|
744
|
+
ordering is a convenience and the guard is the rule.
|
|
745
|
+
- **A COLD run's `already-green` tree leaves no branch behind** — the precheck returns above
|
|
746
|
+
this step, and a run with no work has no blast radius to bound. The clause is about
|
|
747
|
+
MINTING: a RESUME that reads `already-green` off its own branch (which it is standing on by
|
|
748
|
+
then) leaves that branch and the paid work on it exactly where they were.
|
|
749
|
+
|
|
750
|
+
**What it does NOT do.** bareloop never commits, so the branch bounds where work LANDS: the
|
|
751
|
+
tree stands on a branch nobody handed the run, and the handed ref is never moved or written
|
|
752
|
+
through. It is not containment — v1.41's local-trust model is unchanged, and worker/close
|
|
753
|
+
children still run as the operator's OS user with network egress. A workdir that is a
|
|
754
|
+
SUBDIRECTORY of a repository gets its branch in the enclosing repository (git resolves
|
|
755
|
+
upwards, exactly as every other git read in the flow does); the `work-branch` record's `repo`
|
|
756
|
+
field names which repository that was, rather than leaving it to be discovered.
|
|
757
|
+
|
|
536
758
|
**The strike ladder — how a STEP ends (F77–F79).** A plan step is **not** bounded by a
|
|
537
759
|
number of iterations. It runs until it stops making PROGRESS: a *strike* is a red iteration
|
|
538
760
|
that repeats an already-seen gap or wrote nothing, and `strikeLimit` strikes (shell option on
|
|
@@ -557,8 +779,26 @@ escalation and wall-stop records carry `strikes`/`strikeLimit`/`distinctGaps` be
|
|
|
557
779
|
iteration count rather than a cap the step no longer has. **The replan brief
|
|
558
780
|
names the mechanism**: which shape ended the step (converging-cut / stalled-no-write /
|
|
559
781
|
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
|
-
|
|
782
|
+
and wall the stop left unspent (time reported UNBOUNDED when no wall was set, never `0`).
|
|
783
|
+
**It also carries that step's own last EXIT output, verbatim** (F86) — masked through the repo's
|
|
784
|
+
one secret inventory, and labelled as *the exits'* words rather than the close's, because the
|
|
785
|
+
payload is `lastGap`: the join of EVERY failing exit's detail in exit order. The close stage's
|
|
786
|
+
output is one of those details, never reliably all of them, so a step that wrote nothing opens
|
|
787
|
+
the block with the exit evaluator's own `0 files changed under …` prose. That line is kept (it is
|
|
788
|
+
exactly what a replanner should be told); only the label was wrong. The bound over it is a
|
|
789
|
+
**backstop, not a second envelope** — what arrives has already been bounded by `runClose` under
|
|
790
|
+
the stage's own `gapKeep`, which deliberately rescues the `not ok`/`FAILED` names out of the
|
|
791
|
+
elided middle, and re-enveloping that deletes them a second time (F28 reintroduced; measured, a
|
|
792
|
+
second 400/1500 pass dropped 12 of the 78 names the first had rescued). Under `CHECK_GAP_MAX` —
|
|
793
|
+
`src/exits.js`'s own ceiling, imported rather than respelled so the two seams cannot drift — the
|
|
794
|
+
gap passes through VERBATIM, with ~1.8KB of measured headroom over the largest thing the close
|
|
795
|
+
envelope can produce; the backstop fires only on a gap nothing upstream ever bounded. No gap, no
|
|
796
|
+
block, and the brief is byte-identical to one drafted before this existed.
|
|
797
|
+
Handed over as TEXT: the runner makes **no parsed file claim of its own** in this channel —
|
|
798
|
+
a close's summary line ("N error(s) in a.js, b.js" — every file in scope) and its detail line
|
|
799
|
+
(the one location actually failing) are indistinguishable to a regex, and the shapes differ per
|
|
800
|
+
close anyway. What the planner is told about WHERE the work is comes from the close's own
|
|
801
|
+
bytes, never from the runner's reading of them.
|
|
562
802
|
|
|
563
803
|
**The close TREND rule — how the CLOSE-FIX LOOP ends (PRD v1.46 §4).** `capRuns` retired as
|
|
564
804
|
that loop's governor once its own $0 replay over every archived fix loop came back clean (0
|
|
@@ -608,7 +848,9 @@ on money (the step loop, the close-fix loop, and the scout/draft relay), exactly
|
|
|
608
848
|
close rendered no number (F6). **The library only ever REPORTS**: `budgetUsd` is in the spec
|
|
609
849
|
hash, so a top-up is a spec edit a human signs — nothing in a run may widen its own budget.
|
|
610
850
|
|
|
611
|
-
**ONE progress instrument, read by both halts
|
|
851
|
+
**ONE progress instrument, read by both halts — and REPORTED by the variance meter.** The
|
|
852
|
+
`variance` record and its escalation carry the same four fields off the same instance (below);
|
|
853
|
+
the meter is the one consumer that never decides on the reading. The wall halt used to run its own: byte
|
|
612
854
|
equality of the last two close gaps, reading `stalled` / `moving` / `unknown` beside the money
|
|
613
855
|
halt's `flat` / `converging` / `unknown` on the same run. Both now read `runTrend`, the run's
|
|
614
856
|
own `src/trend.js` reader. Where a stage reported a comparable NUMBER it decides; where none
|
|
@@ -640,7 +882,7 @@ is read, so a resume of a resume inherits the previous leg's chain and not the o
|
|
|
640
882
|
(`createClock`, `src/clock.js`) from the signed `maxWallMs` and emits `wall-clock` with the
|
|
641
883
|
requested AND enforced numbers up front, plus the `closeStages` count that explains the gap
|
|
642
884
|
between them (`enforcedMs = maxWallMs + closeStages × closeTimeoutMs`, one expression — the
|
|
643
|
-
stage count comes from `
|
|
885
|
+
stage count comes from `closeStagesOf`, the same staging the runner executes). Enforcement is a
|
|
644
886
|
**between-round deadline** — the only seam that exists, since `loop.stop()` cannot cut an
|
|
645
887
|
in-flight call (F61: fired at 500ms,
|
|
646
888
|
returned at 4,018ms) — so an attempt that crosses the deadline mid-flight emits `wall-bounded`,
|
|
@@ -671,14 +913,57 @@ The planner (never the worker) receives a **materials** block at draft and at re
|
|
|
671
913
|
spread on verification-gap duration, so a per-round constant describes almost no real round
|
|
672
914
|
and a rate framing creates a rush-or-fake incentive. The replan trigger gains a second axis
|
|
673
915
|
(`step-variance`): a step consuming ≥ `VARIANCE_THRESHOLD` (0.5) of the run's REMAINING money
|
|
674
|
-
or time
|
|
916
|
+
or time — either axis — is pre-empted at the head of its next attempt so the planner
|
|
675
917
|
re-allocates instead. **Known and recorded (F63): that trigger fired 0 times across 18
|
|
676
918
|
archived spines / 54 steps** (near misses 0.35–0.45) — the threshold is arbiter territory,
|
|
677
|
-
deliberately not fitted to those points.
|
|
919
|
+
deliberately not fitted to those points. It is no longer inert: it fires on real runs (F85).
|
|
920
|
+
|
|
921
|
+
**The meter REPORTS progress; it never decides on it (F85).** The firing condition above is
|
|
922
|
+
the whole trigger — a progress term inside it would let a governance instrument over an
|
|
923
|
+
operator-owned allowance judge capability, and a converging step that is eating the run is
|
|
924
|
+
still stopped. What the meter says about the work is a MEASURED reading, not an assertion: the
|
|
925
|
+
`variance` spine record gains `trend` (`converging | flat | unknown`), `motion`, `reading` and
|
|
926
|
+
`series` beside its unchanged `step`/`iteration`/`threshold`/`moneyShare`/`timeShare`/`axis`,
|
|
927
|
+
and the escalation `detail` (which the replan brief quotes) ends with that same `reading` as a
|
|
928
|
+
second, separate claim — "it was stopped for eating the run" and "here is what it achieved" are
|
|
929
|
+
different statements. All of it is read off the SAME `src/trend.js` instance the money and wall
|
|
930
|
+
halts read — one reader, so the meter and the halts can never disagree, and `unknown` stays
|
|
931
|
+
`unknown` when the close reported no number (F6). This
|
|
932
|
+
replaces a fixed sentence ("with its exits unmoved") that used to print on every variance stop
|
|
933
|
+
whatever had happened; a consumer parsing that string will not find it.
|
|
934
|
+
|
|
935
|
+
**The replan ceiling is no longer flatly ONE.** A SECOND `step-variance` stop earns exactly one
|
|
936
|
+
more replan when the run's own reading is mechanically `converging`; it is granted by the
|
|
937
|
+
ARBITER off that same reader, bounded by a latch rather than a counter compared against a
|
|
938
|
+
limit, so it cannot creep — a third variance stop is the stop however well the run is going.
|
|
939
|
+
`flat` and `unknown` stop exactly as before, and an exhaustion or stall stop past the ceiling
|
|
940
|
+
is unchanged. The agent has no channel to it: it never asks, is never offered it, and cannot
|
|
941
|
+
influence it (no self-adjusted budgets, ever). Observable on the spine as ADDED fields, never
|
|
942
|
+
repurposed ones: `replan` records carry `replan: <n>` and — only on a granted one — `granted:
|
|
943
|
+
"converging"`, and `plan-executed` carries `replans` beside the unchanged `replanned` boolean.
|
|
944
|
+
|
|
945
|
+
**Both of those count the RUN, not the leg — the ceiling spans a resume chain.** `replanned`
|
|
946
|
+
and `replans` were locals in `runPlan`, and a resume is another `runPlan` call, so a kill used
|
|
947
|
+
to hand the next leg a fresh allowance of an allowance that is the RUN's by doctrine (measured:
|
|
948
|
+
leg 1 replanned and stopped, leg 2 replanned twice more — a run total of 2 with every record
|
|
949
|
+
showing 1). **`plan-executed.replans` therefore now means the CHAIN total**; if you parse spines,
|
|
950
|
+
re-read that field. Each leg DECLARES what it inherited on its own `job-start` — new fields
|
|
951
|
+
`priorReplans` and `priorReplanGrantUsed`, present only when there is a fold — and the next
|
|
952
|
+
reader adds only its own window's `replan` records, exactly the way `priorSpentUsd` folds money.
|
|
953
|
+
The grant latch travels beside the count rather than being derived from it (`1` + `false` is a
|
|
954
|
+
real state: an ordinary ceiling spent, the arbiter's extra still unearned). The leg's own number
|
|
955
|
+
is not lost — a leg emits one `replan` record per replan it drafts, so `chain −
|
|
956
|
+
records-in-this-window` is the fold it inherited. You seed it with `resumeReplans` (below);
|
|
957
|
+
omitted is the cold path and byte-identical to a run nobody resumed.
|
|
678
958
|
|
|
679
959
|
**Per-step model tiers — `providerFor` (P).** A plan step may declare a `model` TIER
|
|
680
|
-
(`sonnet
|
|
681
|
-
|
|
960
|
+
(`sonnet`, `STEP_MODELS` — `haiku` removed 2026-08-06 as a reversible attribution probe,
|
|
961
|
+
F87; the field stays legal and one-token reversible, and the narrowing binds only what the
|
|
962
|
+
AGENT may express, never what the operator may run).
|
|
963
|
+
The tier menu is signed into the schema; mapping a tier
|
|
964
|
+
onto a real model (and any per-model provider params) is the RUNNER's territory — nothing
|
|
965
|
+
forces the `sonnet` tier onto any particular model, and running haiku as your default worker
|
|
966
|
+
model stays an operator choice the plan menu never governed — so both
|
|
682
967
|
`runJob` and `runPlan` take `providerFor?: (tier: string) => provider`. It is called ONLY for
|
|
683
968
|
a step that declared a tier, once per such step — the scout, the plan drafter and every
|
|
684
969
|
untiered step stay on `opts.provider`, so a caller that never plans tiers can omit it
|
|
@@ -792,7 +1077,7 @@ their next green, not repaired.
|
|
|
792
1077
|
door**: before the clock, before the close precheck, before any token. On a pass, the
|
|
793
1078
|
**newest** version's plan rides into the FIRST drafting prompt as a starting draft
|
|
794
1079
|
(`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
|
|
1080
|
+
ordinary shipped path — same validator, same redraft-on-reds, the same replan ceiling, and a
|
|
796
1081
|
**replan never re-injects the bridge** (it drafts from the run's own state). On a fail the
|
|
797
1082
|
run returns the distinct terminal **`recipe-stale`** with `bridge-gate {outcome, name, reds}`
|
|
798
1083
|
and a decision-ready escalation, having spent nothing. **Falling back to cold is the
|
|
@@ -938,8 +1223,8 @@ the secrets scan.
|
|
|
938
1223
|
A reuse run is up to `tries + 1` full jobs long, so a kill mid-run is a real event and it
|
|
939
1224
|
must not cost the whole envelope. **`readResume(events, {deathAt?, direct?, resumableOutcomes?})`**
|
|
940
1225
|
reads the dead run's own spine back into the state a resume continues from: `{ started,
|
|
941
|
-
approvalHash, completed[], tried[], restart (with its step-level `seed
|
|
942
|
-
`grades`), r1Missing, spentUsd,
|
|
1226
|
+
approvalHash, completed[], tried[], restart (with its step-level `seed`, its close
|
|
1227
|
+
`grades`, and its work `branch`), r1Missing, spentUsd,
|
|
943
1228
|
spendComplete, carrySpentUsd, carrySpendComplete, ended, greened, … }`. Hand that object to
|
|
944
1229
|
**`runReuse({…, resume})`** and the run picks up where it stopped.
|
|
945
1230
|
|
|
@@ -950,7 +1235,8 @@ semantics are byte-unchanged (PRD v1.46 §3):
|
|
|
950
1235
|
implicit try opened at `job-start`, through the same window machinery rather than a second
|
|
951
1236
|
reader. Without it such a spine is honestly refused (`started: false`), which is what a
|
|
952
1237
|
reuse runner needs. `job-start` carries the DECLARED fold (`priorSpentUsd` with its
|
|
953
|
-
`priorSpendComplete`,
|
|
1238
|
+
`priorSpendComplete`, `priorWallMs`, and `priorReplans` with its `priorReplanGrantUsed`, each
|
|
1239
|
+
present only when there is one) exactly as
|
|
954
1240
|
`try-start` does, so a chain of resumes adds only each attempt's own new rounds instead of
|
|
955
1241
|
re-deriving and double-billing. **An inherited FLOOR is one** even when the money it qualifies
|
|
956
1242
|
is `$0`: a leg whose every round came back unpriced folds nothing countable and is still not
|
|
@@ -962,10 +1248,17 @@ semantics are byte-unchanged (PRD v1.46 §3):
|
|
|
962
1248
|
work on disk and the plan on the spine. A green or any red stays non-resumable under every
|
|
963
1249
|
setting — a verdict already rendered is never re-bought.
|
|
964
1250
|
|
|
1251
|
+
`restart.branch` is the WORK BRANCH the dead leg was working on, READ off its `work-branch`
|
|
1252
|
+
record and never re-derived: the name is deterministic from the signed spec, so a restart that
|
|
1253
|
+
recomputed it would collide with its own predecessor and be handed a fresh `-2` beside the work
|
|
1254
|
+
it came back for (the declared-fold precedent, `priorSpentUsd`'s exactly). Pass it on as
|
|
1255
|
+
`resumeBranch`; `runReuse` does it for you. `null` — a leg killed before it ever branched — is
|
|
1256
|
+
the cold path, and correct, because a leg that never branched has no work to strand.
|
|
1257
|
+
|
|
965
1258
|
`scripts/run-u.mjs --resume <runid|path>` is the operator-side consumer: it skips the patient
|
|
966
1259
|
reset (`resumeTreeGate` instead — a dirty tree is what a resume expects; only a moved HEAD
|
|
967
|
-
stops it), folds the halted run's money and wall in,
|
|
968
|
-
outside watchdog on the REMAINING wall. The top-up itself stays a spec edit the human signs.
|
|
1260
|
+
stops it), folds the halted run's money and wall in, returns to the dead leg's own work branch,
|
|
1261
|
+
re-enters at the checkpoint, and arms the outside watchdog on the REMAINING wall. The top-up itself stays a spec edit the human signs.
|
|
969
1262
|
**A zero wall remainder REFUSES the launch** (exit 2, before the key and before the patient):
|
|
970
1263
|
W-2 says a run out of time keeps the grade it has and stops, so a resume with no time to start
|
|
971
1264
|
anything in is a decision, not an unbounded launch — and launching it used to hand the outside
|
|
@@ -1018,6 +1311,23 @@ and is re-signed.
|
|
|
1018
1311
|
that re-grades an unchanged tree is flat on its own evidence while the RUN — what the
|
|
1019
1312
|
top-up decision is actually about — may have been converging when the money ran out. See
|
|
1020
1313
|
the trend section above for what the seed does and, deliberately, does not touch.
|
|
1314
|
+
- **Its REPLAN LEDGER comes with it too — and this one is a BOUND, not a readout.**
|
|
1315
|
+
`restart.replans` / `restart.replanGrantUsed` ride into `runJob`/`runPlan` as **`resumeReplans`**
|
|
1316
|
+
(`{count, grantUsed} | null`), seeding the replan ceiling instead of leaving it reborn per call.
|
|
1317
|
+
Without it a kill refilled an allowance PRD v1.12 makes the RUN's — measured, a leg that spent
|
|
1318
|
+
both replans and step-redded handed its checkpoint to a second leg that replanned twice more.
|
|
1319
|
+
**It deliberately does NOT use `resumeGrades`' mechanism.** The grade seed reads ONE spine, so a
|
|
1320
|
+
resume of a resume inherits the previous leg's grades and not the whole chain; that shortening is
|
|
1321
|
+
fail-safe for a READOUT (it can only under-claim a direction) and is the DANGEROUS direction for
|
|
1322
|
+
a ceiling, where an under-claimed ledger is a refilled allowance and every kill buys one. So this
|
|
1323
|
+
seed follows the MONEY fold: each leg declares `priorReplans`/`priorReplanGrantUsed` on its own
|
|
1324
|
+
spine and the next reader adds only its own window, so leg 3 inherits the whole chain. It is also
|
|
1325
|
+
not the same class as the trend's ITERATIONS, which `src/trend.js` refuses to seed on purpose: an
|
|
1326
|
+
attempt allowance is a LEG bound bought with the leg's own money, while a replan ceiling bounds
|
|
1327
|
+
how many times the WORKFLOW may be redrawn, which is the run's span. Belted like every declared
|
|
1328
|
+
fold — a garbage, non-finite or negative `count` reads as `0` rather than widening the ceiling.
|
|
1329
|
+
Null/omitted is the cold path. The disarm control is pinned: a resumed leg with an UNSPENT
|
|
1330
|
+
ceiling still replans, so the bound is bounded, not switched off.
|
|
1021
1331
|
- **Under the REMAINDER, never a fresh allotment.** The killed attempt's spend and wall
|
|
1022
1332
|
FOLD IN (`runJob`'s `priorSpentUsd`/`priorSpendComplete`/`priorWallMs`, `createClock`'s
|
|
1023
1333
|
`priorElapsedMs`), so
|
|
@@ -1080,15 +1390,24 @@ the same corpus), and `seq` continues monotonically across appends. Keys are
|
|
|
1080
1390
|
across runs dedupes and distinct bugs in one verb don't merge. Classes, worst-first
|
|
1081
1391
|
(`LEDGER_CLASSES`, frozen): `silent-degradation` (a failed `primitive-smoke` — the class
|
|
1082
1392
|
failures can't derive), `runtime-red`, `provider-red`, `pricing-red` (F6), possibly-dormant
|
|
1083
|
-
`capability-gap` (cap-halt + request-red in one spine
|
|
1393
|
+
`capability-gap` (cap-halt + request-red in one spine — one row per `(verb, lib)`, never per
|
|
1394
|
+
verb, so two territories asking for one verb string stay two rows rather than collapsing onto
|
|
1395
|
+
whichever territory was seen first), `broken-close` (consumer-attributed),
|
|
1084
1396
|
`request-red` (admission demand for a locked verb — keyed on the red's structured
|
|
1085
|
-
`verb` field, prose-quoted verb as legacy fallback
|
|
1086
|
-
|
|
1397
|
+
`verb` field, prose-quoted verb as legacy fallback; both classes take their `lib` from the
|
|
1398
|
+
red's own stamp, so neither seeds an upstream ask for a bareloop-catalogue refusal),
|
|
1399
|
+
`retention-red`, `config-red`
|
|
1400
|
+
(drafting friction — attributed to bareloop's own schema/prompt). Close-authoring
|
|
1401
|
+
refusals ride the SAME `request-red` channel with `lib: 'bareloop'` stamped at the emit
|
|
1402
|
+
site (`refusalEvents`), so genre/verdict/locked-kind demand is counted without ever
|
|
1403
|
+
seeding an upstream ask. Deliberate exclusions
|
|
1087
1404
|
(`EXCLUDED_ESCALATIONS`, a runtime set — anything outside classified ∪ excluded is
|
|
1088
1405
|
counted as unmapped, never dropped): `cap-halt`/`wall-halt` (budget stories, money and
|
|
1089
1406
|
time), `step-stalled` (the stall fuse firing is our governance, not an observed provider
|
|
1090
1407
|
failure), `step-variance` (a planning story), `gate-red`/`smoke-red` (governance working
|
|
1091
|
-
as intended / already counted), `hitl-close`/`close-unsupported` (by design
|
|
1408
|
+
as intended / already counted), `hitl-close`/`close-unsupported`/`close-unauthorable` (by design — and the last one
|
|
1409
|
+
is excluded for a SECOND reason: its demand is already counted once as the `request-red`
|
|
1410
|
+
the same refusal emits, so counting the escalation too would double every refusal),
|
|
1092
1411
|
`close-timeout`/`close-killed`/`close-crashed` (the arbiter's own named terminals, F17);
|
|
1093
1412
|
`close-verdict`/`artifact-red` stay worker stories, `pr-red` operator environment.
|
|
1094
1413
|
`suggestedAsk` on every row is a template seed for an upstream ask — filing stays human;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bareloop",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.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",
|