bareloop 0.19.1 → 0.20.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 CHANGED
@@ -5,6 +5,29 @@ 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.20.0] — 2026-09-05
9
+
10
+ ### Added
11
+
12
+ - Operator-only scout OFF switch (`docs/product/SCOUT-CONTRAST.md`): `runJob`/`runPlan`
13
+ accept `scout` (boolean, default `true`); `false` on a fresh run skips the read-only
14
+ survey and records `scout-skipped { reason: 'operator-off' }` instead — the planner
15
+ drafts from `(no scout notes)`. `scripts/run-u.mjs --scout on|off` is the runner
16
+ surface, modelled on `--read-shim`; a non-boolean/unrecognised value throws/exits
17
+ before a token is spent. Runner territory — the signed spec hash is unaffected.
18
+ Measured (F126): both arms green on `aurora-u-spawner-types`, OFF cost $0.37 more
19
+ (inside the $0.40 noise band) — the scout is not dead weight; the switch stays as a
20
+ probe knob, no default flip.
21
+
22
+ ### Docs
23
+
24
+ - Export scoping and lock: `docs/product/EXPORT-SCOPING.md` (tree facts, proposed
25
+ bundle shape) and `docs/product/EXPORT-BUILD.md` (frozen build spec — worktree,
26
+ no-resign-on-tighten) record hamr's interview answers ahead of the export build
27
+ itself; no code shipped yet.
28
+ - G4 counting-rule scoping (`docs/product/G4-SCOPING.md`) and the first `docs/logs/BENCH.md`
29
+ bench-ledger row.
30
+
8
31
  ## [0.19.1] — 2026-09-02
9
32
 
10
33
  ### Fixed
@@ -958,7 +958,7 @@ Reserved spine vocabulary (V7, machinery-free until job #1 surfaces one):
958
958
  `coordination-red` — a failure between units (scope contention, step order, store
959
959
  races), never to be folded into worker/interpreter reds.
960
960
 
961
- ### `runJob(spec, { approvals, workdir, provider, nativeProvider?, providerFor?, emit, capRuns?, strikeLimit?, shellCapUsd?, closeTimeoutMs?, layerRoot?, readShim?, bridge?, priorSpentUsd?, priorSpendComplete?, priorWallMs?, resumeSeed?, resumeGrades?, resumeReplans?, resumeBranch?, humanRuling?, heldRuling?, reviewDoor?, doorRerun? })` → outcome — `src/run.js`
961
+ ### `runJob(spec, { approvals, workdir, provider, nativeProvider?, providerFor?, emit, capRuns?, strikeLimit?, shellCapUsd?, closeTimeoutMs?, layerRoot?, readShim?, scout?, bridge?, priorSpentUsd?, priorSpendComplete?, priorWallMs?, resumeSeed?, resumeGrades?, resumeReplans?, resumeBranch?, humanRuling?, heldRuling?, reviewDoor?, doorRerun? })` → outcome — `src/run.js`
962
962
 
963
963
  The last seven are the RESUME fold and are documented under *Resuming a killed run* below; they
964
964
  default to `0` / `true` / `0` / `null` / `[]` / `null` / `null`, so a fresh run passes none of them.
@@ -1027,6 +1027,20 @@ arm replaces the CLI-display cap rather than stacking on it; a non-capping arm l
1027
1027
  installed *inside* the shim, so native never loses its bound. Default-flip pending a paid contrast;
1028
1028
  see CHANGELOG for the replay numbers behind it.
1029
1029
 
1030
+ **`scout` (default `true`) — the operator-only off switch** (`docs/product/SCOUT-CONTRAST.md`),
1031
+ threaded verbatim from `runJob` to `runPlan`. `false` on a FRESH run (no `resumeSeed`) skips the
1032
+ read-only survey entirely — `scout-start`/`scout-result`/`scout-empty` never fire, and a single
1033
+ `scout-skipped { reason: 'operator-off', meaning }` records it (a RECORD, never silence — the
1034
+ resume skip's own rule). The planner then drafts from `(no scout notes)` exactly as it does today
1035
+ when a scout returns an empty blob; nothing else observable changes. Precedence when both a
1036
+ resumed leg and `scout:false` are in play: the resume skip fires first and wins its own reason
1037
+ (`resumed`) — `operator-off` is only ever emitted on a fresh run. A non-boolean value **throws**
1038
+ a `TypeError` at the same door as the `readShim` arm guard, before a token is spent. It is a probe
1039
+ knob, not a product default — the spec names no scout, so the signed hash is unaffected — and
1040
+ `scripts/run-u.mjs --scout on|off` (default `on`) is its runner-territory surface, modelled on
1041
+ `--read-shim`: an unrecognised value exits 2 at argv, and every re-invocation the runner prints
1042
+ carries `--scout off` when set, so a resume never silently drops the arm.
1043
+
1030
1044
  **The two hitl terminals (N4 slice 1, doors re-cut 2026-08-18)** are the class's whole surface
1031
1045
  at this layer, and each is a CLEAN exit (`spendComplete` stays true — only the two casualties floor). `hitl-pause` is a
1032
1046
  decision-ready CHECKPOINT: the close reached a stage no machine can render, so the run stops
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bareloop",
3
- "version": "0.19.1",
3
+ "version": "0.20.0",
4
4
  "description": "Workflows that earn their own design, with receipts \u2014 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",
package/src/planrun.js CHANGED
@@ -642,6 +642,17 @@ ${scoutBlob || '(no scout notes)'}`;
642
642
  * it.
643
643
  * @param {number} [opts.closeTimeoutMs] close/check wall-clock cap (shell territory)
644
644
  * @param {number} [opts.maxStepRounds] the shell's per-step rounds ceiling (validatePlan's bound)
645
+ * @param {boolean} [opts.scout=true] operator-only switch (docs/product/SCOUT-CONTRAST.md):
646
+ * `false` on a FRESH run (no `resumeSeed`) skips the survey entirely — `scoutBlob`
647
+ * stays `''`, no `scout-start`/`scout-result`/`scout-empty` is emitted, and a single
648
+ * `scout-skipped {reason:'operator-off'}` records it (a RECORD, never silence — the
649
+ * resume skip's own rule). The planner then drafts from `(no scout notes)`
650
+ * (line ~587) exactly as it does today when a scout returns an empty blob — OFF is
651
+ * byte-identical to today's run everywhere but the survey. Precedence: when
652
+ * `resumeSeed` is set the existing resume skip fires first and wins its own reason
653
+ * (`resumed`); `operator-off` is only ever emitted on a fresh run. A non-boolean
654
+ * THROWS a TypeError at entry (the `readShimArm` guard class) — a knob that
655
+ * silently coerced would mislabel a bench row.
645
656
  * @param {number} [opts.scoutRounds] the read-only survey's round bound (F59: the LAST round is
646
657
  * reserved — a scout that spends every round on tools gets one toolless round to write its
647
658
  * survey, because the bound halts it mid-tool-use and text is its only deliverable)
@@ -805,7 +816,7 @@ ${scoutBlob || '(no scout notes)'}`;
805
816
  * 'branch-red' | 'cap-halt' | 'wall-halt' | 'provider-red' | 'interpreter-red' |
806
817
  * 'step-stalled' | 'hitl-pause' | 'hitl-decision-red' | `step-red:<id>`
807
818
  */
808
- export async function runPlan(job, { workdir, provider, nativeProvider, providerFor, judgeProvider = null, emit, remainingUsd, isUnpriced = () => false, spendComplete = () => true, capRuns = 3, strikeLimit = STRIKE_LIMIT, closeTimeoutMs, maxStepRounds = 40, layerRoot = false, readShim = false, scoutRounds = SCOUT_ROUNDS, bridge = null, now, priorWallMs = 0, resumeSeed = null, resumeGrades = [], resumeReplans = null, resumeBranch = null, humanRuling = null, heldRuling = null, priorSpentUsd = 0, reviewDoor = null, doorRerun = null }) {
819
+ export async function runPlan(job, { workdir, provider, nativeProvider, providerFor, judgeProvider = null, emit, remainingUsd, isUnpriced = () => false, spendComplete = () => true, capRuns = 3, strikeLimit = STRIKE_LIMIT, closeTimeoutMs, maxStepRounds = 40, layerRoot = false, readShim = false, scout = true, scoutRounds = SCOUT_ROUNDS, bridge = null, now, priorWallMs = 0, resumeSeed = null, resumeGrades = [], resumeReplans = null, resumeBranch = null, humanRuling = null, heldRuling = null, priorSpentUsd = 0, reviewDoor = null, doorRerun = null }) {
809
820
  // MEMORY-CACHE: what the read shim (src/readshim.js) saved THIS run, summed across
810
821
  // every mkWorker's own shim instance (scout, drafter, each step's worker, the fix
811
822
  // worker) — one accumulator closed over by all of them, because the shim's ledger
@@ -822,6 +833,11 @@ export async function runPlan(job, { workdir, provider, nativeProvider, provider
822
833
  // would be wrong and would look fine). Only the guard runs here; the flag itself
823
834
  // is still threaded onward as written.
824
835
  readShimArm(readShim);
836
+ // The scout switch's guard, same class and same door: a mis-typed value
837
+ // coerced by truthiness would run the scout under an "off" label (or skip it
838
+ // under an "on" one) and no reading of the results afterwards could recover
839
+ // which arm actually ran (docs/product/SCOUT-CONTRAST.md).
840
+ if (typeof scout !== 'boolean') throw new TypeError(`scout: expected boolean, got ${JSON.stringify(scout)}`);
825
841
  // G1's OTHER half, refused at the same $0 door. `validatePlan` reds `read-blind`
826
842
  // when a step grants `read` without the retrieval pair — but a step cannot grant
827
843
  // what the SIGNED CEILING does not offer, so against a spec whose `tools` lack
@@ -2456,6 +2472,15 @@ export async function runPlan(job, { workdir, provider, nativeProvider, provider
2456
2472
  reason: 'resumed', phase: resumeSeed.phase ?? null,
2457
2473
  meaning: 'the killed run already paid for this survey; its plan is reloaded, not re-drafted. A replan after this point drafts from an empty survey plus the failure brief.',
2458
2474
  });
2475
+ } else if (scout === false) {
2476
+ // The operator-off arm (docs/product/SCOUT-CONTRAST.md): a fresh run whose
2477
+ // operator asked for no survey. Precedence already settled above — the
2478
+ // resume branch fires first and wins its own reason, so this arm is only
2479
+ // ever reached when there is nothing to resume from.
2480
+ emit('scout-skipped', {
2481
+ reason: 'operator-off',
2482
+ meaning: 'the operator ran with --scout off; the planner drafts from "(no scout notes)"; no survey was paid for.',
2483
+ });
2459
2484
  } else {
2460
2485
  emit('scout-start', { rounds: scoutRounds });
2461
2486
  try {
package/src/run.js CHANGED
@@ -171,6 +171,12 @@ async function primitiveSmoke(workdir) {
171
171
  * frozen A0 baseline arm has to be exactly today, so a guard firing under a disabled
172
172
  * shim would make the baseline a treatment arm. The default-flip belongs to a
173
173
  * paid contrast nobody has approved yet (the `layerRoot` precedent, F41).
174
+ * @param {boolean} [opts.scout=true] operator-only switch (docs/product/SCOUT-CONTRAST.md),
175
+ * forwarded verbatim to the plan flow: `false` on a fresh run (no `resumeSeed`) skips the
176
+ * read-only survey and records `scout-skipped {reason:'operator-off'}` instead — the
177
+ * planner drafts from `(no scout notes)`. Runner territory exactly like `readShim`: the
178
+ * spec names no scout, so the signed hash is unaffected. A non-boolean THROWS at this
179
+ * entry, before the ledger, before the approval gate (the `readShim` guard class).
174
180
  * @param {boolean} [opts.layerRoot=false] Layer R (within-run ratchet) — shell
175
181
  * territory, threaded to the plan flow. Defaults OFF
176
182
  * (decided 2026-07-21): fixation is extinct on every current job (F41), so
@@ -202,7 +208,7 @@ async function primitiveSmoke(workdir) {
202
208
  * 'interpreter-red' | 'cap-halt' | 'wall-halt' | 'step-stalled' |
203
209
  * 'hitl-pause' | 'hitl-decision-red' | `step-red:<id>`
204
210
  */
205
- export async function runJob(rawSpec, { approvals, workdir, provider, nativeProvider, providerFor, judgeProvider = null, emit, capRuns = 3, strikeLimit, shellCapUsd = 2, closeTimeoutMs, layerRoot = false, readShim = false, bridge = null, priorSpentUsd = 0, priorSpendComplete = true, priorWallMs = 0, resumeSeed = null, resumeGrades = [], resumeReplans = null, resumeBranch = null, humanRuling = null, heldRuling = null, reviewDoor = null, doorRerun = null }) {
211
+ export async function runJob(rawSpec, { approvals, workdir, provider, nativeProvider, providerFor, judgeProvider = null, emit, capRuns = 3, strikeLimit, shellCapUsd = 2, closeTimeoutMs, layerRoot = false, readShim = false, scout = true, bridge = null, priorSpentUsd = 0, priorSpendComplete = true, priorWallMs = 0, resumeSeed = null, resumeGrades = [], resumeReplans = null, resumeBranch = null, humanRuling = null, heldRuling = null, reviewDoor = null, doorRerun = null }) {
206
212
  // THE READ SHIM's ARM, resolved at the door — the FIRST thing this entry does,
207
213
  // before the ledger, before the approval gate, before a byte of the spec is read.
208
214
  // An unrecognised spelling throws here at zero cost instead of being coerced by
@@ -211,6 +217,10 @@ export async function runJob(rawSpec, { approvals, workdir, provider, nativeProv
211
217
  // blind-instrument failure this programme keeps logging. Only the guard runs
212
218
  // here; the flag itself is threaded onward to the plan flow as written.
213
219
  readShimArm(readShim);
220
+ // The scout switch's guard, same class and same door (docs/product/SCOUT-CONTRAST.md):
221
+ // a mis-coerced value would run under the wrong label with no way to recover which arm
222
+ // actually fired from the results afterwards.
223
+ if (typeof scout !== 'boolean') throw new TypeError(`scout: expected boolean, got ${JSON.stringify(scout)}`);
214
224
  // 0. the ledger's counters, declared FIRST so that every job-end — including
215
225
  // the pre-token reds below — can state a real figure. An omitted `spentUsd` is
216
226
  // not a zero: a consumer reads `undefined` and either crashes or launders it
@@ -405,7 +415,7 @@ export async function runJob(rawSpec, { approvals, workdir, provider, nativeProv
405
415
  // accounts it natively (F12) and the job-end money contract is unchanged.
406
416
  {
407
417
  const outcome = await runPlan(job, {
408
- workdir, provider, nativeProvider, providerFor, judgeProvider, emit: meter, capRuns, ...(strikeLimit !== undefined ? { strikeLimit } : {}), closeTimeoutMs, layerRoot, readShim, bridge, priorWallMs: chainWallMs, resumeSeed, resumeGrades, resumeReplans, resumeBranch, humanRuling, heldRuling, reviewDoor, doorRerun, priorSpentUsd: chainFoldUsd,
418
+ workdir, provider, nativeProvider, providerFor, judgeProvider, emit: meter, capRuns, ...(strikeLimit !== undefined ? { strikeLimit } : {}), closeTimeoutMs, layerRoot, readShim, scout, bridge, priorWallMs: chainWallMs, resumeSeed, resumeGrades, resumeReplans, resumeBranch, humanRuling, heldRuling, reviewDoor, doorRerun, priorSpentUsd: chainFoldUsd,
409
419
  remainingUsd: () => Math.min(shellCapUsd, job.budgetUsd - spentUsd),
410
420
  isUnpriced: () => unpriced, // F6: let the plan flow bail in-flight, not just after it returns
411
421
  spendComplete, // …and let its money-halt readout say whether the remaining it quotes is exact
@@ -223,6 +223,17 @@ export function planPrompt(job: any, scoutBlob: string, reds: any[] | null, maxS
223
223
  * it.
224
224
  * @param {number} [opts.closeTimeoutMs] close/check wall-clock cap (shell territory)
225
225
  * @param {number} [opts.maxStepRounds] the shell's per-step rounds ceiling (validatePlan's bound)
226
+ * @param {boolean} [opts.scout=true] operator-only switch (docs/product/SCOUT-CONTRAST.md):
227
+ * `false` on a FRESH run (no `resumeSeed`) skips the survey entirely — `scoutBlob`
228
+ * stays `''`, no `scout-start`/`scout-result`/`scout-empty` is emitted, and a single
229
+ * `scout-skipped {reason:'operator-off'}` records it (a RECORD, never silence — the
230
+ * resume skip's own rule). The planner then drafts from `(no scout notes)`
231
+ * (line ~587) exactly as it does today when a scout returns an empty blob — OFF is
232
+ * byte-identical to today's run everywhere but the survey. Precedence: when
233
+ * `resumeSeed` is set the existing resume skip fires first and wins its own reason
234
+ * (`resumed`); `operator-off` is only ever emitted on a fresh run. A non-boolean
235
+ * THROWS a TypeError at entry (the `readShimArm` guard class) — a knob that
236
+ * silently coerced would mislabel a bench row.
226
237
  * @param {number} [opts.scoutRounds] the read-only survey's round bound (F59: the LAST round is
227
238
  * reserved — a scout that spends every round on tools gets one toolless round to write its
228
239
  * survey, because the bound halts it mid-tool-use and text is its only deliverable)
@@ -386,7 +397,7 @@ export function planPrompt(job: any, scoutBlob: string, reds: any[] | null, maxS
386
397
  * 'branch-red' | 'cap-halt' | 'wall-halt' | 'provider-red' | 'interpreter-red' |
387
398
  * 'step-stalled' | 'hitl-pause' | 'hitl-decision-red' | `step-red:<id>`
388
399
  */
389
- export function runPlan(job: any, { workdir, provider, nativeProvider, providerFor, judgeProvider, emit, remainingUsd, isUnpriced, spendComplete, capRuns, strikeLimit, closeTimeoutMs, maxStepRounds, layerRoot, readShim, scoutRounds, bridge, now, priorWallMs, resumeSeed, resumeGrades, resumeReplans, resumeBranch, humanRuling, heldRuling, priorSpentUsd, reviewDoor, doorRerun }: {
400
+ export function runPlan(job: any, { workdir, provider, nativeProvider, providerFor, judgeProvider, emit, remainingUsd, isUnpriced, spendComplete, capRuns, strikeLimit, closeTimeoutMs, maxStepRounds, layerRoot, readShim, scout, scoutRounds, bridge, now, priorWallMs, resumeSeed, resumeGrades, resumeReplans, resumeBranch, humanRuling, heldRuling, priorSpentUsd, reviewDoor, doorRerun }: {
390
401
  workdir: string;
391
402
  provider: any;
392
403
  nativeProvider?: ((o: {
@@ -404,6 +415,7 @@ export function runPlan(job: any, { workdir, provider, nativeProvider, providerF
404
415
  strikeLimit?: number | undefined;
405
416
  closeTimeoutMs?: number | undefined;
406
417
  maxStepRounds?: number | undefined;
418
+ scout?: boolean | undefined;
407
419
  scoutRounds?: number | undefined;
408
420
  readShim?: boolean | "cap" | "diff" | undefined;
409
421
  layerRoot?: boolean | undefined;
package/types/run.d.ts CHANGED
@@ -110,6 +110,12 @@
110
110
  * frozen A0 baseline arm has to be exactly today, so a guard firing under a disabled
111
111
  * shim would make the baseline a treatment arm. The default-flip belongs to a
112
112
  * paid contrast nobody has approved yet (the `layerRoot` precedent, F41).
113
+ * @param {boolean} [opts.scout=true] operator-only switch (docs/product/SCOUT-CONTRAST.md),
114
+ * forwarded verbatim to the plan flow: `false` on a fresh run (no `resumeSeed`) skips the
115
+ * read-only survey and records `scout-skipped {reason:'operator-off'}` instead — the
116
+ * planner drafts from `(no scout notes)`. Runner territory exactly like `readShim`: the
117
+ * spec names no scout, so the signed hash is unaffected. A non-boolean THROWS at this
118
+ * entry, before the ledger, before the approval gate (the `readShim` guard class).
113
119
  * @param {boolean} [opts.layerRoot=false] Layer R (within-run ratchet) — shell
114
120
  * territory, threaded to the plan flow. Defaults OFF
115
121
  * (decided 2026-07-21): fixation is extinct on every current job (F41), so
@@ -141,7 +147,7 @@
141
147
  * 'interpreter-red' | 'cap-halt' | 'wall-halt' | 'step-stalled' |
142
148
  * 'hitl-pause' | 'hitl-decision-red' | `step-red:<id>`
143
149
  */
144
- export function runJob(rawSpec: object | string, { approvals, workdir, provider, nativeProvider, providerFor, judgeProvider, emit, capRuns, strikeLimit, shellCapUsd, closeTimeoutMs, layerRoot, readShim, bridge, priorSpentUsd, priorSpendComplete, priorWallMs, resumeSeed, resumeGrades, resumeReplans, resumeBranch, humanRuling, heldRuling, reviewDoor, doorRerun }: {
150
+ export function runJob(rawSpec: object | string, { approvals, workdir, provider, nativeProvider, providerFor, judgeProvider, emit, capRuns, strikeLimit, shellCapUsd, closeTimeoutMs, layerRoot, readShim, scout, bridge, priorSpentUsd, priorSpendComplete, priorWallMs, resumeSeed, resumeGrades, resumeReplans, resumeBranch, humanRuling, heldRuling, reviewDoor, doorRerun }: {
145
151
  approvals: unknown;
146
152
  workdir: string;
147
153
  provider: any;
@@ -173,6 +179,7 @@ export function runJob(rawSpec: object | string, { approvals, workdir, provider,
173
179
  } | null | undefined;
174
180
  resumeBranch?: string | null | undefined;
175
181
  readShim?: boolean | "cap" | "diff" | undefined;
182
+ scout?: boolean | undefined;
176
183
  layerRoot?: boolean | undefined;
177
184
  humanRuling?: {
178
185
  decision: string;