bareloop 0.12.0 → 0.13.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 +93 -0
- package/bareloop.context.md +106 -2
- package/package.json +1 -1
- package/src/index.js +16 -1
- package/src/ledger.js +102 -0
- package/src/plan.js +43 -2
- package/src/planrun.js +167 -14
- package/src/readshim.js +538 -0
- package/src/reuse.js +3 -1
- package/src/run.js +24 -2
- package/src/tools.js +34 -2
- package/types/index.d.ts +2 -1
- package/types/ledger.d.ts +58 -0
- package/types/plan.d.ts +19 -2
- package/types/planrun.d.ts +37 -2
- package/types/readshim.d.ts +117 -0
- package/types/reuse.d.ts +2 -0
- package/types/run.d.ts +15 -1
- package/types/tools.d.ts +17 -1
package/src/tools.js
CHANGED
|
@@ -21,7 +21,7 @@ const require = createRequire(import.meta.url);
|
|
|
21
21
|
const { createShellTools } = require('bare-agent/tools');
|
|
22
22
|
// litectx's rank-tiered render — a free function, not a ctx method (its contract's
|
|
23
23
|
// own recipe: the caller slices the chunk body, compress renders the signature tier)
|
|
24
|
-
const { compress: compressNode } = require('litectx');
|
|
24
|
+
const { compress: compressNode, StalePointerError } = require('litectx');
|
|
25
25
|
|
|
26
26
|
import { extractArtifact, priceOf } from './text.js';
|
|
27
27
|
// The isolate verbs write MODEL-AUTHORED text into `<workdir>/.litectx` — a file
|
|
@@ -243,8 +243,13 @@ const LINE_SPACE = 'Line numbers here are 0-BASED index positions from the repos
|
|
|
243
243
|
* RESULT is invisible cannot be judged: a `ctx_get` that silently reds (stale pointer,
|
|
244
244
|
* bad range) looks exactly like one that worked, and the worker's fallback to a
|
|
245
245
|
* whole-file read looks like a free choice instead of a forced one.
|
|
246
|
+
* @param {{onStalePointer?: ((req: {file: string, path: string, startLine: number, endLine: number, detail: string}) => {text: string, outcome: string}|null)|null}} [opts]
|
|
247
|
+
* `onStalePointer`: the read shim's stale-pointer serve, wired ONLY by a capping
|
|
248
|
+
* arm (see `createReadShim`). Absent — every caller today except that one — this
|
|
249
|
+
* whole seam is byte-identical to what it was, which is what keeps A0 the
|
|
250
|
+
* untouched baseline an experiment has already run against.
|
|
246
251
|
*/
|
|
247
|
-
export function createCtxTools(lc, workdir, emit) {
|
|
252
|
+
export function createCtxTools(lc, workdir, emit, { onStalePointer = null } = {}) {
|
|
248
253
|
// ctx_get's failure contract, generalised to the palette (review 2026-07-31).
|
|
249
254
|
// `emit` exists in this file because a verb whose RESULT is invisible cannot be
|
|
250
255
|
// judged — and a THROWN call is the most invisible result there is: it looks
|
|
@@ -337,6 +342,33 @@ export function createCtxTools(lc, workdir, emit) {
|
|
|
337
342
|
// StalePointerError: the file changed after indexing, so these lines now describe
|
|
338
343
|
// DIFFERENT code. Its message IS the recovery instruction — hand it to the worker.
|
|
339
344
|
const detail = String(/** @type {Error} */ (e)?.message || e);
|
|
345
|
+
// …and under a CAPPING read-shim arm, the recovery instruction is not
|
|
346
|
+
// enough. That arm's own strategy line steers the worker HERE for a whole
|
|
347
|
+
// function, and a worker that just edited the file it is working on — the
|
|
348
|
+
// normal case — gets a stale pointer and zero bytes for a paid round
|
|
349
|
+
// (measured: 5 of 10 ctx_get calls across the A1 arm's three green runs).
|
|
350
|
+
// So the shim serves the requested LINE RANGE from disk, labelled; see
|
|
351
|
+
// `serveStale`. The branch is on the TYPED error, never on the message
|
|
352
|
+
// prose — a prose sniff would fire on any failure whose wording happens
|
|
353
|
+
// to match, and litectx exports the class precisely so it need not.
|
|
354
|
+
if (onStalePointer && e instanceof StalePointerError) {
|
|
355
|
+
// The gate judged `resolve(workdir, expandHome(path))` for this call
|
|
356
|
+
// (toolAction, 'ctx_get'); the serve reads that same absolute path and
|
|
357
|
+
// no other, so it can never reach a byte the fence did not allow.
|
|
358
|
+
const served = onStalePointer({
|
|
359
|
+
file: resolve(workdir, expandHome(String(p))),
|
|
360
|
+
path: rel,
|
|
361
|
+
startLine: Number(startLine),
|
|
362
|
+
endLine: Number(endLine),
|
|
363
|
+
detail,
|
|
364
|
+
});
|
|
365
|
+
if (served && typeof served.text === 'string') {
|
|
366
|
+
// No `detail` on the event: the served TEXT is file content and the
|
|
367
|
+
// spine is append-only. Bytes and the outcome are what a reader needs.
|
|
368
|
+
emit('ctx-tool', { tool: 'ctx_get', path: rel, startLine, endLine, outcome: served.outcome, bytes: Buffer.byteLength(served.text) });
|
|
369
|
+
return served.text;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
340
372
|
emit('ctx-tool', { tool: 'ctx_get', path: rel, startLine, endLine, outcome: 'stale', bytes: 0, detail });
|
|
341
373
|
return `stale pointer: ${detail}`;
|
|
342
374
|
}
|
package/types/index.d.ts
CHANGED
|
@@ -9,8 +9,9 @@ export { runJob, ACCOUNTED_ROUND_TYPES } from "./run.js";
|
|
|
9
9
|
export { BRIDGE_SCHEMA, validateBridge, deriveStatus, listingRow, loadGate, mintBridge, appendGreen, appendRed, loadBridge, loadRegistry, saveBridge, makeRegistry, registryExists, QUARANTINED_VERDICTS, QUARANTINED_CODE, quarantinesCredit, newestEligibleVersion, reuseEligibility, recordDoor } from "./bridges.js";
|
|
10
10
|
export { renderListing, selectionPrompt } from "./selection.js";
|
|
11
11
|
export { validateEnvelope, resolveTrySpec, resolveReuse, reuseSpecHash, selectBridge, runReuse, REUSE_GRADED_RED, readResume, resumeTreeGate, CHECKPOINT_OUTCOMES, PAUSE_TTL_MS, checkpointAgeGate, applyDoorDecision, writeRunGreenRow } from "./reuse.js";
|
|
12
|
-
export { classifyIncidents, foldLedger, ledgerDeltas, updateLedger, LEDGER_CLASSES } from "./ledger.js";
|
|
12
|
+
export { classifyIncidents, foldLedger, ledgerDeltas, updateLedger, LEDGER_CLASSES, VOUCHED_RATE_SOURCES, rateProvenance, spendProvenance } from "./ledger.js";
|
|
13
13
|
export { workBranchName, prepareWorkBranch, WORK_BRANCH_PREFIX, WORK_BRANCH_RE } from "./workbranch.js";
|
|
14
|
+
export { createReadShim, wrapReadTool, readShimArm, readShimStrategy, READ_SHIM_CAP, READ_SHIM_STRATEGY, READ_SHIM_DIFF_STRATEGY, READ_SHIM_ARMS } from "./readshim.js";
|
|
14
15
|
export { runDeclaredClose, runStage, seedRead, seedAtHead, seedListing, changedSet, makeSeedTrees, LIVE_KINDS, STOP_FAULTS, JUDGED_MARKER, EXIT_GREEN, EXIT_RED, EXIT_STOP, HUMAN_DECISIONS, SEED_EXEMPT_KINDS, JUDGED_FLOOR_KIND, normalizeHumanRuling, resolveHumanRuling, JUDGE_ATTEMPTS, MAX_JUDGED_PATHS, REVIEW_DOOR, DOOR_OPEN_OUTCOMES, REVIEW_DOOR_CLASSES, doorOpens, mechanicalStages } from "./kinds.js";
|
|
15
16
|
export { answerReviewDoor, doorRecordOf, doorAgeGate } from "./reviewdoor.js";
|
|
16
17
|
export { JUDGE_MODEL, JUDGE_MAX_TOKENS, JUDGE_RULES, JUDGE_RULE_IDS, LOCATE_AXES, LOCATE_LABEL, validateCard, validateFacts, locatePrompt, runLocate, decide, defaultJudgeLoop, CALIBRATION_SIZE, CASE_VERDICTS, validateCalibrationSet, validateJudgedArtifacts, expectedOf } from "./judged.js";
|
package/types/ledger.d.ts
CHANGED
|
@@ -48,8 +48,66 @@ export function updateLedger({ ledgerFile, spineFiles }: {
|
|
|
48
48
|
appended: object[];
|
|
49
49
|
fold: Record<string, FoldEntry>;
|
|
50
50
|
};
|
|
51
|
+
/**
|
|
52
|
+
* Read one spend record's rate PROVENANCE. Four-way, and the two absent-ish cases are
|
|
53
|
+
* deliberately distinct facts:
|
|
54
|
+
* - `'vouched'` — a `VOUCHED_RATE_SOURCES` value: somebody stands behind the rate.
|
|
55
|
+
* - `'guessed'` — a rate was used and nobody vouched for it (`'tier'`, `'default'`, or
|
|
56
|
+
* any future value we do not recognise).
|
|
57
|
+
* - `'unpriced'` — `rateSource: null`: nothing was priced, so there was no rate to have
|
|
58
|
+
* guessed. Upstream said so; we are not inferring it.
|
|
59
|
+
* - `'unknown'` — the record carries no `rateSource` at all. Every round archived before
|
|
60
|
+
* this signal existed lands here and is NEVER backfilled: an absent label
|
|
61
|
+
* is reported as unknown, never reconstructed, and never rounded up to
|
|
62
|
+
* vouched (F6's unknown-is-never-zero, applied to provenance).
|
|
63
|
+
* @param {any} record a `worker-round` / `judge-round` / `worker-turn` spine record
|
|
64
|
+
* @returns {'vouched'|'guessed'|'unpriced'|'unknown'}
|
|
65
|
+
*/
|
|
66
|
+
export function rateProvenance(record: any): "vouched" | "guessed" | "unpriced" | "unknown";
|
|
67
|
+
/** @typedef {{rounds: number, usd: number, unpricedRounds: number}} ProvenanceBucket */
|
|
68
|
+
/**
|
|
69
|
+
* How much of a run's spend was priced by a guess. Pure fold over one spine's events,
|
|
70
|
+
* REPORTING ONLY — it mints nothing, halts nothing and refuses nothing.
|
|
71
|
+
*
|
|
72
|
+
* `usd` sums only FINITE costs; a round whose cost is not a finite number is counted in
|
|
73
|
+
* `unpricedRounds` instead of being summed as $0 (F6 — unpriced is never free), so a
|
|
74
|
+
* bucket's `usd` is a FLOOR whenever its `unpricedRounds` is non-zero.
|
|
75
|
+
* @param {any[]} events one spine's parsed events
|
|
76
|
+
* @returns {{vouched: ProvenanceBucket, guessed: ProvenanceBucket, unpriced: ProvenanceBucket, unknown: ProvenanceBucket}}
|
|
77
|
+
*/
|
|
78
|
+
export function spendProvenance(events: any[]): {
|
|
79
|
+
vouched: ProvenanceBucket;
|
|
80
|
+
guessed: ProvenanceBucket;
|
|
81
|
+
unpriced: ProvenanceBucket;
|
|
82
|
+
unknown: ProvenanceBucket;
|
|
83
|
+
};
|
|
51
84
|
/** severity order, worst-first (the fold's display order; frequency ranks within) */
|
|
52
85
|
export const LEDGER_CLASSES: readonly string[];
|
|
86
|
+
/**
|
|
87
|
+
* The two rate sources somebody VOUCHED for: `'provider'` (the provider reported its own
|
|
88
|
+
* authoritative cost, e.g. the claude CLI's `total_cost_usd`) and `'caller'` (we passed
|
|
89
|
+
* the rate in ourselves). Everything else bare-agent can report is a built-in guess.
|
|
90
|
+
*
|
|
91
|
+
* An ALLOW-LIST of vouched values, never a deny-list of guessed ones — this is the
|
|
92
|
+
* load-bearing line of the whole signal, for two reasons.
|
|
93
|
+
*
|
|
94
|
+
* First: BOTH of bareloop's production models resolve to `'tier'`, not to `'default'`
|
|
95
|
+
* (`claude-sonnet-5` and `claude-haiku-4-5-…` each match a recognized Claude tier by
|
|
96
|
+
* model-id substring, so bare-agent prices them off a hardcoded tier rate that could
|
|
97
|
+
* have drifted from Anthropic's). A "is this a guess?" test spelled
|
|
98
|
+
* `rateSource === 'default'` would therefore read EVERY normal bareloop round as
|
|
99
|
+
* confidently priced and see none of the population it exists to measure — this repo's
|
|
100
|
+
* blind-instrument class, seven recorded instances, and not becoming the eighth.
|
|
101
|
+
*
|
|
102
|
+
* Second: written this way, any value bare-agent adds later that we do not recognise
|
|
103
|
+
* reads as a GUESS rather than as vouched. The fail-safe direction, by construction.
|
|
104
|
+
*/
|
|
105
|
+
export const VOUCHED_RATE_SOURCES: readonly string[];
|
|
106
|
+
export type ProvenanceBucket = {
|
|
107
|
+
rounds: number;
|
|
108
|
+
usd: number;
|
|
109
|
+
unpricedRounds: number;
|
|
110
|
+
};
|
|
53
111
|
export type Sample = {
|
|
54
112
|
spine: string;
|
|
55
113
|
seq: number;
|
package/types/plan.d.ts
CHANGED
|
@@ -72,7 +72,7 @@ export function closeStagesOf(job: any): any[] | null;
|
|
|
72
72
|
* job spec. Never throws on JSON text or plain parsed data; every failure is
|
|
73
73
|
* a named red. Returns the parsed plan on ok (single parse), null on any red.
|
|
74
74
|
* @param {object|string} input parsed plan, or raw JSON text (parse failures are a red)
|
|
75
|
-
* @param {{ job?: any, maxStepRounds?: number, scopes?: string[], seedRed?: string[], priorChecks?: string[] }} [opts] `job`: the
|
|
75
|
+
* @param {{ job?: any, maxStepRounds?: number, scopes?: string[], seedRed?: string[], priorChecks?: string[], readShim?: boolean|'cap'|'diff' }} [opts] `job`: the
|
|
76
76
|
* validateJob-GREEN four-field spec (the ceiling, the fence, and the checks
|
|
77
77
|
* menu all come from it — a missing or non-plan-shape job fails CLOSED);
|
|
78
78
|
* `maxStepRounds`: the shell's per-step rounds ceiling (interpret's
|
|
@@ -94,14 +94,23 @@ export function closeStagesOf(job: any): any[] | null;
|
|
|
94
94
|
* only). Rule B — a redraft may not drop one (`check-shed`): exits without
|
|
95
95
|
* the check verify FORM alone, so a shed lets the run "green" unearned
|
|
96
96
|
* (u-msdsmkid mechanism b; the 3 archived sheds were all on step-red runs).
|
|
97
|
+
* `readShim` (G1): the read shim's ARM for this run — `false` (default), `'cap'`,
|
|
98
|
+
* `'diff'` or `true`. The arms that CAP (`'cap'`, `true`) add one rule: a step
|
|
99
|
+
* granting `read` must also grant `recall` and `get` (`read-blind`), because a
|
|
100
|
+
* capped read is only navigable with them. The arms that do not cap (`false`,
|
|
101
|
+
* `'diff'`) render byte-identically to the pre-shim validator, so the frozen A0
|
|
102
|
+
* baseline stays a baseline and A2 isolates the diff instead of importing an
|
|
103
|
+
* admission rule with it. An unrecognised value THROWS (`readShimArm`) — a param
|
|
104
|
+
* guard on the caller's own argument, never a plan red.
|
|
97
105
|
* @returns {{ ok: boolean, reds: Red[], plan: object|null }}
|
|
98
106
|
*/
|
|
99
|
-
export function validatePlan(input: object | string, { job, maxStepRounds, scopes, seedRed, priorChecks }?: {
|
|
107
|
+
export function validatePlan(input: object | string, { job, maxStepRounds, scopes, seedRed, priorChecks, readShim }?: {
|
|
100
108
|
job?: any;
|
|
101
109
|
maxStepRounds?: number;
|
|
102
110
|
scopes?: string[];
|
|
103
111
|
seedRed?: string[];
|
|
104
112
|
priorChecks?: string[];
|
|
113
|
+
readShim?: boolean | "cap" | "diff";
|
|
105
114
|
}): {
|
|
106
115
|
ok: boolean;
|
|
107
116
|
reds: Red[];
|
|
@@ -122,6 +131,14 @@ export const MAX_PLAN_STEPS: 8;
|
|
|
122
131
|
* count. Shallow entries survive the cap (see `legalScopes`) because they are
|
|
123
132
|
* the useful ones — a deep leaf is reachable via its parent. */
|
|
124
133
|
export const MAX_SCOPE_MENU: 24;
|
|
134
|
+
/** G1's pair (read shim only): the two verbs that can reach INTO a file the cap
|
|
135
|
+
* cut short — `recall` names where a symbol lives, `get` reads that symbol in
|
|
136
|
+
* full. Named here because the rule that needs them lives here; the cap that
|
|
137
|
+
* makes them mandatory lives in src/readshim.js.
|
|
138
|
+
* EXPORTED because `runPlan` refuses the same pair at $0 (see the G1 ceiling
|
|
139
|
+
* guard there): the rule and its pre-flight must read ONE spelling of which
|
|
140
|
+
* verbs count, never two that can drift apart. */
|
|
141
|
+
export const RETRIEVAL_PAIR: readonly string[];
|
|
125
142
|
/** P (design record 2026-07-28): the signed per-step model-tier menu. Closed by
|
|
126
143
|
* construction — the agent picks a tier, never names an arbitrary model string
|
|
127
144
|
* (the tier→model mapping and any effort params are the RUNNER's, per provider).
|
package/types/planrun.d.ts
CHANGED
|
@@ -78,6 +78,25 @@
|
|
|
78
78
|
* @returns {string} the labelled block, or '' when there is nothing to show
|
|
79
79
|
*/
|
|
80
80
|
export function closeGapBlock(gap: string | null | undefined): string;
|
|
81
|
+
/**
|
|
82
|
+
* BA-21 — the WRITE side of the pricing-provenance signal (read side: `rateProvenance` /
|
|
83
|
+
* `spendProvenance` in src/ledger.js). bare-agent >=0.37 rides `rateSource` beside
|
|
84
|
+
* `pricing` on every metering payload; both metering callbacks below forward it onto the
|
|
85
|
+
* spine through THIS one helper, because two sites spelling one rule are two instruments
|
|
86
|
+
* (the ripgrep fix that landed in ci.yml but not publish.yml).
|
|
87
|
+
*
|
|
88
|
+
* Forwarded VERBATIM — the write side reports what upstream said, and src/ledger.js is
|
|
89
|
+
* the one place that decides what counts as a guess. And an ABSENT provenance stays
|
|
90
|
+
* absent: a payload carrying no `rateSource` (bare-agent <0.37, and every round already
|
|
91
|
+
* in the archive) emits no field at all, so a reader sees UNKNOWN rather than a label we
|
|
92
|
+
* invented. Never defaulted to `null` here — `null` is upstream saying "nothing was
|
|
93
|
+
* priced", which is a different fact from "nobody told us".
|
|
94
|
+
* @param {any} arg an `onLlmResult` / `onTurn` payload
|
|
95
|
+
* @returns {{rateSource?: string|null}} the field to spread into the spine record
|
|
96
|
+
*/
|
|
97
|
+
export function rateSourceFields(arg: any): {
|
|
98
|
+
rateSource?: string | null;
|
|
99
|
+
};
|
|
81
100
|
/**
|
|
82
101
|
* What the NEXT attempt is told about the bound that cut the previous one.
|
|
83
102
|
*
|
|
@@ -146,6 +165,10 @@ export function boundedNote(bounded: {
|
|
|
146
165
|
* REQUIREMENT the plan must meet, never a replacement for the goal: the signed goal and
|
|
147
166
|
* the signed close are unchanged, and what the person added is what the machine could
|
|
148
167
|
* not see.
|
|
168
|
+
* @param {boolean|'cap'|'diff'} [readShim] the read shim's ARM. Does it carry G1? Then G1
|
|
169
|
+
* is STATED as well as enforced (the mailbox precedent again: a law only the validator
|
|
170
|
+
* knows costs a redraft per draft). The arms without it — `false` and `'diff'` — render
|
|
171
|
+
* byte-identically to the pre-shim prompt.
|
|
149
172
|
*/
|
|
150
173
|
export function planPrompt(job: any, scoutBlob: string, reds: any[] | null, maxStepRounds: number, failure: string | null, scopes: string[] | undefined, materials?: {
|
|
151
174
|
balanceUsd?: number | null;
|
|
@@ -154,7 +177,7 @@ export function planPrompt(job: any, scoutBlob: string, reds: any[] | null, maxS
|
|
|
154
177
|
}, startingDraft?: object | null, checkFacts?: {
|
|
155
178
|
seedRed?: string[];
|
|
156
179
|
priorChecks?: string[];
|
|
157
|
-
}, doorNote?: string | null): string;
|
|
180
|
+
}, doorNote?: string | null, readShim?: boolean | "cap" | "diff"): string;
|
|
158
181
|
/**
|
|
159
182
|
* Execute a validateJob-GREEN plan-shape job (goal/verdictType/close).
|
|
160
183
|
* Called by runJob after the approval gate, validation, and the smoke — this
|
|
@@ -203,6 +226,17 @@ export function planPrompt(job: any, scoutBlob: string, reds: any[] | null, maxS
|
|
|
203
226
|
* @param {number} [opts.scoutRounds] the read-only survey's round bound (F59: the LAST round is
|
|
204
227
|
* reserved — a scout that spends every round on tools gets one toolless round to write its
|
|
205
228
|
* survey, because the bound halts it mid-tool-use and text is its only deliverable)
|
|
229
|
+
* @param {boolean|'cap'|'diff'} [opts.readShim=false] THE READ SHIM (src/readshim.js) — the
|
|
230
|
+
* ARM to run, one of the Phase 2 pre-registration's four: `false` (A0, off — the default),
|
|
231
|
+
* `'cap'` (A1: cap + pointer + next-unseen-slice + G1), `'diff'` (A2: the diff lever alone,
|
|
232
|
+
* no cap, no pointer, no G1), `true` (A3: every lever). Anything else THROWS at the entry
|
|
233
|
+
* below rather than coercing into a truthy shim — a mis-spelled arm that silently ran A3
|
|
234
|
+
* is a corrupted battery row, invisible afterwards.
|
|
235
|
+
* OFF is byte-identical to the pre-shim run in every observable, G1 included: the frozen
|
|
236
|
+
* A0 baseline arm must be exactly today's behaviour, and a guard firing under a disabled
|
|
237
|
+
* shim would quietly make the baseline a treatment arm. The default-flip waits on a paid
|
|
238
|
+
* contrast that has not been approved (the `layerRoot` precedent, F41 — an unproven lever
|
|
239
|
+
* ships OFF and earns its default).
|
|
206
240
|
* @param {boolean} [opts.layerRoot=false] Layer R — the within-run ratchet (src/root.js),
|
|
207
241
|
* scoped PER STEP's ralph loop (each micro-wheel is the Layer-1 atom). Shell-assembled from
|
|
208
242
|
* the step's own books: per-attempt write-sets from the F32 workerWrites audit (teed for
|
|
@@ -352,7 +386,7 @@ export function planPrompt(job: any, scoutBlob: string, reds: any[] | null, maxS
|
|
|
352
386
|
* 'branch-red' | 'cap-halt' | 'wall-halt' | 'provider-red' | 'interpreter-red' |
|
|
353
387
|
* 'step-stalled' | 'hitl-pause' | 'hitl-decision-red' | `step-red:<id>`
|
|
354
388
|
*/
|
|
355
|
-
export function runPlan(job: any, { workdir, provider, nativeProvider, providerFor, judgeProvider, emit, remainingUsd, isUnpriced, spendComplete, capRuns, strikeLimit, closeTimeoutMs, maxStepRounds, layerRoot, scoutRounds, bridge, now, priorWallMs, resumeSeed, resumeGrades, resumeReplans, resumeBranch, humanRuling, heldRuling, priorSpentUsd, reviewDoor, doorRerun }: {
|
|
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 }: {
|
|
356
390
|
workdir: string;
|
|
357
391
|
provider: any;
|
|
358
392
|
nativeProvider?: ((o: {
|
|
@@ -371,6 +405,7 @@ export function runPlan(job: any, { workdir, provider, nativeProvider, providerF
|
|
|
371
405
|
closeTimeoutMs?: number | undefined;
|
|
372
406
|
maxStepRounds?: number | undefined;
|
|
373
407
|
scoutRounds?: number | undefined;
|
|
408
|
+
readShim?: boolean | "cap" | "diff" | undefined;
|
|
374
409
|
layerRoot?: boolean | undefined;
|
|
375
410
|
bridge?: unknown;
|
|
376
411
|
priorWallMs?: number | undefined;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve the `readShim` flag into its arm. THROWS on anything else — the BA-4
|
|
3
|
+
* param-guard class, and deliberately not a red.
|
|
4
|
+
*
|
|
5
|
+
* An unrecognised value is OPERATOR input, arriving before a single token is
|
|
6
|
+
* spent, and there is exactly one safe way to fail on it: loudly, at once. The
|
|
7
|
+
* failure this guard exists to prevent is `readShim: 'diff '` (or 'Diff', or
|
|
8
|
+
* 'cap+diff') coerced by truthiness into A3 — a battery row that RAN the wrong
|
|
9
|
+
* arm, recorded the arm it was asked for, and is invisible in the results
|
|
10
|
+
* afterwards. That is the blind-instrument class this programme keeps paying
|
|
11
|
+
* for, and it would corrupt the contrast rather than merely fail a run.
|
|
12
|
+
*
|
|
13
|
+
* A red was the alternative and is wrong here twice over: a red is a verdict on
|
|
14
|
+
* the AGENT's work (the plan, the artifact), and this is the operator's own
|
|
15
|
+
* argument; and a red would need a terminal outcome name, which is arbiter
|
|
16
|
+
* territory and out of this change's scope.
|
|
17
|
+
* @param {boolean|'cap'|'diff'|undefined} [flag]
|
|
18
|
+
* @returns {ReadShimArm}
|
|
19
|
+
*/
|
|
20
|
+
export function readShimArm(flag?: boolean | "cap" | "diff" | undefined): ReadShimArm;
|
|
21
|
+
/**
|
|
22
|
+
* The persona line this arm is entitled to — empty when the arm changes nothing
|
|
23
|
+
* the worker can perceive. One arm, one line: the two are never concatenated,
|
|
24
|
+
* because a cap arm has no diff and a diff arm has no cap.
|
|
25
|
+
* @param {ReadShimArm} arm
|
|
26
|
+
* @returns {string}
|
|
27
|
+
*/
|
|
28
|
+
export function readShimStrategy(arm: ReadShimArm): string;
|
|
29
|
+
/** @typedef {{file: string, path: string, startLine: number, endLine: number, detail: string}} StaleReq */
|
|
30
|
+
/** @typedef {{text: string, outcome: 'stale-served'|'stale-absent'|'stale-past-eof'}} StaleServe */
|
|
31
|
+
/**
|
|
32
|
+
* Create the shim: one delivery ledger, and the two seams that speak for it.
|
|
33
|
+
*
|
|
34
|
+
* `wrapRead` is the read tool's wrapper (the ledger's only writer). `serveStale`
|
|
35
|
+
* is the answer to a stale `ctx_get` pointer, and it deliberately holds NO
|
|
36
|
+
* reference to the ledger — see its own note. Both live in one object because
|
|
37
|
+
* they are one worker's shim: the read seam and the retrieval seam the cap
|
|
38
|
+
* steers a capped worker towards.
|
|
39
|
+
* @param {{cap?: number, arm?: ReadShimArm}} [opts] `cap`: delivery bound in bytes (the
|
|
40
|
+
* shipped value is `READ_SHIM_CAP`; the option exists so the native path can pin the
|
|
41
|
+
* two bounds to one number rather than two constants drifting apart). `arm`: which
|
|
42
|
+
* levers are live (`readShimArm`). Defaults to ALL of them, so every caller written
|
|
43
|
+
* before the arms existed keeps the behaviour it was written against.
|
|
44
|
+
*/
|
|
45
|
+
export function createReadShim({ cap, arm }?: {
|
|
46
|
+
cap?: number;
|
|
47
|
+
arm?: ReadShimArm;
|
|
48
|
+
}): {
|
|
49
|
+
arm: ReadShimArm;
|
|
50
|
+
cap: number;
|
|
51
|
+
wrapRead: (tool: {
|
|
52
|
+
name: string;
|
|
53
|
+
execute: (args: any) => Promise<any>;
|
|
54
|
+
}) => {
|
|
55
|
+
name: string;
|
|
56
|
+
execute: (args: any) => Promise<any>;
|
|
57
|
+
};
|
|
58
|
+
serveStale: ({ file, path, startLine, endLine, detail }: StaleReq) => StaleServe | null;
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* The one-tool spelling, kept for every caller written before the shim became an
|
|
62
|
+
* object: wrap this read tool with its own private ledger and hand it back.
|
|
63
|
+
* @param {{name: string, execute: (args: any) => Promise<any>}} tool
|
|
64
|
+
* @param {{cap?: number, arm?: ReadShimArm}} [opts]
|
|
65
|
+
* @returns {{name: string, execute: (args: any) => Promise<any>}} the same tool
|
|
66
|
+
*/
|
|
67
|
+
export function wrapReadTool(tool: {
|
|
68
|
+
name: string;
|
|
69
|
+
execute: (args: any) => Promise<any>;
|
|
70
|
+
}, opts?: {
|
|
71
|
+
cap?: number;
|
|
72
|
+
arm?: ReadShimArm;
|
|
73
|
+
}): {
|
|
74
|
+
name: string;
|
|
75
|
+
execute: (args: any) => Promise<any>;
|
|
76
|
+
};
|
|
77
|
+
/** The delivery cap, in bytes. Equal to `NATIVE_READ_CAP` by construction — the
|
|
78
|
+
* native path's CLI display bound and this cap bound the SAME seam, so one
|
|
79
|
+
* number keeps them from double-truncating each other (see planrun's wiring). */
|
|
80
|
+
export const READ_SHIM_CAP: number;
|
|
81
|
+
/** The legal spellings, in one place so an error message cannot drift from the
|
|
82
|
+
* set it describes. */
|
|
83
|
+
export const READ_SHIM_ARMS: readonly string[];
|
|
84
|
+
/** F19 at the bound rather than the verb (the NATIVE_READ_STRATEGY precedent):
|
|
85
|
+
* a cap the worker does not know about is a worker that reads the same file
|
|
86
|
+
* three times wondering why it keeps starting over. It states the bound, what a
|
|
87
|
+
* re-read does, and the two verbs that read a symbol whole — the capability the
|
|
88
|
+
* cap makes mandatory (G1). */
|
|
89
|
+
export const READ_SHIM_STRATEGY: "\nREAD LIMIT: a shell_read of a file over 24KB returns only the next 24KB of it, followed by a notice saying which bytes you got. This is NOT the whole file. Reading the same path again continues where the last read stopped, and once you hold all of it a re-read says so instead of re-sending the bytes. To read a function IN FULL, use ctx_recall(<symbol>) then ctx_get(<pointer>) \u2014 that returns the entire function however large its file is. To locate a line, use shell_grep(<pattern>). Never try to understand a large file by reading it whole.";
|
|
90
|
+
/** The DIFF arm's own line. It says nothing about a 24KB limit and nothing about
|
|
91
|
+
* continuation reads, because under A2 neither exists — a persona describing
|
|
92
|
+
* machinery that is off is a lie to the worker, and a worker rationing its reads
|
|
93
|
+
* against an imaginary cap is a different treatment than the arm names.
|
|
94
|
+
*
|
|
95
|
+
* It does not repeat the steer at ctx_recall/ctx_get either: that steer is the
|
|
96
|
+
* CAP's compensation (G1's other half), and offering it here would import half
|
|
97
|
+
* of A1 into A2. The delivery itself carries `diffNotice`'s framing inline; this
|
|
98
|
+
* line exists so the first diff is not a surprise. */
|
|
99
|
+
export const READ_SHIM_DIFF_STRATEGY: "\nRE-READS: if you shell_read a file you have already been shown in full and it has changed since, the result is a unified DIFF against the copy you already hold \u2014 not the file itself. It is labelled as one. Lines starting '-' were removed, '+' were added, ' ' are unchanged context; apply it to the copy you have and you hold the current file.";
|
|
100
|
+
export type StaleReq = {
|
|
101
|
+
file: string;
|
|
102
|
+
path: string;
|
|
103
|
+
startLine: number;
|
|
104
|
+
endLine: number;
|
|
105
|
+
detail: string;
|
|
106
|
+
};
|
|
107
|
+
export type StaleServe = {
|
|
108
|
+
text: string;
|
|
109
|
+
outcome: "stale-served" | "stale-absent" | "stale-past-eof";
|
|
110
|
+
};
|
|
111
|
+
export type ReadShimArm = {
|
|
112
|
+
on: boolean;
|
|
113
|
+
cap: boolean;
|
|
114
|
+
pointer: boolean;
|
|
115
|
+
diff: boolean;
|
|
116
|
+
g1: boolean;
|
|
117
|
+
};
|
package/types/reuse.d.ts
CHANGED
|
@@ -364,6 +364,7 @@ export function resumeTreeGate({ head, seed, dirty }: {
|
|
|
364
364
|
* @param {number} [opts.strikeLimit] forwarded (a plan step's strike ceiling)
|
|
365
365
|
* @param {number} [opts.closeTimeoutMs] forwarded
|
|
366
366
|
* @param {boolean} [opts.layerRoot] forwarded
|
|
367
|
+
* @param {boolean|'cap'|'diff'} [opts.readShim] the read shim's ARM (A0/A1/A2/A3), forwarded verbatim
|
|
367
368
|
* @param {any} [opts.resume] RESUME (module C) — a `readResume` reading of the KILLED
|
|
368
369
|
* run's own spine. Its `approvalHash` must match this run's, or the run refuses:
|
|
369
370
|
* a resume continues ONE signed run, not any run.
|
|
@@ -397,6 +398,7 @@ export function runReuse(opts: {
|
|
|
397
398
|
strikeLimit?: number | undefined;
|
|
398
399
|
closeTimeoutMs?: number | undefined;
|
|
399
400
|
layerRoot?: boolean | undefined;
|
|
401
|
+
readShim?: boolean | "cap" | "diff" | undefined;
|
|
400
402
|
resume?: any;
|
|
401
403
|
runJob?: typeof shippedRunJob | undefined;
|
|
402
404
|
now?: (() => number) | undefined;
|
package/types/run.d.ts
CHANGED
|
@@ -97,6 +97,19 @@
|
|
|
97
97
|
* collision walk would mint a `-2` beside the work the resume exists to continue. The
|
|
98
98
|
* fold's precedent exactly: the chain's state is DECLARED, never re-derived. Forwarded
|
|
99
99
|
* verbatim to the plan flow; absent is the cold path.
|
|
100
|
+
* @param {boolean|'cap'|'diff'} [opts.readShim=false] THE READ SHIM (src/readshim.js) —
|
|
101
|
+
* WHICH ARM to run, threaded to the plan flow. The Phase 2 pre-registration's four:
|
|
102
|
+
* `false` A0 — off (the default)
|
|
103
|
+
* `'cap'` A1 — cap + pointer + next-unseen-slice + G1, no diff
|
|
104
|
+
* `'diff'` A2 — the diff lever alone: no cap, no pointer, no G1
|
|
105
|
+
* `true` A3 — every lever
|
|
106
|
+
* Anything else THROWS below, before a token is spent: a mis-spelled arm coerced
|
|
107
|
+
* by truthiness into A3 would run one treatment under another's label and be
|
|
108
|
+
* invisible in the results afterwards.
|
|
109
|
+
* OFF is byte-identical to the pre-shim run in every observable (G1 included): the
|
|
110
|
+
* frozen A0 baseline arm has to be exactly today, so a guard firing under a disabled
|
|
111
|
+
* shim would make the baseline a treatment arm. The default-flip belongs to a
|
|
112
|
+
* paid contrast nobody has approved yet (the `layerRoot` precedent, F41).
|
|
100
113
|
* @param {boolean} [opts.layerRoot=false] Layer R (within-run ratchet) — shell
|
|
101
114
|
* territory, threaded to the plan flow. Defaults OFF
|
|
102
115
|
* (decided 2026-07-21): fixation is extinct on every current job (F41), so
|
|
@@ -128,7 +141,7 @@
|
|
|
128
141
|
* 'interpreter-red' | 'cap-halt' | 'wall-halt' | 'step-stalled' |
|
|
129
142
|
* 'hitl-pause' | 'hitl-decision-red' | `step-red:<id>`
|
|
130
143
|
*/
|
|
131
|
-
export function runJob(rawSpec: object | string, { approvals, workdir, provider, nativeProvider, providerFor, judgeProvider, emit, capRuns, strikeLimit, shellCapUsd, closeTimeoutMs, layerRoot, bridge, priorSpentUsd, priorSpendComplete, priorWallMs, resumeSeed, resumeGrades, resumeReplans, resumeBranch, humanRuling, heldRuling, reviewDoor, doorRerun }: {
|
|
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 }: {
|
|
132
145
|
approvals: unknown;
|
|
133
146
|
workdir: string;
|
|
134
147
|
provider: any;
|
|
@@ -159,6 +172,7 @@ export function runJob(rawSpec: object | string, { approvals, workdir, provider,
|
|
|
159
172
|
grantUsed?: boolean;
|
|
160
173
|
} | null | undefined;
|
|
161
174
|
resumeBranch?: string | null | undefined;
|
|
175
|
+
readShim?: boolean | "cap" | "diff" | undefined;
|
|
162
176
|
layerRoot?: boolean | undefined;
|
|
163
177
|
humanRuling?: {
|
|
164
178
|
decision: string;
|
package/types/tools.d.ts
CHANGED
|
@@ -19,8 +19,24 @@ export function strategyFor(granted: string[]): string;
|
|
|
19
19
|
* RESULT is invisible cannot be judged: a `ctx_get` that silently reds (stale pointer,
|
|
20
20
|
* bad range) looks exactly like one that worked, and the worker's fallback to a
|
|
21
21
|
* whole-file read looks like a free choice instead of a forced one.
|
|
22
|
+
* @param {{onStalePointer?: ((req: {file: string, path: string, startLine: number, endLine: number, detail: string}) => {text: string, outcome: string}|null)|null}} [opts]
|
|
23
|
+
* `onStalePointer`: the read shim's stale-pointer serve, wired ONLY by a capping
|
|
24
|
+
* arm (see `createReadShim`). Absent — every caller today except that one — this
|
|
25
|
+
* whole seam is byte-identical to what it was, which is what keeps A0 the
|
|
26
|
+
* untouched baseline an experiment has already run against.
|
|
22
27
|
*/
|
|
23
|
-
export function createCtxTools(lc: any, workdir: string, emit: (type: string, data: object) => void
|
|
28
|
+
export function createCtxTools(lc: any, workdir: string, emit: (type: string, data: object) => void, { onStalePointer }?: {
|
|
29
|
+
onStalePointer?: ((req: {
|
|
30
|
+
file: string;
|
|
31
|
+
path: string;
|
|
32
|
+
startLine: number;
|
|
33
|
+
endLine: number;
|
|
34
|
+
detail: string;
|
|
35
|
+
}) => {
|
|
36
|
+
text: string;
|
|
37
|
+
outcome: string;
|
|
38
|
+
} | null) | null;
|
|
39
|
+
}): ({
|
|
24
40
|
name: string;
|
|
25
41
|
description: string;
|
|
26
42
|
parameters: {
|