bareloop 0.6.0 → 0.7.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 +407 -0
- package/README.md +28 -11
- package/bareloop.context.md +510 -28
- package/package.json +2 -2
- package/src/bridges.js +571 -0
- package/src/clock.js +57 -5
- package/src/index.js +29 -0
- package/src/ladder.js +196 -0
- package/src/ledger.js +27 -0
- package/src/plan.js +75 -7
- package/src/planrun.js +762 -146
- package/src/ralph.js +148 -28
- package/src/reuse.js +1602 -0
- package/src/run.js +73 -6
- package/src/selection.js +160 -0
- package/src/spine.js +7 -2
- package/src/trend.js +405 -0
- package/types/bridges.d.ts +198 -0
- package/types/clock.d.ts +25 -3
- package/types/index.d.ts +3 -0
- package/types/ladder.d.ts +86 -0
- package/types/plan.d.ts +18 -3
- package/types/planrun.d.ts +108 -9
- package/types/ralph.d.ts +82 -4
- package/types/reuse.d.ts +388 -0
- package/types/run.d.ts +50 -3
- package/types/selection.d.ts +46 -0
- package/types/spine.d.ts +8 -1
- package/types/trend.d.ts +108 -0
package/types/clock.d.ts
CHANGED
|
@@ -20,6 +20,14 @@
|
|
|
20
20
|
* the same (raise `maxWallMs`), and the alternative — calling a governance stop
|
|
21
21
|
* transport on the chance that it was — is the failure this closes.
|
|
22
22
|
*
|
|
23
|
+
* BOTH derived bounds land here, because both are derived from the same wall:
|
|
24
|
+
* BA-18's idle timeout (`callTimeoutMs`) and BA-19's total-duration deadline
|
|
25
|
+
* (`callDeadlineMs`). Adding the second CODE does not widen the rule — the
|
|
26
|
+
* discriminator is still `clock.expired()`, never the code. That matters for the
|
|
27
|
+
* deadline specifically: a provider someone else constructed with its own
|
|
28
|
+
* `deadlineMs` could throw this code on a run with time left, and that is transport,
|
|
29
|
+
* exactly as it was before.
|
|
30
|
+
*
|
|
23
31
|
* @param {any} err the throw as caught
|
|
24
32
|
* @param {Clock} clock the run's clock
|
|
25
33
|
* @returns {boolean}
|
|
@@ -35,22 +43,25 @@ export function isWallTimeout(err: any, clock: Clock): boolean;
|
|
|
35
43
|
* @property {() => boolean} expired whether the deadline has passed (always false when unbounded)
|
|
36
44
|
* @property {(closeTimeoutMs: number) => number|null} enforcedMs the HONEST worst case
|
|
37
45
|
* @property {(providerDefaultMs?: number) => number} callTimeoutMs the derived per-call bound
|
|
46
|
+
* @property {() => number|null} callDeadlineMs the derived per-call TOTAL-duration ceiling; `null` when unbounded
|
|
38
47
|
* @property {(closeTimeoutMs: number) => object} report one spine-safe shape carrying both numbers
|
|
39
48
|
*/
|
|
40
49
|
/**
|
|
41
50
|
* Start a wall clock for one run.
|
|
42
|
-
* @param {{ maxWallMs?: number|null, closeStages?: number, now?: () => number }} [opts]
|
|
51
|
+
* @param {{ maxWallMs?: number|null, closeStages?: number, now?: () => number, priorElapsedMs?: number }} [opts]
|
|
43
52
|
* `maxWallMs`: the signed operator cap, or absent/null for an honestly unbounded
|
|
44
53
|
* run. `closeStages`: how many stages the job's close runs (W5) — each one gets
|
|
45
54
|
* the FULL `closeTimeoutMs`, so this is the multiplier on the overshoot, not a
|
|
46
55
|
* decoration. Defaults to 1, the single-predicate close. `now`: injected for
|
|
47
|
-
* tests — the default is the real clock.
|
|
56
|
+
* tests — the default is the real clock. `priorElapsedMs`: time this attempt's
|
|
57
|
+
* PREDECESSOR already consumed (see below).
|
|
48
58
|
* @returns {Clock}
|
|
49
59
|
*/
|
|
50
|
-
export function createClock({ maxWallMs, closeStages, now }?: {
|
|
60
|
+
export function createClock({ maxWallMs, closeStages, now, priorElapsedMs }?: {
|
|
51
61
|
maxWallMs?: number | null;
|
|
52
62
|
closeStages?: number;
|
|
53
63
|
now?: () => number;
|
|
64
|
+
priorElapsedMs?: number;
|
|
54
65
|
}): Clock;
|
|
55
66
|
/** BA-18's default request/idle timeout (`AnthropicProvider.timeoutMs`), the
|
|
56
67
|
* ceiling every derived per-call timeout starts from. Kept in sync deliberately:
|
|
@@ -69,6 +80,13 @@ export const MIN_CALL_TIMEOUT_MS: 30000;
|
|
|
69
80
|
* `category`, so every consumer that defaults an uncategorised throw would file
|
|
70
81
|
* it as transport. */
|
|
71
82
|
export const TIMEOUT_CODE: "ETIMEDOUT";
|
|
83
|
+
/** bare-agent's rejection code when its TOTAL-duration deadline trips (BA-19,
|
|
84
|
+
* `provider-http.js` → `applyRequestDeadline`). A DIFFERENT code from the idle
|
|
85
|
+
* bound's on purpose — upstream splits them precisely so a consumer routing
|
|
86
|
+
* governance stops apart from transport casualties can tell which timer fired. Like
|
|
87
|
+
* `TIMEOUT_CODE` it carries no `category`, so every default-to-transport catch site
|
|
88
|
+
* would file it as a casualty. */
|
|
89
|
+
export const DEADLINE_CODE: "EDEADLINE";
|
|
72
90
|
export type Clock = {
|
|
73
91
|
/**
|
|
74
92
|
* whether the operator set a cap at all
|
|
@@ -102,6 +120,10 @@ export type Clock = {
|
|
|
102
120
|
* the derived per-call bound
|
|
103
121
|
*/
|
|
104
122
|
callTimeoutMs: (providerDefaultMs?: number) => number;
|
|
123
|
+
/**
|
|
124
|
+
* the derived per-call TOTAL-duration ceiling; `null` when unbounded
|
|
125
|
+
*/
|
|
126
|
+
callDeadlineMs: () => number | null;
|
|
105
127
|
/**
|
|
106
128
|
* one spine-safe shape carrying both numbers
|
|
107
129
|
*/
|
package/types/index.d.ts
CHANGED
|
@@ -6,4 +6,7 @@ export { globToPrefix, scanSecrets } from "./validate.js";
|
|
|
6
6
|
export { validateJob, jobSpecHash, checkApproval, checkMenu, CLOSE_TYPES, CLASS_BY_CLOSE, GOLD_COMPARE, CADENCE_UNITS, PROVIDERS, CONDITION_KEYS, TOOL_MENU, LOCKED_TOOLS, STORE_VERBS, VERDICT_TYPES, LOCKED_VERDICTS } from "./job.js";
|
|
7
7
|
export { validatePlan, stageClose, EXIT_TYPES, MAX_EXITS_PER_STEP, MAX_PLAN_STEPS, WRITE_VERBS } from "./plan.js";
|
|
8
8
|
export { snapshotScope, evalExits } from "./exits.js";
|
|
9
|
+
export { BRIDGE_SCHEMA, validateBridge, deriveStatus, listingRow, loadGate, mintBridge, appendGreen, appendRed, loadBridge, loadRegistry, saveBridge, makeRegistry, registryExists } from "./bridges.js";
|
|
10
|
+
export { renderListing, selectionPrompt } from "./selection.js";
|
|
11
|
+
export { validateEnvelope, resolveTrySpec, resolveReuse, reuseSpecHash, selectBridge, runReuse, REUSE_GRADED_RED, readResume, resumeTreeGate } from "./reuse.js";
|
|
9
12
|
export { classifyIncidents, foldLedger, ledgerDeltas, updateLedger, LEDGER_CLASSES } from "./ledger.js";
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalize a gap for comparison. Deliberately NARROW: only bytes a re-run changes
|
|
3
|
+
* on its own are removed. Error COUNTS, file names and line numbers all survive —
|
|
4
|
+
* a trajectory like "30 errors" → "22 errors" must read as two different gaps, and
|
|
5
|
+
* a normalizer that scrubbed numbers would turn the converging case this module
|
|
6
|
+
* exists to protect into an instant strike.
|
|
7
|
+
* @param {string} text @returns {string}
|
|
8
|
+
*/
|
|
9
|
+
export function normalizeGap(text: string): string;
|
|
10
|
+
/**
|
|
11
|
+
* The comparable key for a gap: the normalized text, hashed. Hashed rather than
|
|
12
|
+
* kept whole because the seen-set lives for the whole step and a close's output
|
|
13
|
+
* can be tens of KB — and because the set holds no content, nothing here can leak
|
|
14
|
+
* model or file bytes into a record.
|
|
15
|
+
* @param {string} text @returns {string}
|
|
16
|
+
*/
|
|
17
|
+
export function gapKey(text: string): string;
|
|
18
|
+
/**
|
|
19
|
+
* Build one ladder. ONE PER STEP: the seen-set and the strike count are the step's
|
|
20
|
+
* own history, and a ladder shared across steps would strike a fresh step for a gap
|
|
21
|
+
* an earlier one had already seen.
|
|
22
|
+
*
|
|
23
|
+
* @param {object} opts
|
|
24
|
+
* @param {number} [opts.limit] strikes that end the step (default `STRIKE_LIMIT`)
|
|
25
|
+
* @param {() => number} opts.writeCount the CUMULATIVE count of the worker's
|
|
26
|
+
* allow-decision write/edit records so far this step (planrun reads it off the
|
|
27
|
+
* gate audit). Required, and a throw when it is missing rather than a silent
|
|
28
|
+
* degradation to repeats-only: a strike rule running on one of its two signals
|
|
29
|
+
* with nothing on the record saying so is the blind-instrument class, and this
|
|
30
|
+
* is the BA-4 param-guard class where a throw is the right answer.
|
|
31
|
+
*/
|
|
32
|
+
export function createLadder({ limit, writeCount }: {
|
|
33
|
+
limit?: number | undefined;
|
|
34
|
+
writeCount: () => number;
|
|
35
|
+
}): {
|
|
36
|
+
/**
|
|
37
|
+
* Read one RED iteration. Called by ralph after the judge has rendered a
|
|
38
|
+
* non-green, non-fault verdict — a green never reaches here, so every reading
|
|
39
|
+
* is a failing iteration by construction.
|
|
40
|
+
* @param {{iteration: number, gap?: string}} o
|
|
41
|
+
* @returns {{governor: string, iteration: number, strike: boolean, strikes: number,
|
|
42
|
+
* limit: number, wrote: boolean, repeatOf: number|null, distinctGaps: number}}
|
|
43
|
+
*/
|
|
44
|
+
record({ iteration, gap }: {
|
|
45
|
+
iteration: number;
|
|
46
|
+
gap?: string;
|
|
47
|
+
}): {
|
|
48
|
+
governor: string;
|
|
49
|
+
iteration: number;
|
|
50
|
+
strike: boolean;
|
|
51
|
+
strikes: number;
|
|
52
|
+
limit: number;
|
|
53
|
+
wrote: boolean;
|
|
54
|
+
repeatOf: number | null;
|
|
55
|
+
distinctGaps: number;
|
|
56
|
+
};
|
|
57
|
+
/** has the step run out of strikes? */
|
|
58
|
+
struckOut: () => boolean;
|
|
59
|
+
/** the ladder's own books — counts and iteration numbers, never gap content */
|
|
60
|
+
report: () => {
|
|
61
|
+
iterations: number;
|
|
62
|
+
strikes: number;
|
|
63
|
+
limit: number;
|
|
64
|
+
distinctGaps: number;
|
|
65
|
+
repeats: {
|
|
66
|
+
at: number;
|
|
67
|
+
firstAt: number;
|
|
68
|
+
}[];
|
|
69
|
+
idle: number[];
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
* The mechanism, in one sentence, for the replan brief. It is the only channel
|
|
73
|
+
* the redrafting planner adapts to, so it states WHICH shape ended the step and
|
|
74
|
+
* the evidence for it — never a label on its own, and never a culprit file
|
|
75
|
+
* beyond what the close itself reported (F28): the inputs here are iteration
|
|
76
|
+
* numbers and counts, so naming one is inexpressible.
|
|
77
|
+
* @returns {string}
|
|
78
|
+
*/
|
|
79
|
+
brief(): string;
|
|
80
|
+
};
|
|
81
|
+
/**
|
|
82
|
+
* The strike ceiling. Shell-owned, exactly where the fixed count was: the runner
|
|
83
|
+
* sets it, the agent cannot express it, and it is arbiter territory — a step's own
|
|
84
|
+
* plan may not tighten it and may not raise it. Two is hamr's number.
|
|
85
|
+
*/
|
|
86
|
+
export const STRIKE_LIMIT: 2;
|
package/types/plan.d.ts
CHANGED
|
@@ -90,7 +90,7 @@ export function stageClose(close: any): any[] | null;
|
|
|
90
90
|
* job spec. Never throws on JSON text or plain parsed data; every failure is
|
|
91
91
|
* a named red. Returns the parsed plan on ok (single parse), null on any red.
|
|
92
92
|
* @param {object|string} input parsed plan, or raw JSON text (parse failures are a red)
|
|
93
|
-
* @param {{ job?: any, maxStepRounds?: number, scopes?: string[],
|
|
93
|
+
* @param {{ job?: any, maxStepRounds?: number, scopes?: string[], seedRed?: string[], priorChecks?: string[] }} [opts] `job`: the
|
|
94
94
|
* validateJob-GREEN four-field spec (the ceiling, the fence, and the checks
|
|
95
95
|
* menu all come from it — a missing or non-plan-shape job fails CLOSED);
|
|
96
96
|
* `maxStepRounds`: the shell's per-step rounds ceiling (interpret's
|
|
@@ -98,13 +98,28 @@ export function stageClose(close: any): any[] | null;
|
|
|
98
98
|
* `scopes`: the offered `tree-changed` menu from `legalScopes` (the SAME array
|
|
99
99
|
* the drafting prompt enumerated). Omitted, it derives from the signed
|
|
100
100
|
* writeScope — never a free-text fallback (F50).
|
|
101
|
+
*
|
|
102
|
+
* The two shape-lottery gate rules (2026-08-04) — both keyed on RECORDED
|
|
103
|
+
* facts, never an LLM judgment of "is this job small", and both inactive
|
|
104
|
+
* when their fact is omitted (resume/reuse callers stay byte-identical):
|
|
105
|
+
* `seedRed`: check names whose PREFLIGHT verdict was needs_revision. Rule
|
|
106
|
+
* A-v2 — a seed-red check is the goal itself, not a milestone; it may only
|
|
107
|
+
* sit on the plan's FINAL write step (`check-placement`). The $0 archive
|
|
108
|
+
* sweep: seed-red-check-on-an-early-step has 0 honest greens ever; the one
|
|
109
|
+
* wide closing step greens 7/7. Green-at-seed checks stay free mid-plan
|
|
110
|
+
* (the 20 TESTGEN mid-plan greens — a guard, not a goal).
|
|
111
|
+
* `priorChecks`: the check names the PREDECESSOR plan carried (replan gate
|
|
112
|
+
* only). Rule B — a redraft may not drop one (`check-shed`): exits without
|
|
113
|
+
* the check verify FORM alone, so a shed lets the run "green" unearned
|
|
114
|
+
* (u-msdsmkid mechanism b; the 3 archived sheds were all on step-red runs).
|
|
101
115
|
* @returns {{ ok: boolean, reds: Red[], plan: object|null }}
|
|
102
116
|
*/
|
|
103
|
-
export function validatePlan(input: object | string, { job, maxStepRounds, scopes,
|
|
117
|
+
export function validatePlan(input: object | string, { job, maxStepRounds, scopes, seedRed, priorChecks }?: {
|
|
104
118
|
job?: any;
|
|
105
119
|
maxStepRounds?: number;
|
|
106
120
|
scopes?: string[];
|
|
107
|
-
|
|
121
|
+
seedRed?: string[];
|
|
122
|
+
priorChecks?: string[];
|
|
108
123
|
}): {
|
|
109
124
|
ok: boolean;
|
|
110
125
|
reds: Red[];
|
package/types/planrun.d.ts
CHANGED
|
@@ -1,3 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Files named in an exit gap that the step's worker never wrote — the mechanical
|
|
3
|
+
* fact behind the prose-prohibition trap (u-msdpuaej, 2026-08-03): a drafter wrote
|
|
4
|
+
* "do not modify any file outside X" into a step whose exit check judges the whole
|
|
5
|
+
* goal, the worker obeyed, and the replanner — handed the gap AND the stall note —
|
|
6
|
+
* rebuilt the same shape, because inferring "the exit reds on files this step never
|
|
7
|
+
* touched" is exactly the semantic leap that fails (F38: mechanical gaps convert,
|
|
8
|
+
* semantic ones stall; 3/3 across the mailbox class). This computes that fact from
|
|
9
|
+
* the two books that already exist: path-like tokens in the gap text (extension
|
|
10
|
+
* required — a bare `step/two` is not a file claim) suffix-matched against the F32
|
|
11
|
+
* write audit's absolute paths. Advisory-only and fail-safe: extraction that finds
|
|
12
|
+
* nothing yields no line, never a guess.
|
|
13
|
+
*
|
|
14
|
+
* @param {string | undefined} gap the step's last exit gap text
|
|
15
|
+
* @param {string[] | undefined} writes the step's cumulative write-audit paths
|
|
16
|
+
* @returns {string[]} gap-named files with no matching write, in gap order
|
|
17
|
+
*/
|
|
18
|
+
export function gapFilesNeverWritten(gap: string | undefined, writes: string[] | undefined): string[];
|
|
1
19
|
/**
|
|
2
20
|
* The plan-drafting prompt: a schema DESCRIPTION built from the live validator
|
|
3
21
|
* menus — never a copyable example (the drafter must author, not echo; the
|
|
@@ -13,16 +31,26 @@
|
|
|
13
31
|
* @param {{ balanceUsd?: number|null, remainingMs?: number|null, progress?: string }} [materials]
|
|
14
32
|
* what the run has LEFT (T/A). Omitted → no materials block at all, which is the
|
|
15
33
|
* pre-T behaviour and keeps every existing caller byte-identical.
|
|
16
|
-
* @param {
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
34
|
+
* @param {object|null} [startingDraft] Layer 3 (D4) — a loaded bridge's plan, handed over
|
|
35
|
+
* as the draft to TWEAK. Omitted/null is the COLD path and renders byte-identically to
|
|
36
|
+
* the pre-Layer-3 prompt: the block is additive, never surgery on the prompt's interior,
|
|
37
|
+
* so the two paths cannot drift (the F47 works-both-ways rule). Only the runner passes
|
|
38
|
+
* it, and only for the FIRST draft phase — a replan drafts from the run's own state.
|
|
39
|
+
* @param {{ seedRed?: string[], priorChecks?: string[] }} [checkFacts] the shape-lottery
|
|
40
|
+
* gate rules, STATED as well as enforced (the mailbox precedent: a rule only the
|
|
41
|
+
* validator knows costs a redraft per draft). `seedRed`: preflight-red check names —
|
|
42
|
+
* Rule A-v2's law (final write step only, framed wide). `priorChecks`: the predecessor
|
|
43
|
+
* plan's carried checks, replan phase only — Rule B's law (keep every one). Empty/omitted
|
|
44
|
+
* renders byte-identically to the pre-rules prompt.
|
|
20
45
|
*/
|
|
21
46
|
export function planPrompt(job: any, scoutBlob: string, reds: any[] | null, maxStepRounds: number, failure: string | null, scopes: string[] | undefined, materials?: {
|
|
22
47
|
balanceUsd?: number | null;
|
|
23
48
|
remainingMs?: number | null;
|
|
24
49
|
progress?: string;
|
|
25
|
-
},
|
|
50
|
+
}, startingDraft?: object | null, checkFacts?: {
|
|
51
|
+
seedRed?: string[];
|
|
52
|
+
priorChecks?: string[];
|
|
53
|
+
}): string;
|
|
26
54
|
/**
|
|
27
55
|
* Execute a validateJob-GREEN plan-shape job (goal/verdictType/close).
|
|
28
56
|
* Called by runJob after the approval gate, validation, and the smoke — this
|
|
@@ -48,7 +76,17 @@ export function planPrompt(job: any, scoutBlob: string, reds: any[] | null, maxS
|
|
|
48
76
|
* @param {(tier: string) => any} [opts.providerFor] P: per-step model-tier factory
|
|
49
77
|
* (tier ∈ STEP_MODELS → a provider). A plan naming a tier with no factory supplied is an
|
|
50
78
|
* interpreter-red STOP — never silently run the default tier as if the choice was honoured.
|
|
51
|
-
* @param {number} [opts.capRuns] shell-owned
|
|
79
|
+
* @param {number} [opts.capRuns] shell-owned iteration cap, RETIRED as a governor
|
|
80
|
+
* (v1.46 §4). It stopped bounding a plan step when the strike ladder landed, and it
|
|
81
|
+
* stopped bounding the close-fix loop when the close trend rule did. It survives as
|
|
82
|
+
* that loop's fallback for the ONE case the trend cannot read — a close whose output
|
|
83
|
+
* carries no number at all — because a governor that cannot see the variable must
|
|
84
|
+
* not be the governor, and the honest fallback is the cruder bound it replaced
|
|
85
|
+
* rather than "unbounded". It lifts the moment a stage reports a comparable number.
|
|
86
|
+
* @param {number} [opts.strikeLimit] shell-owned STRIKE ceiling for a plan step's
|
|
87
|
+
* ladder (src/ladder.js). Arbiter territory exactly as the count it replaced was —
|
|
88
|
+
* the runner sets it, the plan cannot express it, and no step may tighten or raise
|
|
89
|
+
* it.
|
|
52
90
|
* @param {number} [opts.closeTimeoutMs] close/check wall-clock cap (shell territory)
|
|
53
91
|
* @param {number} [opts.maxStepRounds] the shell's per-step rounds ceiling (validatePlan's bound)
|
|
54
92
|
* @param {number} [opts.scoutRounds] the read-only survey's round bound (F59: the LAST round is
|
|
@@ -63,15 +101,67 @@ export function planPrompt(job: any, scoutBlob: string, reds: any[] | null, maxS
|
|
|
63
101
|
* first plan-flow job to emit `root-injected` runs the pre-registered ON-vs-OFF acceptance
|
|
64
102
|
* read (the Layer R default-flip, LAYERS.md ⚠). Excluded on native (clipipe): the native
|
|
65
103
|
* worker has no onToolResult seam, so the tee cannot settle and same-path rewrites are blind.
|
|
104
|
+
* @param {unknown} [opts.bridge] Layer 3 (D3/D4) — a bridge-v1 registry entry to REUSE as the
|
|
105
|
+
* drafter's starting point. Absent is the COLD path and is byte-identical to the pre-Layer-3
|
|
106
|
+
* flow. Present, the D2-split LOAD GATE runs at the door, before the close precheck and
|
|
107
|
+
* before any token: on a pass the newest version's plan (the run-as-executed artifact)
|
|
108
|
+
* rides into the FIRST draft prompt only; on a fail the run returns the distinct terminal
|
|
109
|
+
* `recipe-stale` having spent nothing. Selecting the entry — the listing, the pin, the
|
|
110
|
+
* LLM's pick — is the CALLER's, and so is falling back to cold: a silent automatic
|
|
111
|
+
* fall-back would spend a run's budget on a decision nobody made.
|
|
112
|
+
* @param {number} [opts.priorWallMs=0] RESUME (module C) — wall time a PREVIOUS, killed
|
|
113
|
+
* attempt of this same try already consumed, folded into the run clock (see
|
|
114
|
+
* `createClock`'s `priorElapsedMs`). The SIGNED `maxWallMs` is never rewritten: it is in
|
|
115
|
+
* the spec hash, and a resume that edited it would need a signature nobody typed. The
|
|
116
|
+
* restarted attempt simply starts already partly spent, so a kill can never buy a fresh
|
|
117
|
+
* allotment of the operator's wall.
|
|
118
|
+
* @param {any} [opts.resumeSeed] RESUME (module C v2) — the FINEST honest checkpoint of a
|
|
119
|
+
* KILLED run of this same try, read off its own spine by `readResume` (src/reuse.js) and
|
|
120
|
+
* by nothing else: `{ phase, plan, completedSteps: [{id, seq, by}] }`. hamr's ruling is
|
|
121
|
+
* the whole contract — *"it should allow resume and start last step instead from the
|
|
122
|
+
* beginning, why would i want to waste more money on something i already started"* — so
|
|
123
|
+
* with a seed this flow re-pays for NO paid unit the dead run already bought:
|
|
124
|
+
*
|
|
125
|
+
* - the SCOUT does not run (`scout-skipped`, never silence). Its survey is not on the
|
|
126
|
+
* spine (only its byte count is), so the ONE consequence is stated rather than
|
|
127
|
+
* discovered: a replan after a resume drafts from an empty survey plus the failure
|
|
128
|
+
* brief. Re-scouting to refill it would re-pay the exact call this exists to save.
|
|
129
|
+
* - the PLAN is not re-drafted. It is reloaded from the seed and RE-VALIDATED against
|
|
130
|
+
* the current signed spec; a plan that no longer validates is `plan-red` by name
|
|
131
|
+
* (`resume-plan-red`), never run. It is then emitted as this leg's own
|
|
132
|
+
* `plan-accepted` (`phase: 'resume'`) — R1 mints a bridge version from the plan AS
|
|
133
|
+
* EXECUTED, read back off the spine, so a leg that greens without one would mint
|
|
134
|
+
* nothing.
|
|
135
|
+
* - a step whose exit was already SATISFIED is skipped, in prefix order, each with its
|
|
136
|
+
* own `step-skipped` record naming the event that proves it. Never a fake
|
|
137
|
+
* step-start/step-end pair, and never silence: a reader must be able to tell skipped
|
|
138
|
+
* from run (the resume-to-cap invariant).
|
|
139
|
+
*
|
|
140
|
+
* The worker's conversation is deliberately NOT replayed — a transcript is not a
|
|
141
|
+
* checkpoint, and every attempt is fresh by the loop's own design. Absent is the
|
|
142
|
+
* ordinary path and is byte-identical to a run that was never killed.
|
|
143
|
+
* @param {{stage?: string|null, value?: number|null}[]} [opts.resumeGrades] RESUME — the
|
|
144
|
+
* close GRADES the dead leg recorded, oldest first (`readResume`'s `restart.grades`).
|
|
145
|
+
* A resumed leg is the same run continuing, so its halt readouts must judge the whole
|
|
146
|
+
* chain: without this the trend restarts at this leg's first close and can report
|
|
147
|
+
* `flat` — "revise the goal" — on a run that was converging when its allowance ran
|
|
148
|
+
* out. Baselines only (src/trend.js's THE SEED): it spends none of this leg's own
|
|
149
|
+
* bounds and mints none of its strikes. Empty/omitted is the cold path.
|
|
150
|
+
* @param {() => boolean} [opts.spendComplete] is the run's spend EXACT, or a floor? The
|
|
151
|
+
* ledger one level up owns the answer (unpriced rounds, a self-healed stall, a mid-call
|
|
152
|
+
* cut, an inherited resume floor); the money-halt readout states it beside the remaining
|
|
153
|
+
* it quotes, because `budget − floor` is a CEILING and printing it bare reads as exact
|
|
154
|
+
* (F6). Defaults to exact, which is what every pre-record caller was implicitly saying.
|
|
66
155
|
* @param {() => number} [opts.now] the wall clock's time source, injected. The real clock is the
|
|
67
156
|
* default; a caller supplies this to drive time deterministically (the same seam `createClock`
|
|
68
157
|
* already exposes — a run's terminal cannot otherwise be exercised without waiting out a cap
|
|
69
158
|
* whose floor is one close timeout).
|
|
70
159
|
* @returns {Promise<string>} 'green' | 'already-green' | 'escalated' | 'plan-red' |
|
|
71
|
-
* 'check-red' | 'close-red' | 'close-unsupported' | '
|
|
72
|
-
* 'wall-halt' | 'provider-red' | 'interpreter-red' | 'step-stalled' |
|
|
160
|
+
* 'check-red' | 'close-red' | 'close-unsupported' | 'recipe-stale' | 'pricing-red' |
|
|
161
|
+
* 'cap-halt' | 'wall-halt' | 'provider-red' | 'interpreter-red' | 'step-stalled' |
|
|
162
|
+
* `step-red:<id>`
|
|
73
163
|
*/
|
|
74
|
-
export function runPlan(job: any, { workdir, provider, nativeProvider, providerFor, emit, remainingUsd, isUnpriced, capRuns, closeTimeoutMs, maxStepRounds, layerRoot, scoutRounds, now }: {
|
|
164
|
+
export function runPlan(job: any, { workdir, provider, nativeProvider, providerFor, emit, remainingUsd, isUnpriced, spendComplete, capRuns, strikeLimit, closeTimeoutMs, maxStepRounds, layerRoot, scoutRounds, bridge, now, priorWallMs, resumeSeed, resumeGrades }: {
|
|
75
165
|
workdir: string;
|
|
76
166
|
provider: any;
|
|
77
167
|
nativeProvider?: ((o: {
|
|
@@ -85,10 +175,19 @@ export function runPlan(job: any, { workdir, provider, nativeProvider, providerF
|
|
|
85
175
|
isUnpriced?: (() => boolean) | undefined;
|
|
86
176
|
providerFor?: ((tier: string) => any) | undefined;
|
|
87
177
|
capRuns?: number | undefined;
|
|
178
|
+
strikeLimit?: number | undefined;
|
|
88
179
|
closeTimeoutMs?: number | undefined;
|
|
89
180
|
maxStepRounds?: number | undefined;
|
|
90
181
|
scoutRounds?: number | undefined;
|
|
91
182
|
layerRoot?: boolean | undefined;
|
|
183
|
+
bridge?: unknown;
|
|
184
|
+
priorWallMs?: number | undefined;
|
|
185
|
+
resumeSeed?: any;
|
|
186
|
+
resumeGrades?: {
|
|
187
|
+
stage?: string | null;
|
|
188
|
+
value?: number | null;
|
|
189
|
+
}[] | undefined;
|
|
190
|
+
spendComplete?: (() => boolean) | undefined;
|
|
92
191
|
now?: (() => number) | undefined;
|
|
93
192
|
}): Promise<string>;
|
|
94
193
|
export type CategorizedError = Error & {
|
package/types/ralph.d.ts
CHANGED
|
@@ -111,6 +111,26 @@ export function runStages(stages: any[], redact?: (s: string) => string, { timeo
|
|
|
111
111
|
timeoutMs?: number;
|
|
112
112
|
cwd?: string;
|
|
113
113
|
}): Promise<any>;
|
|
114
|
+
/**
|
|
115
|
+
* The exhaustion-by-PROGRESS seam, as a contract rather than "whatever one module
|
|
116
|
+
* happens to return". Two governors implement it and they read different signals —
|
|
117
|
+
* the step ladder's repeats-and-writes (src/ladder.js) and the close-fix loop's
|
|
118
|
+
* per-stage graded numbers (src/trend.js) — which is the whole reason ralph takes an
|
|
119
|
+
* interface here and interprets nothing itself.
|
|
120
|
+
*
|
|
121
|
+
* `record` returns the governor's own reading, which ralph emits verbatim and never
|
|
122
|
+
* inspects; each reading names its `governor` so two instruments under one spine type
|
|
123
|
+
* can never be averaged into one number. `report` is the only shape ralph itself
|
|
124
|
+
* reads. `terminal` is optional and overrides the exhaustion PROSE only — never the
|
|
125
|
+
* category, never the outcome.
|
|
126
|
+
*
|
|
127
|
+
* @typedef {object} LoopGovernor
|
|
128
|
+
* @property {(o: {iteration: number, gap?: string}) => object} record
|
|
129
|
+
* @property {() => boolean} struckOut
|
|
130
|
+
* @property {() => {strikes: number, limit: number, iterations: number, distinctGaps?: number}} report
|
|
131
|
+
* @property {() => string} brief
|
|
132
|
+
* @property {() => {decision: string, options: string[]}} [terminal]
|
|
133
|
+
*/
|
|
114
134
|
/**
|
|
115
135
|
* The loop: `while close-red and under-cap: run the middle`. Stops at first
|
|
116
136
|
* green (within-run tuning past a visible close is the fit-to-pass surface,
|
|
@@ -136,7 +156,28 @@ export function runStages(stages: any[], redact?: (s: string) => string, { timeo
|
|
|
136
156
|
* zone, worker-crash routing (F32), and the cap taxonomy apply unchanged.
|
|
137
157
|
* Injected by run.js only; inexpressible in any config or plan — the agent
|
|
138
158
|
* never authors its judge any more than its close.
|
|
139
|
-
* @param {number} opts.capRuns budget: max middle runs
|
|
159
|
+
* @param {number} [opts.capRuns] budget: max middle runs. Required UNLESS `ladder`
|
|
160
|
+
* is supplied — the two exhaustion rules are alternatives, never both at once.
|
|
161
|
+
* The "unless" is ENFORCED at entry (a param guard that throws), not merely
|
|
162
|
+
* documented: an optional-in-the-contract bound with nothing checking it made a
|
|
163
|
+
* loop that never ran report a governance stop that never happened.
|
|
164
|
+
* @param {LoopGovernor} [opts.ladder]
|
|
165
|
+
* the PROGRESS governor (src/ladder.js) that replaces the fixed count for a plan
|
|
166
|
+
* STEP's micro-loop. With it, the loop is bounded by strikes rather than by a
|
|
167
|
+
* number of runs: iterations float, and money, the wall, the variance meter and
|
|
168
|
+
* the stall fuse keep every bit of their current authority (each still ends the
|
|
169
|
+
* loop through the middle's own throw). Ralph still interprets nothing — it hands
|
|
170
|
+
* the ladder the iteration's gap and reads back a boolean.
|
|
171
|
+
*
|
|
172
|
+
* The CLOSE-FIX loop now supplies one too (PRD v1.46 §4, once its own $0 replay
|
|
173
|
+
* over all 8 archived fix loops came back clean: 0 greens harmed, 1 waste case
|
|
174
|
+
* caught). Its governor reads a different signal — the close's own per-stage
|
|
175
|
+
* graded numbers (src/trend.js), not repeats and writes — which is exactly why
|
|
176
|
+
* this is a SEAM and not a hard-coded rule: ralph still interprets nothing.
|
|
177
|
+
* An optional `terminal()` on the governor overrides the exhaustion prose, because
|
|
178
|
+
* the step ladder's copy offers the human a replan and there is no planner at the
|
|
179
|
+
* close. The category and the outcome are NOT overridable: one exhaustion terminal,
|
|
180
|
+
* two triggers.
|
|
140
181
|
* @param {(type: string, data?: object) => object} opts.emit a spine emitter
|
|
141
182
|
* @param {(s: string) => string} [opts.redact] source scrubber for close output
|
|
142
183
|
* (secrets never enter the spine); injected so the shell stays stdlib-only
|
|
@@ -155,7 +196,7 @@ export function runStages(stages: any[], redact?: (s: string) => string, { timeo
|
|
|
155
196
|
* with zero writes, a crash stays what it always was: an instrument stop.
|
|
156
197
|
* @returns {Promise<'green'|'escalated'>}
|
|
157
198
|
*/
|
|
158
|
-
export function ralph({ middle, close, judge, capRuns, emit, redact, closeTimeoutMs, cwd, expect, judged, gapKeep, workerWrites }: {
|
|
199
|
+
export function ralph({ middle, close, judge, capRuns, ladder, emit, redact, closeTimeoutMs, cwd, expect, judged, gapKeep, workerWrites }: {
|
|
159
200
|
middle: (iteration: number, gap?: string) => void | Promise<void>;
|
|
160
201
|
close?: string[] | undefined;
|
|
161
202
|
judge?: (() => Promise<{
|
|
@@ -167,7 +208,8 @@ export function ralph({ middle, close, judge, capRuns, emit, redact, closeTimeou
|
|
|
167
208
|
gap?: string;
|
|
168
209
|
detail?: string;
|
|
169
210
|
}) | undefined;
|
|
170
|
-
capRuns
|
|
211
|
+
capRuns?: number | undefined;
|
|
212
|
+
ladder?: LoopGovernor | undefined;
|
|
171
213
|
emit: (type: string, data?: object) => object;
|
|
172
214
|
redact?: ((s: string) => string) | undefined;
|
|
173
215
|
closeTimeoutMs?: number | undefined;
|
|
@@ -194,7 +236,12 @@ export function ralph({ middle, close, judge, capRuns, emit, redact, closeTimeou
|
|
|
194
236
|
* Two rules, deliberately kept BOTH ways round:
|
|
195
237
|
* - `names` / `prefixes` — the knowns. A literal denylist is a chase (every
|
|
196
238
|
* new provider mints a new variable), so it is not load-bearing on its own;
|
|
197
|
-
* it exists for names the shape rule cannot see
|
|
239
|
+
* it exists for names the shape rule STRUCTURALLY cannot see — either no
|
|
240
|
+
* `_`-bounded credential suffix at all (`AWS_ACCESS_KEY_ID`, `PGPASSWORD`,
|
|
241
|
+
* `MYSQL_PWD`), a `*_KEY` that is not `*_API_KEY` (`SSH_PRIVATE_KEY`), or a
|
|
242
|
+
* BARE spelling with nothing in front of the suffix (`TOKEN`, `SECRET`).
|
|
243
|
+
* Names are matched EXACTLY, which is what keeps connection config out of
|
|
244
|
+
* the strip (`PGHOST`, `SECRETS_DIR`, `TOKENIZERS_PARALLELISM` all survive).
|
|
198
245
|
* - `shape` — the NAME-SHAPE rule, which is what bounds the chase: anything
|
|
199
246
|
* whose name ends in the credential suffixes goes, whoever minted it and
|
|
200
247
|
* whether or not anyone here has heard of it. Case-insensitive; no `g` flag,
|
|
@@ -228,3 +275,34 @@ export const CLOSE_FAULTS: Record<string, {
|
|
|
228
275
|
decision: string;
|
|
229
276
|
options: string[];
|
|
230
277
|
}>;
|
|
278
|
+
/**
|
|
279
|
+
* The exhaustion-by-PROGRESS seam, as a contract rather than "whatever one module
|
|
280
|
+
* happens to return". Two governors implement it and they read different signals —
|
|
281
|
+
* the step ladder's repeats-and-writes (src/ladder.js) and the close-fix loop's
|
|
282
|
+
* per-stage graded numbers (src/trend.js) — which is the whole reason ralph takes an
|
|
283
|
+
* interface here and interprets nothing itself.
|
|
284
|
+
*
|
|
285
|
+
* `record` returns the governor's own reading, which ralph emits verbatim and never
|
|
286
|
+
* inspects; each reading names its `governor` so two instruments under one spine type
|
|
287
|
+
* can never be averaged into one number. `report` is the only shape ralph itself
|
|
288
|
+
* reads. `terminal` is optional and overrides the exhaustion PROSE only — never the
|
|
289
|
+
* category, never the outcome.
|
|
290
|
+
*/
|
|
291
|
+
export type LoopGovernor = {
|
|
292
|
+
record: (o: {
|
|
293
|
+
iteration: number;
|
|
294
|
+
gap?: string;
|
|
295
|
+
}) => object;
|
|
296
|
+
struckOut: () => boolean;
|
|
297
|
+
report: () => {
|
|
298
|
+
strikes: number;
|
|
299
|
+
limit: number;
|
|
300
|
+
iterations: number;
|
|
301
|
+
distinctGaps?: number;
|
|
302
|
+
};
|
|
303
|
+
brief: () => string;
|
|
304
|
+
terminal?: (() => {
|
|
305
|
+
decision: string;
|
|
306
|
+
options: string[];
|
|
307
|
+
}) | undefined;
|
|
308
|
+
};
|