bareloop 0.3.0 → 0.5.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 +194 -0
- package/README.md +12 -5
- package/bareloop.context.md +167 -16
- package/package.json +2 -2
- package/src/exits.js +163 -0
- package/src/index.js +7 -1
- package/src/interpret.js +192 -25
- package/src/job.js +231 -88
- package/src/plan.js +237 -0
- package/src/planrun.js +677 -0
- package/src/ralph.js +25 -7
- package/src/root.js +305 -0
- package/src/run.js +88 -22
- package/types/exits.d.ts +51 -0
- package/types/index.d.ts +4 -1
- package/types/interpret.d.ts +107 -1
- package/types/job.d.ts +12 -0
- package/types/plan.d.ts +41 -0
- package/types/planrun.d.ts +49 -0
- package/types/ralph.d.ts +21 -3
- package/types/root.d.ts +96 -0
- package/types/run.d.ts +26 -3
package/types/exits.d.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/** @typedef {{ type: string, pass: boolean, detail?: string, fault?: string }} ExitResult
|
|
2
|
+
* `fault` present means the INSTRUMENT could not judge (unwired snapshot/seam,
|
|
3
|
+
* a check in runClose's forbidden zone) — it carries the runClose verdict name
|
|
4
|
+
* ('failed'|'timed-out'|'killed'|'crashed') so the micro-loop escalates through
|
|
5
|
+
* the same CLOSE_FAULTS taxonomy instead of feeding a fake gap to the worker.
|
|
6
|
+
* A plain pass:false with no fault is honest worker feedback. */
|
|
7
|
+
/**
|
|
8
|
+
* Snapshot the file state under a scope prefix — the "before" side of
|
|
9
|
+
* `tree-changed`. Content hashes, not mtimes: only real bytes count as
|
|
10
|
+
* outcome. A scope directory that does not exist yet snapshots empty (the
|
|
11
|
+
* step creating it IS the change). Never throws on a missing dir.
|
|
12
|
+
* @param {string} dir absolute workdir (the run directory)
|
|
13
|
+
* @param {string} scope a validated scope glob (e.g. `tests/**`)
|
|
14
|
+
* @returns {Promise<Map<string, string>>} relative path → sha256
|
|
15
|
+
*/
|
|
16
|
+
export function snapshotScope(dir: string, scope: string): Promise<Map<string, string>>;
|
|
17
|
+
/**
|
|
18
|
+
* Evaluate a step's exits — AND-only (decision 8): the step passes iff EVERY
|
|
19
|
+
* listed exit passes. All exits are evaluated (never short-circuit): the gap
|
|
20
|
+
* must name every wall, not the first one hit.
|
|
21
|
+
* @param {Array<Record<string, any>>} exits validated exit items
|
|
22
|
+
* @param {{ dir: string, snapshot?: Map<string, string>, runCheck?: (name: string) => Promise<{pass: boolean, gap?: string}> }} ctx
|
|
23
|
+
* `dir`: the run directory; `snapshot`: the pre-step `snapshotScope` result
|
|
24
|
+
* (required by tree-changed — absent fails closed); `runCheck`: the runner's
|
|
25
|
+
* runClose-backed seam (required by check-passes — absent fails closed).
|
|
26
|
+
* @returns {Promise<{ pass: boolean, results: ExitResult[] }>}
|
|
27
|
+
*/
|
|
28
|
+
export function evalExits(exits: Array<Record<string, any>>, { dir, snapshot, runCheck }: {
|
|
29
|
+
dir: string;
|
|
30
|
+
snapshot?: Map<string, string>;
|
|
31
|
+
runCheck?: (name: string) => Promise<{
|
|
32
|
+
pass: boolean;
|
|
33
|
+
gap?: string;
|
|
34
|
+
}>;
|
|
35
|
+
}): Promise<{
|
|
36
|
+
pass: boolean;
|
|
37
|
+
results: ExitResult[];
|
|
38
|
+
}>;
|
|
39
|
+
/**
|
|
40
|
+
* `fault` present means the INSTRUMENT could not judge (unwired snapshot/seam,
|
|
41
|
+
* a check in runClose's forbidden zone) — it carries the runClose verdict name
|
|
42
|
+
* ('failed'|'timed-out'|'killed'|'crashed') so the micro-loop escalates through
|
|
43
|
+
* the same CLOSE_FAULTS taxonomy instead of feeding a fake gap to the worker.
|
|
44
|
+
* A plain pass:false with no fault is honest worker feedback.
|
|
45
|
+
*/
|
|
46
|
+
export type ExitResult = {
|
|
47
|
+
type: string;
|
|
48
|
+
pass: boolean;
|
|
49
|
+
detail?: string;
|
|
50
|
+
fault?: string;
|
|
51
|
+
};
|
package/types/index.d.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
export { makeSpine } from "./spine.js";
|
|
2
|
+
export { runPlan } from "./planrun.js";
|
|
2
3
|
export { runJob } from "./run.js";
|
|
3
4
|
export { ralph, runClose, CLOSE_FAULTS } from "./ralph.js";
|
|
4
5
|
export { validateConfig, diffPaths, globToPrefix, scanSecrets, LOOP_SHAPES, SLOTS, VERBS } from "./validate.js";
|
|
5
|
-
export { validateJob, jobSpecHash, checkApproval, CLOSE_TYPES, CLASSES, CLASS_BY_CLOSE, GOLD_COMPARE, CADENCE_UNITS, PROVIDERS, CONDITION_KEYS, STEP_MODES, TOOL_MENU, LOCKED_TOOLS } from "./job.js";
|
|
6
|
+
export { validateJob, jobSpecHash, checkApproval, CLOSE_TYPES, CLASSES, CLASS_BY_CLOSE, GOLD_COMPARE, CADENCE_UNITS, PROVIDERS, CONDITION_KEYS, STEP_MODES, TOOL_MENU, LOCKED_TOOLS, VERDICT_TYPES, LOCKED_VERDICTS } from "./job.js";
|
|
7
|
+
export { validatePlan, EXIT_TYPES, MAX_EXITS_PER_STEP, MAX_PLAN_STEPS, WRITE_VERBS } from "./plan.js";
|
|
8
|
+
export { snapshotScope, evalExits } from "./exits.js";
|
|
6
9
|
export { interpret, STALL_REDS } from "./interpret.js";
|
|
7
10
|
export { extractRules, MAX_RULES, MAX_RULE_CHARS } from "./extract.js";
|
|
8
11
|
export { classifyIncidents, foldLedger, ledgerDeltas, updateLedger, LEDGER_CLASSES } from "./ledger.js";
|
package/types/interpret.d.ts
CHANGED
|
@@ -1,3 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The retrieval pair (F19). `shell_read` cannot seek — it starts at byte zero — so a
|
|
3
|
+
* pointer at a symbol was inert and the worker paged whole files to reach one function.
|
|
4
|
+
* `ctx_recall` hands back a POINTER (no body: a search index returns pointers, and bodies
|
|
5
|
+
* on every hit rebuild the bloat — measured, a 5-hit recall dumps ~15k tokens unbidden);
|
|
6
|
+
* `ctx_get` trades that pointer for ONE chunk. A chunk starts at its doc-comment, so the
|
|
7
|
+
* body arrives WITH the docstring that says what it was supposed to do.
|
|
8
|
+
* @param {any} lc the run's LiteCtx (rooted at workdir)
|
|
9
|
+
* @param {string} workdir
|
|
10
|
+
* @param {(type: string, data: object) => void} emit the spine — a retrieval verb whose
|
|
11
|
+
* RESULT is invisible cannot be judged: a `ctx_get` that silently reds (stale pointer,
|
|
12
|
+
* bad range) looks exactly like one that worked, and the worker's fallback to a
|
|
13
|
+
* whole-file read looks like a free choice instead of a forced one.
|
|
14
|
+
*/
|
|
15
|
+
export function createCtxTools(lc: any, workdir: string, emit: (type: string, data: object) => void): ({
|
|
16
|
+
name: string;
|
|
17
|
+
description: string;
|
|
18
|
+
parameters: {
|
|
19
|
+
type: string;
|
|
20
|
+
properties: {
|
|
21
|
+
query: {
|
|
22
|
+
type: string;
|
|
23
|
+
description: string;
|
|
24
|
+
};
|
|
25
|
+
n: {
|
|
26
|
+
type: string;
|
|
27
|
+
description: string;
|
|
28
|
+
};
|
|
29
|
+
path?: undefined;
|
|
30
|
+
startLine?: undefined;
|
|
31
|
+
endLine?: undefined;
|
|
32
|
+
};
|
|
33
|
+
required: string[];
|
|
34
|
+
};
|
|
35
|
+
execute: ({ query, n }: {
|
|
36
|
+
query: string;
|
|
37
|
+
n?: number;
|
|
38
|
+
}) => Promise<any>;
|
|
39
|
+
} | {
|
|
40
|
+
name: string;
|
|
41
|
+
description: string;
|
|
42
|
+
parameters: {
|
|
43
|
+
type: string;
|
|
44
|
+
properties: {
|
|
45
|
+
path: {
|
|
46
|
+
type: string;
|
|
47
|
+
description: string;
|
|
48
|
+
};
|
|
49
|
+
startLine: {
|
|
50
|
+
type: string;
|
|
51
|
+
description: string;
|
|
52
|
+
};
|
|
53
|
+
endLine: {
|
|
54
|
+
type: string;
|
|
55
|
+
description: string;
|
|
56
|
+
};
|
|
57
|
+
query?: undefined;
|
|
58
|
+
n?: undefined;
|
|
59
|
+
};
|
|
60
|
+
required: string[];
|
|
61
|
+
};
|
|
62
|
+
execute: ({ path: p, startLine, endLine }: {
|
|
63
|
+
path: string;
|
|
64
|
+
startLine: number;
|
|
65
|
+
endLine: number;
|
|
66
|
+
}) => Promise<any>;
|
|
67
|
+
})[];
|
|
1
68
|
/**
|
|
2
69
|
* Execute a workflow config against one task under the dumb shell.
|
|
3
70
|
*
|
|
@@ -42,11 +109,18 @@
|
|
|
42
109
|
* @param {'text'|'tools'} [opts.mode] middle mode (2b): 'text' (default) writes the ONE
|
|
43
110
|
* target from the response artifact; 'tools' gives the worker Gate-governed file
|
|
44
111
|
* tools — SPEC-side territory (the step declares it; the config cannot express it)
|
|
112
|
+
* @param {boolean} [opts.layerRoot=false] Layer R (the within-run ratchet, src/root.js):
|
|
113
|
+
* shell-assembled fixation detection over the run's own books, injecting an
|
|
114
|
+
* escalating summary→verbatim note when consecutive attempts rewrite the same
|
|
115
|
+
* files without moving the reds. Inert when the worker is not stuck (RSI §3.3).
|
|
116
|
+
* Defaults OFF (decided 2026-07-21): fixation is extinct on every current job
|
|
117
|
+
* (F41), so ON is unproven — the field read defers to Layer 2. `true` is the
|
|
118
|
+
* ON/experimental arm; never a worker-visible knob
|
|
45
119
|
* @param {string[]} [opts.tools] the spec's tool grant (subset of read|grep|write,
|
|
46
120
|
* job-v1 validated); defaults to the full menu in tool mode
|
|
47
121
|
* @returns {Promise<'green'|'escalated'|'config-red'>}
|
|
48
122
|
*/
|
|
49
|
-
export function interpret(configRaw: object | string, { task, target, close, workdir, capRuns, emit, provider, shellCapUsd, jobWriteScope, revisor, closeTimeoutMs, mode, tools, closeState, closeExpect, closeJudged, closeGapKeep }: {
|
|
123
|
+
export function interpret(configRaw: object | string, { task, target, close, workdir, capRuns, emit, provider, shellCapUsd, jobWriteScope, revisor, closeTimeoutMs, mode, tools, closeState, closeExpect, closeJudged, closeGapKeep, layerRoot }: {
|
|
50
124
|
task: string;
|
|
51
125
|
target?: string | undefined;
|
|
52
126
|
close: string[];
|
|
@@ -75,10 +149,42 @@ export function interpret(configRaw: object | string, { task, target, close, wor
|
|
|
75
149
|
}>) | undefined;
|
|
76
150
|
closeState?: string | undefined;
|
|
77
151
|
mode?: "text" | "tools" | undefined;
|
|
152
|
+
layerRoot?: boolean | undefined;
|
|
78
153
|
tools?: string[] | undefined;
|
|
79
154
|
}): Promise<"green" | "escalated" | "config-red">;
|
|
80
155
|
/** @typedef {Error & {category?: string, lib?: string}} CategorizedError the failure map's carrier: ralph relays by `category`, and by `lib` when the thrower knows which library owns the failure (the ledger prefers it over prose) */
|
|
81
156
|
export const STALL_REDS: 2;
|
|
157
|
+
export const PERSONA_TOOLS: string;
|
|
158
|
+
export const RETRIEVAL_STRATEGY: string;
|
|
159
|
+
export const EDIT_STRATEGY: string;
|
|
160
|
+
export const TOOL_BY_VERB: Readonly<{
|
|
161
|
+
read: "shell_read";
|
|
162
|
+
grep: "shell_grep";
|
|
163
|
+
write: "shell_write";
|
|
164
|
+
edit: "shell_edit";
|
|
165
|
+
recall: "ctx_recall";
|
|
166
|
+
get: "ctx_get";
|
|
167
|
+
}>;
|
|
168
|
+
export const CTX_TOOLS: readonly string[];
|
|
169
|
+
export function toolAction(name: string, args: any, workdir?: string): {
|
|
170
|
+
type: string;
|
|
171
|
+
path: string;
|
|
172
|
+
args: {
|
|
173
|
+
bytes: number;
|
|
174
|
+
tool?: undefined;
|
|
175
|
+
};
|
|
176
|
+
} | {
|
|
177
|
+
type: string;
|
|
178
|
+
path: string;
|
|
179
|
+
args: {
|
|
180
|
+
tool: string;
|
|
181
|
+
bytes?: undefined;
|
|
182
|
+
};
|
|
183
|
+
} | {
|
|
184
|
+
type: string;
|
|
185
|
+
args: any;
|
|
186
|
+
path?: undefined;
|
|
187
|
+
};
|
|
82
188
|
/**
|
|
83
189
|
* the failure map's carrier: ralph relays by `category`, and by `lib` when the thrower knows which library owns the failure (the ledger prefers it over prose)
|
|
84
190
|
*/
|
package/types/job.d.ts
CHANGED
|
@@ -71,7 +71,19 @@ export const TOOL_MENU: readonly string[];
|
|
|
71
71
|
* menu. Requesting one is a DISTINCT `request-red` (module 4) — the ledger
|
|
72
72
|
* counts admission demand, and a generic invalid-value would bury it as a typo. */
|
|
73
73
|
export const LOCKED_TOOLS: readonly string[];
|
|
74
|
+
/** Layer 2 (design record 2026-07-21, decision 5): the verdict-type radio of
|
|
75
|
+
* the four-field plan shape. v1 ADMITS only `green`; soft-green/hitl are
|
|
76
|
+
* declared-but-locked — declaring one is a `request-red` (the tool-menu
|
|
77
|
+
* pattern: disclosure ≠ admission), so the ledger counts the demand and the
|
|
78
|
+
* non-code rung later fills a declared slot instead of reshaping the schema. */
|
|
79
|
+
export const VERDICT_TYPES: readonly string[];
|
|
80
|
+
export const LOCKED_VERDICTS: readonly string[];
|
|
74
81
|
export const CADENCE_UNITS: readonly string[];
|
|
82
|
+
/** SP-2: API-first; local deferred (PRD §5/§8). `clipipe-subscription` (F42,
|
|
83
|
+
* bare-agent 0.32.0 tool mode): the CLI as a bare turn-provider on a Claude
|
|
84
|
+
* subscription — its costUsd axis is NOTIONAL (API-equivalent value, not
|
|
85
|
+
* billed), so it is a DISTINCT condition: rows never pool with anthropic-api
|
|
86
|
+
* rows on the cost axis, and budgets sized for one do not transfer. */
|
|
75
87
|
export const PROVIDERS: readonly string[];
|
|
76
88
|
/** V3 environment label: declared keys only — every field is a lineage-key
|
|
77
89
|
* candidate at N3. `provider` is part of the key by definition (top-level,
|
package/types/plan.d.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/** @typedef {{code: string, path: string, detail?: string, verb?: string}} Red */
|
|
2
|
+
/**
|
|
3
|
+
* Validate an agent-authored plan (`schema: "plan-v1"`) against the SIGNED
|
|
4
|
+
* job spec. Never throws on JSON text or plain parsed data; every failure is
|
|
5
|
+
* a named red. Returns the parsed plan on ok (single parse), null on any red.
|
|
6
|
+
* @param {object|string} input parsed plan, or raw JSON text (parse failures are a red)
|
|
7
|
+
* @param {{ job?: any, maxStepRounds?: number }} [opts] `job`: the
|
|
8
|
+
* validateJob-GREEN four-field spec (the ceiling, the fence, and the checks
|
|
9
|
+
* menu all come from it — a missing or non-plan-shape job fails CLOSED);
|
|
10
|
+
* `maxStepRounds`: the shell's per-step rounds ceiling (interpret's
|
|
11
|
+
* tool-mode TURNS_PER_ATTEMPT) — a step may tighten it, never exceed it.
|
|
12
|
+
* @returns {{ ok: boolean, reds: Red[], plan: object|null }}
|
|
13
|
+
*/
|
|
14
|
+
export function validatePlan(input: object | string, { job, maxStepRounds }?: {
|
|
15
|
+
job?: any;
|
|
16
|
+
maxStepRounds?: number;
|
|
17
|
+
}): {
|
|
18
|
+
ok: boolean;
|
|
19
|
+
reds: Red[];
|
|
20
|
+
plan: object | null;
|
|
21
|
+
};
|
|
22
|
+
/** the closed exit menu (PRD v1.12 §3 + decision 1's `check-passes`): the
|
|
23
|
+
* shell evaluates every form with its own fixed code, never a command — the
|
|
24
|
+
* same both-directions inexpressibility guard as the two-validator split */
|
|
25
|
+
export const EXIT_TYPES: readonly string[];
|
|
26
|
+
/** decision 8: AND-only conjunction, bounded — no OR (a weak arm would
|
|
27
|
+
* launder the exit), no NOT */
|
|
28
|
+
export const MAX_EXITS_PER_STEP: 2;
|
|
29
|
+
/** plan size bound — per-step rounds bound the spend, but an unbounded step
|
|
30
|
+
* COUNT is still an unbounded claim on the wallet; same 8 as loop.maxIterations */
|
|
31
|
+
export const MAX_PLAN_STEPS: 8;
|
|
32
|
+
/** the write-class verbs (BA-13: `edit` is judged by the same fence as
|
|
33
|
+
* `write`) — a step granting one is a WRITE step for the F17 pairing rule
|
|
34
|
+
* and the v1.18 target requirement */
|
|
35
|
+
export const WRITE_VERBS: readonly string[];
|
|
36
|
+
export type Red = {
|
|
37
|
+
code: string;
|
|
38
|
+
path: string;
|
|
39
|
+
detail?: string;
|
|
40
|
+
verb?: string;
|
|
41
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Execute a validateJob-GREEN plan-shape job (goal/verdictType/close/checks[]).
|
|
3
|
+
* Called by runJob after the approval gate, validation, and the smoke — this
|
|
4
|
+
* function owns the plan flow only; the caller owns the job-end record and the
|
|
5
|
+
* one ledger (every provider round is emitted here as `worker-round`, which
|
|
6
|
+
* the caller's metered emit accounts — F12).
|
|
7
|
+
*
|
|
8
|
+
* @param {any} job the validated plan-shape spec
|
|
9
|
+
* @param {object} opts
|
|
10
|
+
* @param {string} opts.workdir the run directory (the fence's root)
|
|
11
|
+
* @param {any} opts.provider shell-owned LLM binding (the Loop path — `anthropic-api`)
|
|
12
|
+
* @param {(o: {policy: Function, onTurn?: Function, maxTurns: number, hasTools: boolean}) => any} [opts.nativeProvider]
|
|
13
|
+
* NATIVE clipipe factory (BA-16): required when `job.provider === 'clipipe-subscription'`.
|
|
14
|
+
* The runner builds a FRESH provider per worker and picks the mode by `hasTools`:
|
|
15
|
+
* `true` → native tool mode (`toolProtocol:'claude-mcp'`, wire `policy`+`onTurn`+`maxTurns`);
|
|
16
|
+
* `false` → the drafter has no tools, so a native session would report NO cost — return a
|
|
17
|
+
* metered claude-json TEXT provider (`--output-format json`, `parse:'claude-json'`) instead,
|
|
18
|
+
* so its spend is never invisible. The Loop path (`anthropic-api`) never touches this.
|
|
19
|
+
* @param {(type: string, data?: object) => object} opts.emit spine emitter (the caller's METERED emit)
|
|
20
|
+
* @param {() => number} opts.remainingUsd the one wallet: what is left of the signed budget right now
|
|
21
|
+
* @param {() => boolean} [opts.isUnpriced] has any round come back with a null cost? (F6) — the
|
|
22
|
+
* plan flow bails IN-FLIGHT on the first unpriced round instead of burning the whole plan
|
|
23
|
+
* @param {number} [opts.capRuns] shell-owned per-step attempt cap
|
|
24
|
+
* @param {number} [opts.closeTimeoutMs] close/check wall-clock cap (shell territory)
|
|
25
|
+
* @param {number} [opts.maxStepRounds] the shell's per-step rounds ceiling (validatePlan's bound)
|
|
26
|
+
* @returns {Promise<string>} 'green' | 'already-green' | 'escalated' | 'plan-red' |
|
|
27
|
+
* 'check-red' | 'close-red' | 'close-unsupported' | 'pricing-red' | 'cap-halt' |
|
|
28
|
+
* 'provider-red' | 'interpreter-red' | `step-red:<id>`
|
|
29
|
+
*/
|
|
30
|
+
export function runPlan(job: any, { workdir, provider, nativeProvider, emit, remainingUsd, isUnpriced, capRuns, closeTimeoutMs, maxStepRounds }: {
|
|
31
|
+
workdir: string;
|
|
32
|
+
provider: any;
|
|
33
|
+
nativeProvider?: ((o: {
|
|
34
|
+
policy: Function;
|
|
35
|
+
onTurn?: Function;
|
|
36
|
+
maxTurns: number;
|
|
37
|
+
hasTools: boolean;
|
|
38
|
+
}) => any) | undefined;
|
|
39
|
+
emit: (type: string, data?: object) => object;
|
|
40
|
+
remainingUsd: () => number;
|
|
41
|
+
isUnpriced?: (() => boolean) | undefined;
|
|
42
|
+
capRuns?: number | undefined;
|
|
43
|
+
closeTimeoutMs?: number | undefined;
|
|
44
|
+
maxStepRounds?: number | undefined;
|
|
45
|
+
}): Promise<string>;
|
|
46
|
+
export type CategorizedError = Error & {
|
|
47
|
+
category?: string;
|
|
48
|
+
lib?: string;
|
|
49
|
+
};
|
package/types/ralph.d.ts
CHANGED
|
@@ -98,7 +98,15 @@ export function runClose(close: string[], redact?: (s: string) => string, { time
|
|
|
98
98
|
*
|
|
99
99
|
* @param {object} opts
|
|
100
100
|
* @param {(iteration: number, gap?: string) => void|Promise<void>} opts.middle the emergent middle; never sees close/cap
|
|
101
|
-
* @param {string[]} opts.close argv whose exit code is truth
|
|
101
|
+
* @param {string[]} [opts.close] argv whose exit code is truth (required unless `judge` is injected)
|
|
102
|
+
* @param {() => Promise<{verdict: string, gap?: string, detail?: string}>|{verdict: string, gap?: string, detail?: string}} [opts.judge]
|
|
103
|
+
* Layer 2 (PRD v1.12 §4): the judge generalized to a SHELL-owned seam. When
|
|
104
|
+
* present it replaces runClose for this loop — a plan STEP's micro-loop is
|
|
105
|
+
* judged by the exit evaluator (declarative form checks), not a command. It
|
|
106
|
+
* returns the same verdict vocabulary runClose renders, so the forbidden
|
|
107
|
+
* zone, worker-crash routing (F32), and the cap taxonomy apply unchanged.
|
|
108
|
+
* Injected by run.js only; inexpressible in any config or plan — the agent
|
|
109
|
+
* never authors its judge any more than its close.
|
|
102
110
|
* @param {number} opts.capRuns budget: max middle runs
|
|
103
111
|
* @param {(type: string, data?: object) => object} opts.emit a spine emitter
|
|
104
112
|
* @param {(s: string) => string} [opts.redact] source scrubber for close output
|
|
@@ -118,9 +126,18 @@ export function runClose(close: string[], redact?: (s: string) => string, { time
|
|
|
118
126
|
* with zero writes, a crash stays what it always was: an instrument stop.
|
|
119
127
|
* @returns {Promise<'green'|'escalated'>}
|
|
120
128
|
*/
|
|
121
|
-
export function ralph({ middle, close, capRuns, emit, redact, closeTimeoutMs, cwd, expect, judged, gapKeep, workerWrites }: {
|
|
129
|
+
export function ralph({ middle, close, judge, capRuns, emit, redact, closeTimeoutMs, cwd, expect, judged, gapKeep, workerWrites }: {
|
|
122
130
|
middle: (iteration: number, gap?: string) => void | Promise<void>;
|
|
123
|
-
close
|
|
131
|
+
close?: string[] | undefined;
|
|
132
|
+
judge?: (() => Promise<{
|
|
133
|
+
verdict: string;
|
|
134
|
+
gap?: string;
|
|
135
|
+
detail?: string;
|
|
136
|
+
}> | {
|
|
137
|
+
verdict: string;
|
|
138
|
+
gap?: string;
|
|
139
|
+
detail?: string;
|
|
140
|
+
}) | undefined;
|
|
124
141
|
capRuns: number;
|
|
125
142
|
emit: (type: string, data?: object) => object;
|
|
126
143
|
redact?: ((s: string) => string) | undefined;
|
|
@@ -134,6 +151,7 @@ export function ralph({ middle, close, capRuns, emit, redact, closeTimeoutMs, cw
|
|
|
134
151
|
gapKeep?: string | undefined;
|
|
135
152
|
workerWrites?: (() => string[]) | undefined;
|
|
136
153
|
}): Promise<"green" | "escalated">;
|
|
154
|
+
export const GAP_KEEP_TRIM_MARKER: "more elided by the";
|
|
137
155
|
/**
|
|
138
156
|
* The forbidden zone, named (PRD v1.11). Each row is an outcome in which the
|
|
139
157
|
* close rendered NO JUDGMENT — so it is not a verdict, it never feeds a gap
|
package/types/root.d.ts
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The root collector. One per run; state dies with the run.
|
|
3
|
+
*
|
|
4
|
+
* Call order (wired by the interpreter's middle):
|
|
5
|
+
* observe({iteration: 1, writes}) → null (nothing to compare)
|
|
6
|
+
* stageWrite(path, content) → stage, before the tool runs
|
|
7
|
+
* settleWrite(landed) | discardWrite() → the outcome, after it ran
|
|
8
|
+
* noteWrite(path, content) → stage+settle, text mode (which
|
|
9
|
+
* writes before it reports)
|
|
10
|
+
* observe({iteration: N, gap, writes}) → null | {stage, note, event}
|
|
11
|
+
* `writes` is the CUMULATIVE allow-decision write/edit path list from the gate
|
|
12
|
+
* audit (the F32 instrument); the per-attempt set is the delta between calls —
|
|
13
|
+
* so a gate-DENIED write is never counted (the tool never ran at all).
|
|
14
|
+
*
|
|
15
|
+
* Finding 7 — the two axes, deliberately NOT merged. An allow-decision records
|
|
16
|
+
* what the worker REACHED FOR, which is the right instrument for "is it
|
|
17
|
+
* repeating itself"; whether those bytes reached the file is a different
|
|
18
|
+
* question, and only the note (a claim about file contents) needs it. Collapsing
|
|
19
|
+
* them either way is a bug: settling the note on the allow makes it lie, and
|
|
20
|
+
* running the detector off the file makes it blind to a worker re-firing an
|
|
21
|
+
* identical edit whose anchor never matches — measured null on every attempt.
|
|
22
|
+
*
|
|
23
|
+
* @param {{gapKeep?: string, redact?: (s: string) => string, writesInformative?: boolean}} [opts]
|
|
24
|
+
* the signed spec's kept-failures pattern (the red-set source; without it the
|
|
25
|
+
* detector degrades to write-overlap alone and the event says so), the shell's
|
|
26
|
+
* secret scrubber (verbatim content is scrubbed before it rides a prompt), and
|
|
27
|
+
* `writesInformative` (Finding 3, default true): whether the per-attempt
|
|
28
|
+
* write-set carries information. In TOOL mode the worker CHOOSES which files to
|
|
29
|
+
* write, so re-writing the same file is a real repetition signal; in TEXT mode
|
|
30
|
+
* the one target is rewritten every attempt by construction, so write-overlap
|
|
31
|
+
* is constant-true and carries none — with no red-set to compare, the detector
|
|
32
|
+
* cannot tell repetition from progress and must stay inert (it would otherwise
|
|
33
|
+
* steer a progressing worker off course, violating "inert when not stuck").
|
|
34
|
+
*/
|
|
35
|
+
export function createRoot({ gapKeep, redact, writesInformative }?: {
|
|
36
|
+
gapKeep?: string;
|
|
37
|
+
redact?: (s: string) => string;
|
|
38
|
+
writesInformative?: boolean;
|
|
39
|
+
}): {
|
|
40
|
+
stageWrite: (path: string, content: string) => void;
|
|
41
|
+
/**
|
|
42
|
+
* The staged write RAN — record what it actually did (Finding 7). Retained
|
|
43
|
+
* either way: an attempted-but-unapplied edit is still the worker reaching
|
|
44
|
+
* for the same file, which is what the detector measures, and dropping it
|
|
45
|
+
* here would blind the ratchet to the purest fixation there is (a worker
|
|
46
|
+
* re-firing an identical edit whose anchor never matches — measured null on
|
|
47
|
+
* every attempt under a tree-diff detector, 2026-07-20). Only the NOTE's
|
|
48
|
+
* wording turns on `landed`. Last write per path wins (the tree's final
|
|
49
|
+
* state for that attempt is what failed the close).
|
|
50
|
+
* @param {boolean} landed whether the content is now in the file
|
|
51
|
+
*/
|
|
52
|
+
settleWrite(landed: boolean): void;
|
|
53
|
+
/**
|
|
54
|
+
* The staged write was DENIED or HALTED: it is not in the file, so it must
|
|
55
|
+
* never be surfaced as "your own change that landed". Drops the stage and
|
|
56
|
+
* leaves whatever DID land for that path untouched.
|
|
57
|
+
*/
|
|
58
|
+
discardWrite(): void;
|
|
59
|
+
/**
|
|
60
|
+
* Stage and commit in one call — the post-allow write site (text mode
|
|
61
|
+
* gate-checks and writes before it calls this, so the verdict is already in).
|
|
62
|
+
* @param {string} path @param {string} content
|
|
63
|
+
*/
|
|
64
|
+
noteWrite(path: string, content: string): void;
|
|
65
|
+
/**
|
|
66
|
+
* Called at the START of attempt `iteration`, with the PREVIOUS attempt's
|
|
67
|
+
* gap. Finalizes that attempt from the books, runs the detector, and
|
|
68
|
+
* returns the injection for the attempt about to run — or null (inert).
|
|
69
|
+
* @param {{iteration: number, gap?: string, writes: string[]}} o
|
|
70
|
+
* @returns {null | {stage: 'summary'|'verbatim', note: string, event: object}}
|
|
71
|
+
*/
|
|
72
|
+
observe({ iteration, gap, writes }: {
|
|
73
|
+
iteration: number;
|
|
74
|
+
gap?: string;
|
|
75
|
+
writes: string[];
|
|
76
|
+
}): null | {
|
|
77
|
+
stage: "summary" | "verbatim";
|
|
78
|
+
note: string;
|
|
79
|
+
event: object;
|
|
80
|
+
};
|
|
81
|
+
};
|
|
82
|
+
export function normalizeRedLine(line: string): string;
|
|
83
|
+
/**
|
|
84
|
+
* A teed write: the content the worker emitted, plus whether it actually reached
|
|
85
|
+
* the file. `landed` is the OUTCOME axis (Finding 7) — the gate's allow is an
|
|
86
|
+
* intent record written before the tool runs, and a shell_edit whose anchor
|
|
87
|
+
* misses returns a refusal RESULT with the file untouched (bare-agent
|
|
88
|
+
* tools/shell.js:190). A note that says "these are your changes" is a claim
|
|
89
|
+
* about the file's contents and must be true, so it reads this flag, never the
|
|
90
|
+
* audit. The DETECTOR deliberately does not: see `observe`.
|
|
91
|
+
*/
|
|
92
|
+
export type TeeEntry = {
|
|
93
|
+
content: string;
|
|
94
|
+
trimmed: boolean;
|
|
95
|
+
landed: boolean;
|
|
96
|
+
};
|
package/types/run.d.ts
CHANGED
|
@@ -17,32 +17,55 @@
|
|
|
17
17
|
* boundary — the Gate still caps within a step, but an unpriced result cannot
|
|
18
18
|
* be summed, so the run stops rather than counting it $0 (F6).
|
|
19
19
|
*
|
|
20
|
+
* EVERY terminal `job-end` states the money: `spentUsd` (the sum of PRICED
|
|
21
|
+
* rounds only — never an estimate; a real 0 on the pre-token reds) plus
|
|
22
|
+
* `spendComplete`, false when any round came back unpriced and the figure is
|
|
23
|
+
* therefore a FLOOR, not the total. Both are always present, so a consumer
|
|
24
|
+
* never branches on field presence nor launders a blank into $0.
|
|
25
|
+
*
|
|
20
26
|
* @param {object|string} rawSpec the job spec (job-v1), text or parsed
|
|
21
27
|
* @param {object} opts
|
|
22
28
|
* @param {unknown} opts.approvals `{ specHash, signer, ts }` records (from OUTSIDE the spec)
|
|
23
29
|
* @param {string} opts.workdir the run directory (the fence's root)
|
|
24
30
|
* @param {string} [opts.target] the step artifact path (text-mode steps; unused by tool-mode steps)
|
|
25
|
-
* @param {any} opts.provider shell-owned LLM binding (never the config's)
|
|
31
|
+
* @param {any} opts.provider shell-owned LLM binding (never the config's; the Loop path)
|
|
32
|
+
* @param {(o: {policy: Function, onTurn?: Function, maxTurns: number, hasTools: boolean}) => any} [opts.nativeProvider]
|
|
33
|
+
* native clipipe provider factory (BA-16), required for a `clipipe-subscription` plan-shape job —
|
|
34
|
+
* threaded to runPlan, which builds one per worker (native tool mode when `hasTools`, else a
|
|
35
|
+
* metered claude-json text provider for the toolless drafter); ignored on every Loop-driven provider
|
|
26
36
|
* @param {(type: string, data?: object) => object} opts.emit spine emitter
|
|
27
37
|
* @param {number} [opts.capRuns] shell-owned per-step iteration cap
|
|
28
38
|
* @param {number} [opts.shellCapUsd] the shell's hard USD ceiling
|
|
29
39
|
* @param {number} [opts.closeTimeoutMs] close wall-clock cap (shell territory)
|
|
30
40
|
* @param {ExecCmd} [opts.execCmd] process runner for the hitl PR mechanics
|
|
31
|
-
*
|
|
41
|
+
* @param {boolean} [opts.layerRoot=false] Layer R (within-run ratchet) — shell
|
|
42
|
+
* territory, threaded to every predicate step's interpreter. Defaults OFF
|
|
43
|
+
* (decided 2026-07-21): fixation is extinct on every current job (F41), so
|
|
44
|
+
* ON has never won its own A/B — the acceptance read defers to Layer 2 (or
|
|
45
|
+
* a manufactured-fixation probe). `true` is the ON/experimental arm; the
|
|
46
|
+
* default is the OFF arm. (Shell-owned seam, same doctrine as the provider
|
|
47
|
+
* binding.)
|
|
32
48
|
* @returns {Promise<string>} outcome: 'green' | 'escalated' | 'unapproved-spec' |
|
|
33
49
|
* 'job-red' | 'smoke-red' | 'config-red' | 'pricing-red' | 'provider-red' |
|
|
34
50
|
* 'cap-halt' | 'close-unsupported' | `step-red:<id>`
|
|
35
51
|
*/
|
|
36
|
-
export function runJob(rawSpec: object | string, { approvals, workdir, target, provider, emit, capRuns, shellCapUsd, closeTimeoutMs, execCmd }: {
|
|
52
|
+
export function runJob(rawSpec: object | string, { approvals, workdir, target, provider, nativeProvider, emit, capRuns, shellCapUsd, closeTimeoutMs, execCmd, layerRoot }: {
|
|
37
53
|
approvals: unknown;
|
|
38
54
|
workdir: string;
|
|
39
55
|
target?: string | undefined;
|
|
40
56
|
provider: any;
|
|
57
|
+
nativeProvider?: ((o: {
|
|
58
|
+
policy: Function;
|
|
59
|
+
onTurn?: Function;
|
|
60
|
+
maxTurns: number;
|
|
61
|
+
hasTools: boolean;
|
|
62
|
+
}) => any) | undefined;
|
|
41
63
|
emit: (type: string, data?: object) => object;
|
|
42
64
|
capRuns?: number | undefined;
|
|
43
65
|
shellCapUsd?: number | undefined;
|
|
44
66
|
closeTimeoutMs?: number | undefined;
|
|
45
67
|
execCmd?: ExecCmd | undefined;
|
|
68
|
+
layerRoot?: boolean | undefined;
|
|
46
69
|
}): Promise<string>;
|
|
47
70
|
export type Red = {
|
|
48
71
|
code: string;
|