@tangle-network/agent-eval 1.0.15 → 1.0.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/dist/cli.js +4 -4
- package/dist/cli.js.map +1 -1
- package/dist/openapi.json +1 -1
- package/dist/{render-CsHLoMUl.js → runtime-rollouts-kGn1K5ut.js} +196 -284
- package/dist/runtime-rollouts-kGn1K5ut.js.map +1 -0
- package/dist/supervisor-run/index.d.ts +30 -35
- package/dist/supervisor-run/index.d.ts.map +1 -1
- package/dist/supervisor-run/index.js +2 -2
- package/package.json +1 -1
- package/dist/render-CsHLoMUl.js.map +0 -1
|
@@ -5,10 +5,8 @@ import { t as roundDecimal } from "./round-decimal-ZmjAQPo1.js";
|
|
|
5
5
|
import { S as licenseClassOfName } from "./schema-ChIZfnqY.js";
|
|
6
6
|
import { _ as showMeasured, a as readTerminalRecord, c as parseJsonRecord, d as recordOrNull, f as workerSourceKey, g as isUnavailable, h as SUPERVISOR_RUN_SCHEMA, l as parseJsonl, n as supervisorRunRolloutLinesFromFacts, o as asRecord, p as NO_SOURCE_LIMITS, s as nonEmptyString, u as parseSupervisorTree, v as unavailable, y as expandJournalEventGroup } from "./rollout-nodes-DhSPJ2X7.js";
|
|
7
7
|
import { basename, join } from "node:path";
|
|
8
|
-
import { execFile } from "node:child_process";
|
|
9
8
|
import { readFile, readdir, stat } from "node:fs/promises";
|
|
10
9
|
import { sandboxEventsSession, toChatMessages, toOtlpSpans } from "@tangle-network/harness-sessions";
|
|
11
|
-
import { promisify } from "node:util";
|
|
12
10
|
// src/supervisor-run/analyze-context.ts
|
|
13
11
|
/** The parsed sources and shared lookups every metric group reads. `gap` records an unavailable measurement on the report. */
|
|
14
12
|
function createAnalysis(src) {
|
|
@@ -1371,286 +1369,6 @@ async function isFile(path) {
|
|
|
1371
1369
|
throw error;
|
|
1372
1370
|
}
|
|
1373
1371
|
}
|
|
1374
|
-
// src/supervisor-run/discovery-rollouts.ts
|
|
1375
|
-
/**
|
|
1376
|
-
* A Discovery run directory as `tangle.rollout.v1` rows: the supervision tree
|
|
1377
|
-
* `supervisorRunRolloutLines` mints, plus what only a Discovery run records.
|
|
1378
|
-
*
|
|
1379
|
-
* - Producer: each node's `materialized` receipt names the model it ran on
|
|
1380
|
-
* (`opencode/zai-coding-plan/glm-5.3` → harness, provider, model), and the
|
|
1381
|
-
* root's `root-segment` names the root's. The license class follows.
|
|
1382
|
-
* - Episode: each execution's input (`execution-input.taskRef`) and result
|
|
1383
|
-
* (`execution-result.outRef`, a `{content, events}` blob), read as Sandbox
|
|
1384
|
-
* sessions in order, so a node steered twice keeps both turns and every tool
|
|
1385
|
-
* call. A node with no event stream stays a lossy projection.
|
|
1386
|
-
* - A node respawned under the same id (a reopened run) is one row.
|
|
1387
|
-
* - Reward: the run's newest known play-check verdict on the root row
|
|
1388
|
-
* (`solved` → 1, a staged `SCORE` → stages passed / stages), and each
|
|
1389
|
-
* agent's own commits on its `workers/<label>` branch of `deliverables.git`.
|
|
1390
|
-
* - Screen: the calibration frozen in the run's receipt, where a fake fixture
|
|
1391
|
-
* failed every stage of the check, and the goal audit, whose `contradiction`
|
|
1392
|
-
* gates the reward. A check nothing calibrated stays unscreened.
|
|
1393
|
-
*
|
|
1394
|
-
* Every read is of files the run already wrote; nothing in the run dir is changed.
|
|
1395
|
-
*/
|
|
1396
|
-
const run = promisify(execFile);
|
|
1397
|
-
/** A run's pursuit: its directory name without the `-YYYYMMDD<letter>` date suffix. */
|
|
1398
|
-
function discoveryPursuitOf(runDir) {
|
|
1399
|
-
return basename(runDir).replace(/-20\d{6}[a-z0-9]*$/u, "");
|
|
1400
|
-
}
|
|
1401
|
-
/** `opencode/zai-coding-plan/glm-5.3` → harness, provider, model. */
|
|
1402
|
-
function producerOf(id) {
|
|
1403
|
-
const parts = id.split("/");
|
|
1404
|
-
if (parts.length >= 3) return {
|
|
1405
|
-
harness: parts[0],
|
|
1406
|
-
provider: parts[1],
|
|
1407
|
-
model: parts.slice(2).join("/")
|
|
1408
|
-
};
|
|
1409
|
-
if (parts.length === 2) return {
|
|
1410
|
-
harness: null,
|
|
1411
|
-
provider: parts[0],
|
|
1412
|
-
model: parts[1]
|
|
1413
|
-
};
|
|
1414
|
-
return {
|
|
1415
|
-
harness: null,
|
|
1416
|
-
provider: null,
|
|
1417
|
-
model: id
|
|
1418
|
-
};
|
|
1419
|
-
}
|
|
1420
|
-
/** A row holding the agent's whole stream is no longer lossy; a workspace capture gap still stands. */
|
|
1421
|
-
function wholeEpisode({ lossy_projection: _, gap, ...kept }) {
|
|
1422
|
-
const workspace = gap?.match(/workspace capture evidence \w+/u)?.[0];
|
|
1423
|
-
return workspace === void 0 ? kept : {
|
|
1424
|
-
...kept,
|
|
1425
|
-
gap: workspace
|
|
1426
|
-
};
|
|
1427
|
-
}
|
|
1428
|
-
const text = (v) => typeof v === "string" && v.length > 0 ? v : void 0;
|
|
1429
|
-
async function journalFacts(runDir) {
|
|
1430
|
-
const facts = /* @__PURE__ */ new Map();
|
|
1431
|
-
const raw = await readFile(join(runDir, "spawn-journal.jsonl"), "utf8").catch(() => "");
|
|
1432
|
-
const node = (id) => {
|
|
1433
|
-
const fact = facts.get(id) ?? {};
|
|
1434
|
-
facts.set(id, fact);
|
|
1435
|
-
return fact;
|
|
1436
|
-
};
|
|
1437
|
-
for (const row of parseJsonl(raw)) {
|
|
1438
|
-
const expanded = expandJournalEventGroup(row);
|
|
1439
|
-
if (!expanded.succeeded) continue;
|
|
1440
|
-
for (const entry of expanded.value) {
|
|
1441
|
-
const event = recordOrNull(entry.event);
|
|
1442
|
-
const id = text(event?.id);
|
|
1443
|
-
if (event === null || id === void 0) continue;
|
|
1444
|
-
if (event.kind === "spawned") node(id).taskRef ??= text(recordOrNull(event.identity)?.taskDigest);
|
|
1445
|
-
else if (event.kind === "materialized") {
|
|
1446
|
-
const model = recordOrNull(recordOrNull(event.receipt)?.model);
|
|
1447
|
-
if (model?.status === "known" && text(model.id)) node(id).model = text(model.id);
|
|
1448
|
-
} else if (event.kind === "root-segment") {
|
|
1449
|
-
const to = recordOrNull(event.to);
|
|
1450
|
-
const model = text(to?.model);
|
|
1451
|
-
if (model) node(id).model = [
|
|
1452
|
-
text(to?.harness),
|
|
1453
|
-
text(to?.provider),
|
|
1454
|
-
model
|
|
1455
|
-
].join("/");
|
|
1456
|
-
} else if (event.kind === "settled" && text(event.outRef)) node(id).outRef = text(event.outRef);
|
|
1457
|
-
else if (event.kind === "execution-input") {
|
|
1458
|
-
const fact = node(id);
|
|
1459
|
-
fact.inputRef = text(event.taskRef);
|
|
1460
|
-
fact.unansweredSince ??= text(event.at);
|
|
1461
|
-
} else if (event.kind === "execution-result" && text(event.outRef)) {
|
|
1462
|
-
const fact = node(id);
|
|
1463
|
-
fact.unansweredSince = void 0;
|
|
1464
|
-
const taskRef = fact.inputRef ?? fact.taskRef;
|
|
1465
|
-
(fact.executions ??= []).push({
|
|
1466
|
-
taskRef,
|
|
1467
|
-
outRef: event.outRef
|
|
1468
|
-
});
|
|
1469
|
-
}
|
|
1470
|
-
}
|
|
1471
|
-
}
|
|
1472
|
-
return facts;
|
|
1473
|
-
}
|
|
1474
|
-
const readJson = (path) => readFile(path, "utf8").then(JSON.parse).catch(() => void 0);
|
|
1475
|
-
async function blobValue(runDir, ref) {
|
|
1476
|
-
if (ref === void 0 || !/^sha256:[0-9a-f]{64}$/u.test(ref)) return void 0;
|
|
1477
|
-
return readJson(join(runDir, "blobs", `${ref.replace(":", "-")}.json`));
|
|
1478
|
-
}
|
|
1479
|
-
const jsonlRows = async (path) => parseJsonl(await readFile(path, "utf8").catch(() => null));
|
|
1480
|
-
/** One Sandbox session per execution, oldest first; older runs keep only the settled result. */
|
|
1481
|
-
async function nodeSessions(runDir, harness, id, fact) {
|
|
1482
|
-
const executions = fact.executions ?? [{
|
|
1483
|
-
taskRef: fact.taskRef,
|
|
1484
|
-
outRef: fact.outRef
|
|
1485
|
-
}];
|
|
1486
|
-
const sessions = [];
|
|
1487
|
-
for (const [index, { taskRef, outRef }] of executions.entries()) {
|
|
1488
|
-
const [input, result] = await Promise.all([blobValue(runDir, taskRef), blobValue(runDir, outRef)]);
|
|
1489
|
-
const task = text(input) ?? text(recordOrNull(input)?.prompt) ?? text(recordOrNull(input)?.instruction);
|
|
1490
|
-
const answer = text(result) ?? text(recordOrNull(result)?.content);
|
|
1491
|
-
const stream = recordOrNull(result)?.events;
|
|
1492
|
-
const events = Array.isArray(stream) ? stream : [];
|
|
1493
|
-
if (task === void 0 || answer === void 0 && events.length === 0) continue;
|
|
1494
|
-
const nativeSessionId = `${id}:${index}`;
|
|
1495
|
-
sessions.push(sandboxEventsSession({
|
|
1496
|
-
harness,
|
|
1497
|
-
nativeSessionId,
|
|
1498
|
-
task,
|
|
1499
|
-
answer: answer ?? "",
|
|
1500
|
-
events
|
|
1501
|
-
}));
|
|
1502
|
-
}
|
|
1503
|
-
return sessions;
|
|
1504
|
-
}
|
|
1505
|
-
/**
|
|
1506
|
-
* The newest play-check row whose verdict is known. A staged check
|
|
1507
|
-
* (`SCORE {"reached": r, "of": n}`) that is not solved failed at stage r, so it
|
|
1508
|
-
* passed r − 1 of n: Discovery's own `stagesPassed` (runner/calibration.mjs).
|
|
1509
|
-
* Only the SCORE line itself is parsed; the check prints more lines after it.
|
|
1510
|
-
*/
|
|
1511
|
-
async function discoveryPlayCheckReward(runDir) {
|
|
1512
|
-
const rows = await jsonlRows(join(runDir, "play-checks.jsonl"));
|
|
1513
|
-
const single = rows.length === 0 ? recordOrNull(await readJson(join(runDir, "play-check.json"))) : null;
|
|
1514
|
-
if (single) rows.push(single);
|
|
1515
|
-
const last = rows.filter((row) => row.known !== false && typeof row.solved === "boolean").sort((a, b) => String(a.at ?? "").localeCompare(String(b.at ?? ""))).at(-1);
|
|
1516
|
-
if (last === void 0) return null;
|
|
1517
|
-
const detail = typeof last.detail === "string" ? last.detail : "";
|
|
1518
|
-
let reward = last.solved === true ? 1 : 0;
|
|
1519
|
-
const score = detail.split("\n").find((line) => line.startsWith("SCORE {"));
|
|
1520
|
-
if (reward === 0 && score !== void 0) try {
|
|
1521
|
-
const { reached, of } = JSON.parse(score.slice(6));
|
|
1522
|
-
if (Number.isSafeInteger(reached) && Number.isSafeInteger(of) && 1 <= reached && reached <= of) reward = (reached - 1) / of;
|
|
1523
|
-
} catch {}
|
|
1524
|
-
const key = last.contentHash ?? last.commit ?? last.at;
|
|
1525
|
-
const ruling = (await jsonlRows(join(runDir, "goal-audits.jsonl"))).filter((a) => a.key === key && a.verdict !== "unavailable").at(-1);
|
|
1526
|
-
const receipt = recordOrNull(await readJson(join(runDir, "RECEIPT.json")));
|
|
1527
|
-
const calibration = recordOrNull(recordOrNull(receipt?.check)?.calibration);
|
|
1528
|
-
const stages = Object.values(recordOrNull(calibration?.stages) ?? {});
|
|
1529
|
-
return {
|
|
1530
|
-
reward,
|
|
1531
|
-
calibrated: stages.length > 0 && stages.every((state) => state === "calibrated"),
|
|
1532
|
-
contradicted: (ruling?.verdict ?? recordOrNull(last.goalAudit)?.verdict) === "contradiction",
|
|
1533
|
-
at: String(last.at ?? ""),
|
|
1534
|
-
taggedAt: text(receipt?.taggedAt),
|
|
1535
|
-
commit: typeof last.commit === "string" ? last.commit : null,
|
|
1536
|
-
detail: detail.slice(0, 500)
|
|
1537
|
-
};
|
|
1538
|
-
}
|
|
1539
|
-
/** A solved run is stopped, so it reads `cancelled`; it is whole if no turn was open at the tag. */
|
|
1540
|
-
const holdsTheSolve = (check, unansweredSince) => check.reward === 1 && !check.contradicted && (!unansweredSince || Date.parse(check.taggedAt ?? "") <= Date.parse(unansweredSince));
|
|
1541
|
-
/** Commits each `workers/<label>` branch holds beyond the commit every worker started from. */
|
|
1542
|
-
async function deliverableCommits(runDir) {
|
|
1543
|
-
const git = (args) => run("git", [
|
|
1544
|
-
"-C",
|
|
1545
|
-
join(runDir, "deliverables.git"),
|
|
1546
|
-
...args
|
|
1547
|
-
], { timeout: 3e4 });
|
|
1548
|
-
let refs;
|
|
1549
|
-
try {
|
|
1550
|
-
const { stdout } = await git([
|
|
1551
|
-
"for-each-ref",
|
|
1552
|
-
"--format=%(refname:short)",
|
|
1553
|
-
"refs/heads/workers"
|
|
1554
|
-
]);
|
|
1555
|
-
refs = stdout.split("\n").filter(Boolean);
|
|
1556
|
-
} catch {
|
|
1557
|
-
return null;
|
|
1558
|
-
}
|
|
1559
|
-
if (refs.length === 0) return /* @__PURE__ */ new Map();
|
|
1560
|
-
const base = await git([
|
|
1561
|
-
"merge-base",
|
|
1562
|
-
"--octopus",
|
|
1563
|
-
...refs
|
|
1564
|
-
]).then(({ stdout }) => stdout.trim()).catch(() => "");
|
|
1565
|
-
const counts = /* @__PURE__ */ new Map();
|
|
1566
|
-
for (const ref of refs) {
|
|
1567
|
-
const counted = await git([
|
|
1568
|
-
"rev-list",
|
|
1569
|
-
"--count",
|
|
1570
|
-
ref,
|
|
1571
|
-
...base ? [`^${base}`] : []
|
|
1572
|
-
]).catch(() => null);
|
|
1573
|
-
if (counted === null) return null;
|
|
1574
|
-
counts.set(ref.slice(8), Number(counted.stdout.trim()));
|
|
1575
|
-
}
|
|
1576
|
-
return counts;
|
|
1577
|
-
}
|
|
1578
|
-
/** Read one Discovery run directory into rollout rows. */
|
|
1579
|
-
async function discoveryRunRolloutLines(runDir, options = {}) {
|
|
1580
|
-
const pursuit = discoveryPursuitOf(runDir);
|
|
1581
|
-
const src = await readRuntimeSupervisorRun(runDir);
|
|
1582
|
-
const facts0 = parseSupervisorTree(src);
|
|
1583
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1584
|
-
const workerSpawns = facts0.workerSpawns.filter((spawn) => !seen.has(spawn.id) && seen.add(spawn.id));
|
|
1585
|
-
const tree = supervisorRunRolloutLinesFromFacts(src, {
|
|
1586
|
-
...facts0,
|
|
1587
|
-
workerSpawns
|
|
1588
|
-
}, {
|
|
1589
|
-
suite: `discovery/${pursuit}`,
|
|
1590
|
-
runId: basename(runDir),
|
|
1591
|
-
...options.capturedAt === void 0 ? {} : { capturedAt: options.capturedAt }
|
|
1592
|
-
});
|
|
1593
|
-
const facts = await journalFacts(runDir);
|
|
1594
|
-
const check = await discoveryPlayCheckReward(runDir);
|
|
1595
|
-
const commits = await deliverableCommits(runDir);
|
|
1596
|
-
const lines = [];
|
|
1597
|
-
const otlp = /* @__PURE__ */ new Map();
|
|
1598
|
-
for (const node of tree.nodes) {
|
|
1599
|
-
const fact = facts.get(node.rollout_id) ?? {};
|
|
1600
|
-
const producer = fact.model === void 0 ? null : producerOf(fact.model);
|
|
1601
|
-
const license = licenseClassOfName(producer?.model) ?? licenseClassOfName(producer?.harness);
|
|
1602
|
-
const sessions = await nodeSessions(runDir, producer?.harness ?? node.policy.harness ?? "sandbox", node.rollout_id, fact);
|
|
1603
|
-
const messages = sessions.flatMap((session) => toChatMessages(session));
|
|
1604
|
-
const label = typeof node.outcome.metrics.label === "string" ? node.outcome.metrics.label : null;
|
|
1605
|
-
const isRoot = node.parent_rollout_id === null;
|
|
1606
|
-
const outcome = isRoot && check !== null ? {
|
|
1607
|
-
...node.outcome,
|
|
1608
|
-
...check.calibrated || check.contradicted ? screenedRewardFields(check.reward, check.contradicted) : unscreenedRewardFields(check.reward),
|
|
1609
|
-
...holdsTheSolve(check, fact.unansweredSince) ? {
|
|
1610
|
-
is_completed: true,
|
|
1611
|
-
error: null
|
|
1612
|
-
} : {},
|
|
1613
|
-
reward_source: "discovery/play-check",
|
|
1614
|
-
verdict: {
|
|
1615
|
-
at: check.at,
|
|
1616
|
-
commit: check.commit,
|
|
1617
|
-
detail: check.detail
|
|
1618
|
-
}
|
|
1619
|
-
} : node.outcome;
|
|
1620
|
-
const spans = sessions.map((session) => toOtlpSpans(session)).filter((s) => s.length > 1).flat();
|
|
1621
|
-
otlp.set(node.rollout_id, spans.map((span) => `${JSON.stringify(span)}\n`).join(""));
|
|
1622
|
-
lines.push({
|
|
1623
|
-
...node,
|
|
1624
|
-
policy: {
|
|
1625
|
-
...node.policy,
|
|
1626
|
-
...producer === null ? {} : {
|
|
1627
|
-
harness: producer.harness ?? node.policy.harness,
|
|
1628
|
-
provider: producer.provider,
|
|
1629
|
-
model: producer.model
|
|
1630
|
-
},
|
|
1631
|
-
...license === null ? {} : { license_class: license }
|
|
1632
|
-
},
|
|
1633
|
-
messages: sessions.length === 0 ? node.messages : messages,
|
|
1634
|
-
provenance: messages.some((m) => m.role === "tool") ? wholeEpisode(node.provenance) : node.provenance,
|
|
1635
|
-
outcome: {
|
|
1636
|
-
...outcome,
|
|
1637
|
-
metrics: {
|
|
1638
|
-
...outcome.metrics,
|
|
1639
|
-
deliverable_commits: commits === null ? null : commits.get(isRoot ? "root" : label ?? "") ?? 0
|
|
1640
|
-
}
|
|
1641
|
-
}
|
|
1642
|
-
});
|
|
1643
|
-
}
|
|
1644
|
-
const notes = commits === null ? ["deliverables.git unreadable: deliverable_commits is null"] : [];
|
|
1645
|
-
return {
|
|
1646
|
-
runDir,
|
|
1647
|
-
pursuit,
|
|
1648
|
-
lines,
|
|
1649
|
-
gaps: tree.gaps.filter((gap) => check === null || gap.code !== "root-reward-unavailable"),
|
|
1650
|
-
notes,
|
|
1651
|
-
otlp
|
|
1652
|
-
};
|
|
1653
|
-
}
|
|
1654
1372
|
// src/supervisor-run/reader.ts
|
|
1655
1373
|
/**
|
|
1656
1374
|
* Runtime supervisor-run reader and report writers.
|
|
@@ -1903,6 +1621,200 @@ function renderSupervisorRollupMarkdown(rollup, title = "Round rollup") {
|
|
|
1903
1621
|
for (const cell of rollup.perCell) if (cell.resourceRecords !== void 0) out.push(`## Resource receipts — ${cell.instanceId ?? "?"} [${cell.arm ?? "?"}]`, "", ...resourceRows(cell.resourceRecords));
|
|
1904
1622
|
return out.join("\n");
|
|
1905
1623
|
}
|
|
1906
|
-
|
|
1624
|
+
// src/supervisor-run/runtime-rollouts.ts
|
|
1625
|
+
/**
|
|
1626
|
+
* A Runtime run directory as `tangle.rollout.v1` rows: the supervision tree
|
|
1627
|
+
* `supervisorRunRolloutLines` mints, with each node's whole episode.
|
|
1628
|
+
*
|
|
1629
|
+
* - Producer: each node's `materialized` receipt names the model it ran on
|
|
1630
|
+
* (`opencode/zai-coding-plan/glm-5.3` → harness, provider, model), and the
|
|
1631
|
+
* root's `root-segment` names the root's. The license class follows.
|
|
1632
|
+
* - Episode: each execution's input (`execution-input.taskRef`) and result
|
|
1633
|
+
* (`execution-result.outRef`, a `{content, events}` blob), read as Sandbox
|
|
1634
|
+
* sessions in order, so a node steered twice keeps both turns and every tool
|
|
1635
|
+
* call. A node with no event stream stays a lossy projection.
|
|
1636
|
+
* - A node respawned under the same id (a reopened run) is one row.
|
|
1637
|
+
* - Reward: the caller's `RunVerdict` on the root row. The caller owns what
|
|
1638
|
+
* its check means; this reader owns how a reward is screened and when a
|
|
1639
|
+
* stopped episode is whole.
|
|
1640
|
+
*
|
|
1641
|
+
* Every read is of files Runtime already wrote; nothing in the run dir is changed.
|
|
1642
|
+
*/
|
|
1643
|
+
/** `opencode/zai-coding-plan/glm-5.3` → harness, provider, model. */
|
|
1644
|
+
function producerOf(id) {
|
|
1645
|
+
const parts = id.split("/");
|
|
1646
|
+
if (parts.length >= 3) return {
|
|
1647
|
+
harness: parts[0],
|
|
1648
|
+
provider: parts[1],
|
|
1649
|
+
model: parts.slice(2).join("/")
|
|
1650
|
+
};
|
|
1651
|
+
if (parts.length === 2) return {
|
|
1652
|
+
harness: null,
|
|
1653
|
+
provider: parts[0],
|
|
1654
|
+
model: parts[1]
|
|
1655
|
+
};
|
|
1656
|
+
return {
|
|
1657
|
+
harness: null,
|
|
1658
|
+
provider: null,
|
|
1659
|
+
model: id
|
|
1660
|
+
};
|
|
1661
|
+
}
|
|
1662
|
+
/** A row holding the agent's whole stream is no longer lossy; a workspace capture gap still stands. */
|
|
1663
|
+
function wholeEpisode({ lossy_projection: _, gap, ...kept }) {
|
|
1664
|
+
const workspace = gap?.match(/workspace capture evidence \w+/u)?.[0];
|
|
1665
|
+
return workspace === void 0 ? kept : {
|
|
1666
|
+
...kept,
|
|
1667
|
+
gap: workspace
|
|
1668
|
+
};
|
|
1669
|
+
}
|
|
1670
|
+
const text = (v) => typeof v === "string" && v.length > 0 ? v : void 0;
|
|
1671
|
+
async function journalFacts(runDir) {
|
|
1672
|
+
const facts = /* @__PURE__ */ new Map();
|
|
1673
|
+
const raw = await readFile(join(runDir, "spawn-journal.jsonl"), "utf8").catch(() => "");
|
|
1674
|
+
const node = (id) => {
|
|
1675
|
+
const fact = facts.get(id) ?? {};
|
|
1676
|
+
facts.set(id, fact);
|
|
1677
|
+
return fact;
|
|
1678
|
+
};
|
|
1679
|
+
for (const row of parseJsonl(raw)) {
|
|
1680
|
+
const expanded = expandJournalEventGroup(row);
|
|
1681
|
+
if (!expanded.succeeded) continue;
|
|
1682
|
+
for (const entry of expanded.value) {
|
|
1683
|
+
const event = recordOrNull(entry.event);
|
|
1684
|
+
const id = text(event?.id);
|
|
1685
|
+
if (event === null || id === void 0) continue;
|
|
1686
|
+
if (event.kind === "spawned") node(id).taskRef ??= text(recordOrNull(event.identity)?.taskDigest);
|
|
1687
|
+
else if (event.kind === "materialized") {
|
|
1688
|
+
const model = recordOrNull(recordOrNull(event.receipt)?.model);
|
|
1689
|
+
if (model?.status === "known" && text(model.id)) node(id).model = text(model.id);
|
|
1690
|
+
} else if (event.kind === "root-segment") {
|
|
1691
|
+
const to = recordOrNull(event.to);
|
|
1692
|
+
const model = text(to?.model);
|
|
1693
|
+
if (model) node(id).model = [
|
|
1694
|
+
text(to?.harness),
|
|
1695
|
+
text(to?.provider),
|
|
1696
|
+
model
|
|
1697
|
+
].join("/");
|
|
1698
|
+
} else if (event.kind === "settled" && text(event.outRef)) node(id).outRef = text(event.outRef);
|
|
1699
|
+
else if (event.kind === "execution-input") {
|
|
1700
|
+
const fact = node(id);
|
|
1701
|
+
fact.inputRef = text(event.taskRef);
|
|
1702
|
+
fact.unansweredSince ??= text(event.at);
|
|
1703
|
+
} else if (event.kind === "execution-result" && text(event.outRef)) {
|
|
1704
|
+
const fact = node(id);
|
|
1705
|
+
fact.unansweredSince = void 0;
|
|
1706
|
+
const taskRef = fact.inputRef ?? fact.taskRef;
|
|
1707
|
+
(fact.executions ??= []).push({
|
|
1708
|
+
taskRef,
|
|
1709
|
+
outRef: event.outRef
|
|
1710
|
+
});
|
|
1711
|
+
}
|
|
1712
|
+
}
|
|
1713
|
+
}
|
|
1714
|
+
return facts;
|
|
1715
|
+
}
|
|
1716
|
+
const readJson = (path) => readFile(path, "utf8").then(JSON.parse).catch(() => void 0);
|
|
1717
|
+
async function blobValue(runDir, ref) {
|
|
1718
|
+
if (ref === void 0 || !/^sha256:[0-9a-f]{64}$/u.test(ref)) return void 0;
|
|
1719
|
+
return readJson(join(runDir, "blobs", `${ref.replace(":", "-")}.json`));
|
|
1720
|
+
}
|
|
1721
|
+
/** One Sandbox session per execution, oldest first; older runs keep only the settled result. */
|
|
1722
|
+
async function nodeSessions(runDir, harness, id, fact) {
|
|
1723
|
+
const executions = fact.executions ?? [{
|
|
1724
|
+
taskRef: fact.taskRef,
|
|
1725
|
+
outRef: fact.outRef
|
|
1726
|
+
}];
|
|
1727
|
+
const sessions = [];
|
|
1728
|
+
for (const [index, { taskRef, outRef }] of executions.entries()) {
|
|
1729
|
+
const [input, result] = await Promise.all([blobValue(runDir, taskRef), blobValue(runDir, outRef)]);
|
|
1730
|
+
const task = text(input) ?? text(recordOrNull(input)?.prompt) ?? text(recordOrNull(input)?.instruction);
|
|
1731
|
+
const answer = text(result) ?? text(recordOrNull(result)?.content);
|
|
1732
|
+
const stream = recordOrNull(result)?.events;
|
|
1733
|
+
const events = Array.isArray(stream) ? stream : [];
|
|
1734
|
+
if (task === void 0 || answer === void 0 && events.length === 0) continue;
|
|
1735
|
+
const nativeSessionId = `${id}:${index}`;
|
|
1736
|
+
sessions.push(sandboxEventsSession({
|
|
1737
|
+
harness,
|
|
1738
|
+
nativeSessionId,
|
|
1739
|
+
task,
|
|
1740
|
+
answer: answer ?? "",
|
|
1741
|
+
events
|
|
1742
|
+
}));
|
|
1743
|
+
}
|
|
1744
|
+
return sessions;
|
|
1745
|
+
}
|
|
1746
|
+
/**
|
|
1747
|
+
* The root's outcome under the caller's verdict. A run stopped on its success reads `cancelled`;
|
|
1748
|
+
* its episode is whole if no input was pending when it was stopped.
|
|
1749
|
+
*/
|
|
1750
|
+
function judged(node, verdict, pending) {
|
|
1751
|
+
if (verdict === null || node.parent_rollout_id !== null) return node.outcome;
|
|
1752
|
+
const { reward, screened, gated = false, source, stoppedAt = "", commit = null } = verdict;
|
|
1753
|
+
const ended = reward === 1 && !gated && (!pending || Date.parse(stoppedAt) <= Date.parse(pending));
|
|
1754
|
+
return {
|
|
1755
|
+
...node.outcome,
|
|
1756
|
+
...screened || gated ? screenedRewardFields(reward, gated) : unscreenedRewardFields(reward),
|
|
1757
|
+
...ended ? {
|
|
1758
|
+
is_completed: true,
|
|
1759
|
+
error: null
|
|
1760
|
+
} : {},
|
|
1761
|
+
reward_source: source,
|
|
1762
|
+
verdict: {
|
|
1763
|
+
at: verdict.at ?? "",
|
|
1764
|
+
commit,
|
|
1765
|
+
detail: (verdict.detail ?? "").slice(0, 500)
|
|
1766
|
+
}
|
|
1767
|
+
};
|
|
1768
|
+
}
|
|
1769
|
+
/** Read one Runtime run directory into rollout rows; `suite` defaults to `runtime/<run>`. */
|
|
1770
|
+
async function runtimeRunRolloutLines(runDir, options = {}) {
|
|
1771
|
+
const verdict = options.verdict ?? null;
|
|
1772
|
+
const src = await readRuntimeSupervisorRun(runDir);
|
|
1773
|
+
const facts0 = parseSupervisorTree(src);
|
|
1774
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1775
|
+
const workerSpawns = facts0.workerSpawns.filter((spawn) => !seen.has(spawn.id) && seen.add(spawn.id));
|
|
1776
|
+
const tree = supervisorRunRolloutLinesFromFacts(src, {
|
|
1777
|
+
...facts0,
|
|
1778
|
+
workerSpawns
|
|
1779
|
+
}, {
|
|
1780
|
+
suite: options.suite ?? `runtime/${basename(runDir)}`,
|
|
1781
|
+
runId: basename(runDir),
|
|
1782
|
+
...options.capturedAt === void 0 ? {} : { capturedAt: options.capturedAt }
|
|
1783
|
+
});
|
|
1784
|
+
const facts = await journalFacts(runDir);
|
|
1785
|
+
const lines = [];
|
|
1786
|
+
const otlp = /* @__PURE__ */ new Map();
|
|
1787
|
+
for (const node of tree.nodes) {
|
|
1788
|
+
const fact = facts.get(node.rollout_id) ?? {};
|
|
1789
|
+
const producer = fact.model === void 0 ? null : producerOf(fact.model);
|
|
1790
|
+
const license = licenseClassOfName(producer?.model) ?? licenseClassOfName(producer?.harness);
|
|
1791
|
+
const sessions = await nodeSessions(runDir, producer?.harness ?? node.policy.harness ?? "sandbox", node.rollout_id, fact);
|
|
1792
|
+
const messages = sessions.flatMap((session) => toChatMessages(session));
|
|
1793
|
+
const spans = sessions.map((session) => toOtlpSpans(session)).filter((s) => s.length > 1).flat();
|
|
1794
|
+
otlp.set(node.rollout_id, spans.map((span) => `${JSON.stringify(span)}\n`).join(""));
|
|
1795
|
+
lines.push({
|
|
1796
|
+
...node,
|
|
1797
|
+
policy: {
|
|
1798
|
+
...node.policy,
|
|
1799
|
+
...producer === null ? {} : {
|
|
1800
|
+
harness: producer.harness ?? node.policy.harness,
|
|
1801
|
+
provider: producer.provider,
|
|
1802
|
+
model: producer.model
|
|
1803
|
+
},
|
|
1804
|
+
...license === null ? {} : { license_class: license }
|
|
1805
|
+
},
|
|
1806
|
+
messages: sessions.length === 0 ? node.messages : messages,
|
|
1807
|
+
provenance: messages.some((m) => m.role === "tool") ? wholeEpisode(node.provenance) : node.provenance,
|
|
1808
|
+
outcome: judged(node, verdict, fact.unansweredSince)
|
|
1809
|
+
});
|
|
1810
|
+
}
|
|
1811
|
+
return {
|
|
1812
|
+
runDir,
|
|
1813
|
+
lines,
|
|
1814
|
+
gaps: tree.gaps.filter((gap) => verdict === null || gap.code !== "root-reward-unavailable"),
|
|
1815
|
+
otlp
|
|
1816
|
+
};
|
|
1817
|
+
}
|
|
1818
|
+
export { analyzeSupervisorRun as a, readRuntimeSupervisorRun as c, renderSupervisorRunMarkdown as i, analyzeSupervisorRunSources as l, renderSupervisorRollupMarkdown as n, findSupervisorRunDirs as o, renderSupervisorRunHeadline as r, isRuntimeSupervisorRunDir as s, runtimeRunRolloutLines as t, parsePatch as u };
|
|
1907
1819
|
|
|
1908
|
-
//# sourceMappingURL=
|
|
1820
|
+
//# sourceMappingURL=runtime-rollouts-kGn1K5ut.js.map
|