@tangle-network/agent-runtime 0.340.0 → 0.342.1
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/dist/{activation-DGDl4wwI.d.ts → activation-CXqtijue.d.ts} +2 -2
- package/dist/{activation-D2PVljaw.js → activation-DOH7Fnyp.js} +4 -4
- package/dist/{activation-D2PVljaw.js.map → activation-DOH7Fnyp.js.map} +1 -1
- package/dist/agent.d.ts +1 -1
- package/dist/agent.js +2 -2
- package/dist/{bundle-DNUq-OPW.js → bundle-Cwpit6Tg.js} +2 -2
- package/dist/{bundle-DNUq-OPW.js.map → bundle-Cwpit6Tg.js.map} +1 -1
- package/dist/candidate-execution/index.js +4 -4
- package/dist/{driver-control-acknowledgers-MTrs8HAF.js → coordination-manager-HQE0bIav.js} +4852 -4539
- package/dist/coordination-manager-HQE0bIav.js.map +1 -0
- package/dist/{delegate-CtJIS-tp.js → delegate-DmShSdjy.js} +2 -2
- package/dist/{delegate-CtJIS-tp.js.map → delegate-DmShSdjy.js.map} +1 -1
- package/dist/durable.d.ts +1 -1
- package/dist/durable.js +451 -358
- package/dist/durable.js.map +1 -1
- package/dist/{graph-DfWqp_1e.js → graph-BCghJFlS.js} +5 -5
- package/dist/{graph-DfWqp_1e.js.map → graph-BCghJFlS.js.map} +1 -1
- package/dist/{improve-CKRVQqzt.js → improve-Dga6OYt-.js} +837 -704
- package/dist/improve-Dga6OYt-.js.map +1 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.js +9 -9
- package/dist/intelligence.d.ts +2 -2
- package/dist/intelligence.js +205 -191
- package/dist/intelligence.js.map +1 -1
- package/dist/kernel.d.ts +1 -1
- package/dist/kernel.js +9 -9
- package/dist/{loop-runner-DNWbTcVm.js → loop-runner-CW87Vqoj.js} +2 -2
- package/dist/{loop-runner-DNWbTcVm.js.map → loop-runner-CW87Vqoj.js.map} +1 -1
- package/dist/{loop-runner-C4EWEcXf.d.ts → loop-runner-CuhbgVGo.d.ts} +2 -2
- package/dist/loop-runner-bin.d.ts +1 -1
- package/dist/loop-runner-bin.js +1 -1
- package/dist/mcp/bin.js +3 -3
- package/dist/mcp/index.d.ts +1 -1
- package/dist/mcp/index.js +5 -5
- package/dist/{prepare-FzJoS68l.js → prepare-C-cWCyt3.js} +988 -735
- package/dist/prepare-C-cWCyt3.js.map +1 -0
- package/dist/{profile-DToH0XEs.js → profile-CbMw5hAn.js} +2 -2
- package/dist/{profile-DToH0XEs.js.map → profile-CbMw5hAn.js.map} +1 -1
- package/dist/profiles.d.ts +1 -1
- package/dist/{provision-supervisor-DqvWXmAE.js → provision-supervisor-BWR_TJFg.js} +444 -419
- package/dist/provision-supervisor-BWR_TJFg.js.map +1 -0
- package/dist/{runtime-C55Zgo1R.js → runtime-CuRTZFe6.js} +1245 -1028
- package/dist/runtime-CuRTZFe6.js.map +1 -0
- package/dist/{sandbox-profile-compat-DRr3JwDx.js → sandbox-profile-compat-FN2kab2-.js} +72 -72
- package/dist/{sandbox-profile-compat-DRr3JwDx.js.map → sandbox-profile-compat-FN2kab2-.js.map} +1 -1
- package/dist/{search-executor-ChOlRBgg.js → search-executor-0-SjHAao.js} +3 -3
- package/dist/{search-executor-ChOlRBgg.js.map → search-executor-0-SjHAao.js.map} +1 -1
- package/dist/{server-DkVBSN_S.js → server-DTUK2wW1.js} +4 -4
- package/dist/{server-DkVBSN_S.js.map → server-DTUK2wW1.js.map} +1 -1
- package/dist/{structural-rollout-C1LEk_zq.js → structural-rollout-kfaZgAkd.js} +3 -3
- package/dist/{structural-rollout-C1LEk_zq.js.map → structural-rollout-kfaZgAkd.js.map} +1 -1
- package/dist/{supervise-CaUAxZZP.js → supervise-C_GAgiJU.js} +6346 -5447
- package/dist/supervise-C_GAgiJU.js.map +1 -0
- package/dist/{supervisor-VXuN2OHT.js → supervisor-C_03z693.js} +13823 -12826
- package/dist/supervisor-C_03z693.js.map +1 -0
- package/dist/testing.d.ts +1 -1
- package/dist/testing.js +13 -13
- package/dist/tui/index.d.ts +1 -1
- package/dist/tui/index.js +1 -1
- package/dist/{types-DZ6L0Bv3.d.ts → types-Cgs89PQd.d.ts} +266 -356
- package/dist/workspace-store/index.js +273 -265
- package/dist/workspace-store/index.js.map +1 -1
- package/package.json +10 -10
- package/dist/driver-control-acknowledgers-MTrs8HAF.js.map +0 -1
- package/dist/improve-CKRVQqzt.js.map +0 -1
- package/dist/prepare-FzJoS68l.js.map +0 -1
- package/dist/provision-supervisor-DqvWXmAE.js.map +0 -1
- package/dist/runtime-C55Zgo1R.js.map +0 -1
- package/dist/supervise-CaUAxZZP.js.map +0 -1
- package/dist/supervisor-VXuN2OHT.js.map +0 -1
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
import { o as writeAtomicDurableFile } from "./durable-file-gNI9ILAn.js";
|
|
2
|
-
import { $t as createWorktreeCliExecutor, Hi as notifySandboxEventObserver, L as rollingDispatch, M as settledToIteration, Mt as routerBrain, Qn as verdictFromJudgeScore, Si as contentAddress, St as createSandboxLineage, Tt as defaultSelectWinner, Xn as gateOnDeliverable, Yn as CheckUnavailableError, Zn as mapExecutorResult, _r as runAbortable, ai as executableAgentSpecSnapshot, bi as detachedFrozen, bt as readPromptOptions, ct as InMemorySpawnJournal, dt as createExecutorRegistry, ii as executableAgentProfileSnapshot, k as coordinationProfileToolPrefix, mt as createExecutor, n as createSupervisor, p as isTraceAnalysisStore, st as InMemoryResultBlobStore, u as withDriverExecutor, ut as insideCursorNamespace, vt as runAgentRounds, xi as detachedSnapshot, yt as assertBoxlessPromptOptions, zi as createSandboxTurnEventCollector } from "./supervisor-
|
|
2
|
+
import { $t as createWorktreeCliExecutor, Hi as notifySandboxEventObserver, L as rollingDispatch, M as settledToIteration, Mt as routerBrain, Qn as verdictFromJudgeScore, Si as contentAddress, St as createSandboxLineage, Tt as defaultSelectWinner, Xn as gateOnDeliverable, Yn as CheckUnavailableError, Zn as mapExecutorResult, _r as runAbortable, ai as executableAgentSpecSnapshot, bi as detachedFrozen, bt as readPromptOptions, ct as InMemorySpawnJournal, dt as createExecutorRegistry, ii as executableAgentProfileSnapshot, k as coordinationProfileToolPrefix, mt as createExecutor, n as createSupervisor, p as isTraceAnalysisStore, st as InMemoryResultBlobStore, u as withDriverExecutor, ut as insideCursorNamespace, vt as runAgentRounds, xi as detachedSnapshot, yt as assertBoxlessPromptOptions, zi as createSandboxTurnEventCollector } from "./supervisor-C_03z693.js";
|
|
3
3
|
import { i as ConfigError, m as ValidationError, n as AnalystError, s as PlannerError } from "./errors-DodWX-cb.js";
|
|
4
|
-
import { D as runAgentic, E as refine, M as renderReport, N as profileChatClient, O as sample, j as observe, k as ObservationError, v as loadAuthoredStrategy, x as strategyAuthorContract, y as requestStrategySource } from "./structural-rollout-
|
|
5
|
-
import { $ as isAbortError, B as sha256Bytes$1, Ct as armDeadlineTimer, K as chargedTokens, P as canonicalCandidateDigest$1, Q as hasCompleteCacheBreakdown, R as immutableCandidateValue, W as addSpend, X as errorMessage, Z as fnv1a32Hex, et as isAsyncIterable, ft as zeroSpend,
|
|
4
|
+
import { D as runAgentic, E as refine, M as renderReport, N as profileChatClient, O as sample, j as observe, k as ObservationError, v as loadAuthoredStrategy, x as strategyAuthorContract, y as requestStrategySource } from "./structural-rollout-kfaZgAkd.js";
|
|
5
|
+
import { $ as isAbortError, B as sha256Bytes$1, C as WorkspaceScanLimitError, Ct as armDeadlineTimer, E as captureMaterializedWorkspace, K as chargedTokens, P as canonicalCandidateDigest$1, Q as hasCompleteCacheBreakdown, R as immutableCandidateValue, W as addSpend, X as errorMessage, Z as fnv1a32Hex, et as isAsyncIterable, ft as zeroSpend, h as markPutStreamVerified, ot as sleep, q as cloneSpend, rt as randomSuffix, st as stringifySafe, tt as mapWithConcurrency } from "./sandbox-profile-compat-FN2kab2-.js";
|
|
6
6
|
import { c as profileModelExecutionSettings, i as concreteModelId, l as profileProviderModel, o as enforceTokenLimits, p as canonicalObservedModelParts, t as assertExecutableAgentProfile, v as harnessRunsAgent } from "./model-policy-CyCUwHed.js";
|
|
7
7
|
import { i as notifyRuntimeHookEvent } from "./runtime-hooks-tXpAarhW.js";
|
|
8
8
|
import { a as readCommittedJsonLines, i as readCommittedJsonFile, n as isNoEntError, o as upsertCommittedJsonLine, s as withJsonlFileLock, t as appendCommittedJsonLine } from "./jsonl-file-C9Kde_0Z.js";
|
|
9
|
-
import { g as foldCoordinationRecords, p as createInMemoryRunContext } from "./
|
|
10
|
-
import {
|
|
11
|
-
import { a as reconcileInterruptedSearchCalls, c as searchExecutor, r as dedicatedLane, x as runtimeShipDecision } from "./search-executor-
|
|
9
|
+
import { g as foldCoordinationRecords, p as createInMemoryRunContext } from "./coordination-manager-HQE0bIav.js";
|
|
10
|
+
import { _ as runBrainLoop, t as supervise } from "./supervise-C_GAgiJU.js";
|
|
11
|
+
import { a as reconcileInterruptedSearchCalls, c as searchExecutor, r as dedicatedLane, x as runtimeShipDecision } from "./search-executor-0-SjHAao.js";
|
|
12
12
|
import "./run-cancellation-C9oL62sW.js";
|
|
13
13
|
import { a as graphRunsFor, d as recordGraphRun, i as graphRegistry, l as lookupGraph, p as taskShapeDigest, r as graphFromStored, t as digestOfGraph } from "./graph-registry-BnO64cn8.js";
|
|
14
14
|
import { i as redactProtectedValue, r as redactProtectedReason } from "./protected-redaction-DFCeo4vg.js";
|
|
15
|
-
import "./provision-supervisor-
|
|
16
|
-
import "./delegate-
|
|
17
|
-
import "./graph-
|
|
15
|
+
import "./provision-supervisor-BWR_TJFg.js";
|
|
16
|
+
import "./delegate-DmShSdjy.js";
|
|
17
|
+
import "./graph-BCghJFlS.js";
|
|
18
18
|
import { agentProfileSchema, canonicalAgentProfileDigest, canonicalCandidateDigest, sha256Bytes, validateAgentProfileSecurity } from "@tangle-network/agent-interface";
|
|
19
19
|
import { CODING_HARNESSES, DEFAULT_TRACE_ANALYST_KINDS, InMemoryTraceStore, OUTPUT_VALUE, benjaminiHochberg, buildTrajectory, computeFindingId as computeFindingId$1, confidenceInterval, createTraceAnalyst, expandProfileAxes, makeFinding as makeFinding$1, pairedBootstrap, paretoFrontier, wilcoxonSignedRank, wilson } from "@tangle-network/agent-eval";
|
|
20
20
|
import { createAgentRunOutcomeTracker } from "@tangle-network/sandbox/runtime";
|
|
@@ -2808,27 +2808,24 @@ function gitSha() {
|
|
|
2808
2808
|
function normalizeScore(s) {
|
|
2809
2809
|
return typeof s === "number" ? { composite: s } : s;
|
|
2810
2810
|
}
|
|
2811
|
-
/**
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
if (
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
});
|
|
2830
|
-
};
|
|
2831
|
-
const scoreJudge = {
|
|
2811
|
+
/** The stable string id of a case. */
|
|
2812
|
+
function leaderboardCaseId(spec, c) {
|
|
2813
|
+
const id = spec.caseId ? spec.caseId(c) : c.id;
|
|
2814
|
+
if (typeof id !== "string" || id.length === 0) throw new Error(`defineLeaderboard(${spec.name}): every case needs a stable string id — give cases an \`id\` property or supply spec.caseId`);
|
|
2815
|
+
return id;
|
|
2816
|
+
}
|
|
2817
|
+
/** The cases named by `ids`, in that order; every case when `ids` is omitted. */
|
|
2818
|
+
function selectLeaderboardCases(spec, ids) {
|
|
2819
|
+
if (!ids) return spec.cases;
|
|
2820
|
+
const byId = new Map(spec.cases.map((c) => [leaderboardCaseId(spec, c), c]));
|
|
2821
|
+
return ids.map((id) => {
|
|
2822
|
+
const c = byId.get(id);
|
|
2823
|
+
if (c === void 0) throw new Error(`defineLeaderboard(${spec.name}): unknown case "${id}" (have: ${[...byId.keys()].join(", ")})`);
|
|
2824
|
+
return c;
|
|
2825
|
+
});
|
|
2826
|
+
}
|
|
2827
|
+
function leaderboardScoreJudge(spec) {
|
|
2828
|
+
return {
|
|
2832
2829
|
name: `${spec.name}-score`,
|
|
2833
2830
|
dimensions: [{
|
|
2834
2831
|
key: "composite",
|
|
@@ -2846,20 +2843,174 @@ function defineLeaderboard(spec) {
|
|
|
2846
2843
|
};
|
|
2847
2844
|
}
|
|
2848
2845
|
};
|
|
2846
|
+
}
|
|
2847
|
+
/** The standard flags plus every `spec.flags` entry, read from argv. */
|
|
2848
|
+
function parseLeaderboardArgs(spec, argv) {
|
|
2849
|
+
const args = {};
|
|
2850
|
+
for (const name of [
|
|
2851
|
+
"backend",
|
|
2852
|
+
"harnesses",
|
|
2853
|
+
"models",
|
|
2854
|
+
"cases",
|
|
2855
|
+
"shots",
|
|
2856
|
+
"reps",
|
|
2857
|
+
"model-snapshot",
|
|
2858
|
+
"run-dir",
|
|
2859
|
+
"export-dir"
|
|
2860
|
+
]) args[name] = argOf(argv, name);
|
|
2861
|
+
for (const [name, flag] of Object.entries(spec.flags ?? {})) args[name] = argOf(argv, name) ?? flag.default;
|
|
2862
|
+
return args;
|
|
2863
|
+
}
|
|
2864
|
+
/** The harness×model axes and the executable profiles they expand to. */
|
|
2865
|
+
function resolveLeaderboardProfiles(spec, args, snapshot) {
|
|
2866
|
+
const harnesses = splitList(args.harnesses) ?? spec.axis?.harnesses ?? CODING_HARNESSES;
|
|
2867
|
+
const rawModels = splitList(args.models) ?? spec.axis?.models ?? (spec.baseProfile?.model?.default !== void 0 ? [spec.baseProfile.model.default] : void 0);
|
|
2868
|
+
if (!rawModels || rawModels.length === 0) throw new Error(`defineLeaderboard(${spec.name}): no models — pass --models, set spec.axis.models, or give spec.baseProfile a model.default`);
|
|
2869
|
+
const models = rawModels.map((m) => withSnapshot(m, snapshot));
|
|
2870
|
+
const profiles = expandProfileAxes({
|
|
2871
|
+
base: spec.baseProfile,
|
|
2872
|
+
harnesses,
|
|
2873
|
+
models
|
|
2874
|
+
});
|
|
2875
|
+
for (const profile of profiles) assertExecutableAgentProfile(profile, `defineLeaderboard(${spec.name})`);
|
|
2876
|
+
return {
|
|
2877
|
+
harnesses,
|
|
2878
|
+
models,
|
|
2879
|
+
profiles
|
|
2880
|
+
};
|
|
2881
|
+
}
|
|
2882
|
+
/** The sandbox client for the named backend: defaults plus spec overrides (spec wins).
|
|
2883
|
+
* Factories are lazy so an unused backend never resolves credentials. */
|
|
2884
|
+
function resolveLeaderboardClient(spec, backendName) {
|
|
2885
|
+
const backends = {
|
|
2886
|
+
sandbox: () => {
|
|
2887
|
+
throw new Error(`defineLeaderboard(${spec.name}): the 'sandbox' backend needs your product's real Sandbox client — supply spec.backends.sandbox (e.g. () => new Sandbox({ apiKey, baseUrl }))`);
|
|
2888
|
+
},
|
|
2889
|
+
"cli-bridge": () => {
|
|
2890
|
+
const bearer = process.env.BRIDGE_BEARER ?? process.env.CLI_BRIDGE_BEARER;
|
|
2891
|
+
if (!bearer) throw new Error(`defineLeaderboard(${spec.name}): backend 'cli-bridge' needs BRIDGE_BEARER or CLI_BRIDGE_BEARER set`);
|
|
2892
|
+
return resolveSandboxClient({
|
|
2893
|
+
backend: "bridge",
|
|
2894
|
+
bridge: {
|
|
2895
|
+
url: process.env.CLI_BRIDGE_URL,
|
|
2896
|
+
bearer,
|
|
2897
|
+
timeoutMs: 9e5
|
|
2898
|
+
}
|
|
2899
|
+
});
|
|
2900
|
+
},
|
|
2901
|
+
...spec.backends
|
|
2902
|
+
};
|
|
2903
|
+
const makeClient = backends[backendName];
|
|
2904
|
+
if (!makeClient) throw new Error(`defineLeaderboard(${spec.name}): unknown backend "${backendName}" (have: ${Object.keys(backends).join(", ")})`);
|
|
2905
|
+
return makeClient();
|
|
2906
|
+
}
|
|
2907
|
+
/**
|
|
2908
|
+
* The default dispatch wraps loopDispatch per cell (closures only — no
|
|
2909
|
+
* per-cell resource cost) so the loop's finished iterations can be joined
|
|
2910
|
+
* with the campaign ctx: onCellEvents gets EVERY shot's outcome (a thrown
|
|
2911
|
+
* shot never reaches parse, so parse-time tapping would hide it), and a
|
|
2912
|
+
* the cost receipt records the spec-resolved served model.
|
|
2913
|
+
*/
|
|
2914
|
+
function leaderboardDispatch(spec, sandboxClient, shots, promptOf) {
|
|
2915
|
+
let shotNonce = 0;
|
|
2916
|
+
const maximumCharge = spec.maximumCharge;
|
|
2917
|
+
return (profile, scenario, dispatchCtx) => {
|
|
2918
|
+
return loopDispatch({
|
|
2919
|
+
sandboxClient,
|
|
2920
|
+
maximumCharge: typeof maximumCharge === "function" ? (cellScenario, cellProfile) => maximumCharge(cellProfile, cellScenario) : maximumCharge,
|
|
2921
|
+
resolveCostModel: (result, _cellScenario, cellProfile) => {
|
|
2922
|
+
if (!spec.resolveModel) return cellProfile.model?.default;
|
|
2923
|
+
const served = new Set(result.iterations.map((iteration) => spec.resolveModel?.(iteration.events)).filter((model) => model !== void 0));
|
|
2924
|
+
if (served.size > 1) throw new Error(`defineLeaderboard(${spec.name}): one cell reported multiple served models: ${[...served].join(", ")}`);
|
|
2925
|
+
return [...served][0] ?? cellProfile.model?.default;
|
|
2926
|
+
},
|
|
2927
|
+
toLoopOptions: (cellScenario, cellProfile) => {
|
|
2928
|
+
return {
|
|
2929
|
+
driver: naiveRetryDriver(shots),
|
|
2930
|
+
agentRun: {
|
|
2931
|
+
profile: cellProfile,
|
|
2932
|
+
taskToPrompt: (s) => `${promptOf(s)}\n\n<!-- independent-attempt:${shotNonce++} -->`
|
|
2933
|
+
},
|
|
2934
|
+
output: { parse: (events) => spec.parseOutput ? spec.parseOutput(events, cellScenario.case) : collectAgentResponseText(events) ?? "" },
|
|
2935
|
+
validator: { validate: async (output) => {
|
|
2936
|
+
const s = normalizeScore(spec.score(output, cellScenario.case));
|
|
2937
|
+
return {
|
|
2938
|
+
valid: s.composite > 0,
|
|
2939
|
+
score: s.composite
|
|
2940
|
+
};
|
|
2941
|
+
} },
|
|
2942
|
+
task: cellScenario,
|
|
2943
|
+
maxIterations: shots
|
|
2944
|
+
};
|
|
2945
|
+
},
|
|
2946
|
+
toArtifact: (result) => {
|
|
2947
|
+
for (const iter of result.iterations) spec.onCellEvents?.(iter.events, scenario.case, {
|
|
2948
|
+
index: iter.index,
|
|
2949
|
+
...iter.error ? { error: iter.error.message } : {},
|
|
2950
|
+
...iter.verdict ? { verdict: { score: iter.verdict.score } } : {}
|
|
2951
|
+
});
|
|
2952
|
+
return result.winner?.output;
|
|
2953
|
+
}
|
|
2954
|
+
})(profile, scenario, dispatchCtx);
|
|
2955
|
+
};
|
|
2956
|
+
}
|
|
2957
|
+
/** Hand the result to `spec.export`, or write the default JSON and markdown leaderboard. */
|
|
2958
|
+
async function exportLeaderboardResult(spec, result, ctx) {
|
|
2959
|
+
if (spec.export) {
|
|
2960
|
+
await spec.export(result, ctx);
|
|
2961
|
+
return;
|
|
2962
|
+
}
|
|
2963
|
+
mkdirSync(ctx.exportDir, { recursive: true });
|
|
2964
|
+
writeFileSync(join(ctx.runDir, "matrix-result.json"), `${JSON.stringify(result, null, 2)}\n`);
|
|
2965
|
+
const table = renderLeaderboardMarkdown(leaderboard(result.records, {
|
|
2966
|
+
title: spec.name,
|
|
2967
|
+
meta: { backend: ctx.backend }
|
|
2968
|
+
}));
|
|
2969
|
+
writeFileSync(join(ctx.exportDir, "leaderboard.md"), table);
|
|
2970
|
+
console.log(table);
|
|
2971
|
+
}
|
|
2972
|
+
/** The leaderboard's domain surface in the structural `BenchmarkAdapter` shape. */
|
|
2973
|
+
function leaderboardBenchmarkAdapter(spec) {
|
|
2974
|
+
return {
|
|
2975
|
+
name: spec.name,
|
|
2976
|
+
async preflight() {
|
|
2977
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2978
|
+
for (const c of spec.cases) {
|
|
2979
|
+
const id = leaderboardCaseId(spec, c);
|
|
2980
|
+
if (seen.has(id)) throw new Error(`defineLeaderboard(${spec.name}): duplicate case id "${id}"`);
|
|
2981
|
+
seen.add(id);
|
|
2982
|
+
}
|
|
2983
|
+
},
|
|
2984
|
+
async loadTasks(opts) {
|
|
2985
|
+
const selected = selectLeaderboardCases(spec, opts?.ids);
|
|
2986
|
+
const limited = opts?.limit !== void 0 ? selected.slice(0, opts.limit) : selected;
|
|
2987
|
+
return Promise.all(limited.map(async (c) => ({
|
|
2988
|
+
id: leaderboardCaseId(spec, c),
|
|
2989
|
+
prompt: await spec.prompt(c),
|
|
2990
|
+
metadata: { case: c }
|
|
2991
|
+
})));
|
|
2992
|
+
},
|
|
2993
|
+
async judge(task, artifact) {
|
|
2994
|
+
const [c] = selectLeaderboardCases(spec, [task.id]);
|
|
2995
|
+
if (c === void 0) throw new Error(`defineLeaderboard(${spec.name}): no case "${task.id}"`);
|
|
2996
|
+
const s = normalizeScore(spec.score(artifact, c));
|
|
2997
|
+
return {
|
|
2998
|
+
resolved: s.composite > 0,
|
|
2999
|
+
score: s.composite,
|
|
3000
|
+
detail: s.notes
|
|
3001
|
+
};
|
|
3002
|
+
},
|
|
3003
|
+
async goldArtifact() {}
|
|
3004
|
+
};
|
|
3005
|
+
}
|
|
3006
|
+
/**
|
|
3007
|
+
* Assemble a declarative spec (`cases` + `prompt` + `score`) into a runnable
|
|
3008
|
+
* harness×model leaderboard — `run()` executes the matrix, `toBenchmarkAdapter()`
|
|
3009
|
+
* exposes the same domain as a structural `BenchmarkAdapter`.
|
|
3010
|
+
*/
|
|
3011
|
+
function defineLeaderboard(spec) {
|
|
2849
3012
|
async function run(argv = process.argv.slice(2)) {
|
|
2850
|
-
const args =
|
|
2851
|
-
for (const name of [
|
|
2852
|
-
"backend",
|
|
2853
|
-
"harnesses",
|
|
2854
|
-
"models",
|
|
2855
|
-
"cases",
|
|
2856
|
-
"shots",
|
|
2857
|
-
"reps",
|
|
2858
|
-
"model-snapshot",
|
|
2859
|
-
"run-dir",
|
|
2860
|
-
"export-dir"
|
|
2861
|
-
]) args[name] = argOf(argv, name);
|
|
2862
|
-
for (const [name, flag] of Object.entries(spec.flags ?? {})) args[name] = argOf(argv, name) ?? flag.default;
|
|
3013
|
+
const args = parseLeaderboardArgs(spec, argv);
|
|
2863
3014
|
const backendName = args.backend ?? "sandbox";
|
|
2864
3015
|
const shots = Number(args.shots ?? spec.shots ?? 1);
|
|
2865
3016
|
const reps = Number(args.reps ?? spec.reps ?? 1);
|
|
@@ -2867,23 +3018,14 @@ function defineLeaderboard(spec) {
|
|
|
2867
3018
|
const runDir = args["run-dir"] ?? join(tmpdir(), `leaderboard-${spec.name}-${Date.now()}-${process.pid}`);
|
|
2868
3019
|
const exportDir = args["export-dir"] ?? join(runDir, "export");
|
|
2869
3020
|
mkdirSync(runDir, { recursive: true });
|
|
2870
|
-
const cases =
|
|
3021
|
+
const cases = selectLeaderboardCases(spec, splitList(args.cases));
|
|
2871
3022
|
if (cases.length === 0) throw new Error(`defineLeaderboard(${spec.name}): no cases to run`);
|
|
2872
3023
|
const scenarios = cases.map((c) => ({
|
|
2873
|
-
id:
|
|
3024
|
+
id: leaderboardCaseId(spec, c),
|
|
2874
3025
|
kind: spec.name,
|
|
2875
3026
|
case: c
|
|
2876
3027
|
}));
|
|
2877
|
-
const harnesses =
|
|
2878
|
-
const rawModels = splitList(args.models) ?? spec.axis?.models ?? (spec.baseProfile?.model?.default !== void 0 ? [spec.baseProfile.model.default] : void 0);
|
|
2879
|
-
if (!rawModels || rawModels.length === 0) throw new Error(`defineLeaderboard(${spec.name}): no models — pass --models, set spec.axis.models, or give spec.baseProfile a model.default`);
|
|
2880
|
-
const models = rawModels.map((m) => withSnapshot(m, snapshot));
|
|
2881
|
-
const profiles = expandProfileAxes({
|
|
2882
|
-
base: spec.baseProfile,
|
|
2883
|
-
harnesses,
|
|
2884
|
-
models
|
|
2885
|
-
});
|
|
2886
|
-
for (const profile of profiles) assertExecutableAgentProfile(profile, `defineLeaderboard(${spec.name})`);
|
|
3028
|
+
const { harnesses, models, profiles } = resolveLeaderboardProfiles(spec, args, snapshot);
|
|
2887
3029
|
const ctx = {
|
|
2888
3030
|
name: spec.name,
|
|
2889
3031
|
backend: backendName,
|
|
@@ -2896,27 +3038,7 @@ function defineLeaderboard(spec) {
|
|
|
2896
3038
|
shots,
|
|
2897
3039
|
reps
|
|
2898
3040
|
};
|
|
2899
|
-
const
|
|
2900
|
-
sandbox: () => {
|
|
2901
|
-
throw new Error(`defineLeaderboard(${spec.name}): the 'sandbox' backend needs your product's real Sandbox client — supply spec.backends.sandbox (e.g. () => new Sandbox({ apiKey, baseUrl }))`);
|
|
2902
|
-
},
|
|
2903
|
-
"cli-bridge": () => {
|
|
2904
|
-
const bearer = process.env.BRIDGE_BEARER ?? process.env.CLI_BRIDGE_BEARER;
|
|
2905
|
-
if (!bearer) throw new Error(`defineLeaderboard(${spec.name}): backend 'cli-bridge' needs BRIDGE_BEARER or CLI_BRIDGE_BEARER set`);
|
|
2906
|
-
return resolveSandboxClient({
|
|
2907
|
-
backend: "bridge",
|
|
2908
|
-
bridge: {
|
|
2909
|
-
url: process.env.CLI_BRIDGE_URL,
|
|
2910
|
-
bearer,
|
|
2911
|
-
timeoutMs: 9e5
|
|
2912
|
-
}
|
|
2913
|
-
});
|
|
2914
|
-
},
|
|
2915
|
-
...spec.backends
|
|
2916
|
-
};
|
|
2917
|
-
const makeClient = backends[backendName];
|
|
2918
|
-
if (!makeClient) throw new Error(`defineLeaderboard(${spec.name}): unknown backend "${backendName}" (have: ${Object.keys(backends).join(", ")})`);
|
|
2919
|
-
const sandboxClient = makeClient();
|
|
3041
|
+
const sandboxClient = resolveLeaderboardClient(spec, backendName);
|
|
2920
3042
|
const promptById = /* @__PURE__ */ new Map();
|
|
2921
3043
|
for (const s of scenarios) promptById.set(s.id, await spec.prompt(s.case));
|
|
2922
3044
|
const promptOf = (s) => {
|
|
@@ -2924,111 +3046,28 @@ function defineLeaderboard(spec) {
|
|
|
2924
3046
|
if (p === void 0) throw new Error(`defineLeaderboard(${spec.name}): no prompt for case "${s.id}"`);
|
|
2925
3047
|
return p;
|
|
2926
3048
|
};
|
|
2927
|
-
|
|
2928
|
-
const maximumCharge = spec.maximumCharge;
|
|
2929
|
-
const dispatch = spec.dispatch ?? ((profile, scenario, dispatchCtx) => {
|
|
2930
|
-
return loopDispatch({
|
|
2931
|
-
sandboxClient,
|
|
2932
|
-
maximumCharge: typeof maximumCharge === "function" ? (cellScenario, cellProfile) => maximumCharge(cellProfile, cellScenario) : maximumCharge,
|
|
2933
|
-
resolveCostModel: (result, _cellScenario, cellProfile) => {
|
|
2934
|
-
if (!spec.resolveModel) return cellProfile.model?.default;
|
|
2935
|
-
const served = new Set(result.iterations.map((iteration) => spec.resolveModel?.(iteration.events)).filter((model) => model !== void 0));
|
|
2936
|
-
if (served.size > 1) throw new Error(`defineLeaderboard(${spec.name}): one cell reported multiple served models: ${[...served].join(", ")}`);
|
|
2937
|
-
return [...served][0] ?? cellProfile.model?.default;
|
|
2938
|
-
},
|
|
2939
|
-
toLoopOptions: (cellScenario, cellProfile) => {
|
|
2940
|
-
return {
|
|
2941
|
-
driver: naiveRetryDriver(shots),
|
|
2942
|
-
agentRun: {
|
|
2943
|
-
profile: cellProfile,
|
|
2944
|
-
taskToPrompt: (s) => `${promptOf(s)}\n\n<!-- independent-attempt:${shotNonce++} -->`
|
|
2945
|
-
},
|
|
2946
|
-
output: { parse: (events) => spec.parseOutput ? spec.parseOutput(events, cellScenario.case) : collectAgentResponseText(events) ?? "" },
|
|
2947
|
-
validator: { validate: async (output) => {
|
|
2948
|
-
const s = normalizeScore(spec.score(output, cellScenario.case));
|
|
2949
|
-
return {
|
|
2950
|
-
valid: s.composite > 0,
|
|
2951
|
-
score: s.composite
|
|
2952
|
-
};
|
|
2953
|
-
} },
|
|
2954
|
-
task: cellScenario,
|
|
2955
|
-
maxIterations: shots
|
|
2956
|
-
};
|
|
2957
|
-
},
|
|
2958
|
-
toArtifact: (result) => {
|
|
2959
|
-
for (const iter of result.iterations) spec.onCellEvents?.(iter.events, scenario.case, {
|
|
2960
|
-
index: iter.index,
|
|
2961
|
-
...iter.error ? { error: iter.error.message } : {},
|
|
2962
|
-
...iter.verdict ? { verdict: { score: iter.verdict.score } } : {}
|
|
2963
|
-
});
|
|
2964
|
-
return result.winner?.output;
|
|
2965
|
-
}
|
|
2966
|
-
})(profile, scenario, dispatchCtx);
|
|
2967
|
-
});
|
|
3049
|
+
const dispatch = spec.dispatch ?? leaderboardDispatch(spec, sandboxClient, shots, promptOf);
|
|
2968
3050
|
await spec.setup?.(ctx);
|
|
2969
3051
|
try {
|
|
2970
3052
|
const result = await runProfileMatrix({
|
|
2971
3053
|
profiles,
|
|
2972
3054
|
scenarios,
|
|
2973
3055
|
dispatch,
|
|
2974
|
-
judges: spec.judges ?? [
|
|
3056
|
+
judges: spec.judges ?? [leaderboardScoreJudge(spec)],
|
|
2975
3057
|
runDir,
|
|
2976
3058
|
commitSha: gitSha(),
|
|
2977
3059
|
reps,
|
|
2978
3060
|
...spec.matrix
|
|
2979
3061
|
});
|
|
2980
|
-
|
|
2981
|
-
else {
|
|
2982
|
-
mkdirSync(exportDir, { recursive: true });
|
|
2983
|
-
writeFileSync(join(runDir, "matrix-result.json"), `${JSON.stringify(result, null, 2)}\n`);
|
|
2984
|
-
const table = renderLeaderboardMarkdown(leaderboard(result.records, {
|
|
2985
|
-
title: spec.name,
|
|
2986
|
-
meta: { backend: backendName }
|
|
2987
|
-
}));
|
|
2988
|
-
writeFileSync(join(exportDir, "leaderboard.md"), table);
|
|
2989
|
-
console.log(table);
|
|
2990
|
-
}
|
|
3062
|
+
await exportLeaderboardResult(spec, result, ctx);
|
|
2991
3063
|
return result;
|
|
2992
3064
|
} finally {
|
|
2993
3065
|
await spec.teardown?.(ctx);
|
|
2994
3066
|
}
|
|
2995
3067
|
}
|
|
2996
|
-
function toBenchmarkAdapter() {
|
|
2997
|
-
return {
|
|
2998
|
-
name: spec.name,
|
|
2999
|
-
async preflight() {
|
|
3000
|
-
const seen = /* @__PURE__ */ new Set();
|
|
3001
|
-
for (const c of spec.cases) {
|
|
3002
|
-
const id = caseId(c);
|
|
3003
|
-
if (seen.has(id)) throw new Error(`defineLeaderboard(${spec.name}): duplicate case id "${id}"`);
|
|
3004
|
-
seen.add(id);
|
|
3005
|
-
}
|
|
3006
|
-
},
|
|
3007
|
-
async loadTasks(opts) {
|
|
3008
|
-
const selected = selectCases(opts?.ids);
|
|
3009
|
-
const limited = opts?.limit !== void 0 ? selected.slice(0, opts.limit) : selected;
|
|
3010
|
-
return Promise.all(limited.map(async (c) => ({
|
|
3011
|
-
id: caseId(c),
|
|
3012
|
-
prompt: await spec.prompt(c),
|
|
3013
|
-
metadata: { case: c }
|
|
3014
|
-
})));
|
|
3015
|
-
},
|
|
3016
|
-
async judge(task, artifact) {
|
|
3017
|
-
const [c] = selectCases([task.id]);
|
|
3018
|
-
if (c === void 0) throw new Error(`defineLeaderboard(${spec.name}): no case "${task.id}"`);
|
|
3019
|
-
const s = normalizeScore(spec.score(artifact, c));
|
|
3020
|
-
return {
|
|
3021
|
-
resolved: s.composite > 0,
|
|
3022
|
-
score: s.composite,
|
|
3023
|
-
detail: s.notes
|
|
3024
|
-
};
|
|
3025
|
-
},
|
|
3026
|
-
async goldArtifact() {}
|
|
3027
|
-
};
|
|
3028
|
-
}
|
|
3029
3068
|
return {
|
|
3030
3069
|
run,
|
|
3031
|
-
toBenchmarkAdapter
|
|
3070
|
+
toBenchmarkAdapter: () => leaderboardBenchmarkAdapter(spec)
|
|
3032
3071
|
};
|
|
3033
3072
|
}
|
|
3034
3073
|
//#endregion
|
|
@@ -4650,8 +4689,10 @@ function dispatch() {
|
|
|
4650
4689
|
run(worker, jobs.shift());
|
|
4651
4690
|
}
|
|
4652
4691
|
}
|
|
4692
|
+
let syncs = 0;
|
|
4653
4693
|
/** fsync an open file or directory on a sync thread; keep the handle open until this settles. */
|
|
4654
4694
|
function syncHandle(handle) {
|
|
4695
|
+
syncs += 1;
|
|
4655
4696
|
if (threadsUnavailable) return handle.sync();
|
|
4656
4697
|
return new Promise((resolve, reject) => {
|
|
4657
4698
|
jobs.push({
|
|
@@ -4671,21 +4712,42 @@ async function syncDirectory(directory) {
|
|
|
4671
4712
|
}
|
|
4672
4713
|
}
|
|
4673
4714
|
//#endregion
|
|
4674
|
-
//#region src/runtime/private-cas.ts
|
|
4715
|
+
//#region src/runtime/private-cas-objects.ts
|
|
4716
|
+
/**
|
|
4717
|
+
* The local object store of a private CAS: where an object lives, how it is written, read back
|
|
4718
|
+
* and verified, and how a locator is checked. An object's name is published only after its bytes
|
|
4719
|
+
* are synced, and that order is what makes a name trustworthy, except in an `ephemeral` store, which
|
|
4720
|
+
* syncs nothing and is never read after its process ends: see `createPrivateCasArtifactPort`.
|
|
4721
|
+
* Durable copies build on these.
|
|
4722
|
+
*/
|
|
4675
4723
|
const DIGEST$1 = /^sha256:([0-9a-f]{64})$/u;
|
|
4676
|
-
const
|
|
4677
|
-
const
|
|
4678
|
-
|
|
4679
|
-
|
|
4680
|
-
|
|
4681
|
-
|
|
4682
|
-
|
|
4683
|
-
|
|
4684
|
-
|
|
4724
|
+
const isMissingPath = (error) => error?.code === "ENOENT";
|
|
4725
|
+
const casSha256 = (bytes) => `sha256:${createHash("sha256").update(bytes).digest("hex")}`;
|
|
4726
|
+
function privateCasRef(namespace, digest, byteLength) {
|
|
4727
|
+
return {
|
|
4728
|
+
locator: {
|
|
4729
|
+
kind: "private-cas",
|
|
4730
|
+
namespace,
|
|
4731
|
+
digest
|
|
4732
|
+
},
|
|
4733
|
+
sha256: digest,
|
|
4734
|
+
byteLength
|
|
4735
|
+
};
|
|
4736
|
+
}
|
|
4737
|
+
function hexOf(digest) {
|
|
4738
|
+
const match = DIGEST$1.exec(digest);
|
|
4739
|
+
if (!match) throw new Error("private CAS digest must be sha256:<64 lowercase hex>");
|
|
4740
|
+
return match[1];
|
|
4741
|
+
}
|
|
4742
|
+
function objectPath(layout, digest) {
|
|
4743
|
+
const hex = hexOf(digest);
|
|
4744
|
+
return join(layout.store, hex.slice(0, 2), hex);
|
|
4745
|
+
}
|
|
4685
4746
|
async function realDirectory(path) {
|
|
4686
4747
|
const entry = await lstat(path);
|
|
4687
4748
|
if (!entry.isDirectory() || entry.isSymbolicLink()) throw new Error(`private CAS directory is not a real directory: ${path}`);
|
|
4688
4749
|
}
|
|
4750
|
+
/** Require every component of `path` to be a real directory, creating them when `create`. */
|
|
4689
4751
|
async function checkDirectories(path, create) {
|
|
4690
4752
|
const parts = resolve(path).split(sep);
|
|
4691
4753
|
let current = sep;
|
|
@@ -4700,16 +4762,13 @@ async function checkDirectories(path, create) {
|
|
|
4700
4762
|
await realDirectory(current);
|
|
4701
4763
|
}
|
|
4702
4764
|
}
|
|
4703
|
-
const missing = (error) => error?.code === "ENOENT";
|
|
4704
|
-
const message = (error) => error instanceof Error ? error.message : String(error);
|
|
4705
|
-
const sha256 = (bytes) => `sha256:${createHash("sha256").update(bytes).digest("hex")}`;
|
|
4706
4765
|
/** Read one local object, or undefined when the host no longer holds it. */
|
|
4707
4766
|
async function readLocal(path) {
|
|
4708
4767
|
let file;
|
|
4709
4768
|
try {
|
|
4710
4769
|
file = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
4711
4770
|
} catch (error) {
|
|
4712
|
-
if (
|
|
4771
|
+
if (isMissingPath(error)) return void 0;
|
|
4713
4772
|
throw error;
|
|
4714
4773
|
}
|
|
4715
4774
|
try {
|
|
@@ -4718,219 +4777,308 @@ async function readLocal(path) {
|
|
|
4718
4777
|
await file.close();
|
|
4719
4778
|
}
|
|
4720
4779
|
}
|
|
4721
|
-
|
|
4722
|
-
|
|
4723
|
-
|
|
4724
|
-
|
|
4725
|
-
|
|
4726
|
-
|
|
4727
|
-
*
|
|
4728
|
-
|
|
4729
|
-
|
|
4730
|
-
|
|
4731
|
-
|
|
4732
|
-
|
|
4733
|
-
|
|
4734
|
-
|
|
4735
|
-
*
|
|
4736
|
-
|
|
4737
|
-
|
|
4738
|
-
|
|
4739
|
-
|
|
4740
|
-
|
|
4741
|
-
|
|
4742
|
-
|
|
4743
|
-
|
|
4744
|
-
|
|
4745
|
-
|
|
4746
|
-
|
|
4747
|
-
|
|
4748
|
-
|
|
4749
|
-
|
|
4750
|
-
|
|
4751
|
-
|
|
4752
|
-
|
|
4753
|
-
|
|
4754
|
-
|
|
4755
|
-
|
|
4756
|
-
|
|
4757
|
-
|
|
4758
|
-
|
|
4759
|
-
|
|
4760
|
-
|
|
4761
|
-
|
|
4762
|
-
|
|
4763
|
-
|
|
4764
|
-
await checkDirectories(
|
|
4765
|
-
|
|
4766
|
-
|
|
4767
|
-
|
|
4780
|
+
async function* readObject(path, ref, signal) {
|
|
4781
|
+
signal?.throwIfAborted();
|
|
4782
|
+
const file = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
4783
|
+
try {
|
|
4784
|
+
const stats = await file.stat();
|
|
4785
|
+
if (!stats.isFile() || stats.size !== ref.byteLength) throw new Error("private CAS object failed digest or length verification");
|
|
4786
|
+
yield* checkedChunks(file.createReadStream({
|
|
4787
|
+
autoClose: false,
|
|
4788
|
+
highWaterMark: 64 * 1024
|
|
4789
|
+
}), ref, signal);
|
|
4790
|
+
} finally {
|
|
4791
|
+
await file.close();
|
|
4792
|
+
}
|
|
4793
|
+
}
|
|
4794
|
+
async function* checkedChunks(chunks, ref, signal) {
|
|
4795
|
+
const hash = createHash("sha256");
|
|
4796
|
+
let byteLength = 0;
|
|
4797
|
+
for await (const chunk of chunks) {
|
|
4798
|
+
signal?.throwIfAborted();
|
|
4799
|
+
if (!(chunk instanceof Uint8Array)) throw new Error("private CAS emitted a non-byte chunk");
|
|
4800
|
+
byteLength += chunk.byteLength;
|
|
4801
|
+
if (byteLength > ref.byteLength) throw new Error("private CAS object failed digest or length verification");
|
|
4802
|
+
hash.update(chunk);
|
|
4803
|
+
yield chunk;
|
|
4804
|
+
}
|
|
4805
|
+
signal?.throwIfAborted();
|
|
4806
|
+
if (`sha256:${hash.digest("hex")}` !== ref.sha256 || byteLength !== ref.byteLength) throw new Error("private CAS object failed digest or length verification");
|
|
4807
|
+
}
|
|
4808
|
+
function verified(bytes, ref) {
|
|
4809
|
+
if (casSha256(bytes) !== ref.sha256 || bytes.byteLength !== ref.byteLength) throw new Error("private CAS object failed digest or length verification");
|
|
4810
|
+
return bytes;
|
|
4811
|
+
}
|
|
4812
|
+
function assertOwnLocator(layout, ref) {
|
|
4813
|
+
if (ref.locator.kind !== "private-cas" || ref.locator.namespace !== layout.namespace || ref.locator.digest !== ref.sha256) throw new Error("private CAS locator does not match its digest and namespace");
|
|
4814
|
+
}
|
|
4815
|
+
/** Stream an object from the host, falling back to the durable store when the host lacks it. */
|
|
4816
|
+
async function* readPrivateCasStream(layout, ref, options = {}) {
|
|
4817
|
+
const { signal } = options;
|
|
4818
|
+
const { durable } = layout;
|
|
4819
|
+
signal?.throwIfAborted();
|
|
4820
|
+
assertOwnLocator(layout, ref);
|
|
4821
|
+
const path = objectPath(layout, ref.sha256);
|
|
4822
|
+
try {
|
|
4823
|
+
await checkDirectories(join(layout.store, hexOf(ref.sha256).slice(0, 2)), false);
|
|
4824
|
+
yield* readObject(path, ref, signal);
|
|
4825
|
+
return;
|
|
4826
|
+
} catch (error) {
|
|
4827
|
+
if (!isMissingPath(error)) throw error;
|
|
4828
|
+
}
|
|
4829
|
+
if (!durable?.getStream) throw new Error(`private CAS object is not on this host; streaming durable reads are unavailable: ${ref.sha256}`);
|
|
4830
|
+
const chunks = await durable.getStream({
|
|
4831
|
+
namespace: layout.namespace,
|
|
4832
|
+
digest: ref.sha256,
|
|
4833
|
+
signal
|
|
4834
|
+
});
|
|
4835
|
+
if (!chunks) throw new Error(`private CAS object is neither on this host nor in ${durable.location}: ${ref.sha256}`);
|
|
4836
|
+
yield* checkedChunks(chunks, ref, signal);
|
|
4837
|
+
}
|
|
4838
|
+
/** Read a whole object from the host, falling back to the durable store. */
|
|
4839
|
+
async function readPrivateCasRef(layout, ref) {
|
|
4840
|
+
const { durable } = layout;
|
|
4841
|
+
assertOwnLocator(layout, ref);
|
|
4842
|
+
const path = objectPath(layout, ref.sha256);
|
|
4843
|
+
const shard = join(layout.store, hexOf(ref.sha256).slice(0, 2));
|
|
4844
|
+
let local;
|
|
4845
|
+
try {
|
|
4846
|
+
await checkDirectories(shard, false);
|
|
4847
|
+
local = await readLocal(path);
|
|
4848
|
+
} catch (error) {
|
|
4849
|
+
if (!isMissingPath(error)) throw error;
|
|
4850
|
+
}
|
|
4851
|
+
if (local !== void 0) return verified(local, ref);
|
|
4852
|
+
if (durable === void 0) throw new Error(`private CAS object is not on this host: ${ref.sha256}`);
|
|
4853
|
+
const stored = await durable.get({
|
|
4854
|
+
namespace: layout.namespace,
|
|
4855
|
+
digest: ref.sha256
|
|
4856
|
+
});
|
|
4857
|
+
if (stored === void 0) throw new Error(`private CAS object is neither on this host nor in ${durable.location}: ${ref.sha256}`);
|
|
4858
|
+
return verified(stored, ref);
|
|
4859
|
+
}
|
|
4860
|
+
/** The ref of an object this CAS holds with exactly this length, on the host or by receipt. */
|
|
4861
|
+
async function locatePrivateCasObject(layout, object, options = {}) {
|
|
4862
|
+
options.signal?.throwIfAborted();
|
|
4863
|
+
const digest = object.sha256;
|
|
4864
|
+
const hex = hexOf(digest);
|
|
4865
|
+
const ref = privateCasRef(layout.namespace, digest, object.byteLength);
|
|
4866
|
+
try {
|
|
4867
|
+
await checkDirectories(join(layout.store, hex.slice(0, 2)), false);
|
|
4868
|
+
const local = await lstat(objectPath(layout, digest));
|
|
4869
|
+
if (local.isFile()) return local.size === object.byteLength ? ref : void 0;
|
|
4870
|
+
} catch (error) {
|
|
4871
|
+
if (!isMissingPath(error)) throw error;
|
|
4872
|
+
}
|
|
4873
|
+
if (layout.durable === void 0) return void 0;
|
|
4874
|
+
const receipt = await readLocal(join(layout.receipts, hex));
|
|
4875
|
+
if (receipt === void 0) return void 0;
|
|
4876
|
+
return JSON.parse(Buffer.from(receipt).toString("utf8")).byteLength === object.byteLength ? ref : void 0;
|
|
4877
|
+
}
|
|
4878
|
+
const CREATE_EXCLUSIVE = constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW;
|
|
4879
|
+
/** Store a stream: stage it under a temporary name, verify the staged inode, then publish it. */
|
|
4880
|
+
async function putPrivateCasStream(layout, offload, input) {
|
|
4881
|
+
const { signal } = input;
|
|
4882
|
+
const { store, durable } = layout;
|
|
4883
|
+
signal?.throwIfAborted();
|
|
4884
|
+
await checkDirectories(store, true);
|
|
4885
|
+
const temporary = join(store, `.${randomUUID()}.tmp`);
|
|
4886
|
+
try {
|
|
4887
|
+
const file = await open(temporary, CREATE_EXCLUSIVE, 384);
|
|
4888
|
+
const hash = createHash("sha256");
|
|
4889
|
+
let byteLength = 0;
|
|
4768
4890
|
try {
|
|
4769
|
-
await
|
|
4770
|
-
|
|
4771
|
-
|
|
4772
|
-
|
|
4773
|
-
|
|
4774
|
-
byteLength
|
|
4775
|
-
|
|
4776
|
-
}
|
|
4777
|
-
|
|
4891
|
+
for await (const chunk of input.chunks) {
|
|
4892
|
+
signal?.throwIfAborted();
|
|
4893
|
+
if (!(chunk instanceof Uint8Array)) throw new Error("private CAS received a non-byte chunk");
|
|
4894
|
+
hash.update(chunk);
|
|
4895
|
+
byteLength += chunk.byteLength;
|
|
4896
|
+
if (!Number.isSafeInteger(byteLength)) throw new Error("private CAS object is too large");
|
|
4897
|
+
await file.writeFile(chunk);
|
|
4898
|
+
}
|
|
4899
|
+
signal?.throwIfAborted();
|
|
4900
|
+
if (!layout.ephemeral) await syncHandle(file);
|
|
4778
4901
|
} finally {
|
|
4779
4902
|
await file.close();
|
|
4780
4903
|
}
|
|
4781
|
-
|
|
4782
|
-
|
|
4783
|
-
|
|
4784
|
-
|
|
4785
|
-
const
|
|
4786
|
-
|
|
4787
|
-
|
|
4788
|
-
|
|
4789
|
-
|
|
4790
|
-
};
|
|
4791
|
-
touch();
|
|
4792
|
-
const attempt = signal === void 0 ? stall.signal : AbortSignal.any([signal, stall.signal]);
|
|
4793
|
-
return {
|
|
4794
|
-
signal: attempt,
|
|
4795
|
-
touch,
|
|
4796
|
-
run: (act) => runAbortable(act, attempt, "private CAS durable copy was cancelled"),
|
|
4797
|
-
done: () => clearTimeout(timer)
|
|
4798
|
-
};
|
|
4799
|
-
};
|
|
4800
|
-
const storeDurably = async (digest, bytes, signal) => {
|
|
4801
|
-
let failure;
|
|
4802
|
-
for (let attempt = 1; attempt <= DURABLE_ATTEMPTS; attempt++) {
|
|
4803
|
-
signal?.throwIfAborted();
|
|
4804
|
-
const watch = watchAttempt(durableStallMs + Math.ceil(bytes.byteLength / BUFFERED_BYTES_PER_MS), signal);
|
|
4805
|
-
try {
|
|
4806
|
-
await watch.run(() => durable.put({
|
|
4807
|
-
namespace,
|
|
4808
|
-
digest,
|
|
4809
|
-
bytes,
|
|
4810
|
-
signal: watch.signal
|
|
4811
|
-
}));
|
|
4812
|
-
await writeReceipt(digest, bytes.byteLength);
|
|
4813
|
-
return;
|
|
4814
|
-
} catch (error) {
|
|
4815
|
-
failure = error;
|
|
4816
|
-
if (signal?.aborted) throw error;
|
|
4817
|
-
if (attempt < DURABLE_ATTEMPTS) await new Promise((settle) => setTimeout(settle, 1e3 * 4 ** (attempt - 1)));
|
|
4818
|
-
} finally {
|
|
4819
|
-
watch.done();
|
|
4820
|
-
}
|
|
4821
|
-
}
|
|
4822
|
-
throw failure;
|
|
4823
|
-
};
|
|
4824
|
-
const localObjects = async () => {
|
|
4825
|
-
let shards;
|
|
4904
|
+
const sha = hash.digest("hex");
|
|
4905
|
+
const digest = `sha256:${sha}`;
|
|
4906
|
+
const ref = privateCasRef(layout.namespace, digest, byteLength);
|
|
4907
|
+
for await (const _chunk of readObject(temporary, ref, signal));
|
|
4908
|
+
const path = objectPath(layout, digest);
|
|
4909
|
+
const directory = join(store, sha.slice(0, 2));
|
|
4910
|
+
await checkDirectories(directory, true);
|
|
4911
|
+
signal?.throwIfAborted();
|
|
4912
|
+
let linked = true;
|
|
4826
4913
|
try {
|
|
4827
|
-
|
|
4914
|
+
await link(temporary, path);
|
|
4828
4915
|
} catch (error) {
|
|
4829
|
-
if (
|
|
4830
|
-
|
|
4916
|
+
if (error.code !== "EEXIST") throw error;
|
|
4917
|
+
linked = false;
|
|
4918
|
+
for await (const _chunk of readPrivateCasStream(layout, ref, { signal }));
|
|
4919
|
+
}
|
|
4920
|
+
if (!layout.ephemeral) await syncDirectory(directory);
|
|
4921
|
+
if (durable !== void 0) await offload.settlePut({
|
|
4922
|
+
sha,
|
|
4923
|
+
byteLength,
|
|
4924
|
+
linked
|
|
4925
|
+
}, signal);
|
|
4926
|
+
return ref;
|
|
4927
|
+
} finally {
|
|
4928
|
+
await unlink(temporary).catch((error) => {
|
|
4929
|
+
if (error.code !== "ENOENT") throw error;
|
|
4930
|
+
});
|
|
4931
|
+
}
|
|
4932
|
+
}
|
|
4933
|
+
/** Store bytes already in memory; the durable copy then reuses those bytes. */
|
|
4934
|
+
async function putPrivateCasBuffered(layout, offload, object, signal) {
|
|
4935
|
+
const { bytes, digest, sha } = object;
|
|
4936
|
+
const path = objectPath(layout, digest);
|
|
4937
|
+
const directory = join(layout.store, sha.slice(0, 2));
|
|
4938
|
+
await checkDirectories(directory, true);
|
|
4939
|
+
const temporary = join(directory, `.${sha}.${randomUUID()}.tmp`);
|
|
4940
|
+
const file = await open(temporary, CREATE_EXCLUSIVE, 384);
|
|
4941
|
+
try {
|
|
4942
|
+
await file.writeFile(bytes);
|
|
4943
|
+
if (!layout.ephemeral) await syncHandle(file);
|
|
4944
|
+
} finally {
|
|
4945
|
+
await file.close();
|
|
4946
|
+
}
|
|
4947
|
+
let linked = true;
|
|
4948
|
+
try {
|
|
4949
|
+
signal?.throwIfAborted();
|
|
4950
|
+
try {
|
|
4951
|
+
await link(temporary, path);
|
|
4952
|
+
} catch (error) {
|
|
4953
|
+
if (error.code !== "EEXIST") throw error;
|
|
4954
|
+
linked = false;
|
|
4831
4955
|
}
|
|
4832
|
-
|
|
4833
|
-
|
|
4834
|
-
|
|
4835
|
-
|
|
4836
|
-
|
|
4837
|
-
|
|
4838
|
-
|
|
4839
|
-
|
|
4840
|
-
|
|
4841
|
-
|
|
4842
|
-
|
|
4843
|
-
|
|
4844
|
-
|
|
4845
|
-
|
|
4846
|
-
|
|
4847
|
-
|
|
4848
|
-
|
|
4849
|
-
|
|
4850
|
-
|
|
4851
|
-
|
|
4852
|
-
|
|
4853
|
-
|
|
4854
|
-
|
|
4855
|
-
|
|
4856
|
-
|
|
4857
|
-
|
|
4858
|
-
|
|
4859
|
-
|
|
4860
|
-
|
|
4861
|
-
|
|
4862
|
-
|
|
4863
|
-
|
|
4864
|
-
|
|
4865
|
-
|
|
4866
|
-
|
|
4867
|
-
|
|
4868
|
-
|
|
4869
|
-
|
|
4870
|
-
|
|
4871
|
-
|
|
4872
|
-
|
|
4956
|
+
if (!layout.ephemeral) await syncDirectory(directory);
|
|
4957
|
+
} finally {
|
|
4958
|
+
await unlink(temporary).catch(() => void 0);
|
|
4959
|
+
}
|
|
4960
|
+
const ref = privateCasRef(layout.namespace, digest, bytes.byteLength);
|
|
4961
|
+
await readPrivateCasRef(layout, ref);
|
|
4962
|
+
if (layout.durable !== void 0) await offload.settlePut({
|
|
4963
|
+
sha,
|
|
4964
|
+
byteLength: bytes.byteLength,
|
|
4965
|
+
linked,
|
|
4966
|
+
bytes
|
|
4967
|
+
}, signal);
|
|
4968
|
+
return ref;
|
|
4969
|
+
}
|
|
4970
|
+
//#endregion
|
|
4971
|
+
//#region src/runtime/private-cas-offload.ts
|
|
4972
|
+
/**
|
|
4973
|
+
* Durable copies of a private CAS, and the offload passes that make them: one object at a time to
|
|
4974
|
+
* the off-host store, with a stall bound, retries, a receipt on the host once the store holds the
|
|
4975
|
+
* bytes, and one copy in flight per digest. A pass copies every local object without a receipt,
|
|
4976
|
+
* then deletes the oldest durable local copies above the host's budget.
|
|
4977
|
+
*/
|
|
4978
|
+
const HEX = /^[0-9a-f]{64}$/u;
|
|
4979
|
+
const SHARD = /^[0-9a-f]{2}$/u;
|
|
4980
|
+
const OFFLOAD_COPIES_AT_ONCE = 16;
|
|
4981
|
+
const LIST_SHARDS_AT_ONCE = 16;
|
|
4982
|
+
async function receiptCount(layout) {
|
|
4983
|
+
try {
|
|
4984
|
+
return (await readdir(layout.receipts)).filter((name) => HEX.test(name)).length;
|
|
4985
|
+
} catch (error) {
|
|
4986
|
+
if (!isMissingPath(error)) throw error;
|
|
4987
|
+
return 0;
|
|
4988
|
+
}
|
|
4989
|
+
}
|
|
4990
|
+
async function listLocalObjects(layout) {
|
|
4991
|
+
let shards;
|
|
4992
|
+
try {
|
|
4993
|
+
shards = await readdir(layout.store);
|
|
4994
|
+
} catch (error) {
|
|
4995
|
+
if (isMissingPath(error)) return [];
|
|
4996
|
+
throw error;
|
|
4997
|
+
}
|
|
4998
|
+
return (await mapWithConcurrency(shards.filter((name) => SHARD.test(name)), LIST_SHARDS_AT_ONCE, async (shard) => {
|
|
4999
|
+
const objects = [];
|
|
5000
|
+
for (const hex of await readdir(join(layout.store, shard))) {
|
|
5001
|
+
if (!HEX.test(hex) || hex.slice(0, 2) !== shard) continue;
|
|
5002
|
+
const path = join(layout.store, shard, hex);
|
|
4873
5003
|
try {
|
|
4874
|
-
await
|
|
4875
|
-
|
|
4876
|
-
|
|
4877
|
-
|
|
4878
|
-
|
|
4879
|
-
|
|
4880
|
-
})
|
|
4881
|
-
await writeReceipt(digest, byteLength);
|
|
4882
|
-
return;
|
|
5004
|
+
const entry = await lstat(path);
|
|
5005
|
+
if (entry.isFile()) objects.push({
|
|
5006
|
+
hex,
|
|
5007
|
+
path,
|
|
5008
|
+
size: entry.size,
|
|
5009
|
+
modifiedMs: entry.mtimeMs
|
|
5010
|
+
});
|
|
4883
5011
|
} catch (error) {
|
|
4884
|
-
|
|
4885
|
-
if (signal?.aborted) throw error;
|
|
4886
|
-
if (attempt < DURABLE_ATTEMPTS) await new Promise((settle) => setTimeout(settle, 1e3 * 4 ** (attempt - 1)));
|
|
4887
|
-
} finally {
|
|
4888
|
-
watch.done();
|
|
4889
|
-
await chunks.return(void 0);
|
|
5012
|
+
if (!isMissingPath(error)) throw error;
|
|
4890
5013
|
}
|
|
4891
5014
|
}
|
|
4892
|
-
|
|
4893
|
-
};
|
|
4894
|
-
|
|
4895
|
-
|
|
4896
|
-
|
|
4897
|
-
|
|
4898
|
-
|
|
4899
|
-
|
|
4900
|
-
|
|
4901
|
-
|
|
4902
|
-
|
|
5015
|
+
return objects;
|
|
5016
|
+
})).flat();
|
|
5017
|
+
}
|
|
5018
|
+
/**
|
|
5019
|
+
* The state offload passes share, with the passes that read and write it.
|
|
5020
|
+
*
|
|
5021
|
+
* A pass after the first reads only the objects this process wrote without a receipt: a full
|
|
5022
|
+
* pass lists every object of the store and reads its receipt, and on canary
|
|
5023
|
+
* test-acceptance-canary-20261019q two of them per capture (the Lab offloads before and after)
|
|
5024
|
+
* took 60.1-63.2 s on each native copy, on a store whose puts had already uploaded their own
|
|
5025
|
+
* objects. The first pass of every process is full, so objects an earlier process left on disk
|
|
5026
|
+
* without a receipt are always found.
|
|
5027
|
+
*/
|
|
5028
|
+
var PrivateCasOffload = class {
|
|
5029
|
+
layout;
|
|
5030
|
+
lastPass;
|
|
5031
|
+
localIndex = /* @__PURE__ */ new Map();
|
|
5032
|
+
undurable = /* @__PURE__ */ new Set();
|
|
5033
|
+
copier;
|
|
5034
|
+
passes = Promise.resolve();
|
|
5035
|
+
waiting;
|
|
5036
|
+
constructor(layout) {
|
|
5037
|
+
this.layout = layout;
|
|
5038
|
+
this.copier = createDurableCopier(layout);
|
|
5039
|
+
}
|
|
5040
|
+
/** Make a just-written object durable, then run a pass unless the last one proves it needs none. */
|
|
5041
|
+
async settlePut(put, signal) {
|
|
5042
|
+
const { sha, byteLength, linked } = put;
|
|
5043
|
+
const { layout } = this;
|
|
5044
|
+
if (!await hasReceipt(layout, sha)) await this.copier.copyOnce(sha, put.bytes === void 0 ? {
|
|
5045
|
+
path: join(layout.store, sha.slice(0, 2), sha),
|
|
5046
|
+
byteLength
|
|
5047
|
+
} : { bytes: put.bytes }, signal).catch(() => void 0);
|
|
5048
|
+
const durableNow = await hasReceipt(layout, sha);
|
|
5049
|
+
if (linked) this.localIndex.set(sha, byteLength);
|
|
5050
|
+
if (durableNow) this.undurable.delete(sha);
|
|
5051
|
+
else this.undurable.add(sha);
|
|
4903
5052
|
const added = linked ? byteLength : 0;
|
|
4904
|
-
const known = lastPass;
|
|
4905
|
-
if (durableNow && known !== void 0 && known.pending === 0 && (localBudgetBytes === void 0 || known.localBytes + added <= localBudgetBytes)) {
|
|
5053
|
+
const known = this.lastPass;
|
|
5054
|
+
if (durableNow && known !== void 0 && known.pending === 0 && (layout.localBudgetBytes === void 0 || known.localBytes + added <= layout.localBudgetBytes)) {
|
|
4906
5055
|
known.localBytes += added;
|
|
4907
5056
|
return;
|
|
4908
5057
|
}
|
|
4909
|
-
await offload(signal);
|
|
4910
|
-
}
|
|
4911
|
-
|
|
4912
|
-
|
|
4913
|
-
|
|
4914
|
-
|
|
4915
|
-
|
|
4916
|
-
|
|
4917
|
-
|
|
4918
|
-
|
|
4919
|
-
|
|
4920
|
-
}
|
|
4921
|
-
const current = copy();
|
|
4922
|
-
uploading.set(sha, current);
|
|
4923
|
-
try {
|
|
4924
|
-
await current;
|
|
4925
|
-
return true;
|
|
4926
|
-
} finally {
|
|
4927
|
-
if (uploading.get(sha) === current) uploading.delete(sha);
|
|
5058
|
+
await this.offload(signal);
|
|
5059
|
+
}
|
|
5060
|
+
offload(signal) {
|
|
5061
|
+
let pass = this.waiting;
|
|
5062
|
+
if (pass === void 0) {
|
|
5063
|
+
pass = this.passes.then(() => {
|
|
5064
|
+
this.waiting = void 0;
|
|
5065
|
+
return this.sweep();
|
|
5066
|
+
});
|
|
5067
|
+
this.waiting = pass;
|
|
5068
|
+
this.passes = pass.catch(() => void 0);
|
|
4928
5069
|
}
|
|
4929
|
-
|
|
4930
|
-
|
|
5070
|
+
const shared = pass;
|
|
5071
|
+
return signal === void 0 ? shared : runAbortable(() => shared, signal, "private CAS offload wait was cancelled");
|
|
5072
|
+
}
|
|
5073
|
+
indexedBytes() {
|
|
5074
|
+
let total = 0;
|
|
5075
|
+
for (const size of this.localIndex.values()) total += size;
|
|
5076
|
+
return total;
|
|
5077
|
+
}
|
|
5078
|
+
async undurableObjects() {
|
|
4931
5079
|
const objects = [];
|
|
4932
|
-
for (const hex of [...undurable]) {
|
|
4933
|
-
const path = join(store, hex.slice(0, 2), hex);
|
|
5080
|
+
for (const hex of [...this.undurable]) {
|
|
5081
|
+
const path = join(this.layout.store, hex.slice(0, 2), hex);
|
|
4934
5082
|
try {
|
|
4935
5083
|
const entry = await lstat(path);
|
|
4936
5084
|
if (entry.isFile()) objects.push({
|
|
@@ -4940,30 +5088,92 @@ function createPrivateCasArtifactPort(root, namespace, options = {}) {
|
|
|
4940
5088
|
modifiedMs: entry.mtimeMs
|
|
4941
5089
|
});
|
|
4942
5090
|
} catch (error) {
|
|
4943
|
-
if (!
|
|
4944
|
-
undurable.delete(hex);
|
|
5091
|
+
if (!isMissingPath(error)) throw error;
|
|
5092
|
+
this.undurable.delete(hex);
|
|
4945
5093
|
}
|
|
4946
5094
|
}
|
|
4947
5095
|
return objects;
|
|
4948
|
-
}
|
|
4949
|
-
|
|
4950
|
-
|
|
4951
|
-
|
|
4952
|
-
|
|
4953
|
-
|
|
4954
|
-
|
|
5096
|
+
}
|
|
5097
|
+
async sweep() {
|
|
5098
|
+
const { durable, localBudgetBytes } = this.layout;
|
|
5099
|
+
const full = this.lastPass === void 0 || durable === void 0 || localBudgetBytes !== void 0 && this.indexedBytes() > localBudgetBytes;
|
|
5100
|
+
const objects = full ? await listLocalObjects(this.layout) : await this.undurableObjects();
|
|
5101
|
+
const outcomes = await this.copyObjects(objects);
|
|
5102
|
+
const pending = [];
|
|
5103
|
+
let uploaded = 0;
|
|
5104
|
+
const durableHex = /* @__PURE__ */ new Set();
|
|
5105
|
+
for (const outcome of outcomes) if (outcome.durable) {
|
|
5106
|
+
durableHex.add(outcome.hex);
|
|
5107
|
+
if (outcome.copied) uploaded++;
|
|
5108
|
+
} else if (outcome.error !== void 0) pending.push({
|
|
5109
|
+
digest: `sha256:${outcome.hex}`,
|
|
5110
|
+
error: outcome.error
|
|
5111
|
+
});
|
|
5112
|
+
for (const outcome of outcomes) if (outcome.durable) this.undurable.delete(outcome.hex);
|
|
5113
|
+
else if (durable !== void 0) this.undurable.add(outcome.hex);
|
|
5114
|
+
if (!full) {
|
|
5115
|
+
const localBytes = this.indexedBytes();
|
|
5116
|
+
this.lastPass = {
|
|
5117
|
+
localBytes,
|
|
5118
|
+
pending: pending.length
|
|
5119
|
+
};
|
|
5120
|
+
return {
|
|
5121
|
+
durable: await receiptCount(this.layout),
|
|
5122
|
+
uploaded,
|
|
5123
|
+
pending,
|
|
5124
|
+
evicted: 0,
|
|
5125
|
+
localObjects: this.localIndex.size,
|
|
5126
|
+
localBytes
|
|
5127
|
+
};
|
|
4955
5128
|
}
|
|
4956
|
-
|
|
4957
|
-
|
|
4958
|
-
|
|
4959
|
-
|
|
4960
|
-
|
|
4961
|
-
|
|
5129
|
+
this.localIndex.clear();
|
|
5130
|
+
for (const object of objects) this.localIndex.set(object.hex, object.size);
|
|
5131
|
+
const total = objects.reduce((sum, object) => sum + object.size, 0);
|
|
5132
|
+
const { evicted, localBytes } = await this.evictOldest(objects, durableHex, total);
|
|
5133
|
+
this.lastPass = {
|
|
5134
|
+
localBytes,
|
|
5135
|
+
pending: pending.length
|
|
5136
|
+
};
|
|
5137
|
+
return {
|
|
5138
|
+
durable: await receiptCount(this.layout),
|
|
5139
|
+
uploaded,
|
|
5140
|
+
pending,
|
|
5141
|
+
evicted,
|
|
5142
|
+
localObjects: objects.length - evicted,
|
|
5143
|
+
localBytes
|
|
5144
|
+
};
|
|
5145
|
+
}
|
|
5146
|
+
async evictOldest(objects, durableHex, totalBytes) {
|
|
5147
|
+
const { localBudgetBytes } = this.layout;
|
|
5148
|
+
let localBytes = totalBytes;
|
|
5149
|
+
let evicted = 0;
|
|
5150
|
+
if (localBudgetBytes === void 0) return {
|
|
5151
|
+
evicted,
|
|
5152
|
+
localBytes
|
|
5153
|
+
};
|
|
5154
|
+
for (const object of [...objects].sort((a, b) => a.modifiedMs - b.modifiedMs)) {
|
|
5155
|
+
if (localBytes <= localBudgetBytes) break;
|
|
5156
|
+
if (!durableHex.has(object.hex) || !await hasReceipt(this.layout, object.hex)) continue;
|
|
5157
|
+
try {
|
|
5158
|
+
await unlink(object.path);
|
|
5159
|
+
} catch (error) {
|
|
5160
|
+
if (!isMissingPath(error)) throw error;
|
|
5161
|
+
}
|
|
5162
|
+
localBytes -= object.size;
|
|
5163
|
+
this.localIndex.delete(object.hex);
|
|
5164
|
+
evicted++;
|
|
5165
|
+
}
|
|
5166
|
+
return {
|
|
5167
|
+
evicted,
|
|
5168
|
+
localBytes
|
|
5169
|
+
};
|
|
5170
|
+
}
|
|
5171
|
+
async copyObjects(objects) {
|
|
5172
|
+
const { durable } = this.layout;
|
|
4962
5173
|
let refusal;
|
|
4963
|
-
|
|
4964
|
-
|
|
4965
|
-
if (
|
|
4966
|
-
if (await hasReceipt(hex)) return {
|
|
5174
|
+
return mapWithConcurrency(objects, durable === void 0 || durable.putStream && durable.getStream ? OFFLOAD_COPIES_AT_ONCE : 1, async ({ hex, path, size }) => {
|
|
5175
|
+
await this.copier.waitForCopy(hex);
|
|
5176
|
+
if (await hasReceipt(this.layout, hex)) return {
|
|
4967
5177
|
hex,
|
|
4968
5178
|
durable: true,
|
|
4969
5179
|
copied: false
|
|
@@ -4982,14 +5192,17 @@ function createPrivateCasArtifactPort(root, namespace, options = {}) {
|
|
|
4982
5192
|
if (durable.putStream && durable.getStream) return {
|
|
4983
5193
|
hex,
|
|
4984
5194
|
durable: true,
|
|
4985
|
-
copied: await copyOnce(hex,
|
|
5195
|
+
copied: await this.copier.copyOnce(hex, {
|
|
5196
|
+
path,
|
|
5197
|
+
byteLength: size
|
|
5198
|
+
})
|
|
4986
5199
|
};
|
|
4987
5200
|
const bytes = await readLocal(path);
|
|
4988
5201
|
if (bytes === void 0) return {
|
|
4989
5202
|
hex,
|
|
4990
5203
|
durable: false
|
|
4991
5204
|
};
|
|
4992
|
-
if (
|
|
5205
|
+
if (casSha256(bytes) !== digest) return {
|
|
4993
5206
|
hex,
|
|
4994
5207
|
durable: false,
|
|
4995
5208
|
error: "local object failed digest verification; it stays on the host"
|
|
@@ -4997,199 +5210,206 @@ function createPrivateCasArtifactPort(root, namespace, options = {}) {
|
|
|
4997
5210
|
return {
|
|
4998
5211
|
hex,
|
|
4999
5212
|
durable: true,
|
|
5000
|
-
copied: await copyOnce(hex,
|
|
5213
|
+
copied: await this.copier.copyOnce(hex, { bytes })
|
|
5001
5214
|
};
|
|
5002
5215
|
} catch (error) {
|
|
5003
|
-
|
|
5004
|
-
refusal ??= message(error);
|
|
5216
|
+
refusal ??= errorMessage(error);
|
|
5005
5217
|
return {
|
|
5006
5218
|
hex,
|
|
5007
5219
|
durable: false,
|
|
5008
|
-
error:
|
|
5220
|
+
error: errorMessage(error)
|
|
5009
5221
|
};
|
|
5010
5222
|
}
|
|
5011
5223
|
});
|
|
5012
|
-
|
|
5013
|
-
|
|
5014
|
-
|
|
5015
|
-
|
|
5016
|
-
|
|
5017
|
-
|
|
5018
|
-
|
|
5019
|
-
|
|
5020
|
-
|
|
5021
|
-
|
|
5022
|
-
|
|
5023
|
-
|
|
5024
|
-
|
|
5025
|
-
|
|
5026
|
-
|
|
5027
|
-
|
|
5028
|
-
|
|
5029
|
-
|
|
5030
|
-
|
|
5031
|
-
|
|
5032
|
-
|
|
5033
|
-
|
|
5034
|
-
|
|
5035
|
-
|
|
5036
|
-
|
|
5037
|
-
|
|
5038
|
-
|
|
5039
|
-
}
|
|
5040
|
-
|
|
5041
|
-
|
|
5042
|
-
|
|
5043
|
-
|
|
5044
|
-
|
|
5045
|
-
|
|
5046
|
-
|
|
5047
|
-
|
|
5048
|
-
|
|
5049
|
-
|
|
5050
|
-
|
|
5051
|
-
|
|
5052
|
-
|
|
5053
|
-
|
|
5054
|
-
|
|
5055
|
-
|
|
5056
|
-
|
|
5057
|
-
|
|
5058
|
-
|
|
5059
|
-
|
|
5060
|
-
|
|
5061
|
-
durable: await receiptCount(),
|
|
5062
|
-
uploaded,
|
|
5063
|
-
pending,
|
|
5064
|
-
evicted,
|
|
5065
|
-
localObjects: objects.length - evicted,
|
|
5066
|
-
localBytes
|
|
5067
|
-
};
|
|
5068
|
-
};
|
|
5069
|
-
let passes = Promise.resolve();
|
|
5070
|
-
let waiting;
|
|
5071
|
-
const offload = (signal) => {
|
|
5072
|
-
let pass = waiting;
|
|
5073
|
-
if (pass === void 0) {
|
|
5074
|
-
pass = passes.then(() => {
|
|
5075
|
-
waiting = void 0;
|
|
5076
|
-
return sweep();
|
|
5077
|
-
});
|
|
5078
|
-
waiting = pass;
|
|
5079
|
-
passes = pass.catch(() => void 0);
|
|
5080
|
-
}
|
|
5081
|
-
const shared = pass;
|
|
5082
|
-
return signal === void 0 ? shared : runAbortable(() => shared, signal, "private CAS offload wait was cancelled");
|
|
5083
|
-
};
|
|
5084
|
-
const verified = (bytes, ref) => {
|
|
5085
|
-
if (sha256(bytes) !== ref.sha256 || bytes.byteLength !== ref.byteLength) throw new Error("private CAS object failed digest or length verification");
|
|
5086
|
-
return bytes;
|
|
5224
|
+
}
|
|
5225
|
+
};
|
|
5226
|
+
const RECEIPT_SCHEMA = "agent-runtime.private-cas-durable-receipt.v1";
|
|
5227
|
+
const DURABLE_ATTEMPTS = 3;
|
|
5228
|
+
const BUFFERED_BYTES_PER_MS = 256;
|
|
5229
|
+
async function hasReceipt(layout, hex) {
|
|
5230
|
+
try {
|
|
5231
|
+
return (await lstat(join(layout.receipts, hex))).isFile();
|
|
5232
|
+
} catch (error) {
|
|
5233
|
+
if (isMissingPath(error)) return false;
|
|
5234
|
+
throw error;
|
|
5235
|
+
}
|
|
5236
|
+
}
|
|
5237
|
+
async function writeReceipt(layout, digest, byteLength) {
|
|
5238
|
+
const { receipts } = layout;
|
|
5239
|
+
await checkDirectories(receipts, true);
|
|
5240
|
+
const hex = hexOf(digest);
|
|
5241
|
+
const temporary = join(receipts, `.${hex}.${randomUUID()}.tmp`);
|
|
5242
|
+
const file = await open(temporary, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, 384);
|
|
5243
|
+
try {
|
|
5244
|
+
await file.writeFile(`${JSON.stringify({
|
|
5245
|
+
schema: RECEIPT_SCHEMA,
|
|
5246
|
+
location: layout.durable.location,
|
|
5247
|
+
namespace: layout.namespace,
|
|
5248
|
+
digest,
|
|
5249
|
+
byteLength,
|
|
5250
|
+
storedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
5251
|
+
})}\n`);
|
|
5252
|
+
if (!layout.ephemeral) await syncHandle(file);
|
|
5253
|
+
} finally {
|
|
5254
|
+
await file.close();
|
|
5255
|
+
}
|
|
5256
|
+
await rename(temporary, join(receipts, hex));
|
|
5257
|
+
if (!layout.ephemeral) await syncDirectory(receipts);
|
|
5258
|
+
}
|
|
5259
|
+
function watchAttempt(idleMs, signal) {
|
|
5260
|
+
const stall = new AbortController();
|
|
5261
|
+
let timer;
|
|
5262
|
+
const touch = () => {
|
|
5263
|
+
clearTimeout(timer);
|
|
5264
|
+
timer = setTimeout(() => stall.abort(/* @__PURE__ */ new Error(`durable copy moved no bytes for ${idleMs} ms`)), idleMs);
|
|
5265
|
+
};
|
|
5266
|
+
touch();
|
|
5267
|
+
const attempt = signal === void 0 ? stall.signal : AbortSignal.any([signal, stall.signal]);
|
|
5268
|
+
return {
|
|
5269
|
+
signal: attempt,
|
|
5270
|
+
touch,
|
|
5271
|
+
run: (act) => runAbortable(act, attempt, "private CAS durable copy was cancelled"),
|
|
5272
|
+
done: () => clearTimeout(timer)
|
|
5087
5273
|
};
|
|
5088
|
-
|
|
5089
|
-
|
|
5274
|
+
}
|
|
5275
|
+
async function retryCopy(signal, attemptCopy) {
|
|
5276
|
+
let failure;
|
|
5277
|
+
for (let attempt = 1; attempt <= DURABLE_ATTEMPTS; attempt++) {
|
|
5090
5278
|
signal?.throwIfAborted();
|
|
5091
|
-
if (ref.locator.kind !== "private-cas" || ref.locator.namespace !== namespace || ref.locator.digest !== ref.sha256) throw new Error("private CAS locator does not match its digest and namespace");
|
|
5092
|
-
const path = destination(ref.sha256);
|
|
5093
5279
|
try {
|
|
5094
|
-
await
|
|
5095
|
-
yield* readObject(path, ref, signal);
|
|
5280
|
+
await attemptCopy();
|
|
5096
5281
|
return;
|
|
5097
5282
|
} catch (error) {
|
|
5098
|
-
|
|
5283
|
+
failure = error;
|
|
5284
|
+
if (signal?.aborted) throw error;
|
|
5285
|
+
if (attempt < DURABLE_ATTEMPTS) await new Promise((settle) => setTimeout(settle, 1e3 * 4 ** (attempt - 1)));
|
|
5099
5286
|
}
|
|
5100
|
-
if (!durable?.getStream) throw new Error(`private CAS object is not on this host; streaming durable reads are unavailable: ${ref.sha256}`);
|
|
5101
|
-
const chunks = await durable.getStream({
|
|
5102
|
-
namespace,
|
|
5103
|
-
digest: ref.sha256,
|
|
5104
|
-
signal
|
|
5105
|
-
});
|
|
5106
|
-
if (!chunks) throw new Error(`private CAS object is neither on this host nor in ${durable.location}: ${ref.sha256}`);
|
|
5107
|
-
yield* checkedChunks(chunks, ref, signal);
|
|
5108
5287
|
}
|
|
5109
|
-
|
|
5110
|
-
|
|
5111
|
-
|
|
5288
|
+
throw failure;
|
|
5289
|
+
}
|
|
5290
|
+
function storeDurably(layout, digest, bytes, signal) {
|
|
5291
|
+
return retryCopy(signal, async () => {
|
|
5292
|
+
const watch = watchAttempt(layout.durableStallMs + Math.ceil(bytes.byteLength / BUFFERED_BYTES_PER_MS), signal);
|
|
5112
5293
|
try {
|
|
5113
|
-
|
|
5114
|
-
|
|
5115
|
-
|
|
5116
|
-
|
|
5117
|
-
|
|
5118
|
-
})
|
|
5294
|
+
await watch.run(() => layout.durable.put({
|
|
5295
|
+
namespace: layout.namespace,
|
|
5296
|
+
digest,
|
|
5297
|
+
bytes,
|
|
5298
|
+
signal: watch.signal
|
|
5299
|
+
}));
|
|
5300
|
+
await writeReceipt(layout, digest, bytes.byteLength);
|
|
5119
5301
|
} finally {
|
|
5120
|
-
|
|
5302
|
+
watch.done();
|
|
5121
5303
|
}
|
|
5122
|
-
}
|
|
5123
|
-
|
|
5124
|
-
|
|
5125
|
-
|
|
5126
|
-
|
|
5127
|
-
|
|
5128
|
-
|
|
5129
|
-
|
|
5130
|
-
|
|
5131
|
-
|
|
5132
|
-
|
|
5133
|
-
|
|
5134
|
-
|
|
5135
|
-
if (`sha256:${hash.digest("hex")}` !== ref.sha256 || byteLength !== ref.byteLength) throw new Error("private CAS object failed digest or length verification");
|
|
5136
|
-
}
|
|
5137
|
-
async function putStream(input) {
|
|
5138
|
-
const { signal } = input;
|
|
5139
|
-
signal?.throwIfAborted();
|
|
5140
|
-
await checkDirectories(store, true);
|
|
5141
|
-
const temporary = join(store, `.${randomUUID()}.tmp`);
|
|
5304
|
+
});
|
|
5305
|
+
}
|
|
5306
|
+
function streamDurably(layout, digest, path, byteLength, signal) {
|
|
5307
|
+
const ref = privateCasRef(layout.namespace, digest, byteLength);
|
|
5308
|
+
return retryCopy(signal, async () => {
|
|
5309
|
+
const watch = watchAttempt(layout.durableStallMs, signal);
|
|
5310
|
+
const chunks = readObject(path, ref, watch.signal);
|
|
5311
|
+
const pulled = (async function* () {
|
|
5312
|
+
for await (const chunk of chunks) {
|
|
5313
|
+
watch.touch();
|
|
5314
|
+
yield chunk;
|
|
5315
|
+
}
|
|
5316
|
+
})();
|
|
5142
5317
|
try {
|
|
5143
|
-
|
|
5144
|
-
|
|
5145
|
-
|
|
5146
|
-
|
|
5147
|
-
|
|
5148
|
-
|
|
5149
|
-
|
|
5150
|
-
|
|
5151
|
-
|
|
5152
|
-
|
|
5153
|
-
|
|
5154
|
-
|
|
5155
|
-
|
|
5156
|
-
|
|
5157
|
-
|
|
5158
|
-
|
|
5318
|
+
await watch.run(() => layout.durable.putStream({
|
|
5319
|
+
namespace: layout.namespace,
|
|
5320
|
+
digest,
|
|
5321
|
+
byteLength,
|
|
5322
|
+
chunks: pulled,
|
|
5323
|
+
signal: watch.signal
|
|
5324
|
+
}));
|
|
5325
|
+
await writeReceipt(layout, digest, byteLength);
|
|
5326
|
+
} finally {
|
|
5327
|
+
watch.done();
|
|
5328
|
+
await chunks.return(void 0);
|
|
5329
|
+
}
|
|
5330
|
+
});
|
|
5331
|
+
}
|
|
5332
|
+
/**
|
|
5333
|
+
* The one copy of each digest to the durable store in flight, from a put or a pass. A put or pass
|
|
5334
|
+
* that finds its digest in flight waits for that copy, then reads the receipt. Copying it again
|
|
5335
|
+
* doubles the upload and, for a buffered store, the archive in memory. Reporting it pending
|
|
5336
|
+
* failed captures: a capture refuses on any pending object, so two captures sharing one store
|
|
5337
|
+
* refused each other (discovery-lab provider-retention, agent-runtime 0.325.6, 2026-10-18).
|
|
5338
|
+
* A copy never waits for a pass, so a pass may wait for a copy.
|
|
5339
|
+
*/
|
|
5340
|
+
function createDurableCopier(layout) {
|
|
5341
|
+
const uploading = /* @__PURE__ */ new Map();
|
|
5342
|
+
const waitFor = (copy, signal) => {
|
|
5343
|
+
const settled = copy.catch(() => void 0);
|
|
5344
|
+
return signal === void 0 ? settled : runAbortable(() => settled, signal, "private CAS wait for a copy in flight was cancelled");
|
|
5345
|
+
};
|
|
5346
|
+
return {
|
|
5347
|
+
async waitForCopy(hex, signal) {
|
|
5348
|
+
const inflight = uploading.get(hex);
|
|
5349
|
+
if (inflight !== void 0) await waitFor(inflight, signal);
|
|
5350
|
+
},
|
|
5351
|
+
async copyOnce(hex, source, signal) {
|
|
5352
|
+
for (let inflight = uploading.get(hex); inflight !== void 0; inflight = uploading.get(hex)) {
|
|
5353
|
+
await waitFor(inflight, signal);
|
|
5354
|
+
if (await hasReceipt(layout, hex)) return false;
|
|
5159
5355
|
}
|
|
5160
|
-
const
|
|
5161
|
-
const
|
|
5162
|
-
|
|
5163
|
-
locator: {
|
|
5164
|
-
kind: "private-cas",
|
|
5165
|
-
namespace,
|
|
5166
|
-
digest
|
|
5167
|
-
},
|
|
5168
|
-
sha256: digest,
|
|
5169
|
-
byteLength
|
|
5170
|
-
};
|
|
5171
|
-
for await (const _chunk of readObject(temporary, ref, signal));
|
|
5172
|
-
const path = destination(digest);
|
|
5173
|
-
const directory = join(store, sha.slice(0, 2));
|
|
5174
|
-
await checkDirectories(directory, true);
|
|
5175
|
-
signal?.throwIfAborted();
|
|
5176
|
-
let linked = true;
|
|
5356
|
+
const digest = `sha256:${hex}`;
|
|
5357
|
+
const current = "bytes" in source ? storeDurably(layout, digest, source.bytes, signal) : streamDurably(layout, digest, source.path, source.byteLength, signal);
|
|
5358
|
+
uploading.set(hex, current);
|
|
5177
5359
|
try {
|
|
5178
|
-
await
|
|
5179
|
-
|
|
5180
|
-
|
|
5181
|
-
|
|
5182
|
-
for await (const _chunk of readStream(ref, { signal }));
|
|
5360
|
+
await current;
|
|
5361
|
+
return true;
|
|
5362
|
+
} finally {
|
|
5363
|
+
if (uploading.get(hex) === current) uploading.delete(hex);
|
|
5183
5364
|
}
|
|
5184
|
-
await syncDirectory(directory);
|
|
5185
|
-
if (durable !== void 0) await settlePut(sha, byteLength, linked, () => streamDurably(digest, path, byteLength, signal), signal);
|
|
5186
|
-
return ref;
|
|
5187
|
-
} finally {
|
|
5188
|
-
await unlink(temporary).catch((error) => {
|
|
5189
|
-
if (error.code !== "ENOENT") throw error;
|
|
5190
|
-
});
|
|
5191
5365
|
}
|
|
5192
|
-
}
|
|
5366
|
+
};
|
|
5367
|
+
}
|
|
5368
|
+
//#endregion
|
|
5369
|
+
//#region src/runtime/private-cas.ts
|
|
5370
|
+
const SAFE_NAMESPACE = /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,127}$/u;
|
|
5371
|
+
const DURABLE_STALL_MS = 12e4;
|
|
5372
|
+
/**
|
|
5373
|
+
* Private content-addressed storage scoped to one recorded run identity.
|
|
5374
|
+
*
|
|
5375
|
+
* Bytes are written to the host first. With a durable store, every object is also copied off the
|
|
5376
|
+
* host as it is written, and `localBudgetBytes` bounds what the host keeps: an object's local copy
|
|
5377
|
+
* is deleted only after its durable receipt exists, and reads fall back to the durable store.
|
|
5378
|
+
*
|
|
5379
|
+
* An object's name is published only after its bytes are synced, and that order is what makes a
|
|
5380
|
+
* name trustworthy. A put whose digest already exists links nothing: it re-reads the existing
|
|
5381
|
+
* object and refuses on a mismatch, and no later put replaces that file. So a deferred-sync mode
|
|
5382
|
+
* that published names before one batch sync would, after a crash, leave a torn object under its
|
|
5383
|
+
* valid name, and every later put of that digest would fail. Any batching must keep the order:
|
|
5384
|
+
* write under temporary names, sync, publish the names, sync again (discovery-lab
|
|
5385
|
+
* runner/evidence-store.mjs commits receipts this way). On drew-gtr-pro's USB SMR disk on
|
|
5386
|
+
* 2026-10-21 these per-object fsyncs, 545 ms each on the 16 SYNC_THREADS, set a store's rate at
|
|
5387
|
+
* about 26 files/s.
|
|
5388
|
+
*
|
|
5389
|
+
* The one exception is an `ephemeral` store, which syncs nothing: its root is discarded with its
|
|
5390
|
+
* process, so no name in it is ever read after a crash, and nothing durable may be derived from it
|
|
5391
|
+
* but by a caller that copies and syncs what it keeps.
|
|
5392
|
+
*/
|
|
5393
|
+
function createPrivateCasArtifactPort(root, namespace, options = {}) {
|
|
5394
|
+
if (!isAbsolute(root)) throw new Error("private CAS root must be absolute");
|
|
5395
|
+
if (!SAFE_NAMESPACE.test(namespace) || namespace === "." || namespace === "..") throw new Error("private CAS namespace must be one safe identifier");
|
|
5396
|
+
const { durable, localBudgetBytes, durableStallMs = DURABLE_STALL_MS, ephemeral = false } = options;
|
|
5397
|
+
if (!Number.isSafeInteger(durableStallMs) || durableStallMs <= 0) throw new Error("private CAS durableStallMs must be a positive integer");
|
|
5398
|
+
if (typeof ephemeral !== "boolean") throw new Error("private CAS ephemeral must be a boolean");
|
|
5399
|
+
if (ephemeral && durable === void 0) throw new Error("private CAS ephemeral needs a durable store");
|
|
5400
|
+
if (localBudgetBytes !== void 0 && (durable === void 0 || !Number.isSafeInteger(localBudgetBytes) || localBudgetBytes < 0)) throw new Error("private CAS localBudgetBytes needs a durable store and a non-negative integer");
|
|
5401
|
+
const layout = {
|
|
5402
|
+
namespace,
|
|
5403
|
+
store: join(resolve(root), "sha256"),
|
|
5404
|
+
receipts: join(resolve(root), "durable", "sha256"),
|
|
5405
|
+
durable,
|
|
5406
|
+
localBudgetBytes,
|
|
5407
|
+
durableStallMs,
|
|
5408
|
+
ephemeral
|
|
5409
|
+
};
|
|
5410
|
+
const offload = new PrivateCasOffload(layout);
|
|
5411
|
+
const readStream = (ref, options) => readPrivateCasStream(layout, ref, options);
|
|
5412
|
+
const putStream = (input) => putPrivateCasStream(layout, offload, input);
|
|
5193
5413
|
const port = {
|
|
5194
5414
|
...durable === void 0 || durable.putStream && durable.getStream ? {
|
|
5195
5415
|
putStream,
|
|
@@ -5197,97 +5417,17 @@ function createPrivateCasArtifactPort(root, namespace, options = {}) {
|
|
|
5197
5417
|
} : {},
|
|
5198
5418
|
async put({ bytes, signal }) {
|
|
5199
5419
|
signal?.throwIfAborted();
|
|
5200
|
-
const digest =
|
|
5201
|
-
return
|
|
5202
|
-
|
|
5203
|
-
|
|
5204
|
-
|
|
5205
|
-
|
|
5206
|
-
async locate(object, options = {}) {
|
|
5207
|
-
options.signal?.throwIfAborted();
|
|
5208
|
-
const digest = object.sha256;
|
|
5209
|
-
const hex = hexOf(digest);
|
|
5210
|
-
const ref = {
|
|
5211
|
-
locator: {
|
|
5212
|
-
kind: "private-cas",
|
|
5213
|
-
namespace,
|
|
5214
|
-
digest
|
|
5215
|
-
},
|
|
5216
|
-
sha256: digest,
|
|
5217
|
-
byteLength: object.byteLength
|
|
5218
|
-
};
|
|
5219
|
-
try {
|
|
5220
|
-
await checkDirectories(join(store, hex.slice(0, 2)), false);
|
|
5221
|
-
const local = await lstat(destination(digest));
|
|
5222
|
-
if (local.isFile()) return local.size === object.byteLength ? ref : void 0;
|
|
5223
|
-
} catch (error) {
|
|
5224
|
-
if (!missing(error)) throw error;
|
|
5225
|
-
}
|
|
5226
|
-
if (durable === void 0) return void 0;
|
|
5227
|
-
const receipt = await readLocal(join(receipts, hex));
|
|
5228
|
-
if (receipt === void 0) return void 0;
|
|
5229
|
-
return JSON.parse(Buffer.from(receipt).toString("utf8")).byteLength === object.byteLength ? ref : void 0;
|
|
5420
|
+
const digest = casSha256(bytes);
|
|
5421
|
+
return putPrivateCasBuffered(layout, offload, {
|
|
5422
|
+
bytes,
|
|
5423
|
+
digest,
|
|
5424
|
+
sha: hexOf(digest)
|
|
5425
|
+
}, signal);
|
|
5230
5426
|
},
|
|
5231
|
-
|
|
5427
|
+
read: (ref) => readPrivateCasRef(layout, ref),
|
|
5428
|
+
locate: (object, options) => locatePrivateCasObject(layout, object, options),
|
|
5429
|
+
offload: (signal) => offload.offload(signal)
|
|
5232
5430
|
};
|
|
5233
|
-
async function putBuffered(bytes, digest, sha, signal) {
|
|
5234
|
-
const path = destination(digest);
|
|
5235
|
-
const directory = join(store, sha.slice(0, 2));
|
|
5236
|
-
await checkDirectories(directory, true);
|
|
5237
|
-
const temporary = join(directory, `.${sha}.${randomUUID()}.tmp`);
|
|
5238
|
-
const file = await open(temporary, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, 384);
|
|
5239
|
-
try {
|
|
5240
|
-
await file.writeFile(bytes);
|
|
5241
|
-
await syncHandle(file);
|
|
5242
|
-
} finally {
|
|
5243
|
-
await file.close();
|
|
5244
|
-
}
|
|
5245
|
-
let linked = true;
|
|
5246
|
-
try {
|
|
5247
|
-
signal?.throwIfAborted();
|
|
5248
|
-
try {
|
|
5249
|
-
await link(temporary, path);
|
|
5250
|
-
} catch (error) {
|
|
5251
|
-
if (error.code !== "EEXIST") throw error;
|
|
5252
|
-
linked = false;
|
|
5253
|
-
}
|
|
5254
|
-
await syncDirectory(directory);
|
|
5255
|
-
} finally {
|
|
5256
|
-
await unlink(temporary).catch(() => void 0);
|
|
5257
|
-
}
|
|
5258
|
-
const ref = {
|
|
5259
|
-
locator: {
|
|
5260
|
-
kind: "private-cas",
|
|
5261
|
-
namespace,
|
|
5262
|
-
digest
|
|
5263
|
-
},
|
|
5264
|
-
sha256: digest,
|
|
5265
|
-
byteLength: bytes.byteLength
|
|
5266
|
-
};
|
|
5267
|
-
await readRef(ref);
|
|
5268
|
-
if (durable !== void 0) await settlePut(sha, bytes.byteLength, linked, () => storeDurably(digest, bytes, signal), signal);
|
|
5269
|
-
return ref;
|
|
5270
|
-
}
|
|
5271
|
-
async function readRef(ref) {
|
|
5272
|
-
if (ref.locator.kind !== "private-cas" || ref.locator.namespace !== namespace || ref.locator.digest !== ref.sha256) throw new Error("private CAS locator does not match its digest and namespace");
|
|
5273
|
-
const path = destination(ref.sha256);
|
|
5274
|
-
const shard = join(store, hexOf(ref.sha256).slice(0, 2));
|
|
5275
|
-
let local;
|
|
5276
|
-
try {
|
|
5277
|
-
await checkDirectories(shard, false);
|
|
5278
|
-
local = await readLocal(path);
|
|
5279
|
-
} catch (error) {
|
|
5280
|
-
if (!missing(error)) throw error;
|
|
5281
|
-
}
|
|
5282
|
-
if (local !== void 0) return verified(local, ref);
|
|
5283
|
-
if (durable === void 0) throw new Error(`private CAS object is not on this host: ${ref.sha256}`);
|
|
5284
|
-
const stored = await durable.get({
|
|
5285
|
-
namespace,
|
|
5286
|
-
digest: ref.sha256
|
|
5287
|
-
});
|
|
5288
|
-
if (stored === void 0) throw new Error(`private CAS object is neither on this host nor in ${durable.location}: ${ref.sha256}`);
|
|
5289
|
-
return verified(stored, ref);
|
|
5290
|
-
}
|
|
5291
5431
|
if (durable === void 0) markPutStreamVerified(putStream, readStream);
|
|
5292
5432
|
return port;
|
|
5293
5433
|
}
|
|
@@ -6040,30 +6180,169 @@ function hashText(value) {
|
|
|
6040
6180
|
return (hash >>> 0).toString(16).padStart(8, "0");
|
|
6041
6181
|
}
|
|
6042
6182
|
//#endregion
|
|
6043
|
-
//#region src/runtime/strategy-evolution.ts
|
|
6183
|
+
//#region src/runtime/strategy-evolution-cells.ts
|
|
6044
6184
|
/**
|
|
6045
|
-
*
|
|
6046
|
-
*
|
|
6047
|
-
*
|
|
6048
|
-
* train results of the parent the policy chose. The default policy is `beam({ width: 2 })`
|
|
6049
|
-
* and the default allocator `asha()`, so a strategy is screened on a few selection tasks and
|
|
6050
|
-
* earns more tasks only by ranking well. Each cell is one `runAgentic` run of one strategy on
|
|
6051
|
-
* one task, scored by the environment's own check.
|
|
6052
|
-
*
|
|
6053
|
-
* Measurement follows the kernel's splits. The author reads train results only; the policy
|
|
6054
|
-
* and allocator rank on the private selection split; the claim runs the root and at most 3
|
|
6055
|
-
* finalists together on the sealed test split, once, with a power check and a Bonferroni
|
|
6056
|
-
* decision. The report is a projection of the ledger, and the ledger is the only checkpoint:
|
|
6057
|
-
* calling again with the same inputs continues an interrupted search and returns a finished
|
|
6058
|
-
* one without running anything.
|
|
6059
|
-
*
|
|
6060
|
-
* @experimental
|
|
6185
|
+
* Cells run in this process on one dedicated lane of Runtime's search executor, which
|
|
6186
|
+
* records each finished attempt under its run id (a restarted search adopts it instead of
|
|
6187
|
+
* running it again), meters it through the search's cost ledger, and traces it.
|
|
6061
6188
|
*/
|
|
6062
|
-
|
|
6063
|
-
|
|
6064
|
-
|
|
6065
|
-
|
|
6066
|
-
|
|
6189
|
+
async function openStrategyCellExecutor(cfg, plan, strategyOf) {
|
|
6190
|
+
const storage = fsCampaignStorage();
|
|
6191
|
+
const costLedger = createRunCostLedger({
|
|
6192
|
+
storage,
|
|
6193
|
+
runDir: plan.searchDir
|
|
6194
|
+
});
|
|
6195
|
+
const lanes = [dedicatedLane({
|
|
6196
|
+
name: "in-process",
|
|
6197
|
+
capacity: plan.concurrency,
|
|
6198
|
+
cellUsd: plan.cellUsd
|
|
6199
|
+
})];
|
|
6200
|
+
await reconcileInterruptedSearchCalls(costLedger, plan.searchId, lanes);
|
|
6201
|
+
return searchExecutor({
|
|
6202
|
+
lanes,
|
|
6203
|
+
profileOf: () => cfg.worker.workerProfile,
|
|
6204
|
+
costLedger,
|
|
6205
|
+
identity: plan.execution,
|
|
6206
|
+
storage,
|
|
6207
|
+
dir: plan.searchDir,
|
|
6208
|
+
...cfg.workerSlots ? { workerSlots: cfg.workerSlots } : {},
|
|
6209
|
+
runAttempt: ({ work, lane, costLedger: cellLedger }) => runStrategyCell(cfg, plan, {
|
|
6210
|
+
work,
|
|
6211
|
+
lane,
|
|
6212
|
+
costLedger: cellLedger
|
|
6213
|
+
}, strategyOf)
|
|
6214
|
+
});
|
|
6215
|
+
}
|
|
6216
|
+
async function runStrategyCell(cfg, plan, attempt, strategyOf) {
|
|
6217
|
+
const { work, lane, costLedger: cellLedger } = attempt;
|
|
6218
|
+
const { workerModel } = plan;
|
|
6219
|
+
if (work.artifact.kind === "authored" && work.artifact.refusal !== null) throw new Error(`evolution: refused node ${work.nodeId} was given a cell`);
|
|
6220
|
+
const strategy = await strategyOf(work.artifact);
|
|
6221
|
+
const task = plan.taskById.get(work.taskId);
|
|
6222
|
+
if (!task) throw new Error(`evolution: cell ${work.cellId} names unknown task ${work.taskId}`);
|
|
6223
|
+
const faults = [];
|
|
6224
|
+
const environment = faultRecording(cfg.environment, faults);
|
|
6225
|
+
const paid = await cellLedger.runPaidCall({
|
|
6226
|
+
channel: "agent",
|
|
6227
|
+
phase: `search.${work.stage}`,
|
|
6228
|
+
actor: `strategy:${strategy.name}`,
|
|
6229
|
+
model: workerModel,
|
|
6230
|
+
signal: work.signal,
|
|
6231
|
+
execute: () => runAgentic({
|
|
6232
|
+
...cfg.worker,
|
|
6233
|
+
surface: environment,
|
|
6234
|
+
task,
|
|
6235
|
+
strategy,
|
|
6236
|
+
budget: plan.budget,
|
|
6237
|
+
...cfg.hooks ? { hooks: cfg.hooks } : {}
|
|
6238
|
+
}),
|
|
6239
|
+
receipt: (run) => ({
|
|
6240
|
+
model: workerModel,
|
|
6241
|
+
inputTokens: run.tokens.input,
|
|
6242
|
+
outputTokens: run.tokens.output,
|
|
6243
|
+
...run.usdKnown ? { actualCostUsd: run.usd } : { costUnknown: true },
|
|
6244
|
+
...run.tokensKnown ? {} : { usageUnknown: true }
|
|
6245
|
+
}),
|
|
6246
|
+
receiptFromError: () => ({
|
|
6247
|
+
model: workerModel,
|
|
6248
|
+
inputTokens: 0,
|
|
6249
|
+
outputTokens: 0,
|
|
6250
|
+
costUnknown: true,
|
|
6251
|
+
usageUnknown: true
|
|
6252
|
+
})
|
|
6253
|
+
});
|
|
6254
|
+
const settled = {
|
|
6255
|
+
identity: plan.execution,
|
|
6256
|
+
placement: {
|
|
6257
|
+
lane: lane.name,
|
|
6258
|
+
boxId: null
|
|
6259
|
+
},
|
|
6260
|
+
accounting: unknownAccounting("replaced by the attempt receipts"),
|
|
6261
|
+
...paid.succeeded ? { wallMs: paid.value.ms } : {}
|
|
6262
|
+
};
|
|
6263
|
+
if (faults.length > 0) return {
|
|
6264
|
+
...settled,
|
|
6265
|
+
...environmentFault(faults)
|
|
6266
|
+
};
|
|
6267
|
+
if (!paid.succeeded) return {
|
|
6268
|
+
...settled,
|
|
6269
|
+
outcome: {
|
|
6270
|
+
status: "failed",
|
|
6271
|
+
score: 0,
|
|
6272
|
+
metrics: {},
|
|
6273
|
+
failure: {
|
|
6274
|
+
code: "strategy-threw",
|
|
6275
|
+
message: shareText(paid.error)
|
|
6276
|
+
}
|
|
6277
|
+
}
|
|
6278
|
+
};
|
|
6279
|
+
const run = paid.value;
|
|
6280
|
+
return {
|
|
6281
|
+
...settled,
|
|
6282
|
+
outcome: {
|
|
6283
|
+
status: "passed",
|
|
6284
|
+
score: run.score,
|
|
6285
|
+
metrics: {
|
|
6286
|
+
resolved: run.resolved ? 1 : 0,
|
|
6287
|
+
shots: run.shots
|
|
6288
|
+
}
|
|
6289
|
+
}
|
|
6290
|
+
};
|
|
6291
|
+
}
|
|
6292
|
+
/** The environment as one attempt sees it, recording each throw from `open`, `tools`,
|
|
6293
|
+
* `score` or `close`: those are the environment's faults. A `call` that throws is a tool
|
|
6294
|
+
* error the agent's loop handles. */
|
|
6295
|
+
function faultRecording(environment, faults) {
|
|
6296
|
+
const recorded = (name, method) => async (...args) => {
|
|
6297
|
+
try {
|
|
6298
|
+
return await method(...args);
|
|
6299
|
+
} catch (error) {
|
|
6300
|
+
faults.push(`${name}: ${shareText(error)}`);
|
|
6301
|
+
throw error;
|
|
6302
|
+
}
|
|
6303
|
+
};
|
|
6304
|
+
return {
|
|
6305
|
+
name: environment.name,
|
|
6306
|
+
open: recorded("open", (task) => environment.open(task)),
|
|
6307
|
+
tools: recorded("tools", (task, handle) => environment.tools(task, handle)),
|
|
6308
|
+
call: (handle, name, args) => environment.call(handle, name, args),
|
|
6309
|
+
score: recorded("score", (task, handle) => environment.score(task, handle)),
|
|
6310
|
+
close: recorded("close", (handle) => environment.close(handle))
|
|
6311
|
+
};
|
|
6312
|
+
}
|
|
6313
|
+
function environmentFault(faults) {
|
|
6314
|
+
return { outcome: {
|
|
6315
|
+
status: "errored",
|
|
6316
|
+
metrics: {},
|
|
6317
|
+
error: {
|
|
6318
|
+
code: "environment-fault",
|
|
6319
|
+
message: faults[0],
|
|
6320
|
+
retryable: true
|
|
6321
|
+
}
|
|
6322
|
+
} };
|
|
6323
|
+
}
|
|
6324
|
+
function unknownAccounting(reason) {
|
|
6325
|
+
return {
|
|
6326
|
+
tokens: {
|
|
6327
|
+
status: "unknown",
|
|
6328
|
+
reason
|
|
6329
|
+
},
|
|
6330
|
+
cost: {
|
|
6331
|
+
status: "unknown",
|
|
6332
|
+
knownLowerBoundUsd: 0,
|
|
6333
|
+
reason
|
|
6334
|
+
}
|
|
6335
|
+
};
|
|
6336
|
+
}
|
|
6337
|
+
/** Free text as the ledger stores it: redacted with the share profile and bounded. */
|
|
6338
|
+
function shareText(error) {
|
|
6339
|
+
return redact(error instanceof Error ? error.message : String(error), { profile: "share" }).value.slice(0, 300);
|
|
6340
|
+
}
|
|
6341
|
+
const STRATEGY_EVOLUTION_SOURCE = {
|
|
6342
|
+
uri: "npm:@tangle-network/agent-runtime#runStrategyEvolution",
|
|
6343
|
+
revision: canonicalCandidateDigest$1({
|
|
6344
|
+
name: "agent-runtime.strategy-evolution.2026-09",
|
|
6345
|
+
node: "the root strategy by name under the caller executionRef, or an authored module content-addressed by the sha256 of its source",
|
|
6067
6346
|
edge: "a text diff from the parent module source to the child; unknown from the root, which has no source here",
|
|
6068
6347
|
admission: "a module is refused when the reply carries none, it breaks the author contract, or it does not load a default Strategy",
|
|
6069
6348
|
cell: "one runAgentic run on one task, metered as one paid call on the search cost ledger through the Runtime search executor; an environment fault during it is errored and retried, a thrown strategy is failed at score 0, anything else passed at its harness-verified score",
|
|
@@ -6071,9 +6350,8 @@ const STRATEGY_EVOLUTION_SOURCE = {
|
|
|
6071
6350
|
ship: "the claim shipped, re-derives from the ledger, cost accounting is complete, and the shipped finalist test lower bound exceeds 0"
|
|
6072
6351
|
})
|
|
6073
6352
|
};
|
|
6074
|
-
|
|
6075
|
-
|
|
6076
|
-
async function runStrategyEvolution(cfg) {
|
|
6353
|
+
/** Check every option and derive the search's identity; throws `ConfigError` on a bad one. */
|
|
6354
|
+
function planStrategyEvolution(cfg) {
|
|
6077
6355
|
const root = cfg.root ?? refine;
|
|
6078
6356
|
const budget = positiveInteger("budget", cfg.budget ?? 3);
|
|
6079
6357
|
const concurrency = positiveInteger("concurrency", cfg.concurrency ?? 3);
|
|
@@ -6090,36 +6368,14 @@ async function runStrategyEvolution(cfg) {
|
|
|
6090
6368
|
const claim = defineEvaluationClaim(cfg.claim);
|
|
6091
6369
|
if (claim.minimumEffect === void 0) throw new ConfigError("evolution: the claim needs minimumEffect for its power check");
|
|
6092
6370
|
const taskIds = /* @__PURE__ */ new Set();
|
|
6093
|
-
const
|
|
6094
|
-
|
|
6095
|
-
|
|
6096
|
-
if (typeof task.id !== "string" || !task.id.trim() || taskIds.has(task.id)) throw new ConfigError(`evolution: task IDs must be non-empty and unique across splits`);
|
|
6097
|
-
taskIds.add(task.id);
|
|
6098
|
-
}
|
|
6099
|
-
return immutableCandidateValue(tasks);
|
|
6100
|
-
};
|
|
6101
|
-
const train = splitTasks("train", cfg.train);
|
|
6102
|
-
const selection = splitTasks("selection", cfg.selection);
|
|
6103
|
-
const test = splitTasks("test", cfg.test);
|
|
6104
|
-
const taskById = new Map([
|
|
6105
|
-
...train,
|
|
6106
|
-
...selection,
|
|
6107
|
-
...test
|
|
6108
|
-
].map((task) => [task.id, task]));
|
|
6109
|
-
const searchTasks = (tasks) => tasks.map((task) => ({
|
|
6110
|
-
taskId: task.id,
|
|
6111
|
-
unitId: summarizeEvaluationUnits(claim, [task]).units[0].id,
|
|
6112
|
-
source: {
|
|
6113
|
-
uri: `task://${task.id}`,
|
|
6114
|
-
revision: canonicalCandidateDigest$1(task)
|
|
6115
|
-
}
|
|
6116
|
-
}));
|
|
6371
|
+
const train = splitTasks(taskIds, "train", cfg.train);
|
|
6372
|
+
const selection = splitTasks(taskIds, "selection", cfg.selection);
|
|
6373
|
+
const test = splitTasks(taskIds, "test", cfg.test);
|
|
6117
6374
|
const splits = {
|
|
6118
|
-
train: searchTasks(train),
|
|
6119
|
-
selection: searchTasks(selection),
|
|
6120
|
-
test: searchTasks(test)
|
|
6375
|
+
train: searchTasks(claim, train),
|
|
6376
|
+
selection: searchTasks(claim, selection),
|
|
6377
|
+
test: searchTasks(claim, test)
|
|
6121
6378
|
};
|
|
6122
|
-
const developmentUnits = new Set([...splits.train, ...splits.selection].map((task) => task.unitId));
|
|
6123
6379
|
const workerProfile = cfg.worker.workerProfile;
|
|
6124
6380
|
const analystProfile = cfg.worker.analystProfile ?? workerProfile;
|
|
6125
6381
|
const workerModel = concreteModelId(workerProfile.model?.default);
|
|
@@ -6150,11 +6406,6 @@ async function runStrategyEvolution(cfg) {
|
|
|
6150
6406
|
uri: `environment:${cfg.environment.name}`,
|
|
6151
6407
|
revision: cfg.executionRef
|
|
6152
6408
|
};
|
|
6153
|
-
const execution = {
|
|
6154
|
-
model: searchModelIdentity(workerModel, workerProfile.model?.provider ?? "unknown"),
|
|
6155
|
-
agent: workerSource,
|
|
6156
|
-
benchmark: environmentSource
|
|
6157
|
-
};
|
|
6158
6409
|
const searchId = `evolution-${canonicalCandidateDigest$1({
|
|
6159
6410
|
process: STRATEGY_EVOLUTION_SOURCE,
|
|
6160
6411
|
executionRef: cfg.executionRef,
|
|
@@ -6175,12 +6426,135 @@ async function runStrategyEvolution(cfg) {
|
|
|
6175
6426
|
seed
|
|
6176
6427
|
}).slice(7, 31)}`;
|
|
6177
6428
|
const searchDir = `${cfg.outDir}/${searchId}`;
|
|
6178
|
-
|
|
6179
|
-
|
|
6180
|
-
|
|
6181
|
-
|
|
6182
|
-
|
|
6183
|
-
|
|
6429
|
+
return {
|
|
6430
|
+
root,
|
|
6431
|
+
budget,
|
|
6432
|
+
concurrency,
|
|
6433
|
+
maxExpansions,
|
|
6434
|
+
maxAttempts,
|
|
6435
|
+
seed,
|
|
6436
|
+
cellUsd,
|
|
6437
|
+
maxUsd,
|
|
6438
|
+
policy,
|
|
6439
|
+
allocation,
|
|
6440
|
+
claim,
|
|
6441
|
+
train,
|
|
6442
|
+
taskById: new Map([
|
|
6443
|
+
...train,
|
|
6444
|
+
...selection,
|
|
6445
|
+
...test
|
|
6446
|
+
].map((task) => [task.id, task])),
|
|
6447
|
+
splits,
|
|
6448
|
+
developmentUnits: new Set([...splits.train, ...splits.selection].map((task) => task.unitId)),
|
|
6449
|
+
workerModel,
|
|
6450
|
+
authorModel,
|
|
6451
|
+
authorSource,
|
|
6452
|
+
environmentSource,
|
|
6453
|
+
execution: {
|
|
6454
|
+
model: searchModelIdentity(workerModel, workerProfile.model?.provider ?? "unknown"),
|
|
6455
|
+
agent: workerSource,
|
|
6456
|
+
benchmark: environmentSource
|
|
6457
|
+
},
|
|
6458
|
+
searchId,
|
|
6459
|
+
searchDir,
|
|
6460
|
+
modulesDir: `${searchDir}/strategies`,
|
|
6461
|
+
ledgerPath: `${searchDir}/ledger.jsonl`
|
|
6462
|
+
};
|
|
6463
|
+
}
|
|
6464
|
+
function splitTasks(taskIds, label, tasks) {
|
|
6465
|
+
if (!Array.isArray(tasks) || tasks.length === 0) throw new ConfigError(`evolution: ${label} needs at least one task`);
|
|
6466
|
+
for (const task of tasks) {
|
|
6467
|
+
if (typeof task.id !== "string" || !task.id.trim() || taskIds.has(task.id)) throw new ConfigError(`evolution: task IDs must be non-empty and unique across splits`);
|
|
6468
|
+
taskIds.add(task.id);
|
|
6469
|
+
}
|
|
6470
|
+
return immutableCandidateValue(tasks);
|
|
6471
|
+
}
|
|
6472
|
+
function searchTasks(claim, tasks) {
|
|
6473
|
+
return tasks.map((task) => ({
|
|
6474
|
+
taskId: task.id,
|
|
6475
|
+
unitId: summarizeEvaluationUnits(claim, [task]).units[0].id,
|
|
6476
|
+
source: {
|
|
6477
|
+
uri: `task://${task.id}`,
|
|
6478
|
+
revision: canonicalCandidateDigest$1(task)
|
|
6479
|
+
}
|
|
6480
|
+
}));
|
|
6481
|
+
}
|
|
6482
|
+
function positiveInteger(name, value) {
|
|
6483
|
+
if (!Number.isSafeInteger(value) || value < 1) throw new ConfigError(`evolution: ${name} must be a positive integer`);
|
|
6484
|
+
return value;
|
|
6485
|
+
}
|
|
6486
|
+
function nonNegativeInteger(name, value) {
|
|
6487
|
+
if (!Number.isSafeInteger(value) || value < 0) throw new ConfigError(`evolution: ${name} must be a non-negative integer`);
|
|
6488
|
+
return value;
|
|
6489
|
+
}
|
|
6490
|
+
function dollars(name, value) {
|
|
6491
|
+
if (value === void 0) return null;
|
|
6492
|
+
if (!Number.isFinite(value) || value < 0) throw new ConfigError(`evolution: ${name} must be a finite non-negative number`);
|
|
6493
|
+
return value;
|
|
6494
|
+
}
|
|
6495
|
+
//#endregion
|
|
6496
|
+
//#region src/runtime/strategy-evolution.ts
|
|
6497
|
+
/**
|
|
6498
|
+
* runStrategyEvolution: strategy search on Eval's search kernel (`runSearch`).
|
|
6499
|
+
*
|
|
6500
|
+
* Every node is a strategy: the caller's root, or a module an author model wrote from the
|
|
6501
|
+
* train results of the parent the policy chose. The default policy is `beam({ width: 2 })`
|
|
6502
|
+
* and the default allocator `asha()`, so a strategy is screened on a few selection tasks and
|
|
6503
|
+
* earns more tasks only by ranking well. Each cell is one `runAgentic` run of one strategy on
|
|
6504
|
+
* one task, scored by the environment's own check.
|
|
6505
|
+
*
|
|
6506
|
+
* Measurement follows the kernel's splits. The author reads train results only; the policy
|
|
6507
|
+
* and allocator rank on the private selection split; the claim runs the root and at most 3
|
|
6508
|
+
* finalists together on the sealed test split, once, with a power check and a Bonferroni
|
|
6509
|
+
* decision. The report is a projection of the ledger, and the ledger is the only checkpoint:
|
|
6510
|
+
* calling again with the same inputs continues an interrupted search and returns a finished
|
|
6511
|
+
* one without running anything.
|
|
6512
|
+
*
|
|
6513
|
+
* @experimental
|
|
6514
|
+
*/
|
|
6515
|
+
const NO_MODULE = "the author reply carried no fenced ts module";
|
|
6516
|
+
/** Evolve a strategy on the search kernel and claim once on the sealed test split. */
|
|
6517
|
+
async function runStrategyEvolution(cfg) {
|
|
6518
|
+
const plan = planStrategyEvolution(cfg);
|
|
6519
|
+
mkdirSync(plan.searchDir, { recursive: true });
|
|
6520
|
+
const recorder = await openEvolutionRecorder(cfg, plan);
|
|
6521
|
+
if (!(await recorder.state()).closed) await preflightModels({
|
|
6522
|
+
worker: cfg.worker,
|
|
6523
|
+
modelPreflight: cfg.modelPreflight,
|
|
6524
|
+
modelPreflightTimeoutMs: cfg.modelPreflightTimeoutMs
|
|
6525
|
+
});
|
|
6526
|
+
const rootNode = {
|
|
6527
|
+
kind: "root",
|
|
6528
|
+
name: plan.root.name
|
|
6529
|
+
};
|
|
6530
|
+
const codec = strategyNodeCodec(plan.root);
|
|
6531
|
+
const executor = await openStrategyCellExecutor(cfg, plan, strategyLoader(plan.root, plan.modulesDir));
|
|
6532
|
+
const proposer = strategyAuthorProposer(cfg, plan, recorder, codec);
|
|
6533
|
+
const result = await runSearch({
|
|
6534
|
+
recorder,
|
|
6535
|
+
root: rootNode,
|
|
6536
|
+
codec,
|
|
6537
|
+
policy: plan.policy,
|
|
6538
|
+
allocation: plan.allocation,
|
|
6539
|
+
proposer,
|
|
6540
|
+
executor,
|
|
6541
|
+
admit: (node) => node.kind === "authored" ? node.refusal : null,
|
|
6542
|
+
maxExpansions: plan.maxExpansions,
|
|
6543
|
+
maxAttempts: plan.maxAttempts,
|
|
6544
|
+
...cfg.signal ? { signal: cfg.signal } : {}
|
|
6545
|
+
});
|
|
6546
|
+
if (!result.claim || !result.claimVerification) throw new Error(`evolution: search ${plan.searchId} closed without a claim`);
|
|
6547
|
+
return evolutionReport(plan, recorder, codec, {
|
|
6548
|
+
...result,
|
|
6549
|
+
claim: result.claim,
|
|
6550
|
+
claimVerification: result.claimVerification
|
|
6551
|
+
});
|
|
6552
|
+
}
|
|
6553
|
+
function openEvolutionRecorder(cfg, plan) {
|
|
6554
|
+
const { splits, policy, allocation, maxUsd } = plan;
|
|
6555
|
+
return SearchRecorder.open({ ledger: openSearchLedger({
|
|
6556
|
+
path: plan.ledgerPath,
|
|
6557
|
+
searchId: plan.searchId
|
|
6184
6558
|
}) }, {
|
|
6185
6559
|
subject: `strategy/${cfg.environment.name}`,
|
|
6186
6560
|
process: {
|
|
@@ -6191,269 +6565,124 @@ async function runStrategyEvolution(cfg) {
|
|
|
6191
6565
|
objective: {
|
|
6192
6566
|
metric: "score",
|
|
6193
6567
|
direction: "maximize",
|
|
6194
|
-
judge: environmentSource,
|
|
6195
|
-
claim
|
|
6568
|
+
judge: plan.environmentSource,
|
|
6569
|
+
claim: plan.claim
|
|
6196
6570
|
},
|
|
6197
6571
|
splits: {
|
|
6198
6572
|
...splits,
|
|
6199
|
-
heldOutUnits: splits.test.every((task) => !developmentUnits.has(task.unitId))
|
|
6573
|
+
heldOutUnits: splits.test.every((task) => !plan.developmentUnits.has(task.unitId))
|
|
6200
6574
|
},
|
|
6201
6575
|
policy: {
|
|
6202
6576
|
expansion: policy.name,
|
|
6203
6577
|
allocation: allocation.name,
|
|
6204
|
-
seed
|
|
6578
|
+
seed: plan.seed
|
|
6205
6579
|
},
|
|
6206
6580
|
budget: {
|
|
6207
6581
|
maxUsd,
|
|
6208
6582
|
maxCells: null,
|
|
6209
|
-
maxNodes: 1 + maxExpansions,
|
|
6583
|
+
maxNodes: 1 + plan.maxExpansions,
|
|
6210
6584
|
deadline: cfg.deadline === void 0 ? null : new Date(cfg.deadline).toISOString(),
|
|
6211
|
-
maxConcurrency: concurrency,
|
|
6585
|
+
maxConcurrency: plan.concurrency,
|
|
6212
6586
|
reservedClaimUsd: maxUsd === null ? 0 : searchClaimReserveUsd({
|
|
6213
6587
|
testTasks: splits.test.length,
|
|
6214
6588
|
reps: allocation.reps,
|
|
6215
|
-
cellUsd
|
|
6589
|
+
cellUsd: plan.cellUsd
|
|
6216
6590
|
})
|
|
6217
6591
|
},
|
|
6218
6592
|
containment: null,
|
|
6219
6593
|
derivedFrom: null,
|
|
6220
|
-
identity: execution
|
|
6594
|
+
identity: plan.execution
|
|
6221
6595
|
});
|
|
6222
|
-
|
|
6223
|
-
|
|
6224
|
-
|
|
6225
|
-
|
|
6226
|
-
|
|
6227
|
-
const
|
|
6228
|
-
|
|
6229
|
-
|
|
6596
|
+
}
|
|
6597
|
+
/** The author model as the search's proposer: it reads the train results and writes a module. */
|
|
6598
|
+
function strategyAuthorProposer(cfg, plan, recorder, codec) {
|
|
6599
|
+
const { authorSource } = plan;
|
|
6600
|
+
let toolCatalog;
|
|
6601
|
+
const catalog = () => {
|
|
6602
|
+
toolCatalog ??= listToolCatalog(cfg.environment, plan.train[0]);
|
|
6603
|
+
return toolCatalog;
|
|
6230
6604
|
};
|
|
6231
|
-
const
|
|
6232
|
-
|
|
6233
|
-
|
|
6234
|
-
|
|
6235
|
-
|
|
6236
|
-
|
|
6237
|
-
|
|
6238
|
-
|
|
6239
|
-
|
|
6240
|
-
surfaces: [{
|
|
6241
|
-
surfaceId: "strategy",
|
|
6242
|
-
kind: "code",
|
|
6243
|
-
artifact
|
|
6244
|
-
}]
|
|
6245
|
-
};
|
|
6246
|
-
},
|
|
6247
|
-
diff(rec, parent, child) {
|
|
6248
|
-
if (parent.kind === "root" || child.kind === "root") return { unknown: "the root is a caller-supplied strategy with no source here" };
|
|
6249
|
-
return surfaceDiff(rec, parent.code, child.code);
|
|
6605
|
+
const authorProvider = cfg.author.profile.model?.provider ?? "unknown";
|
|
6606
|
+
return {
|
|
6607
|
+
name: "strategy-author",
|
|
6608
|
+
kind: "frontier-author",
|
|
6609
|
+
source: authorSource,
|
|
6610
|
+
execution: {
|
|
6611
|
+
kind: "model",
|
|
6612
|
+
model: searchModelIdentity(plan.authorModel, authorProvider),
|
|
6613
|
+
source: authorSource
|
|
6250
6614
|
},
|
|
6251
|
-
|
|
6252
|
-
|
|
6253
|
-
|
|
6254
|
-
|
|
6255
|
-
|
|
6256
|
-
|
|
6257
|
-
|
|
6258
|
-
|
|
6259
|
-
|
|
6260
|
-
|
|
6261
|
-
|
|
6262
|
-
|
|
6263
|
-
|
|
6264
|
-
|
|
6265
|
-
|
|
6266
|
-
|
|
6267
|
-
|
|
6268
|
-
|
|
6269
|
-
|
|
6270
|
-
|
|
6271
|
-
|
|
6272
|
-
|
|
6273
|
-
|
|
6274
|
-
|
|
6275
|
-
|
|
6276
|
-
const lanes = [dedicatedLane({
|
|
6277
|
-
name: "in-process",
|
|
6278
|
-
capacity: concurrency,
|
|
6279
|
-
cellUsd
|
|
6280
|
-
})];
|
|
6281
|
-
await reconcileInterruptedSearchCalls(costLedger, searchId, lanes);
|
|
6282
|
-
const executor = searchExecutor({
|
|
6283
|
-
lanes,
|
|
6284
|
-
profileOf: () => workerProfile,
|
|
6285
|
-
costLedger,
|
|
6286
|
-
identity: execution,
|
|
6287
|
-
storage,
|
|
6288
|
-
dir: searchDir,
|
|
6289
|
-
...cfg.workerSlots ? { workerSlots: cfg.workerSlots } : {},
|
|
6290
|
-
async runAttempt({ work, lane, costLedger: cellLedger }) {
|
|
6291
|
-
if (work.artifact.kind === "authored" && work.artifact.refusal !== null) throw new Error(`evolution: refused node ${work.nodeId} was given a cell`);
|
|
6292
|
-
const strategy = await strategyOf(work.artifact);
|
|
6293
|
-
const task = taskById.get(work.taskId);
|
|
6294
|
-
if (!task) throw new Error(`evolution: cell ${work.cellId} names unknown task ${work.taskId}`);
|
|
6295
|
-
const faults = [];
|
|
6296
|
-
const environment = faultRecording(cfg.environment, faults);
|
|
6297
|
-
const paid = await cellLedger.runPaidCall({
|
|
6298
|
-
channel: "agent",
|
|
6299
|
-
phase: `search.${work.stage}`,
|
|
6300
|
-
actor: `strategy:${strategy.name}`,
|
|
6301
|
-
model: workerModel,
|
|
6302
|
-
signal: work.signal,
|
|
6303
|
-
execute: () => runAgentic({
|
|
6304
|
-
...cfg.worker,
|
|
6305
|
-
surface: environment,
|
|
6306
|
-
task,
|
|
6307
|
-
strategy,
|
|
6308
|
-
budget,
|
|
6309
|
-
...cfg.hooks ? { hooks: cfg.hooks } : {}
|
|
6310
|
-
}),
|
|
6311
|
-
receipt: (run) => ({
|
|
6312
|
-
model: workerModel,
|
|
6313
|
-
inputTokens: run.tokens.input,
|
|
6314
|
-
outputTokens: run.tokens.output,
|
|
6315
|
-
...run.usdKnown ? { actualCostUsd: run.usd } : { costUnknown: true },
|
|
6316
|
-
...run.tokensKnown ? {} : { usageUnknown: true }
|
|
6317
|
-
}),
|
|
6318
|
-
receiptFromError: () => ({
|
|
6319
|
-
model: workerModel,
|
|
6320
|
-
inputTokens: 0,
|
|
6321
|
-
outputTokens: 0,
|
|
6322
|
-
costUnknown: true,
|
|
6323
|
-
usageUnknown: true
|
|
6324
|
-
})
|
|
6615
|
+
childrenPerProposal: 1,
|
|
6616
|
+
async propose(request) {
|
|
6617
|
+
const state = await recorder.state();
|
|
6618
|
+
const names = strategyNames(state, recorder, codec);
|
|
6619
|
+
const losses = JSON.stringify(strategyTournament(state, "train", names).rows.map((row) => ({
|
|
6620
|
+
task: row.taskId,
|
|
6621
|
+
...row.rep > 0 ? { rep: row.rep } : {},
|
|
6622
|
+
cells: row.cells
|
|
6623
|
+
}))).slice(0, 12e3);
|
|
6624
|
+
const summary = renderSearchSummary(state, { split: "train" });
|
|
6625
|
+
const parent = request.parents[0];
|
|
6626
|
+
const parentName = names.get(parent.nodeId) ?? parent.nodeId;
|
|
6627
|
+
const tried = [...names.values()].join(", ");
|
|
6628
|
+
const parentSource = parent.artifact.kind === "authored" ? `\n\nPARENT STRATEGY "${parentName}" (improve on it):\n${parent.artifact.code}` : `\n\nPARENT STRATEGY "${parentName}" is the built-in baseline; improve on it.`;
|
|
6629
|
+
const contract = `${strategyAuthorContract}\n\nEXAMPLE TOOLS FROM ONE TASK (tool sets VARY per task on this domain — a strategy MUST select tool names from await listTools(handle) at runtime; hardcoding these example names will zero your score on most tasks):\n${await catalog()}\n\nTHE SEARCH SO FAR (train split):\n${summary}\n\nSTRATEGIES ALREADY TRIED (author something MEANINGFULLY different — a new composition, not a rename): ${tried}${parentSource}`;
|
|
6630
|
+
const source = await requestStrategySource({
|
|
6631
|
+
profile: cfg.author.profile,
|
|
6632
|
+
executor: cfg.author.executor,
|
|
6633
|
+
...cfg.author.fallbackProfile ? { fallbackProfile: cfg.author.fallbackProfile } : {},
|
|
6634
|
+
contract,
|
|
6635
|
+
environmentName: cfg.environment.name,
|
|
6636
|
+
lossesJson: losses,
|
|
6637
|
+
budget: plan.budget,
|
|
6638
|
+
outDir: plan.modulesDir,
|
|
6639
|
+
signal: request.signal
|
|
6325
6640
|
});
|
|
6326
|
-
const
|
|
6327
|
-
|
|
6328
|
-
placement: {
|
|
6329
|
-
lane: lane.name,
|
|
6330
|
-
boxId: null
|
|
6331
|
-
},
|
|
6332
|
-
accounting: unknownAccounting("replaced by the attempt receipts"),
|
|
6333
|
-
...paid.succeeded ? { wallMs: paid.value.ms } : {}
|
|
6334
|
-
};
|
|
6335
|
-
if (faults.length > 0) return {
|
|
6336
|
-
...settled,
|
|
6337
|
-
...environmentFault(faults)
|
|
6338
|
-
};
|
|
6339
|
-
if (!paid.succeeded) return {
|
|
6340
|
-
...settled,
|
|
6341
|
-
outcome: {
|
|
6342
|
-
status: "failed",
|
|
6343
|
-
score: 0,
|
|
6344
|
-
metrics: {},
|
|
6345
|
-
failure: {
|
|
6346
|
-
code: "strategy-threw",
|
|
6347
|
-
message: shareText(paid.error)
|
|
6348
|
-
}
|
|
6349
|
-
}
|
|
6350
|
-
};
|
|
6351
|
-
const run = paid.value;
|
|
6641
|
+
const child = await authoredNode(source.code, plan.modulesDir);
|
|
6642
|
+
const answered = source.turns.at(-1);
|
|
6352
6643
|
return {
|
|
6353
|
-
|
|
6354
|
-
|
|
6355
|
-
|
|
6356
|
-
|
|
6357
|
-
|
|
6358
|
-
|
|
6359
|
-
|
|
6360
|
-
|
|
6361
|
-
|
|
6644
|
+
children: [{
|
|
6645
|
+
artifact: child,
|
|
6646
|
+
label: child.name ?? "refused module",
|
|
6647
|
+
rationale: `authored from the train results of ${parentName} and ${names.size - 1} other strategies`
|
|
6648
|
+
}],
|
|
6649
|
+
accounting: authorAccounting(source.turns),
|
|
6650
|
+
...answered ? { execution: {
|
|
6651
|
+
kind: "model",
|
|
6652
|
+
model: searchModelIdentity(answered.model, authorProvider),
|
|
6653
|
+
source: authorSource
|
|
6654
|
+
} } : {}
|
|
6362
6655
|
};
|
|
6363
6656
|
}
|
|
6364
|
-
});
|
|
6365
|
-
let toolCatalog;
|
|
6366
|
-
const catalog = () => {
|
|
6367
|
-
toolCatalog ??= listToolCatalog(cfg.environment, train[0]);
|
|
6368
|
-
return toolCatalog;
|
|
6369
6657
|
};
|
|
6370
|
-
|
|
6371
|
-
|
|
6372
|
-
|
|
6373
|
-
|
|
6374
|
-
|
|
6375
|
-
|
|
6376
|
-
|
|
6377
|
-
|
|
6378
|
-
|
|
6379
|
-
|
|
6380
|
-
|
|
6381
|
-
|
|
6382
|
-
|
|
6383
|
-
|
|
6384
|
-
|
|
6385
|
-
|
|
6386
|
-
|
|
6387
|
-
|
|
6388
|
-
|
|
6389
|
-
|
|
6390
|
-
|
|
6391
|
-
|
|
6392
|
-
|
|
6393
|
-
|
|
6394
|
-
|
|
6395
|
-
|
|
6396
|
-
|
|
6397
|
-
|
|
6398
|
-
const tried = [...names.values()].join(", ");
|
|
6399
|
-
const parentSource = parent.artifact.kind === "authored" ? `\n\nPARENT STRATEGY "${parentName}" (improve on it):\n${parent.artifact.code}` : `\n\nPARENT STRATEGY "${parentName}" is the built-in baseline; improve on it.`;
|
|
6400
|
-
const contract = `${strategyAuthorContract}\n\nEXAMPLE TOOLS FROM ONE TASK (tool sets VARY per task on this domain — a strategy MUST select tool names from await listTools(handle) at runtime; hardcoding these example names will zero your score on most tasks):\n${await catalog()}\n\nTHE SEARCH SO FAR (train split):\n${summary}\n\nSTRATEGIES ALREADY TRIED (author something MEANINGFULLY different — a new composition, not a rename): ${tried}${parentSource}`;
|
|
6401
|
-
const source = await requestStrategySource({
|
|
6402
|
-
profile: cfg.author.profile,
|
|
6403
|
-
executor: cfg.author.executor,
|
|
6404
|
-
...cfg.author.fallbackProfile ? { fallbackProfile: cfg.author.fallbackProfile } : {},
|
|
6405
|
-
contract,
|
|
6406
|
-
environmentName: cfg.environment.name,
|
|
6407
|
-
lossesJson: losses,
|
|
6408
|
-
budget,
|
|
6409
|
-
outDir: modulesDir,
|
|
6410
|
-
signal: request.signal
|
|
6411
|
-
});
|
|
6412
|
-
let child = {
|
|
6413
|
-
kind: "authored",
|
|
6414
|
-
name: null,
|
|
6415
|
-
code: "",
|
|
6416
|
-
refusal: NO_MODULE
|
|
6417
|
-
};
|
|
6418
|
-
if (source.code !== null) try {
|
|
6419
|
-
const { strategy } = await loadAuthoredStrategy(source.code, modulesDir);
|
|
6420
|
-
child = {
|
|
6421
|
-
kind: "authored",
|
|
6422
|
-
name: strategy.name,
|
|
6423
|
-
code: source.code,
|
|
6424
|
-
refusal: null
|
|
6425
|
-
};
|
|
6426
|
-
} catch (error) {
|
|
6427
|
-
child = {
|
|
6428
|
-
kind: "authored",
|
|
6429
|
-
name: null,
|
|
6430
|
-
code: source.code,
|
|
6431
|
-
refusal: shareText(error)
|
|
6432
|
-
};
|
|
6433
|
-
}
|
|
6434
|
-
const answered = source.turns.at(-1);
|
|
6435
|
-
return {
|
|
6436
|
-
children: [{
|
|
6437
|
-
artifact: child,
|
|
6438
|
-
label: child.name ?? "refused module",
|
|
6439
|
-
rationale: `authored from the train results of ${parentName} and ${names.size - 1} other strategies`
|
|
6440
|
-
}],
|
|
6441
|
-
accounting: authorAccounting(source.turns),
|
|
6442
|
-
...answered ? { execution: {
|
|
6443
|
-
kind: "model",
|
|
6444
|
-
model: searchModelIdentity(answered.model, authorProvider),
|
|
6445
|
-
source: authorSource
|
|
6446
|
-
} } : {}
|
|
6447
|
-
};
|
|
6448
|
-
}
|
|
6449
|
-
},
|
|
6450
|
-
executor,
|
|
6451
|
-
admit: (node) => node.kind === "authored" ? node.refusal : null,
|
|
6452
|
-
maxExpansions,
|
|
6453
|
-
maxAttempts,
|
|
6454
|
-
...cfg.signal ? { signal: cfg.signal } : {}
|
|
6455
|
-
});
|
|
6456
|
-
if (!result.claim || !result.claimVerification) throw new Error(`evolution: search ${searchId} closed without a claim`);
|
|
6658
|
+
}
|
|
6659
|
+
/** The node an author reply makes: admitted when the module loads, else carrying the refusal. */
|
|
6660
|
+
async function authoredNode(code, modulesDir) {
|
|
6661
|
+
if (code === null) return {
|
|
6662
|
+
kind: "authored",
|
|
6663
|
+
name: null,
|
|
6664
|
+
code: "",
|
|
6665
|
+
refusal: NO_MODULE
|
|
6666
|
+
};
|
|
6667
|
+
try {
|
|
6668
|
+
const { strategy } = await loadAuthoredStrategy(code, modulesDir);
|
|
6669
|
+
return {
|
|
6670
|
+
kind: "authored",
|
|
6671
|
+
name: strategy.name,
|
|
6672
|
+
code,
|
|
6673
|
+
refusal: null
|
|
6674
|
+
};
|
|
6675
|
+
} catch (error) {
|
|
6676
|
+
return {
|
|
6677
|
+
kind: "authored",
|
|
6678
|
+
name: null,
|
|
6679
|
+
code,
|
|
6680
|
+
refusal: shareText(error)
|
|
6681
|
+
};
|
|
6682
|
+
}
|
|
6683
|
+
}
|
|
6684
|
+
/** The report: a projection of the ledger the finished search leaves. */
|
|
6685
|
+
function evolutionReport(plan, recorder, codec, result) {
|
|
6457
6686
|
const { state } = result;
|
|
6458
6687
|
const names = strategyNames(state, recorder, codec);
|
|
6459
6688
|
const rootId = state.rootNodeId;
|
|
@@ -6487,8 +6716,8 @@ async function runStrategyEvolution(cfg) {
|
|
|
6487
6716
|
minimumLift: 0
|
|
6488
6717
|
});
|
|
6489
6718
|
return {
|
|
6490
|
-
searchId,
|
|
6491
|
-
ledger: ledgerPath,
|
|
6719
|
+
searchId: plan.searchId,
|
|
6720
|
+
ledger: plan.ledgerPath,
|
|
6492
6721
|
closeReason: result.reason,
|
|
6493
6722
|
strategies,
|
|
6494
6723
|
selected: strategies.find((strategy) => strategy.nodeId === result.leader),
|
|
@@ -6565,50 +6794,6 @@ function strategyTournament(state, split, names) {
|
|
|
6565
6794
|
perStrategy
|
|
6566
6795
|
};
|
|
6567
6796
|
}
|
|
6568
|
-
/** Report names: each node's strategy name, suffixed `~<ordinal>` when an earlier node has it. */
|
|
6569
|
-
function strategyNames(state, recorder, codec) {
|
|
6570
|
-
const names = /* @__PURE__ */ new Map();
|
|
6571
|
-
const taken = /* @__PURE__ */ new Set();
|
|
6572
|
-
for (const node of state.nodes()) {
|
|
6573
|
-
const base = codec.load(recorder, node).name ?? "refused-module";
|
|
6574
|
-
const name = taken.has(base) ? `${base}~${node.ordinal}` : base;
|
|
6575
|
-
taken.add(name);
|
|
6576
|
-
names.set(node.nodeId, name);
|
|
6577
|
-
}
|
|
6578
|
-
return names;
|
|
6579
|
-
}
|
|
6580
|
-
/** The environment as one attempt sees it, recording each throw from `open`, `tools`,
|
|
6581
|
-
* `score` or `close`: those are the environment's faults. A `call` that throws is a tool
|
|
6582
|
-
* error the agent's loop handles. */
|
|
6583
|
-
function faultRecording(environment, faults) {
|
|
6584
|
-
const recorded = (name, method) => async (...args) => {
|
|
6585
|
-
try {
|
|
6586
|
-
return await method(...args);
|
|
6587
|
-
} catch (error) {
|
|
6588
|
-
faults.push(`${name}: ${shareText(error)}`);
|
|
6589
|
-
throw error;
|
|
6590
|
-
}
|
|
6591
|
-
};
|
|
6592
|
-
return {
|
|
6593
|
-
name: environment.name,
|
|
6594
|
-
open: recorded("open", (task) => environment.open(task)),
|
|
6595
|
-
tools: recorded("tools", (task, handle) => environment.tools(task, handle)),
|
|
6596
|
-
call: (handle, name, args) => environment.call(handle, name, args),
|
|
6597
|
-
score: recorded("score", (task, handle) => environment.score(task, handle)),
|
|
6598
|
-
close: recorded("close", (handle) => environment.close(handle))
|
|
6599
|
-
};
|
|
6600
|
-
}
|
|
6601
|
-
function environmentFault(faults) {
|
|
6602
|
-
return { outcome: {
|
|
6603
|
-
status: "errored",
|
|
6604
|
-
metrics: {},
|
|
6605
|
-
error: {
|
|
6606
|
-
code: "environment-fault",
|
|
6607
|
-
message: faults[0],
|
|
6608
|
-
retryable: true
|
|
6609
|
-
}
|
|
6610
|
-
} };
|
|
6611
|
-
}
|
|
6612
6797
|
function authorAccounting(turns) {
|
|
6613
6798
|
let usd = 0;
|
|
6614
6799
|
let costKnown = true;
|
|
@@ -6635,19 +6820,6 @@ function authorAccounting(turns) {
|
|
|
6635
6820
|
}
|
|
6636
6821
|
};
|
|
6637
6822
|
}
|
|
6638
|
-
function unknownAccounting(reason) {
|
|
6639
|
-
return {
|
|
6640
|
-
tokens: {
|
|
6641
|
-
status: "unknown",
|
|
6642
|
-
reason
|
|
6643
|
-
},
|
|
6644
|
-
cost: {
|
|
6645
|
-
status: "unknown",
|
|
6646
|
-
knownLowerBoundUsd: 0,
|
|
6647
|
-
reason
|
|
6648
|
-
}
|
|
6649
|
-
};
|
|
6650
|
-
}
|
|
6651
6823
|
/** One probe round-trip lists the domain's tools so the author can focus shots, names and
|
|
6652
6824
|
* descriptions only, never the implementations. */
|
|
6653
6825
|
async function listToolCatalog(environment, task) {
|
|
@@ -6658,25 +6830,70 @@ async function listToolCatalog(environment, task) {
|
|
|
6658
6830
|
await environment.close(handle);
|
|
6659
6831
|
}
|
|
6660
6832
|
}
|
|
6661
|
-
/** Free text as the ledger stores it: redacted with the share profile and bounded. */
|
|
6662
|
-
function shareText(error) {
|
|
6663
|
-
return redact(error instanceof Error ? error.message : String(error), { profile: "share" }).value.slice(0, 300);
|
|
6664
|
-
}
|
|
6665
6833
|
function compare(left, right) {
|
|
6666
6834
|
return left < right ? -1 : left > right ? 1 : 0;
|
|
6667
6835
|
}
|
|
6668
|
-
|
|
6669
|
-
|
|
6670
|
-
|
|
6836
|
+
/** How the ledger stores a node's artifact and reads it back. */
|
|
6837
|
+
function strategyNodeCodec(root) {
|
|
6838
|
+
const rootNode = {
|
|
6839
|
+
kind: "root",
|
|
6840
|
+
name: root.name
|
|
6841
|
+
};
|
|
6842
|
+
return {
|
|
6843
|
+
node(rec, node) {
|
|
6844
|
+
const artifact = rec.blob("strategy", { ...node });
|
|
6845
|
+
return {
|
|
6846
|
+
artifactDigest: node.kind === "root" ? canonicalCandidateDigest$1({
|
|
6847
|
+
kind: "root",
|
|
6848
|
+
name: node.name
|
|
6849
|
+
}) : sha256Bytes$1(Buffer.from(node.code, "utf8")),
|
|
6850
|
+
artifact,
|
|
6851
|
+
surfaces: [{
|
|
6852
|
+
surfaceId: "strategy",
|
|
6853
|
+
kind: "code",
|
|
6854
|
+
artifact
|
|
6855
|
+
}]
|
|
6856
|
+
};
|
|
6857
|
+
},
|
|
6858
|
+
diff(rec, parent, child) {
|
|
6859
|
+
if (parent.kind === "root" || child.kind === "root") return { unknown: "the root is a caller-supplied strategy with no source here" };
|
|
6860
|
+
return surfaceDiff(rec, parent.code, child.code);
|
|
6861
|
+
},
|
|
6862
|
+
load(rec, node) {
|
|
6863
|
+
const stored = rec.readBlob(node.artifact);
|
|
6864
|
+
if (stored.kind === "root") {
|
|
6865
|
+
if (stored.name !== root.name) throw new Error(`evolution: node ${node.nodeId} is root '${stored.name}', not '${root.name}'`);
|
|
6866
|
+
return rootNode;
|
|
6867
|
+
}
|
|
6868
|
+
if (stored.kind === "authored" && typeof stored.code === "string") return stored;
|
|
6869
|
+
throw new Error(`evolution: node ${node.nodeId} holds no strategy`);
|
|
6870
|
+
}
|
|
6871
|
+
};
|
|
6671
6872
|
}
|
|
6672
|
-
|
|
6673
|
-
|
|
6674
|
-
|
|
6873
|
+
/** Resolve a node to its runnable strategy; an authored module loads once per source. */
|
|
6874
|
+
function strategyLoader(root, modulesDir) {
|
|
6875
|
+
const loaded = /* @__PURE__ */ new Map();
|
|
6876
|
+
return (node) => {
|
|
6877
|
+
if (node.kind === "root") return Promise.resolve(root);
|
|
6878
|
+
let strategy = loaded.get(node.code);
|
|
6879
|
+
if (!strategy) {
|
|
6880
|
+
strategy = loadAuthoredStrategy(node.code, modulesDir).then((module) => module.strategy);
|
|
6881
|
+
loaded.set(node.code, strategy);
|
|
6882
|
+
}
|
|
6883
|
+
return strategy;
|
|
6884
|
+
};
|
|
6675
6885
|
}
|
|
6676
|
-
|
|
6677
|
-
|
|
6678
|
-
|
|
6679
|
-
|
|
6886
|
+
/** Report names: each node's strategy name, suffixed `~<ordinal>` when an earlier node has it. */
|
|
6887
|
+
function strategyNames(state, recorder, codec) {
|
|
6888
|
+
const names = /* @__PURE__ */ new Map();
|
|
6889
|
+
const taken = /* @__PURE__ */ new Set();
|
|
6890
|
+
for (const node of state.nodes()) {
|
|
6891
|
+
const base = codec.load(recorder, node).name ?? "refused-module";
|
|
6892
|
+
const name = taken.has(base) ? `${base}~${node.ordinal}` : base;
|
|
6893
|
+
taken.add(name);
|
|
6894
|
+
names.set(node.nodeId, name);
|
|
6895
|
+
}
|
|
6896
|
+
return names;
|
|
6680
6897
|
}
|
|
6681
6898
|
//#endregion
|
|
6682
6899
|
//#region src/runtime/supervise/chat-transport-executor.ts
|
|
@@ -7445,7 +7662,7 @@ function compileSearchSpec(raw, inputs) {
|
|
|
7445
7662
|
})
|
|
7446
7663
|
});
|
|
7447
7664
|
const continuation = {
|
|
7448
|
-
deadline: inputs.deadline,
|
|
7665
|
+
...inputs.deadline === void 0 ? {} : { deadline: inputs.deadline },
|
|
7449
7666
|
maxBarren: spec.stop.maxBarren,
|
|
7450
7667
|
profile: inputs.continuation.profile,
|
|
7451
7668
|
failures: spec.evaluate.feedback,
|
|
@@ -8980,4 +9197,4 @@ function tail(s) {
|
|
|
8980
9197
|
//#endregion
|
|
8981
9198
|
export { runPersonified as $, openSqlRunStore as $t, searchSpecDigest as A, secretEnvOfMcpServer as At, chatWorkerSeam as B, readDeclaredCheck as Bt, withUntrackedArtifacts as C, materializeLocalMcp as Ct, assertSearchSpec as D, mcpSecretEnvMetadataKey as Dt, SEARCH_SPEC_CORRELATION_KEY as E, envKeyProvider as Et, profileRegistry as F, assertDeclaredCheck as Ft, openSandboxRun as G, stopSentinel as Gt, runStrategyEvolution as H, completionAuthorizes as Ht, profileRunsForName as I, checkProgramDigest as It, runBenchmark as J, renderLeaderboardHtml as Jt, createS3PrivateCasStore as K, leaderboard as Kt, recordProfileRun as L, declaredCheckDeliverable as Lt, bestProfileFor as M, loopCampaignDispatch as Mt, loadProfile as N, loopDispatch as Nt, compileSearchSpec as O, resolveMcpServerLaunch as Ot, pickBestProfile as P, superviseDispatch as Pt, definePersona as Q, SqlRunOwnershipError as Qt, graphDesignerExtraTools as R, declaredCheckDigest as Rt, copyUntrackedIntoClone as S, connectStdioMcp as St, createFencedSqlRunContext as T, sanitizeMcpToolSchema as Tt, strategyTournament as U, deterministicCompletion as Ut, createChatSessionStore as V, runIsolatedCheck as Vt, SandboxRunAbortError as W, sentinelCompletion as Wt, equalKOnCost as X, renderLeaderboardSvg as Xt, createPrivateCasArtifactPort as Y, renderLeaderboardMarkdown as Yt, trajectoryReport as Z, renderPairwiseMarkdown as Zt, NOTE_MAX_CHARS as _, HarvestError as _t, localShell as a, loopUntil as at, composeWorkerEvidence as b, resolveSandboxClient as bt, createVerifierEnvironment as c, selectValidWinner as ct, harvestSurfaceDiffs as d, assertTraceDerivedFindings as dt, FileCorpus as et, analystsFromRegistry as f, buildSteerContext as ft, EVIDENCE_MAX_CHARS as g, inProcessSandboxClient as gt, runCoderChecks as h, observationFromRegistry as ht, jjWorkspace as i, flatWidenGate as it, connectRemoteSupervisor as j, inlineSandboxClient as jt, searchPresets as k, resolveSecretEnv as kt, boxSurfaceReader as l, verify as lt, worktreeFanout as m, registryScopeAnalyst as mt, makeFinding$1 as n, renderCorpusToInstructions as nt, runInWorkspace as o, panel as ot, failuresAnalyst as p, createScopeAnalyst as pt, printBenchmarkReport as q, pairwiseSignificance as qt, gitWorkspace as r, fanout as rt, createWaterfallCollector as s, pipeline as st, computeFindingId$1 as t, InMemoryCorpus as tt, fsSurfaceReader as u, widen as ut, VERIFY_TAIL_CHARS as v, harvestCorpus as vt, analyzeTrace as w, createMcpEnvironment as wt, settledWorkerOut as x, McpSpawnFault as xt, closingWorkerNote as y, defineLeaderboard as yt, chatTransportExecutor as z, declaredCheckJudge as zt };
|
|
8982
9199
|
|
|
8983
|
-
//# sourceMappingURL=runtime-
|
|
9200
|
+
//# sourceMappingURL=runtime-CuRTZFe6.js.map
|