@tangle-network/agent-eval 0.186.2 → 0.187.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/dist/analyst/index.d.ts +1 -1
- package/dist/analyst/index.js +1 -1
- package/dist/{benchmark-command-NATyhXB4.js → benchmark-command-j1YV50m1.js} +2 -2
- package/dist/{benchmark-command-NATyhXB4.js.map → benchmark-command-j1YV50m1.js.map} +1 -1
- package/dist/cli.js +1 -1
- package/dist/{exec-D9WpA2p-.js → exec-ChJ6d5if.js} +10 -6
- package/dist/exec-ChJ6d5if.js.map +1 -0
- package/dist/experiment/index.d.ts +155 -1
- package/dist/experiment/index.d.ts.map +1 -1
- package/dist/experiment/index.js +268 -2
- package/dist/experiment/index.js.map +1 -1
- package/dist/index.d.ts +45 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +53 -1
- package/dist/index.js.map +1 -1
- package/dist/openapi.json +1 -1
- package/dist/{steps-CiNVJry_.d.ts → steps-Bg5-KVx1.d.ts} +4 -4
- package/dist/{steps-CiNVJry_.d.ts.map → steps-Bg5-KVx1.d.ts.map} +1 -1
- package/dist/trace-repair/index.d.ts +1 -1
- package/dist/trace-repair/index.js +1 -1
- package/dist/trajectory-replay/index.d.ts +11 -11
- package/dist/trajectory-replay/index.d.ts.map +1 -1
- package/dist/trajectory-replay/index.js +35 -16
- package/dist/trajectory-replay/index.js.map +1 -1
- package/docs/experiment.md +94 -0
- package/docs/trajectory-replay.md +9 -4
- package/package.json +1 -1
- package/dist/exec-D9WpA2p-.js.map +0 -1
package/docs/experiment.md
CHANGED
|
@@ -26,6 +26,100 @@ Calling `registered.decide()` does not automatically run admission, power, budge
|
|
|
26
26
|
| Cluster-aware power | `clusteredPower`, `assertDesignAdequate` | Assess a declared effect under a simulated outcome model and cluster-count policy. |
|
|
27
27
|
| Denominator chain | `buildFunnel`, `executeAdmissionRule`, `composeFunnels`, `renderFunnelTable` | Reconcile retained and excluded evidence. |
|
|
28
28
|
| Matched budgets | `verifyMatchedBudgets`, `assertMatchedBudgets` | Check realized tokens against a declared tolerance. |
|
|
29
|
+
| Randomized served canary | `sealRandomizedCanaryRule`, `decideRandomizedCanary` | Decide fixed-horizon assignment-level lift from checked binary outcomes. |
|
|
30
|
+
|
|
31
|
+
### Randomized served canary
|
|
32
|
+
|
|
33
|
+
Use the canary decision when control and candidate receive different live assignments.
|
|
34
|
+
The paired promotion rule applies to replayed cases, not this served comparison.
|
|
35
|
+
|
|
36
|
+
Seal the prospective protocol before the first eligible assignment.
|
|
37
|
+
Register eligibility, randomization, both profiles, served revision, checker revision, assignment unit, cluster unit, fixed cutoff, outcome maturity, and minimum lift.
|
|
38
|
+
Register one confirmatory family slot before traffic.
|
|
39
|
+
The family size sets an additional Bonferroni interval; the nominal 95% headline keeps its original definition.
|
|
40
|
+
An independent witness must establish protocol timing and prevent reusing a family slot.
|
|
41
|
+
The seal detects edits but cannot authenticate its own creation time or family reservation.
|
|
42
|
+
|
|
43
|
+
At the fixed assignment cutoff, freeze the complete append-only assignment ledger.
|
|
44
|
+
Its roster digest covers every assignment ID, assignment source ID, cluster ID, and arm.
|
|
45
|
+
Assignment IDs and assignment source IDs must each be unique, so a retry cannot enter the denominator twice.
|
|
46
|
+
At exactly `analysisCutoff + outcomeMaturityMs`, freeze the complete outcome, trace, turn, and billed-cost snapshot.
|
|
47
|
+
Retain the assignment ledger tip, observation snapshot digest, and independent attestation source.
|
|
48
|
+
The required host verifier checks ledger signatures, authority, registration timing, family reservation, randomizer execution, and the fixed snapshot.
|
|
49
|
+
It receives the cohort receipt and sealed protocol, so it can resolve each registered source against the independent services.
|
|
50
|
+
It verifies eligibility before assignment and an immutable disposition for every eligible arrival through the cutoff.
|
|
51
|
+
It resolves each assignment to the raw trace or checked no-execution record, checker result, settled bill, and served revision.
|
|
52
|
+
It also verifies arm-isolated memory and persistent state, or witnessed statelessness, for every assignment design.
|
|
53
|
+
The library checks the receipt's identities, counts, digests, and times against the supplied rows and sealed protocol.
|
|
54
|
+
An arbitrary verifier that returns success does not establish independent evidence.
|
|
55
|
+
|
|
56
|
+
Keep every assigned unit in its original arm, including failed executions.
|
|
57
|
+
Use outcome `0` only when the registered checker established failure.
|
|
58
|
+
Use `null` when a checked outcome is unavailable; missing evidence refuses inference.
|
|
59
|
+
A missing trace is valid only with an independent no-execution source, zero turns, zero billed cost, and checked failure.
|
|
60
|
+
The library refuses changed served profile, code revision, or checker revision.
|
|
61
|
+
It also refuses fewer than 40 clusters, fewer than 20 clusters per arm, a dominant cluster, or zero variance.
|
|
62
|
+
|
|
63
|
+
The estimand is candidate minus control checked success probability across **all assigned units**.
|
|
64
|
+
The interval uses a cluster sandwich variance and Student-t critical value with `G - 1` degrees of freedom.
|
|
65
|
+
A customer cluster may contain sessions in both arms; their covariance remains in one contribution.
|
|
66
|
+
Such traffic needs arm-isolated memory and persistent state to avoid treatment spillover into control.
|
|
67
|
+
Use customer assignment when that isolation cannot be proved.
|
|
68
|
+
This large-cluster approximation requires independent clusters, trustworthy randomization, stable serving, and the fixed observation snapshot.
|
|
69
|
+
It is neither an anytime-valid sequence nor an exact small-sample test.
|
|
70
|
+
|
|
71
|
+
`nominal95Interval` and `headline95Pass` report the fixed live headline.
|
|
72
|
+
`familyAdjustedInterval` and `familywisePass` provide the additional repeated-candidate guard.
|
|
73
|
+
`successCriterionMet` requires both, but it is only the primary lift criterion.
|
|
74
|
+
The product still applies its registered cost and latency guardrails before any release.
|
|
75
|
+
Arm coverage counts and billed-cost totals remain visible; missing cost stays `null`, never a measured zero.
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
import {
|
|
79
|
+
decideRandomizedCanary,
|
|
80
|
+
sealRandomizedCanaryRule,
|
|
81
|
+
} from '@tangle-network/agent-eval/experiment'
|
|
82
|
+
|
|
83
|
+
const protocol = sealRandomizedCanaryRule({
|
|
84
|
+
experimentId: 'served-agent-2026-09-24',
|
|
85
|
+
populationId: 'eligible-live-requests',
|
|
86
|
+
eligibilityRuleDigest: 'eligibility-revision',
|
|
87
|
+
randomizationSourceId: 'randomizer-receipt-id',
|
|
88
|
+
assignmentLedgerAuthorityId: 'platform-ledger',
|
|
89
|
+
controlProfileDigest: 'control-profile-digest',
|
|
90
|
+
candidateProfileDigest: 'candidate-profile-digest',
|
|
91
|
+
servedCodeRevisionDigest: 'served-revision-digest',
|
|
92
|
+
outcomeCheckerDigest: 'checker-revision-digest',
|
|
93
|
+
confirmatoryFamilyId: 'weekly-live-agent-improvement',
|
|
94
|
+
confirmatoryFamilySize: 1,
|
|
95
|
+
confirmatoryIndex: 1,
|
|
96
|
+
familyReservationSourceId: 'family-ledger-slot-id',
|
|
97
|
+
assignmentUnit: 'session',
|
|
98
|
+
clusterUnit: 'customer',
|
|
99
|
+
stoppingRule: 'fixed-time',
|
|
100
|
+
analysisCutoff: '2026-09-25T06:00:00Z',
|
|
101
|
+
outcomeMaturityMs: 120_000,
|
|
102
|
+
minimumLift: 0,
|
|
103
|
+
minimumClusters: 40,
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
// Witness `protocol` and reserve the family slot before serving traffic.
|
|
107
|
+
// The host freezes `cohortReceipt` and `checkedObservations` at the registered times.
|
|
108
|
+
const decision = decideRandomizedCanary(
|
|
109
|
+
protocol,
|
|
110
|
+
cohortReceipt,
|
|
111
|
+
checkedObservations,
|
|
112
|
+
verifyPlatformCohortReceipt,
|
|
113
|
+
)
|
|
114
|
+
if (decision.refusal !== null) throw new Error(decision.refusal)
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
`cohortReceipt` binds the post-cutoff complete roster and the fixed observation snapshot to the prospective protocol.
|
|
118
|
+
The deterministic calibration in `src/experiment/randomized-canary.test.ts` used 500 trials per law, 60 customers, and four sessions per customer.
|
|
119
|
+
Each customer had a shared pass propensity of 0.25 or 0.65 with equal probability.
|
|
120
|
+
Two sessions entered each arm in random order; the positive law added 0.20 to candidate pass probability.
|
|
121
|
+
The nominal 95% headline passed 14/500 null trials (2.8%) and 465/500 positive trials (93.0%).
|
|
122
|
+
These frequencies only calibrate this stated synthetic law and do not prove coverage for other cluster structures or live traffic.
|
|
29
123
|
|
|
30
124
|
### Sealing and execution
|
|
31
125
|
|
|
@@ -55,8 +55,8 @@ Those are `runReplayBatch`, `replayVerifyFinding`, and `verifyFindings`.
|
|
|
55
55
|
|
|
56
56
|
| Field | Meaning |
|
|
57
57
|
|---|---|
|
|
58
|
-
| `armA.failureSignatureMatch` |
|
|
59
|
-
| `armB.failureVanished` |
|
|
58
|
+
| `armA.failureSignatureMatch` | A recorded error substring exists, the returncode matched, and the substring appeared. It is false when the recording has no signature. |
|
|
59
|
+
| `armB.failureVanished` | The corrected command exited 0, the error substring was gone, and arm B's prefix stayed within tolerance. |
|
|
60
60
|
| `prefixDivergences` | Prefix steps that did not confirm the recording, each with its `kind`. |
|
|
61
61
|
| `prefixDivergencePct` | Divergent steps over executed steps. This is the number an admission pre-pass gates on. |
|
|
62
62
|
| `prefixWithinTolerance` | `prefixDivergencePct` is at most `PREFIX_DIVERGENCE_TOLERANCE_PCT` (10). |
|
|
@@ -79,7 +79,11 @@ Counting it as agreement lets a replay that fails on every step report a perfect
|
|
|
79
79
|
|
|
80
80
|
`runReplayBatch` reports the same split per case and across the corpus, under `headline.prefixFidelity`.
|
|
81
81
|
Its `replayed` predicate requires `prefixWithinTolerance`, so an unconfirmed prefix is never admitted.
|
|
82
|
+
An exit-0 gold action does not establish a reproduced failure, even if its output mentions an error.
|
|
83
|
+
The batch excludes a gold target with no recorded returncode before it starts a sandbox.
|
|
84
|
+
The batch reports each excluded case and reason.
|
|
82
85
|
`verifyFindings` applies the same rule: a proof whose prefix fell outside the tolerance is `divergent`, whatever its arms did.
|
|
86
|
+
The finding wire rejects a target with exit 0 or no recorded exit before it calls a fix model.
|
|
83
87
|
|
|
84
88
|
Prefix divergence is reported, never hidden.
|
|
85
89
|
A high divergence rate is a finding about replay fidelity, not a harness error.
|
|
@@ -122,8 +126,9 @@ A row whose commands cannot be reconstructed exactly as recorded is rejected, be
|
|
|
122
126
|
Only trajectories that record their image are replayable.
|
|
123
127
|
A task whose environment needs external compose peers cannot be replayed this way.
|
|
124
128
|
|
|
125
|
-
A
|
|
126
|
-
A submit
|
|
129
|
+
A pure submit decision is never a replay target.
|
|
130
|
+
A submit command that also changes state stays in the trajectory; without a recorded exit, it is excluded as `no-recorded-returncode-at-k`.
|
|
131
|
+
The same exclusion prevents shell replay of SWE-agent ACI editor commands whose recordings carry no shell returncode.
|
|
127
132
|
|
|
128
133
|
`dockerImagePreparer` shells out to `docker`.
|
|
129
134
|
Pass `preparer: null` on a corpus source when the images are already replay-ready.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tangle-network/agent-eval",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.187.0",
|
|
4
4
|
"description": "Evaluate and improve AI agents from runs, traces, judges, and feedback. Compare candidates, cluster failures, measure lift, and gate releases.",
|
|
5
5
|
"homepage": "https://github.com/tangle-network/agent-eval#readme",
|
|
6
6
|
"repository": {
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"exec-D9WpA2p-.js","names":[],"sources":["../src/trajectory-replay/steps.ts","../src/trajectory-replay/exec.ts"],"sourcesContent":["/**\n * Recorded shell-trajectory steps and the observation grammar they carry.\n *\n * A recorded trajectory is the action/observation sequence an agent actually\n * ran. Scaffolds that execute one shell command per step (mini-SWE and the\n * CodeTracer-normalized corpora built from it) tag each observation with the\n * command's returncode and its combined output:\n *\n * <returncode>2</returncode>\n * <output>\n * …command output…\n * </output>\n *\n * That is one of four shapes a recorded turn carries, and the other three are\n * not command results at all:\n *\n * - a timeout notice, when the environment killed the command at its bound;\n * - a format-error notice, when the scaffold rejected the turn and ran\n * nothing;\n * - an elision marker `$<hex>`, when the published dump dropped the string.\n *\n * A turn also carries no observation when it is the run's last turn, because\n * the scaffold records an observation only when it hands one back to the model.\n *\n * The parsers here are the only place that grammar is decoded. Everything\n * downstream — replay verdicts, corpus enumeration, admission funnels, fix\n * prompts — reads the returncode, the output, and the failure signature through\n * these functions. A second decoder elsewhere is how a corpus reads as\n * unreplayable when it is not.\n */\n\n/**\n * One step of a recorded shell trajectory. Structural: any richer step record\n * (file refs, thinking text, tool type) satisfies it.\n */\nexport interface RecordedTrajectoryStep {\n /** 1-based position in the trajectory. */\n readonly step_id: number\n readonly action: string\n /** Null when the step recorded no observation (terminal submit steps). */\n readonly observation: string | null\n}\n\n/** Recorded returncode of a step, or null when the observation carries none. */\nexport function parseRecordedReturncode(observation: string | null): number | null {\n if (!observation) return null\n const m = /<returncode>(-?\\d+)<\\/returncode>/.exec(observation)\n return m ? Number(m[1]) : null\n}\n\n/** Text between the observation's <output> tags, or the raw observation when\n * the tags are absent. */\nexport function parseObservationOutput(observation: string | null): string {\n if (!observation) return ''\n const m = /<output>\\n?([\\s\\S]*?)\\n?<\\/output>/.exec(observation)\n return m ? m[1]! : observation\n}\n\n/**\n * Stable failure-signature candidate: the first line of the recorded output\n * that contains the word \"error\". Null when no such line exists — a verdict\n * then falls back to returncode-only matching and says so.\n * Pass an explicit signature to override (compiler quote glyphs vary with\n * locale, so a hand-picked ASCII substring is often more robust).\n */\nexport function deriveFailureSignature(observation: string | null): string | null {\n const line = parseObservationOutput(observation)\n .split('\\n')\n .find((l) => /\\berror\\b/i.test(l))\n return line ? line.trim().slice(0, 200) : null\n}\n\n/** mini-SWE's end-of-run submit convention: the agent echoes this sentinel\n * and dumps the diff. A label on this step marks a bad SUBMIT DECISION, not a\n * failed command — there is no executable failure to reproduce, so it is\n * never a counterfactual replay target. */\nexport const SUBMIT_ACTION_SIGNATURE = 'COMPLETE_TASK_AND_SUBMIT_FINAL_OUTPUT'\n\nexport function isSubmitAction(action: string): boolean {\n return action.includes(SUBMIT_ACTION_SIGNATURE)\n}\n\n/**\n * True when the action is the sentinel echo and nothing else.\n *\n * The distinction decides whether a step may be dropped. An agent is told to\n * issue the sentinel alone, and 5.7% of recorded runs end on a command that\n * writes files or edits them and then echoes it. Dropping such a step because\n * it holds the sentinel would remove the run's last state change from the\n * replay, so the recorded end state and the replayed one would differ.\n */\nexport function isSubmitOnlyAction(action: string): boolean {\n return /^echo\\s+(?:\"COMPLETE_TASK_AND_SUBMIT_FINAL_OUTPUT\"|'COMPLETE_TASK_AND_SUBMIT_FINAL_OUTPUT'|COMPLETE_TASK_AND_SUBMIT_FINAL_OUTPUT)$/.test(\n action.trim(),\n )\n}\n\n// ── Fields the published dump dropped ────────────────────────────────\n\n/**\n * A field the dump replaced with a counter instead of its text.\n *\n * The counter is hexadecimal and rises by one per dropped string in document\n * order, so a decimal-only reading (`$12`) accepts every marker that carries a\n * letter (`$3a`) as if it were real text. A command read that way replays as\n * the literal two-to-four characters `$3a`, which is not the command the run\n * executed.\n *\n * Nothing in the dump maps a marker back to its text: the same marker carries\n * different content in different rows, so there is no dictionary to read. A\n * field that matches is unrecoverable, so a caller rejects the row that holds\n * it rather than replaying the marker.\n */\nconst RECORDED_ELISION_PATTERN = /^\\$[0-9a-f]+$/\n\nexport function isElidedField(value: string | null | undefined): boolean {\n return typeof value === 'string' && RECORDED_ELISION_PATTERN.test(value)\n}\n\n// ── Observation grammar ──────────────────────────────────────────────\n\n/** Substring the timeout notice always carries, whatever the command was. */\nexport const TIMEOUT_OBSERVATION_MARKER = 'timed out and has been killed'\n\n/** Opening of the notice the scaffold writes when a turn held no single action. */\nexport const FORMAT_ERROR_OBSERVATION_PREFIX =\n 'Please always provide EXACTLY ONE action in triple backticks, found '\n\n/**\n * What a recorded observation is.\n *\n * `command-result` is the only kind that carries an exit status. `timeout` and\n * `format-error` are the scaffold speaking rather than a command: the first\n * says the environment killed the command, the second says no command ran at\n * all. `elided` and `absent` carry no information about the step, and\n * `unreadable` is a shape this grammar does not know — never assumed to be\n * anything.\n */\nexport type RecordedObservationKind =\n | 'command-result'\n | 'timeout'\n | 'format-error'\n | 'elided'\n | 'absent'\n | 'unreadable'\n\nexport function classifyObservation(observation: string | null): RecordedObservationKind {\n if (observation === null) return 'absent'\n if (isElidedField(observation)) return 'elided'\n if (parseRecordedReturncode(observation) !== null) return 'command-result'\n if (observation.startsWith(FORMAT_ERROR_OBSERVATION_PREFIX)) return 'format-error'\n if (observation.includes(TIMEOUT_OBSERVATION_MARKER)) return 'timeout'\n return 'unreadable'\n}\n\n/** True when the recording shows the environment killed this step at its\n * wall-clock bound. Such a step carries no returncode, so no replay can\n * confirm or contradict it. */\nexport function isRecordedTimeout(observation: string | null): boolean {\n return classifyObservation(observation) === 'timeout'\n}\n\n// ── Turns to steps ───────────────────────────────────────────────────\n\n/**\n * One turn as the published trajectory dump holds it.\n *\n * A turn is not a step: the system prompt, the task statement and every turn\n * the scaffold rejected are turns that executed nothing.\n */\nexport interface RecordedTrajectoryTurn {\n readonly src?: string | null\n readonly msg?: string | null\n readonly tools?: readonly { readonly cmd?: string | null }[] | null\n readonly obs?: string | null\n}\n\nexport interface DecodedTrajectory {\n /** Executed commands in recorded order, each holding its OWN observation. */\n readonly steps: readonly RecordedTrajectoryStep[]\n /** Turns the scaffold rejected before anything ran. */\n readonly formatErrorTurns: number\n /**\n * Executed commands whose text the dump dropped.\n *\n * The step stays in `steps` carrying the marker, because the run did execute\n * a command there and a shorter list would misreport the trajectory. The\n * marker is not a command, so any count above zero means this trajectory\n * cannot be replayed: gate on it, or call `assertReplayableTrajectory`.\n */\n readonly elidedCommands: number\n /** True when the run's last turn was the submit sentinel with no observation. */\n readonly endedOnSubmitSentinel: boolean\n /** Turns carrying an observation this grammar cannot read. */\n readonly unreadableTurns: number\n}\n\n/**\n * Pair every recorded command with the observation of its own turn.\n *\n * Collecting commands and observations into two lists and zipping them is the\n * decode that looks right and is not: a turn the scaffold rejected carries an\n * observation of its own, so from the first such turn onward every observation\n * belongs to a different command than the one it is read against.\n *\n * A rejected turn executed nothing, so it is never a step — including when the\n * dump recorded a command for it. The scaffold rejects a turn holding several\n * bash blocks and runs none of them, while the dump keeps one of the blocks in\n * the command field. Replaying that field would execute a command the recorded\n * run did not, which is a worse corpus than a smaller one.\n *\n * A rejected turn is recognised by its observation, so a rejected turn whose\n * observation the dump elided is indistinguishable from an executed command\n * whose observation it elided. Both read as a step. Nothing in the dump\n * separates them, and the row-level defence is the share of unreadable exits a\n * caller admits: a row with no elided observation cannot hold this case at all.\n *\n * A trailing step that echoes the sentinel and nothing else, with no\n * observation, is dropped from `steps` and reported as `endedOnSubmitSentinel`.\n * The scaffold records an observation only when it hands one to the model, and\n * the sentinel ends the run, so the missing observation is the end of the\n * transcript rather than a gap in it. Echoing the sentinel changes no state, so\n * the recorded end state is the state the step before it left.\n *\n * A step that DID get an observation stays a step: the run continued past it.\n * So does a step that echoes the sentinel after doing real work — its state\n * change is part of the recorded end state, and with no observation its exit is\n * unknown, which `finalRecordedOutcome` reports rather than hides.\n */\nexport function decodeRecordedTurns(turns: readonly RecordedTrajectoryTurn[]): DecodedTrajectory {\n const steps: RecordedTrajectoryStep[] = []\n let formatErrorTurns = 0\n let unreadableTurns = 0\n for (const turn of turns) {\n const command = turn.tools?.[0]?.cmd ?? null\n const observation = turn.obs ?? null\n const kind = classifyObservation(observation)\n if (kind === 'format-error') {\n formatErrorTurns += 1\n continue\n }\n if (command === null) {\n if (kind === 'unreadable' || kind === 'elided') unreadableTurns += 1\n continue\n }\n steps.push({ step_id: steps.length + 1, action: command, observation })\n }\n const last = steps[steps.length - 1]\n const endedOnSubmitSentinel =\n last !== undefined && last.observation === null && isSubmitOnlyAction(last.action)\n if (endedOnSubmitSentinel) steps.pop()\n return {\n steps,\n formatErrorTurns,\n elidedCommands: steps.filter((step) => isElidedField(step.action)).length,\n endedOnSubmitSentinel,\n unreadableTurns,\n }\n}\n\n// ── The state a trajectory ended in ──────────────────────────────────\n\n/**\n * How the recorded run's last executed command ended.\n *\n * `killed` is a measured outcome, not a missing one: the environment stopped\n * the command at its bound and wrote a notice instead of an exit status.\n * `unreadable` names the observation kind that blocked the read, so a funnel\n * can report which shape cost it the row.\n */\nexport type RecordedFinalOutcome =\n | { readonly kind: 'returncode'; readonly value: number }\n | { readonly kind: 'killed' }\n | { readonly kind: 'unreadable'; readonly reason: RecordedObservationKind }\n\n/**\n * The outcome of the last step, or `null` when the trajectory has no steps.\n *\n * Reads only the last step. `decodeRecordedTurns` has already removed the turns\n * that executed nothing, so the last step is the last command the run ran.\n */\nexport function finalRecordedOutcome(\n steps: readonly RecordedTrajectoryStep[],\n): RecordedFinalOutcome | null {\n const last = steps[steps.length - 1]\n if (last === undefined) return null\n const kind = classifyObservation(last.observation)\n if (kind === 'command-result') {\n return { kind: 'returncode', value: parseRecordedReturncode(last.observation)! }\n }\n if (kind === 'timeout') return { kind: 'killed' }\n return { kind: 'unreadable', reason: kind }\n}\n\n/**\n * Steps whose recorded exit a replay cannot check.\n *\n * A killed step counts: the recording holds no exit status to compare a replay\n * against, so agreement on it cannot be measured either way.\n */\nexport function unreadableExitCount(steps: readonly RecordedTrajectoryStep[]): number {\n return steps.filter((step) => classifyObservation(step.observation) !== 'command-result').length\n}\n\n/**\n * Throw unless every recorded command survived the dump.\n *\n * A replay executes `steps` verbatim, so one elision marker in an action means\n * the replay runs the literal two-to-four characters of the marker instead of\n * the command the run executed. The guard is here so a replayer needs one call\n * rather than a field check it can forget.\n */\nexport function assertReplayableTrajectory(decoded: DecodedTrajectory): void {\n if (decoded.elidedCommands === 0) return\n const markers = decoded.steps\n .filter((step) => isElidedField(step.action))\n .map((step) => step.action)\n throw new Error(\n `trajectory holds ${decoded.elidedCommands} command(s) the dump elided (${markers.slice(0, 5).join(', ')}); ` +\n 'no dictionary maps a marker back to its text, so this trajectory cannot be replayed',\n )\n}\n","/**\n * The execution boundary replay runs across.\n *\n * A replay needs one thing from its environment: a session that runs a shell\n * command inside the trajectory's own image and reports the exit code and\n * output. That is the whole contract. Concrete backends — a sandbox platform\n * client, a docker exec, an SSH shell — live with the consumer that owns the\n * infrastructure, so this package depends on no sandbox client.\n */\n\nexport interface ReplayExecResult {\n exitCode: number\n stdout: string\n stderr: string\n}\n\nexport interface ReplayExecSession {\n exec(command: string, timeoutMs: number): Promise<ReplayExecResult>\n close(): Promise<void>\n}\n\nexport interface ReplayExecBackend {\n /** One fresh execution environment per call; the caller closes it. */\n open(): Promise<ReplayExecSession>\n}\n\n/** Builds a backend pinned to one image. Callers that resolve images\n * internally (batch, corpus wire, finding verification) take this instead of\n * a backend, so every case runs against its own image. */\nexport type ReplayExecBackendFactory = (image: string) => ReplayExecBackend\n\n/**\n * mini-SWE runs every action as a fresh /bin/sh subshell from a fixed\n * workdir. Reproduce that exactly — and stay quote-proof for arbitrary\n * recorded actions — by piping the base64 of the action into `sh` after\n * cd-ing to the workdir. Exit code is sh's, i.e. the action's.\n */\nexport function wrapActionForExec(action: string, cwd: string): string {\n const b64 = Buffer.from(action, 'utf8').toString('base64')\n const quotedCwd = `'${cwd.replaceAll(\"'\", `'\\\\''`)}'`\n return `cd ${quotedCwd} && printf %s ${b64} | base64 -d | sh`\n}\n"],"mappings":"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"}
|