bareloop 0.13.0 → 0.15.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 +189 -0
- package/README.md +1 -1
- package/bareloop.context.md +375 -5
- package/package.json +3 -3
- package/src/behaviour.js +150 -0
- package/src/bridges.js +1 -1
- package/src/calibrate.js +1 -1
- package/src/cardauthor.js +1 -1
- package/src/codeversion.js +155 -0
- package/src/index.js +17 -0
- package/src/job.js +1 -1
- package/src/judged.js +1 -1
- package/src/ledger.js +2 -2
- package/src/planrun.js +140 -3
- package/src/promptregisters.js +93 -0
- package/src/readshim.js +39 -7
- package/src/replay.js +1212 -0
- package/src/reuse.js +24 -2
- package/src/root.js +1 -1
- package/src/run.js +29 -2
- package/src/selection.js +1 -1
- package/src/transport.js +71 -0
- package/types/behaviour.d.ts +46 -0
- package/types/codeversion.d.ts +18 -0
- package/types/index.d.ts +3 -0
- package/types/ledger.d.ts +14 -0
- package/types/promptregisters.d.ts +31 -0
- package/types/readshim.d.ts +17 -6
- package/types/replay.d.ts +195 -0
- package/types/reuse.d.ts +22 -0
- package/types/transport.d.ts +19 -0
package/src/reuse.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Layer 3 — the D7 ENVELOPE and the REUSE RUNNER (design record
|
|
2
|
-
// docs/
|
|
2
|
+
// docs/product/2026-08-01-layer-3-reuse-design.md, D3/D5/D6/D7 + R1).
|
|
3
3
|
//
|
|
4
4
|
// hamr's sentence is the whole specification of this file, verbatim:
|
|
5
5
|
//
|
|
@@ -115,8 +115,30 @@ export const REUSE_GRADED_RED = Object.freeze(['escalated']);
|
|
|
115
115
|
* how they would come to disagree about which member is which: the first three are
|
|
116
116
|
* allowances the loop's remaining tries are meant to spend against, and the human
|
|
117
117
|
* ones are the stop no further try can change (see `hardStop`).
|
|
118
|
+
*
|
|
119
|
+
* `provider-red` folded in 2026-08-25 (PRD v1.80 TODO #4, F115 "Ruled: one transport
|
|
120
|
+
* retry"; hamr's ruling verbatim: *"joins resume anyways and you decide but you are
|
|
121
|
+
* given an honest readout and with 1 retry"*). It is a MACHINE halt, not a human one
|
|
122
|
+
* — it does NOT go into `HUMAN_CHECKPOINTS` — but it is a checkpoint by the same
|
|
123
|
+
* test every other member here passes: the close never got the say it was asked
|
|
124
|
+
* for (no verdict was rendered, so it is not a graded red — see `REUSE_GRADED_RED`
|
|
125
|
+
* above), and the scout+draft and every finished step it bought stand on disk
|
|
126
|
+
* exactly like a cap-halt or a wall-halt. The one transport retry
|
|
127
|
+
* (`src/planrun.js`'s `withTransportRetry`) already covers the common case; when
|
|
128
|
+
* that retry ALSO fails, throwing the run away because the transport hiccuped
|
|
129
|
+
* twice is throwing away real work for no reason a machine can judge. Deliberately
|
|
130
|
+
* NO cost/step threshold gates this: a threshold is a number picked from a small
|
|
131
|
+
* observed sample (F115 logged exactly 7 rows), which is arbiter territory
|
|
132
|
+
* reserved for hamr — the operator decides case by case, every time, off the
|
|
133
|
+
* honest readout `scripts/run-u.mjs` prints.
|
|
134
|
+
*
|
|
135
|
+
* This entry was built 2026-08-24 as a LOCAL widening in `scripts/run-u.mjs`
|
|
136
|
+
* (`RESUMABLE_HALTS = [...CHECKPOINT_OUTCOMES, 'provider-red']`) because this file
|
|
137
|
+
* sat outside that pass's scope — the fold here is the follow-up, so this repo
|
|
138
|
+
* does not end up with two lists that can drift, which is the exact failure this
|
|
139
|
+
* constant exists to end (see the paragraph above).
|
|
118
140
|
*/
|
|
119
|
-
export const CHECKPOINT_OUTCOMES = Object.freeze(['cap-halt', 'wall-halt', 'step-stalled', ...HUMAN_CHECKPOINTS]);
|
|
141
|
+
export const CHECKPOINT_OUTCOMES = Object.freeze(['cap-halt', 'wall-halt', 'step-stalled', 'provider-red', ...HUMAN_CHECKPOINTS]);
|
|
120
142
|
|
|
121
143
|
/**
|
|
122
144
|
* v1's definition of "the same KIND of recipe" — and it is the LOAD GATE's own: the
|
package/src/root.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Layer R — the root: the within-run ratchet (LAYERS.md item 2; design record
|
|
2
|
-
// docs/
|
|
2
|
+
// docs/product/2026-07-19-layer-r-design.md). One state that survives attempts
|
|
3
3
|
// inside ONE run so a never-green run stops repeating itself (F21). Role is
|
|
4
4
|
// CONTINUITY ONLY — F39 measured that hand-delivered state buys no conversion;
|
|
5
5
|
// the semantic converter is Layer 2's job, not this module's.
|
package/src/run.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// smoke, the ONE budget ledger (cap-not-estimate; unpriced is never free, F6),
|
|
4
4
|
// and the decision-ready stops. It composes the plan flow below it and
|
|
5
5
|
// interprets nothing itself.
|
|
6
|
-
// Design record: docs/
|
|
6
|
+
// Design record: docs/product/2026-07-12-n2-headless-loop-design.md.
|
|
7
7
|
//
|
|
8
8
|
// The legacy operator-authored `steps[]` path was DELETED 2026-07-26 (PRD
|
|
9
9
|
// v1.32): the drafting call, config-v1, the per-step interpret loops and the
|
|
@@ -17,6 +17,7 @@ import { validateJob, jobSpecHash, checkApproval } from './job.js';
|
|
|
17
17
|
import { runPlan } from './planrun.js';
|
|
18
18
|
import { SMOKE_STORE, resolveHumanRuling } from './kinds.js';
|
|
19
19
|
import { readShimArm } from './readshim.js';
|
|
20
|
+
import { codeVersion } from './codeversion.js';
|
|
20
21
|
|
|
21
22
|
/** @typedef {{code: string, path: string, detail?: string}} Red */
|
|
22
23
|
|
|
@@ -254,6 +255,15 @@ export async function runJob(rawSpec, { approvals, workdir, provider, nativeProv
|
|
|
254
255
|
// outcome — keying on the outcome would floor the exact stop too, which is the same
|
|
255
256
|
// dishonesty pointing the other way.
|
|
256
257
|
let cutMidCall = false;
|
|
258
|
+
// F115 — did any worker call in this run absorb a transport-class retry
|
|
259
|
+
// (hamr's ruling: one retry, transport-only, planrun.js `withTransportRetry`)?
|
|
260
|
+
// The FIRST attempt of a retried call may already have been billed and threw
|
|
261
|
+
// before returning any usage figure — the same unknown a self-healed stall
|
|
262
|
+
// and a mid-call wall cut carry, so the same one-way floor applies even when
|
|
263
|
+
// the retry recovered and the run finishes green. Read off the `transport-retry`
|
|
264
|
+
// spine record's presence alone, never its `recovered` field: a recovered
|
|
265
|
+
// retry still hides the first attempt's possible spend.
|
|
266
|
+
let transportRetried = false;
|
|
257
267
|
// The money on the terminal record, and whether the money is EXACT. `spentUsd`
|
|
258
268
|
// is the accumulated sum of PRICED rounds ONLY — never an estimate derived
|
|
259
269
|
// from tokens or averages (cap-not-estimate). When any round came back
|
|
@@ -263,7 +273,7 @@ export async function runJob(rawSpec, { approvals, workdir, provider, nativeProv
|
|
|
263
273
|
// branch on field presence.
|
|
264
274
|
// ONE spelling of the four causes, so the terminal and the in-flight money readout can
|
|
265
275
|
// never disagree about whether the same run's figure is exact.
|
|
266
|
-
const spendComplete = () => !unpriced && !stalled && !cutMidCall && !priorFloor;
|
|
276
|
+
const spendComplete = () => !unpriced && !stalled && !cutMidCall && !priorFloor && !transportRetried;
|
|
267
277
|
const spend = () => ({
|
|
268
278
|
spentUsd,
|
|
269
279
|
spendComplete: spendComplete(),
|
|
@@ -294,6 +304,19 @@ export async function runJob(rawSpec, { approvals, workdir, provider, nativeProv
|
|
|
294
304
|
emit('job-start', {
|
|
295
305
|
job: job.job, specHash: jobSpecHash(job), budgetUsd: job.budgetUsd,
|
|
296
306
|
shape: 'plan', goal: job.goal,
|
|
307
|
+
// F117: replay reads these; report-only. `verdictType` is a REQUIRED spec
|
|
308
|
+
// field (src/job.js:552 reds `missing-required` before this line is ever
|
|
309
|
+
// reached), so it is always a real validated string here, never a
|
|
310
|
+
// fabricated default. `model` rides along only when the shell-owned
|
|
311
|
+
// provider binding actually carries one (bare-agent's Loop reads
|
|
312
|
+
// `baseProvider.model` directly — src/planrun.js:76's own comment); a
|
|
313
|
+
// native/clipipe call path with no such provider omits the field rather
|
|
314
|
+
// than guessing.
|
|
315
|
+
verdictType: job.verdictType,
|
|
316
|
+
...(typeof provider?.model === 'string' ? { model: provider.model } : {}),
|
|
317
|
+
// F118: replay reads this; report-only. `dirty` is deliberately omitted —
|
|
318
|
+
// it can't be known without a shell, and null = unknown, never faked.
|
|
319
|
+
code: (() => { const { version, sha } = codeVersion(); return { version, sha }; })(),
|
|
297
320
|
// RESUME (v1.46 §3) — the DECLARED fold, the `try-start` precedent one level
|
|
298
321
|
// down. A reader reconstructing a chain of resumes must add only each attempt's
|
|
299
322
|
// OWN new rounds; without this record the second resume of a halted run would
|
|
@@ -364,6 +387,10 @@ export async function runJob(rawSpec, { approvals, workdir, provider, nativeProv
|
|
|
364
387
|
// One-way, and read by `spend()` — the run ends on this record either way, but the
|
|
365
388
|
// between-rounds stop must keep its exact figure rather than inheriting a floor.
|
|
366
389
|
if (type === 'wall-halt' && /** @type {any} */ (data)?.cutMidCall === true) cutMidCall = true;
|
|
390
|
+
// F115 — one-way, same lane as `stall`/`cutMidCall`: a transport retry's
|
|
391
|
+
// FIRST attempt threw before any usage figure came back, so the priced sum
|
|
392
|
+
// is a floor for the rest of the run regardless of `recovered`.
|
|
393
|
+
if (type === 'transport-retry') transportRetried = true;
|
|
367
394
|
return emit(type, data);
|
|
368
395
|
};
|
|
369
396
|
const pricingRed = () => {
|
package/src/selection.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Layer 3 — D3's DISPLAY half: the workflow LISTING, and the prompt that asks for a
|
|
2
|
-
// pick (design record docs/
|
|
2
|
+
// pick (design record docs/product/2026-08-01-layer-3-reuse-design.md, D3).
|
|
3
3
|
//
|
|
4
4
|
// hamr's ruling, verbatim: *"i think there should be a listing for workflows names that
|
|
5
5
|
// user can choose or auto-choose, or do we want llm to always decide (we still need to
|
package/src/transport.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// hamr's ruling, verbatim: "one retry for transport layer failure and
|
|
2
|
+
// reporting with the rest end of gate." (F115, docs/logs/FINDINGS.md.)
|
|
3
|
+
//
|
|
4
|
+
// A transport failure is fetch THROWING — the request never produced an HTTP
|
|
5
|
+
// response at all (a TLS fault mid-record, a reset socket, a DNS/network
|
|
6
|
+
// failure). That is a DIFFERENT unknown from an HTTP response the provider
|
|
7
|
+
// DID send (4xx/5xx/429): a response means the provider answered, so
|
|
8
|
+
// bare-agent's own retry policy for those stays exactly what it is — this
|
|
9
|
+
// module never widens that. Retrying only the no-response case is the whole
|
|
10
|
+
// point: a call that dies mid-read may already have been billed (retry can
|
|
11
|
+
// pay twice), so the retry budget stays as tight as hamr authorized it —
|
|
12
|
+
// exactly one extra attempt, never configurable upward from a job spec or
|
|
13
|
+
// argv (tighten-only doctrine).
|
|
14
|
+
//
|
|
15
|
+
// Scope: this module wires ONE seam — the worker Loop built in
|
|
16
|
+
// src/planrun.js's `newLoop` (~line 2239), which is the ONE factory behind
|
|
17
|
+
// every scout/drafter/step/fix-worker call. It deliberately does NOT reach
|
|
18
|
+
// the judge loops (src/judged.js), the native/CLI loop (src/planrun.js
|
|
19
|
+
// ~2089), or the authoring scout (src/authorscout.js) — none of those are in
|
|
20
|
+
// scope of hamr's ruling and each has its own transport story.
|
|
21
|
+
|
|
22
|
+
/** The fixed retry budget: ONE extra attempt after the first transport
|
|
23
|
+
* throw. Never read from a job spec or CLI flag — raising it is arbiter
|
|
24
|
+
* territory (only a code change hamr signs, never a run-time knob). */
|
|
25
|
+
export const TRANSPORT_RETRIES = 1;
|
|
26
|
+
|
|
27
|
+
/** bare-agent's `Retry.call` counts ATTEMPTS, not retries. */
|
|
28
|
+
export const TRANSPORT_MAX_ATTEMPTS = TRANSPORT_RETRIES + 1;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Is `err` a transport-class failure — the request never produced an HTTP
|
|
32
|
+
* response? True for: a TLS fault mid-record ("bad record mac" and kin), a
|
|
33
|
+
* reset/broken/timed-out socket (ECONNRESET, EPIPE, ETIMEDOUT, ECONNREFUSED,
|
|
34
|
+
* ENETUNREACH — by code on the error itself or on `err.cause`), and
|
|
35
|
+
* `fetch failed` wrapping a network `cause`. False for anything carrying an
|
|
36
|
+
* HTTP status (`err.status`/`err.statusCode`, 4xx/5xx/429) or an explicit
|
|
37
|
+
* `err.retryable === false` — those are provider RESPONSES, not transport
|
|
38
|
+
* casualties, and bare-agent's own policy governs them unchanged.
|
|
39
|
+
* @param {any} err
|
|
40
|
+
* @returns {boolean}
|
|
41
|
+
*/
|
|
42
|
+
export function isTransportFailure(err) {
|
|
43
|
+
if (err == null) return false;
|
|
44
|
+
// An explicit HTTP response is never a transport casualty, no matter what
|
|
45
|
+
// its message says — the provider answered.
|
|
46
|
+
const status = err.status ?? err.statusCode;
|
|
47
|
+
if (typeof status === 'number') return false;
|
|
48
|
+
if (err.retryable === false) return false;
|
|
49
|
+
|
|
50
|
+
const codeOf = (e) => (e && typeof e.code === 'string' ? e.code : null);
|
|
51
|
+
const TRANSPORT_CODES = new Set(['ECONNRESET', 'EPIPE', 'ETIMEDOUT', 'ECONNREFUSED', 'ENETUNREACH', 'EAI_AGAIN']);
|
|
52
|
+
const code = codeOf(err) ?? codeOf(err.cause);
|
|
53
|
+
if (code && TRANSPORT_CODES.has(code)) return true;
|
|
54
|
+
|
|
55
|
+
const message = String(err.message ?? err ?? '');
|
|
56
|
+
if (/bad record mac/i.test(message)) return true;
|
|
57
|
+
if (TRANSPORT_CODES.has(message.trim())) return true;
|
|
58
|
+
for (const c of TRANSPORT_CODES) if (message.includes(c)) return true;
|
|
59
|
+
if (/fetch failed/i.test(message)) {
|
|
60
|
+
// "fetch failed" alone is Node's generic wrapper — it is only a
|
|
61
|
+
// transport casualty when its cause names a network fault, never on
|
|
62
|
+
// message text alone (a fetch can also fail on a non-network throw
|
|
63
|
+
// upstream, and that must not silently qualify).
|
|
64
|
+
const causeCode = codeOf(err.cause);
|
|
65
|
+
if (causeCode) return true;
|
|
66
|
+
const causeMsg = String(err.cause?.message ?? '');
|
|
67
|
+
if (/bad record mac/i.test(causeMsg) || [...TRANSPORT_CODES].some((c) => causeMsg.includes(c))) return true;
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Summarize what a run DID with its tools, from its gate-audit records
|
|
3
|
+
* alone. Pure and read-only: no IO, no spine writes, no verdict.
|
|
4
|
+
*
|
|
5
|
+
* Counting rules:
|
|
6
|
+
* - `llm` rows are provider rounds, never tool calls — always excluded.
|
|
7
|
+
* - a tool call counts toward `totalCalls`/`byTool`/`uniquePaths`/repeats
|
|
8
|
+
* only when `decision === 'allow'`; a `deny` is tallied separately in
|
|
9
|
+
* `denied` and touches nothing else (denied ≠ done).
|
|
10
|
+
* - `byTool` is keyed by `action.args.tool` when present, else
|
|
11
|
+
* `action.type` (an edit or ctx_remember has no `args.tool`).
|
|
12
|
+
* - exact repeats: see {@link repeatKey}. A first occurrence of an action
|
|
13
|
+
* is not itself a "repeat" — only the 2nd, 3rd, ... occurrences count,
|
|
14
|
+
* so `repeats` can never exceed `totalCalls - byTool-group-count`.
|
|
15
|
+
* @param {Array<{run_id?: string, action?: {type?: string, path?: string, args?: any}, decision?: string|null}>} events
|
|
16
|
+
* @param {{runId?: string}} [opts]
|
|
17
|
+
* @returns {{totalCalls: number, byTool: Record<string, number>, denied: number,
|
|
18
|
+
* uniquePaths: number, repeats: number, repeatPct: number|null}}
|
|
19
|
+
*/
|
|
20
|
+
export function runBehaviour(events: Array<{
|
|
21
|
+
run_id?: string;
|
|
22
|
+
action?: {
|
|
23
|
+
type?: string;
|
|
24
|
+
path?: string;
|
|
25
|
+
args?: any;
|
|
26
|
+
};
|
|
27
|
+
decision?: string | null;
|
|
28
|
+
}>, { runId }?: {
|
|
29
|
+
runId?: string;
|
|
30
|
+
}): {
|
|
31
|
+
totalCalls: number;
|
|
32
|
+
byTool: Record<string, number>;
|
|
33
|
+
denied: number;
|
|
34
|
+
uniquePaths: number;
|
|
35
|
+
repeats: number;
|
|
36
|
+
repeatPct: number | null;
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Render a {@link runBehaviour} summary as the agreed printable block
|
|
40
|
+
* (2–4 short lines). A zero-call run formats plainly ("0 tool calls") rather
|
|
41
|
+
* than a NaN-laced percentage — an empty run is a complete, reportable
|
|
42
|
+
* answer, not a division error.
|
|
43
|
+
* @param {ReturnType<typeof runBehaviour>} summary
|
|
44
|
+
* @returns {string}
|
|
45
|
+
*/
|
|
46
|
+
export function formatBehaviour(summary: ReturnType<typeof runBehaviour>): string;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Report-only stamp of the bareloop code this process is running as. Never
|
|
3
|
+
* throws — an unreadable package.json or absent `.git` reads as `null`,
|
|
4
|
+
* honestly, rather than crashing a caller over a report field.
|
|
5
|
+
* @returns {{ version: string|null, sha: string|null, dirty: null }}
|
|
6
|
+
*/
|
|
7
|
+
export function codeVersion(): {
|
|
8
|
+
version: string | null;
|
|
9
|
+
sha: string | null;
|
|
10
|
+
dirty: null;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* First 8 characters of a sha, or null. Mirrors the short-sha convention
|
|
14
|
+
* `replay.js` already uses for `specHash` (`s.specHash.slice(0, 8)`).
|
|
15
|
+
* @param {string|null|undefined} sha
|
|
16
|
+
* @returns {string|null}
|
|
17
|
+
*/
|
|
18
|
+
export function shortSha(sha: string | null | undefined): string | null;
|
package/types/index.d.ts
CHANGED
|
@@ -22,4 +22,7 @@ export { runAuthorScout, buildSeedListing, seedFileList, classifySurvey, AUTHOR_
|
|
|
22
22
|
export { scrubRaw, RAW_PERSIST_MAX, RAW_TRIM_MARKER } from "./text.js";
|
|
23
23
|
export { authorClose, authorPrompt, declarationSchema, makeLoopGenerate, MAX_REVISIONS, QUESTION_SETS, GREEN_QUESTIONS, SOFTGREEN_QUESTIONS, CLASS_STATEMENTS, questionsFor, requiredAnswersFor } from "./authorflow.js";
|
|
24
24
|
export { validateCloseDecl, runDeclaredStages, declaredStages, isDeclaredClose, guardNames, closeGrade, DECLARED_GENRES, DECLARED_GAP_PREFIX, DECLARED_GAP_KEEP, DECLARED_CLOSE_CLASSES, CLOSE_DECL_FIELDS, HUMAN_PAUSE, HITL_PAUSE, HITL_DECISION_RED, HUMAN_CHECKPOINTS } from "./declaredclose.js";
|
|
25
|
+
export { runBehaviour, formatBehaviour } from "./behaviour.js";
|
|
26
|
+
export { replayRun, formatReplay } from "./replay.js";
|
|
25
27
|
export { runInterview, authorCloseForJob, assembleSpec, prepareSigning, refusalEvents, refuseLockedKind, GENRE, REFUSAL_LIB, REFUSAL_CATEGORY, AUTHORED_SPEC_FIELDS } from "./authorjob.js";
|
|
28
|
+
export { PROMPT_REGISTERS, isPromptFile } from "./promptregisters.js";
|
package/types/ledger.d.ts
CHANGED
|
@@ -103,6 +103,20 @@ export const LEDGER_CLASSES: readonly string[];
|
|
|
103
103
|
* reads as a GUESS rather than as vouched. The fail-safe direction, by construction.
|
|
104
104
|
*/
|
|
105
105
|
export const VOUCHED_RATE_SOURCES: readonly string[];
|
|
106
|
+
/** The spend records a run's money is read from. `worker-round` and `judge-round` are the
|
|
107
|
+
* ACCOUNTED types — exactly `ACCOUNTED_ROUND_TYPES` in src/run.js, which is the ledger that
|
|
108
|
+
* enforces the budget (F12). `judge-round` is the softgreen rung's judged floor: the first
|
|
109
|
+
* spend a CLOSE has ever had, and a provenance readout that skipped it would be blind to the
|
|
110
|
+
* one seam whose rate nobody has audited. `worker-turn` is the native surface's per-turn
|
|
111
|
+
* attribution — unpriced by design, included so the readout covers every metering row the
|
|
112
|
+
* plan flow writes rather than only the ones carrying money. The attempt-level totals
|
|
113
|
+
* (`worker-result`, `worker-plan`) are ECHOES of these same rounds and are excluded, for
|
|
114
|
+
* the same reason the ledger in run.js excludes them: counting them double-counts.
|
|
115
|
+
*
|
|
116
|
+
* Re-spelled rather than imported (src/run.js imports this module's siblings and a cycle
|
|
117
|
+
* buys nothing here) — `tests/ledger.test.js` asserts the two spellings agree, because a
|
|
118
|
+
* spend-slicing instrument that misses a writer is the F45 class this list exists to avoid. */
|
|
119
|
+
export const SPEND_RECORD_TYPES: readonly string[];
|
|
106
120
|
export type ProvenanceBucket = {
|
|
107
121
|
rounds: number;
|
|
108
122
|
usd: number;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* True when `path` names one of the inventoried prompt-register files. Accepts
|
|
3
|
+
* either a repo-root-relative path (`src/tools.js`) or an absolute/relative
|
|
4
|
+
* path ending in one (`/home/x/bareloop/src/tools.js`, `./src/tools.js`) — git
|
|
5
|
+
* hands the check root-relative paths, but a caller working from an absolute
|
|
6
|
+
* path should not have to strip the prefix itself.
|
|
7
|
+
* @param {string} path
|
|
8
|
+
* @returns {boolean}
|
|
9
|
+
*/
|
|
10
|
+
export function isPromptFile(path: string): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* One entry in the prompt-register inventory: a file known to carry
|
|
13
|
+
* model-facing prompt/instruction text, and the name(s) that live there for a
|
|
14
|
+
* human reading this file. `name` may point at a named const OR at a function
|
|
15
|
+
* that builds a prompt inline (e.g. `scoutPrompt()`) — the check only ever
|
|
16
|
+
* keys on `file`.
|
|
17
|
+
* @typedef {{file: string, name: string}} PromptRegisterEntry
|
|
18
|
+
*/
|
|
19
|
+
/** @type {ReadonlyArray<PromptRegisterEntry>} */
|
|
20
|
+
export const PROMPT_REGISTERS: ReadonlyArray<PromptRegisterEntry>;
|
|
21
|
+
/**
|
|
22
|
+
* One entry in the prompt-register inventory: a file known to carry
|
|
23
|
+
* model-facing prompt/instruction text, and the name(s) that live there for a
|
|
24
|
+
* human reading this file. `name` may point at a named const OR at a function
|
|
25
|
+
* that builds a prompt inline (e.g. `scoutPrompt()`) — the check only ever
|
|
26
|
+
* keys on `file`.
|
|
27
|
+
*/
|
|
28
|
+
export type PromptRegisterEntry = {
|
|
29
|
+
file: string;
|
|
30
|
+
name: string;
|
|
31
|
+
};
|
package/types/readshim.d.ts
CHANGED
|
@@ -36,15 +36,21 @@ export function readShimStrategy(arm: ReadShimArm): string;
|
|
|
36
36
|
* reference to the ledger — see its own note. Both live in one object because
|
|
37
37
|
* they are one worker's shim: the read seam and the retrieval seam the cap
|
|
38
38
|
* steers a capped worker towards.
|
|
39
|
-
* @param {{cap?: number, arm?: ReadShimArm
|
|
40
|
-
* shipped value is `READ_SHIM_CAP`; the option
|
|
41
|
-
* two bounds to one number rather than two
|
|
42
|
-
* levers are live (`readShimArm`). Defaults to
|
|
43
|
-
* before the arms existed keeps the behaviour it
|
|
39
|
+
* @param {{cap?: number, arm?: ReadShimArm, onCount?: ((kind: 'pointered'|'capped', bytesWithheld: number) => void)|null}} [opts]
|
|
40
|
+
* `cap`: delivery bound in bytes (the shipped value is `READ_SHIM_CAP`; the option
|
|
41
|
+
* exists so the native path can pin the two bounds to one number rather than two
|
|
42
|
+
* constants drifting apart). `arm`: which levers are live (`readShimArm`). Defaults to
|
|
43
|
+
* ALL of them, so every caller written before the arms existed keeps the behaviour it
|
|
44
|
+
* was written against. `onCount`: MEMORY-CACHE — fired once per pointer/capped-slice
|
|
45
|
+
* response with exactly the bytes withheld on THAT response, for a caller (e.g.
|
|
46
|
+
* `runPlan`) that sums several workers' shims into one run-level readout. Never called
|
|
47
|
+
* for a diff response (L2 is its own lever, uncounted here) or a first read under the
|
|
48
|
+
* cap (nothing was withheld).
|
|
44
49
|
*/
|
|
45
|
-
export function createReadShim({ cap, arm }?: {
|
|
50
|
+
export function createReadShim({ cap, arm, onCount }?: {
|
|
46
51
|
cap?: number;
|
|
47
52
|
arm?: ReadShimArm;
|
|
53
|
+
onCount?: ((kind: "pointered" | "capped", bytesWithheld: number) => void) | null;
|
|
48
54
|
}): {
|
|
49
55
|
arm: ReadShimArm;
|
|
50
56
|
cap: number;
|
|
@@ -56,6 +62,11 @@ export function createReadShim({ cap, arm }?: {
|
|
|
56
62
|
execute: (args: any) => Promise<any>;
|
|
57
63
|
};
|
|
58
64
|
serveStale: ({ file, path, startLine, endLine, detail }: StaleReq) => StaleServe | null;
|
|
65
|
+
summary: () => {
|
|
66
|
+
pointered: number;
|
|
67
|
+
capped: number;
|
|
68
|
+
bytesWithheld: number;
|
|
69
|
+
};
|
|
59
70
|
};
|
|
60
71
|
/**
|
|
61
72
|
* The one-tool spelling, kept for every caller written before the shim became an
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reconstruct one run's whole story from its spine + gate-audit. Pure: no
|
|
3
|
+
* IO, no spine writes, no verdict. Malformed/non-object entries in either
|
|
4
|
+
* array are skipped and counted, never thrown on.
|
|
5
|
+
*
|
|
6
|
+
* A step id can recur (a replan re-executes the same step): `steps[].occurrence`
|
|
7
|
+
* is 1 for that id's first step-start..step-end pair, 2 for its second, etc.
|
|
8
|
+
* Every field on an occurrence — rounds, toolCalls, checks, treeChanged, wallMs,
|
|
9
|
+
* spentUsd — is scoped to records strictly between ITS OWN start/end `seq`,
|
|
10
|
+
* never pooled across every occurrence sharing the id.
|
|
11
|
+
*
|
|
12
|
+
* A run whose spine carries no `step-start` at all (the plan's only step was
|
|
13
|
+
* already-green-skipped on a resumed leg, or a genuinely legacy loop-only
|
|
14
|
+
* shape) has no STEPS timeline to build — `timelineKind` reads `'iterations'`
|
|
15
|
+
* and `iterations[]` is populated from `iteration-start`/`close-verdict`/
|
|
16
|
+
* `run-end`/`escalation` instead. `steps`/`iterations` are never both
|
|
17
|
+
* populated: exactly one is non-empty, selected by `timelineKind`.
|
|
18
|
+
*
|
|
19
|
+
* @param {any[]} spineEvents parsed records from `u-<id>.jsonl`
|
|
20
|
+
* @param {any[]} [auditEvents] parsed records from the sibling `-gate-audit.jsonl`
|
|
21
|
+
* @param {{runId?: string|null}} [opts] `runId`: caller-supplied (the spine
|
|
22
|
+
* carries no run-id field of its own — see the file header)
|
|
23
|
+
*/
|
|
24
|
+
export function replayRun(spineEvents: any[], auditEvents?: any[], { runId }?: {
|
|
25
|
+
runId?: string | null;
|
|
26
|
+
}): {
|
|
27
|
+
runId: string | null;
|
|
28
|
+
job: any;
|
|
29
|
+
verdictType: any;
|
|
30
|
+
model: any;
|
|
31
|
+
code: {
|
|
32
|
+
version: any;
|
|
33
|
+
sha: any;
|
|
34
|
+
} | null;
|
|
35
|
+
goal: any;
|
|
36
|
+
lastEscalation: any;
|
|
37
|
+
budgetUsd: any;
|
|
38
|
+
specHash: any;
|
|
39
|
+
branch: any;
|
|
40
|
+
outcome: any;
|
|
41
|
+
stopReason: string | null;
|
|
42
|
+
spentUsd: any;
|
|
43
|
+
spendComplete: boolean;
|
|
44
|
+
floorReasons: string[];
|
|
45
|
+
transportRetryCount: number;
|
|
46
|
+
transportRetriesOutsideWindows: number;
|
|
47
|
+
wallMs: number | null;
|
|
48
|
+
chainClock: {
|
|
49
|
+
source: "wall-halt" | "wall-clock";
|
|
50
|
+
elapsedMs: number;
|
|
51
|
+
requestedMs: number | null;
|
|
52
|
+
enforcedMs: number | null;
|
|
53
|
+
bounded: boolean;
|
|
54
|
+
} | null;
|
|
55
|
+
resumed: boolean;
|
|
56
|
+
resumeSeed: {
|
|
57
|
+
phase: any;
|
|
58
|
+
completed: any;
|
|
59
|
+
skipping: any;
|
|
60
|
+
divergence: any;
|
|
61
|
+
} | null;
|
|
62
|
+
thisFileSpend: {
|
|
63
|
+
value: any;
|
|
64
|
+
source: "engagementSpentUsd" | "summed rounds";
|
|
65
|
+
totalRounds: number;
|
|
66
|
+
unpricedRounds: number;
|
|
67
|
+
};
|
|
68
|
+
spendMismatch: {
|
|
69
|
+
spentUsd: any;
|
|
70
|
+
thisFileUsd: any;
|
|
71
|
+
diffUsd: number;
|
|
72
|
+
} | null;
|
|
73
|
+
timelineKind: "steps" | "iterations";
|
|
74
|
+
steps: {
|
|
75
|
+
checks: {
|
|
76
|
+
passed: number;
|
|
77
|
+
failed: number;
|
|
78
|
+
};
|
|
79
|
+
treeChanged: boolean;
|
|
80
|
+
rounds: number;
|
|
81
|
+
toolCalls: number;
|
|
82
|
+
wallMs: number | null;
|
|
83
|
+
spentUsd: number | null;
|
|
84
|
+
unpricedRounds: number;
|
|
85
|
+
tripped: {
|
|
86
|
+
category: any;
|
|
87
|
+
detail: string | null;
|
|
88
|
+
} | null;
|
|
89
|
+
transportRetries: {
|
|
90
|
+
count: number;
|
|
91
|
+
label: string;
|
|
92
|
+
error: string | null;
|
|
93
|
+
} | null;
|
|
94
|
+
id: any;
|
|
95
|
+
occurrence: any;
|
|
96
|
+
outcome: any;
|
|
97
|
+
}[];
|
|
98
|
+
iterations: {
|
|
99
|
+
rounds: number;
|
|
100
|
+
toolCalls: number;
|
|
101
|
+
wallMs: number | null;
|
|
102
|
+
spentUsd: number | null;
|
|
103
|
+
unpricedRounds: number;
|
|
104
|
+
tripped: {
|
|
105
|
+
category: any;
|
|
106
|
+
detail: string | null;
|
|
107
|
+
} | null;
|
|
108
|
+
transportRetries: {
|
|
109
|
+
count: number;
|
|
110
|
+
label: string;
|
|
111
|
+
error: string | null;
|
|
112
|
+
} | null;
|
|
113
|
+
iteration: any;
|
|
114
|
+
verdict: any;
|
|
115
|
+
boundary: any;
|
|
116
|
+
closeStage: {
|
|
117
|
+
verdict: any;
|
|
118
|
+
stages: any;
|
|
119
|
+
} | null;
|
|
120
|
+
}[];
|
|
121
|
+
replans: any;
|
|
122
|
+
close: {
|
|
123
|
+
source: "close-verdict" | "outer-close";
|
|
124
|
+
iteration: number | null;
|
|
125
|
+
verdict: string | null;
|
|
126
|
+
stages: any[] | null;
|
|
127
|
+
} | null;
|
|
128
|
+
ending: {
|
|
129
|
+
record: any;
|
|
130
|
+
before: any[];
|
|
131
|
+
escalation: any;
|
|
132
|
+
};
|
|
133
|
+
behaviour: {
|
|
134
|
+
totalCalls: number;
|
|
135
|
+
byTool: Record<string, number>;
|
|
136
|
+
denied: number;
|
|
137
|
+
uniquePaths: number;
|
|
138
|
+
repeats: number;
|
|
139
|
+
repeatPct: number | null;
|
|
140
|
+
};
|
|
141
|
+
memoryCache: any;
|
|
142
|
+
skipped: number;
|
|
143
|
+
};
|
|
144
|
+
/**
|
|
145
|
+
* Render a {@link replayRun} summary as one printable page: a header block
|
|
146
|
+
* (goal/shape/signed/branch/outcome/spent), a timeline (steps or iterations,
|
|
147
|
+
* whichever the spine has), the CLOSE section (never omitted), the ending
|
|
148
|
+
* (last record + 3 before it, plus an out-of-window escalation), the
|
|
149
|
+
* behaviour block, and the MEMORY-CACHE line (always printed — `not armed on
|
|
150
|
+
* this run` when there is no record, never silently absent).
|
|
151
|
+
* @param {ReturnType<typeof replayRun>} summary
|
|
152
|
+
* @returns {string}
|
|
153
|
+
*/
|
|
154
|
+
export function formatReplay(summary: ReturnType<typeof replayRun>): string;
|
|
155
|
+
/**
|
|
156
|
+
* One `--all` row's raw field strings for one real spine:
|
|
157
|
+
* `id job shape outcome spend wall steps reason`. `reason` is
|
|
158
|
+
* `category — <first clause of detail, cut at 60 chars>` off the last
|
|
159
|
+
* escalation — never the generic `decision` prose. `-` on a green row;
|
|
160
|
+
* `no job-end (killed mid-run)` when the spine never reached a `job-end` at
|
|
161
|
+
* all (outcome itself is `null`) — a distinct, named case, not folded into
|
|
162
|
+
* the ordinary escalation reason.
|
|
163
|
+
* @param {ReturnType<typeof replayRun>} summary
|
|
164
|
+
* @returns {{id: string, resumed: boolean, job: string, shape: string, class: string, model: string, outcome: string, hadTransportRetries: boolean, spend: string, wall: string, steps: string, reason: string}}
|
|
165
|
+
*/
|
|
166
|
+
export function summarizeForAllLine(summary: ReturnType<typeof replayRun>): {
|
|
167
|
+
id: string;
|
|
168
|
+
resumed: boolean;
|
|
169
|
+
job: string;
|
|
170
|
+
shape: string;
|
|
171
|
+
class: string;
|
|
172
|
+
model: string;
|
|
173
|
+
outcome: string;
|
|
174
|
+
hadTransportRetries: boolean;
|
|
175
|
+
spend: string;
|
|
176
|
+
wall: string;
|
|
177
|
+
steps: string;
|
|
178
|
+
reason: string;
|
|
179
|
+
};
|
|
180
|
+
/**
|
|
181
|
+
* Render every row {@link summarizeForAllLine} built for one directory's
|
|
182
|
+
* spines as a fixed-column, aligned table WITH a header row (plus any
|
|
183
|
+
* `<name> not-a-spine` lines interleaved at their original position — those
|
|
184
|
+
* are not part of the aligned columns, since a filename carries no outcome/
|
|
185
|
+
* spend/wall of its own). Never CSV: columns are padded, not delimited.
|
|
186
|
+
* @param {Array<{kind: 'spine', row: ReturnType<typeof summarizeForAllLine>}|{kind: 'not-a-spine', name: string}>} entries
|
|
187
|
+
* @returns {string}
|
|
188
|
+
*/
|
|
189
|
+
export function formatAllLines(entries: Array<{
|
|
190
|
+
kind: "spine";
|
|
191
|
+
row: ReturnType<typeof summarizeForAllLine>;
|
|
192
|
+
} | {
|
|
193
|
+
kind: "not-a-spine";
|
|
194
|
+
name: string;
|
|
195
|
+
}>): string;
|
package/types/reuse.d.ts
CHANGED
|
@@ -589,6 +589,28 @@ export const REUSE_GRADED_RED: readonly string[];
|
|
|
589
589
|
* how they would come to disagree about which member is which: the first three are
|
|
590
590
|
* allowances the loop's remaining tries are meant to spend against, and the human
|
|
591
591
|
* ones are the stop no further try can change (see `hardStop`).
|
|
592
|
+
*
|
|
593
|
+
* `provider-red` folded in 2026-08-25 (PRD v1.80 TODO #4, F115 "Ruled: one transport
|
|
594
|
+
* retry"; hamr's ruling verbatim: *"joins resume anyways and you decide but you are
|
|
595
|
+
* given an honest readout and with 1 retry"*). It is a MACHINE halt, not a human one
|
|
596
|
+
* — it does NOT go into `HUMAN_CHECKPOINTS` — but it is a checkpoint by the same
|
|
597
|
+
* test every other member here passes: the close never got the say it was asked
|
|
598
|
+
* for (no verdict was rendered, so it is not a graded red — see `REUSE_GRADED_RED`
|
|
599
|
+
* above), and the scout+draft and every finished step it bought stand on disk
|
|
600
|
+
* exactly like a cap-halt or a wall-halt. The one transport retry
|
|
601
|
+
* (`src/planrun.js`'s `withTransportRetry`) already covers the common case; when
|
|
602
|
+
* that retry ALSO fails, throwing the run away because the transport hiccuped
|
|
603
|
+
* twice is throwing away real work for no reason a machine can judge. Deliberately
|
|
604
|
+
* NO cost/step threshold gates this: a threshold is a number picked from a small
|
|
605
|
+
* observed sample (F115 logged exactly 7 rows), which is arbiter territory
|
|
606
|
+
* reserved for hamr — the operator decides case by case, every time, off the
|
|
607
|
+
* honest readout `scripts/run-u.mjs` prints.
|
|
608
|
+
*
|
|
609
|
+
* This entry was built 2026-08-24 as a LOCAL widening in `scripts/run-u.mjs`
|
|
610
|
+
* (`RESUMABLE_HALTS = [...CHECKPOINT_OUTCOMES, 'provider-red']`) because this file
|
|
611
|
+
* sat outside that pass's scope — the fold here is the follow-up, so this repo
|
|
612
|
+
* does not end up with two lists that can drift, which is the exact failure this
|
|
613
|
+
* constant exists to end (see the paragraph above).
|
|
592
614
|
*/
|
|
593
615
|
export const CHECKPOINT_OUTCOMES: readonly string[];
|
|
594
616
|
/**
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Is `err` a transport-class failure — the request never produced an HTTP
|
|
3
|
+
* response? True for: a TLS fault mid-record ("bad record mac" and kin), a
|
|
4
|
+
* reset/broken/timed-out socket (ECONNRESET, EPIPE, ETIMEDOUT, ECONNREFUSED,
|
|
5
|
+
* ENETUNREACH — by code on the error itself or on `err.cause`), and
|
|
6
|
+
* `fetch failed` wrapping a network `cause`. False for anything carrying an
|
|
7
|
+
* HTTP status (`err.status`/`err.statusCode`, 4xx/5xx/429) or an explicit
|
|
8
|
+
* `err.retryable === false` — those are provider RESPONSES, not transport
|
|
9
|
+
* casualties, and bare-agent's own policy governs them unchanged.
|
|
10
|
+
* @param {any} err
|
|
11
|
+
* @returns {boolean}
|
|
12
|
+
*/
|
|
13
|
+
export function isTransportFailure(err: any): boolean;
|
|
14
|
+
/** The fixed retry budget: ONE extra attempt after the first transport
|
|
15
|
+
* throw. Never read from a job spec or CLI flag — raising it is arbiter
|
|
16
|
+
* territory (only a code change hamr signs, never a run-time knob). */
|
|
17
|
+
export const TRANSPORT_RETRIES: 1;
|
|
18
|
+
/** bare-agent's `Retry.call` counts ATTEMPTS, not retries. */
|
|
19
|
+
export const TRANSPORT_MAX_ATTEMPTS: number;
|