@tangle-network/agent-bench 0.4.0 → 0.4.3
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 +15 -0
- package/dist/adapters.d.ts +4 -11
- package/dist/adapters.js +78 -41
- package/dist/adapters.js.map +1 -1
- package/dist/benchmarks/_harness.d.ts +51 -66
- package/dist/benchmarks/_harness.js +329 -31
- package/dist/benchmarks/_harness.js.map +1 -1
- package/dist/benchmarks/aec-bench.d.ts +4 -25
- package/dist/benchmarks/aec-bench.js +242 -7
- package/dist/benchmarks/aec-bench.js.map +1 -1
- package/dist/benchmarks/agentbench.d.ts +5 -13
- package/dist/benchmarks/agentbench.js +114 -9
- package/dist/benchmarks/agentbench.js.map +1 -1
- package/dist/benchmarks/appworld.d.ts +9 -29
- package/dist/benchmarks/appworld.js +317 -12
- package/dist/benchmarks/appworld.js.map +1 -1
- package/dist/benchmarks/bfcl.d.ts +5 -15
- package/dist/benchmarks/bfcl.js +264 -9
- package/dist/benchmarks/bfcl.js.map +1 -1
- package/dist/benchmarks/cad-design.d.ts +16 -41
- package/dist/benchmarks/cad-design.js +512 -6
- package/dist/benchmarks/cad-design.js.map +1 -1
- package/dist/benchmarks/cadbench.d.ts +4 -17
- package/dist/benchmarks/cadbench.js +2 -8
- package/dist/benchmarks/cadgenbench.d.ts +4 -20
- package/dist/benchmarks/cadgenbench.js +2 -8
- package/dist/benchmarks/commit0.d.ts +5 -27
- package/dist/benchmarks/commit0.js +187 -9
- package/dist/benchmarks/commit0.js.map +1 -1
- package/dist/benchmarks/crag.d.ts +4 -12
- package/dist/benchmarks/crag.js +110 -8
- package/dist/benchmarks/crag.js.map +1 -1
- package/dist/benchmarks/dabstep.d.ts +5 -15
- package/dist/benchmarks/dabstep.js +177 -9
- package/dist/benchmarks/dabstep.js.map +1 -1
- package/dist/benchmarks/enterpriseops-gym.d.ts +5 -34
- package/dist/benchmarks/enterpriseops-gym.js +236 -9
- package/dist/benchmarks/enterpriseops-gym.js.map +1 -1
- package/dist/benchmarks/finresearchbench.d.ts +4 -13
- package/dist/benchmarks/finresearchbench.js +218 -7
- package/dist/benchmarks/finresearchbench.js.map +1 -1
- package/dist/benchmarks/finsearchcomp.d.ts +8 -39
- package/dist/benchmarks/finsearchcomp.js +267 -6
- package/dist/benchmarks/finsearchcomp.js.map +1 -1
- package/dist/benchmarks/frames.d.ts +15 -37
- package/dist/benchmarks/frames.js +408 -11
- package/dist/benchmarks/frames.js.map +1 -1
- package/dist/benchmarks/hotpotqa.d.ts +4 -24
- package/dist/benchmarks/hotpotqa.js +250 -14
- package/dist/benchmarks/hotpotqa.js.map +1 -1
- package/dist/benchmarks/humaneval.d.ts +19 -37
- package/dist/benchmarks/humaneval.js +279 -16
- package/dist/benchmarks/humaneval.js.map +1 -1
- package/dist/benchmarks/mind2web.d.ts +7 -34
- package/dist/benchmarks/mind2web.js +257 -8
- package/dist/benchmarks/mind2web.js.map +1 -1
- package/dist/benchmarks/nomiracl.d.ts +4 -13
- package/dist/benchmarks/nomiracl.js +146 -8
- package/dist/benchmarks/nomiracl.js.map +1 -1
- package/dist/benchmarks/open-rag-bench.d.ts +4 -12
- package/dist/benchmarks/open-rag-bench.js +110 -8
- package/dist/benchmarks/open-rag-bench.js.map +1 -1
- package/dist/benchmarks/programbench.d.ts +5 -29
- package/dist/benchmarks/programbench.js +161 -9
- package/dist/benchmarks/programbench.js.map +1 -1
- package/dist/benchmarks/rag-shared.d.ts +21 -20
- package/dist/benchmarks/rag-shared.js +245 -38
- package/dist/benchmarks/rag-shared.js.map +1 -1
- package/dist/benchmarks/ragbench.d.ts +4 -14
- package/dist/benchmarks/ragbench.js +127 -8
- package/dist/benchmarks/ragbench.js.map +1 -1
- package/dist/benchmarks/simpleqa.d.ts +17 -44
- package/dist/benchmarks/simpleqa.js +293 -10
- package/dist/benchmarks/simpleqa.js.map +1 -1
- package/dist/benchmarks/swe-bench.d.ts +23 -36
- package/dist/benchmarks/swe-bench.js +234 -13
- package/dist/benchmarks/swe-bench.js.map +1 -1
- package/dist/benchmarks/t2-ragbench.d.ts +4 -12
- package/dist/benchmarks/t2-ragbench.js +118 -8
- package/dist/benchmarks/t2-ragbench.js.map +1 -1
- package/dist/benchmarks/tau-bench-shared.d.ts +14 -22
- package/dist/benchmarks/tau-bench-shared.js +169 -9
- package/dist/benchmarks/tau-bench-shared.js.map +1 -1
- package/dist/benchmarks/tau2-bench.d.ts +5 -5
- package/dist/benchmarks/tau2-bench.js +28 -10
- package/dist/benchmarks/tau2-bench.js.map +1 -1
- package/dist/benchmarks/tau3-banking.d.ts +4 -13
- package/dist/benchmarks/tau3-banking.js +28 -8
- package/dist/benchmarks/tau3-banking.js.map +1 -1
- package/dist/benchmarks/terminal-bench.d.ts +4 -22
- package/dist/benchmarks/terminal-bench.js +140 -7
- package/dist/benchmarks/terminal-bench.js.map +1 -1
- package/dist/benchmarks/toollm.d.ts +5 -13
- package/dist/benchmarks/toollm.js +184 -9
- package/dist/benchmarks/toollm.js.map +1 -1
- package/dist/benchmarks/trata-hedge.d.ts +4 -30
- package/dist/benchmarks/trata-hedge.js +336 -6
- package/dist/benchmarks/trata-hedge.js.map +1 -1
- package/dist/benchmarks/types.d.ts +85 -94
- package/dist/benchmarks/types.js +1 -1
- package/dist/benchmarks/webarena-verified.d.ts +5 -13
- package/dist/benchmarks/webarena-verified.js +152 -9
- package/dist/benchmarks/webarena-verified.js.map +1 -1
- package/dist/cadbench-DpQWZHp4.js +285 -0
- package/dist/cadbench-DpQWZHp4.js.map +1 -0
- package/dist/cadgenbench-DRhczfsG.js +151 -0
- package/dist/cadgenbench-DRhczfsG.js.map +1 -0
- package/dist/index.d.ts +245 -293
- package/dist/index.js +1669 -1791
- package/dist/index.js.map +1 -1
- package/package.json +17 -14
- package/pier_agents/candidate_contract.py +238 -24
- package/pier_agents/tangle_candidate.py +75 -5
- package/scripts/verify-packed-consumer.mjs +84 -17
- package/scripts/verify-pier-agent.mts +6 -4
- package/src/benchmarks/_harness.test.mts +16 -1
- package/src/benchmarks/_harness.ts +9 -2
- package/src/benchmarks/terminal-bench.ts +2 -1
- package/src/corpus.test.mts +13 -0
- package/src/corpus.ts +4 -0
- package/src/profile-coordinates.ts +2 -2
- package/src/rollout-ledger/backfill-swe-arena.mts +5 -1
- package/src/rollout-ledger/settle-capture.mts +7 -1
- package/src/search-bench/profiles.ts +1 -1
- package/src/skill-sandbox-smoke.mts +2 -1
- package/src/swe-arena/gepa-seat.mts +1 -1
- package/src/swe-arena/ledger-orphans.test.mts +36 -34
- package/dist/benchmarks/cadbench.js.map +0 -1
- package/dist/benchmarks/cadgenbench.js.map +0 -1
- package/dist/benchmarks/types.js.map +0 -1
- package/dist/chunk-3U5TXJZS.js +0 -251
- package/dist/chunk-3U5TXJZS.js.map +0 -1
- package/dist/chunk-53UPUNBZ.js +0 -325
- package/dist/chunk-53UPUNBZ.js.map +0 -1
- package/dist/chunk-5H5XV76F.js +0 -240
- package/dist/chunk-5H5XV76F.js.map +0 -1
- package/dist/chunk-7GRVHU22.js +0 -208
- package/dist/chunk-7GRVHU22.js.map +0 -1
- package/dist/chunk-C7T7WEK2.js +0 -103
- package/dist/chunk-C7T7WEK2.js.map +0 -1
- package/dist/chunk-HWST3SED.js +0 -162
- package/dist/chunk-HWST3SED.js.map +0 -1
- package/dist/chunk-IA2FBTWC.js +0 -318
- package/dist/chunk-IA2FBTWC.js.map +0 -1
- package/dist/chunk-IFVINJ4B.js +0 -142
- package/dist/chunk-IFVINJ4B.js.map +0 -1
- package/dist/chunk-INNOYXCP.js +0 -387
- package/dist/chunk-INNOYXCP.js.map +0 -1
- package/dist/chunk-IZ5M6OAC.js +0 -169
- package/dist/chunk-IZ5M6OAC.js.map +0 -1
- package/dist/chunk-JTHWEDEW.js +0 -32
- package/dist/chunk-JTHWEDEW.js.map +0 -1
- package/dist/chunk-K3BQGZCT.js +0 -221
- package/dist/chunk-K3BQGZCT.js.map +0 -1
- package/dist/chunk-KP5KD6EN.js +0 -276
- package/dist/chunk-KP5KD6EN.js.map +0 -1
- package/dist/chunk-MQMRLGOG.js +0 -136
- package/dist/chunk-MQMRLGOG.js.map +0 -1
- package/dist/chunk-NQG5XDSB.js +0 -147
- package/dist/chunk-NQG5XDSB.js.map +0 -1
- package/dist/chunk-PA2ZKHJC.js +0 -230
- package/dist/chunk-PA2ZKHJC.js.map +0 -1
- package/dist/chunk-PB64GYIG.js +0 -118
- package/dist/chunk-PB64GYIG.js.map +0 -1
- package/dist/chunk-PUIRNYI7.js +0 -189
- package/dist/chunk-PUIRNYI7.js.map +0 -1
- package/dist/chunk-RCYQEFNX.js +0 -30
- package/dist/chunk-RCYQEFNX.js.map +0 -1
- package/dist/chunk-RH5F53JT.js +0 -182
- package/dist/chunk-RH5F53JT.js.map +0 -1
- package/dist/chunk-SEVJPLZC.js +0 -260
- package/dist/chunk-SEVJPLZC.js.map +0 -1
- package/dist/chunk-SFLA7OH3.js +0 -27
- package/dist/chunk-SFLA7OH3.js.map +0 -1
- package/dist/chunk-SHM6MRRF.js +0 -130
- package/dist/chunk-SHM6MRRF.js.map +0 -1
- package/dist/chunk-SHYIRB7I.js +0 -120
- package/dist/chunk-SHYIRB7I.js.map +0 -1
- package/dist/chunk-SVR2LKYI.js +0 -116
- package/dist/chunk-SVR2LKYI.js.map +0 -1
- package/dist/chunk-TBKU5XQI.js +0 -228
- package/dist/chunk-TBKU5XQI.js.map +0 -1
- package/dist/chunk-UPAMRDX4.js +0 -233
- package/dist/chunk-UPAMRDX4.js.map +0 -1
- package/dist/chunk-V7AEBY6U.js +0 -144
- package/dist/chunk-V7AEBY6U.js.map +0 -1
- package/dist/chunk-VQRS7VUC.js +0 -342
- package/dist/chunk-VQRS7VUC.js.map +0 -1
- package/dist/chunk-WSKWVEQB.js +0 -317
- package/dist/chunk-WSKWVEQB.js.map +0 -1
- package/dist/chunk-X3BTXCJ4.js +0 -262
- package/dist/chunk-X3BTXCJ4.js.map +0 -1
- package/dist/chunk-XKEFIFIC.js +0 -197
- package/dist/chunk-XKEFIFIC.js.map +0 -1
- package/dist/chunk-XYA4XSNU.js +0 -148
- package/dist/chunk-XYA4XSNU.js.map +0 -1
- package/dist/chunk-YSMEKBTD.js +0 -211
- package/dist/chunk-YSMEKBTD.js.map +0 -1
- package/dist/chunk-Z4TZ76N7.js +0 -170
- package/dist/chunk-Z4TZ76N7.js.map +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tangle-network/agent-bench",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "The unified benchmark suite for agent-runtime agents: 31 adapters (commit0, enterpriseops-gym, ragbench, crag, nomiracl, open-rag-bench, t2-ragbench, tau3-banking, bfcl, finresearchbench, …) behind one resolveAdapter registry, each with a real judge or fail-loud unsupported scorer. Score any profile/skill/prompt change against them. Map: bench/HARNESS.md.",
|
|
6
6
|
"repository": {
|
|
@@ -25,20 +25,22 @@
|
|
|
25
25
|
}
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@tangle-network/agent-eval": "0.
|
|
29
|
-
"@tangle-network/agent-interface": "0.
|
|
30
|
-
"@tangle-network/agent-knowledge": "
|
|
31
|
-
"@tangle-network/sandbox": "
|
|
32
|
-
"@tangle-network/agent-runtime": "0.
|
|
28
|
+
"@tangle-network/agent-eval": "0.133.0",
|
|
29
|
+
"@tangle-network/agent-interface": "0.36.0",
|
|
30
|
+
"@tangle-network/agent-knowledge": "6.1.2",
|
|
31
|
+
"@tangle-network/sandbox": "0.14.0",
|
|
32
|
+
"@tangle-network/agent-runtime": "0.107.1"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
|
-
"@
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"
|
|
35
|
+
"@arethetypeswrong/cli": "0.18.5",
|
|
36
|
+
"@types/node": "26.1.1",
|
|
37
|
+
"publint": "0.3.22",
|
|
38
|
+
"tsdown": "0.22.14",
|
|
39
|
+
"tsx": "4.23.1",
|
|
40
|
+
"typescript": "6.0.3"
|
|
39
41
|
},
|
|
40
42
|
"engines": {
|
|
41
|
-
"node": ">=
|
|
43
|
+
"node": ">=22.13.0"
|
|
42
44
|
},
|
|
43
45
|
"files": [
|
|
44
46
|
"dist",
|
|
@@ -61,7 +63,7 @@
|
|
|
61
63
|
},
|
|
62
64
|
"scripts": {
|
|
63
65
|
"prebuild": "pnpm --dir .. build",
|
|
64
|
-
"build": "
|
|
66
|
+
"build": "tsdown",
|
|
65
67
|
"gate": "tsx src/corpus-replay.mts corpus/finsearch.jsonl --selector",
|
|
66
68
|
"gate-cli": "tsx src/gate-cli.mts",
|
|
67
69
|
"run-benchmarks": "tsx src/run-benchmarks-cli.mts",
|
|
@@ -69,8 +71,9 @@
|
|
|
69
71
|
"terminal-compare": "tsx src/terminal-compare.ts",
|
|
70
72
|
"test": "node scripts/run-package-tests.mjs",
|
|
71
73
|
"typecheck:public": "tsc -p tsconfig.public.json",
|
|
72
|
-
"verify:package": "node scripts/verify-packed-consumer.mjs",
|
|
73
|
-
"verify:package:local-runtime": "node scripts/verify-packed-consumer.mjs --local-runtime",
|
|
74
|
+
"verify:package": "pnpm run verify:package:static && node scripts/verify-packed-consumer.mjs",
|
|
75
|
+
"verify:package:local-runtime": "pnpm run verify:package:static && node scripts/verify-packed-consumer.mjs --local-runtime",
|
|
76
|
+
"verify:package:static": "pnpm run build && publint && attw --pack --profile esm-only .",
|
|
74
77
|
"verify:pier": "tsx scripts/verify-pier-pair.mts"
|
|
75
78
|
}
|
|
76
79
|
}
|
|
@@ -34,8 +34,13 @@ _RESERVED_PATH_PARTS = {".git", ".sidecar"}
|
|
|
34
34
|
_PLAN_KIND = "agent-candidate-execution-plan-material"
|
|
35
35
|
_RECEIPT_KIND = "agent-candidate-materialization"
|
|
36
36
|
_PROFILE_PLAN_KIND = "agent-profile-workspace-plan"
|
|
37
|
+
_PROFILE_ACTIVATION_KIND = "agent-candidate-profile-activation"
|
|
37
38
|
_EXECUTION_EVIDENCE_KIND = "agent-candidate-execution-plan"
|
|
39
|
+
_RUN_CELL_KIND = "agent-candidate-run-cell"
|
|
40
|
+
_BENCHMARK_SUITE_KIND = "agent-candidate-benchmark-suite"
|
|
41
|
+
_BENCHMARK_TASK_KIND = "agent-candidate-benchmark-task"
|
|
38
42
|
_STDIN_TASK_PATH = "/tangle/input/stdin.txt"
|
|
43
|
+
_MAX_SAFE_INTEGER = 9_007_199_254_740_991
|
|
39
44
|
|
|
40
45
|
|
|
41
46
|
class CandidateContractError(ValueError):
|
|
@@ -130,6 +135,30 @@ def _integer(value: Any, label: str, *, minimum: int = 0) -> int:
|
|
|
130
135
|
return value
|
|
131
136
|
|
|
132
137
|
|
|
138
|
+
def _safe_integer(value: Any, label: str) -> int:
|
|
139
|
+
if (
|
|
140
|
+
isinstance(value, bool)
|
|
141
|
+
or not isinstance(value, int)
|
|
142
|
+
or value < -_MAX_SAFE_INTEGER
|
|
143
|
+
or value > _MAX_SAFE_INTEGER
|
|
144
|
+
):
|
|
145
|
+
raise CandidateContractError(f"{label} must be a safe integer")
|
|
146
|
+
return value
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def _canonical_bytes(value: Any, label: str) -> bytes:
|
|
150
|
+
try:
|
|
151
|
+
return json.dumps(
|
|
152
|
+
value,
|
|
153
|
+
ensure_ascii=False,
|
|
154
|
+
allow_nan=False,
|
|
155
|
+
separators=(",", ":"),
|
|
156
|
+
sort_keys=True,
|
|
157
|
+
).encode("utf-8")
|
|
158
|
+
except (TypeError, ValueError, UnicodeEncodeError) as exc:
|
|
159
|
+
raise CandidateContractError(f"{label} is not canonical JSON") from exc
|
|
160
|
+
|
|
161
|
+
|
|
133
162
|
def _number(value: Any, label: str, *, minimum: float = 0) -> float:
|
|
134
163
|
if (
|
|
135
164
|
isinstance(value, bool)
|
|
@@ -246,6 +275,21 @@ def _embedded_bytes(value: Any, label: str) -> bytes:
|
|
|
246
275
|
return raw
|
|
247
276
|
|
|
248
277
|
|
|
278
|
+
def _embedded_contract(
|
|
279
|
+
value: Any, label: str, *, kind: str
|
|
280
|
+
) -> tuple[dict[str, Any], bytes, str]:
|
|
281
|
+
evidence = _object(value, label)
|
|
282
|
+
digest = _digest(evidence.get("digest"), f"{label}.digest")
|
|
283
|
+
raw = _embedded_bytes(evidence.get("material"), f"{label}.material")
|
|
284
|
+
if sha256_bytes(raw) != digest:
|
|
285
|
+
raise CandidateContractError(f"{label} material does not match its digest")
|
|
286
|
+
try:
|
|
287
|
+
material = json.loads(raw)
|
|
288
|
+
except (UnicodeDecodeError, json.JSONDecodeError) as exc:
|
|
289
|
+
raise CandidateContractError(f"{label} material is not UTF-8 JSON") from exc
|
|
290
|
+
return _contract_object(material, f"{label}.material", kind=kind), raw, digest
|
|
291
|
+
|
|
292
|
+
|
|
249
293
|
def _workspace_files(value: Any, label: str) -> tuple[WorkspaceFile, ...]:
|
|
250
294
|
snapshot = _contract_object(
|
|
251
295
|
value, label, kind="agent-candidate-workspace-snapshot"
|
|
@@ -318,11 +362,10 @@ def _profile_files(value: Any) -> tuple[ProfileFile, ...]:
|
|
|
318
362
|
return tuple(files)
|
|
319
363
|
|
|
320
364
|
|
|
321
|
-
def _instruction(value: Any) -> InstructionEvidence:
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
delivery = _object(obj.get("delivery"), "task.instruction.delivery")
|
|
365
|
+
def _instruction(value: Any, delivery_value: Any) -> InstructionEvidence:
|
|
366
|
+
text = _string(value, "benchmark task instruction")
|
|
367
|
+
raw = text.encode("utf-8")
|
|
368
|
+
delivery = _object(delivery_value, "plan.instructionDelivery")
|
|
326
369
|
kind = delivery.get("kind")
|
|
327
370
|
if kind == "argv-append" or kind == "stdin-utf8":
|
|
328
371
|
if set(delivery) != {"kind"}:
|
|
@@ -342,10 +385,8 @@ def _instruction(value: Any) -> InstructionEvidence:
|
|
|
342
385
|
else:
|
|
343
386
|
raise CandidateContractError("unsupported task instruction delivery")
|
|
344
387
|
return InstructionEvidence(
|
|
345
|
-
sha256=
|
|
346
|
-
byte_length=
|
|
347
|
-
obj.get("byteLength"), "task.instruction.byteLength", minimum=1
|
|
348
|
-
),
|
|
388
|
+
sha256=sha256_bytes(raw),
|
|
389
|
+
byte_length=len(raw),
|
|
349
390
|
delivery_kind=kind,
|
|
350
391
|
delivery_env=env,
|
|
351
392
|
delivery_path=path,
|
|
@@ -400,12 +441,87 @@ def load_prepared_candidate_contract(
|
|
|
400
441
|
"receipt execution-plan artifact differs from the executed bytes"
|
|
401
442
|
)
|
|
402
443
|
|
|
403
|
-
|
|
444
|
+
run_cell = _contract_object(
|
|
445
|
+
plan.get("runCell"), "plan.runCell", kind=_RUN_CELL_KIND
|
|
446
|
+
)
|
|
447
|
+
_digest(run_cell.get("experimentDigest"), "plan.runCell.experimentDigest")
|
|
448
|
+
run_cell_digest = _digest(run_cell.get("digest"), "plan.runCell.digest")
|
|
449
|
+
if run_cell.get("arm") not in {"baseline", "candidate"}:
|
|
450
|
+
raise CandidateContractError("plan.runCell.arm is unsupported")
|
|
451
|
+
run_cell_seed = _safe_integer(run_cell.get("seed"), "plan.runCell.seed")
|
|
452
|
+
if sha256_bytes(
|
|
453
|
+
_canonical_bytes(
|
|
454
|
+
{key: value for key, value in run_cell.items() if key != "digest"},
|
|
455
|
+
"plan.runCell",
|
|
456
|
+
)
|
|
457
|
+
) != run_cell_digest:
|
|
458
|
+
raise CandidateContractError("plan.runCell digest does not match")
|
|
459
|
+
bundle_digest = _digest(
|
|
460
|
+
run_cell.get("bundleDigest"), "plan.runCell.bundleDigest"
|
|
461
|
+
)
|
|
404
462
|
if receipt.get("bundleDigest") != bundle_digest:
|
|
405
463
|
raise CandidateContractError("receipt and plan bundle digests differ")
|
|
406
464
|
execution_id = _string(plan.get("executionId"), "plan.executionId")
|
|
407
465
|
|
|
408
|
-
|
|
466
|
+
benchmark = _object(receipt.get("benchmark"), "receipt.benchmark")
|
|
467
|
+
suite, _, suite_digest = _embedded_contract(
|
|
468
|
+
benchmark.get("suite"),
|
|
469
|
+
"receipt.benchmark.suite",
|
|
470
|
+
kind=_BENCHMARK_SUITE_KIND,
|
|
471
|
+
)
|
|
472
|
+
task, _, task_digest = _embedded_contract(
|
|
473
|
+
benchmark.get("task"),
|
|
474
|
+
"receipt.benchmark.task",
|
|
475
|
+
kind=_BENCHMARK_TASK_KIND,
|
|
476
|
+
)
|
|
477
|
+
if (
|
|
478
|
+
run_cell.get("suiteDigest") != suite_digest
|
|
479
|
+
or run_cell.get("taskDigest") != task_digest
|
|
480
|
+
):
|
|
481
|
+
raise CandidateContractError(
|
|
482
|
+
"run cell does not bind the receipt benchmark suite and task"
|
|
483
|
+
)
|
|
484
|
+
task_index = _integer(run_cell.get("taskIndex"), "plan.runCell.taskIndex")
|
|
485
|
+
repetition = _integer(run_cell.get("repetition"), "plan.runCell.repetition")
|
|
486
|
+
if suite.get("digestAlgorithm") != "rfc8785-sha256":
|
|
487
|
+
raise CandidateContractError(
|
|
488
|
+
"benchmark suite digestAlgorithm must equal rfc8785-sha256"
|
|
489
|
+
)
|
|
490
|
+
reps = _integer(suite.get("reps"), "benchmark suite reps", minimum=1)
|
|
491
|
+
task_digests = suite.get("taskDigests")
|
|
492
|
+
seeds = suite.get("seeds")
|
|
493
|
+
if (
|
|
494
|
+
not isinstance(task_digests, list)
|
|
495
|
+
or not task_digests
|
|
496
|
+
or len(set(task_digests)) != len(task_digests)
|
|
497
|
+
):
|
|
498
|
+
raise CandidateContractError(
|
|
499
|
+
"benchmark suite taskDigests must be a non-empty unique array"
|
|
500
|
+
)
|
|
501
|
+
for index, value in enumerate(task_digests):
|
|
502
|
+
_digest(value, f"benchmark suite taskDigests[{index}]")
|
|
503
|
+
if (
|
|
504
|
+
not isinstance(seeds, list)
|
|
505
|
+
or len(seeds) != len(task_digests) * reps
|
|
506
|
+
):
|
|
507
|
+
raise CandidateContractError(
|
|
508
|
+
"benchmark suite must provide one seed per task repetition"
|
|
509
|
+
)
|
|
510
|
+
for index, value in enumerate(seeds):
|
|
511
|
+
_safe_integer(value, f"benchmark suite seeds[{index}]")
|
|
512
|
+
if (
|
|
513
|
+
task_index >= len(task_digests)
|
|
514
|
+
or task_digests[task_index] != task_digest
|
|
515
|
+
or repetition >= reps
|
|
516
|
+
or seeds[task_index * reps + repetition] != run_cell_seed
|
|
517
|
+
):
|
|
518
|
+
raise CandidateContractError(
|
|
519
|
+
"run cell coordinates do not match the receipt benchmark suite"
|
|
520
|
+
)
|
|
521
|
+
if task.get("digestAlgorithm") != "rfc8785-sha256":
|
|
522
|
+
raise CandidateContractError(
|
|
523
|
+
"benchmark task digestAlgorithm must equal rfc8785-sha256"
|
|
524
|
+
)
|
|
409
525
|
outcome = _object(task.get("outcome"), "plan.task.outcome")
|
|
410
526
|
if outcome.get("kind") != "workspace":
|
|
411
527
|
raise CandidateContractError("Pier requires a workspace task outcome")
|
|
@@ -418,7 +534,9 @@ def load_prepared_candidate_contract(
|
|
|
418
534
|
)
|
|
419
535
|
if len(base_commit) != len(base_tree):
|
|
420
536
|
raise CandidateContractError("task Git objects use different hash formats")
|
|
421
|
-
instruction = _instruction(
|
|
537
|
+
instruction = _instruction(
|
|
538
|
+
task.get("instruction"), plan.get("instructionDelivery")
|
|
539
|
+
)
|
|
422
540
|
task_files = _workspace_files(task.get("workspace"), "plan.task.workspace")
|
|
423
541
|
if not task_files:
|
|
424
542
|
raise CandidateContractError("task workspace cannot be empty")
|
|
@@ -480,16 +598,40 @@ def load_prepared_candidate_contract(
|
|
|
480
598
|
if receipt.get("candidateWorkspace") != candidate_snapshot:
|
|
481
599
|
raise CandidateContractError("receipt and plan candidate workspaces differ")
|
|
482
600
|
|
|
601
|
+
profile_activation = _contract_object(
|
|
602
|
+
receipt.get("profileActivation"),
|
|
603
|
+
"receipt.profileActivation",
|
|
604
|
+
kind=_PROFILE_ACTIVATION_KIND,
|
|
605
|
+
)
|
|
606
|
+
profile_activation_digest = _digest(
|
|
607
|
+
profile_activation.get("digest"),
|
|
608
|
+
"receipt.profileActivation.digest",
|
|
609
|
+
)
|
|
610
|
+
if sha256_bytes(
|
|
611
|
+
_canonical_bytes(
|
|
612
|
+
{
|
|
613
|
+
key: value
|
|
614
|
+
for key, value in profile_activation.items()
|
|
615
|
+
if key != "digest"
|
|
616
|
+
},
|
|
617
|
+
"receipt.profileActivation",
|
|
618
|
+
)
|
|
619
|
+
) != profile_activation_digest:
|
|
620
|
+
raise CandidateContractError(
|
|
621
|
+
"profile activation digest does not match"
|
|
622
|
+
)
|
|
483
623
|
profile_evidence = _contract_object(
|
|
484
|
-
|
|
485
|
-
"receipt.profilePlan",
|
|
624
|
+
profile_activation.get("profilePlan"),
|
|
625
|
+
"receipt.profileActivation.profilePlan",
|
|
486
626
|
kind=_PROFILE_PLAN_KIND,
|
|
487
627
|
)
|
|
488
628
|
profile_digest = _digest(
|
|
489
|
-
profile_evidence.get("digest"),
|
|
629
|
+
profile_evidence.get("digest"),
|
|
630
|
+
"receipt.profileActivation.profilePlan.digest",
|
|
490
631
|
)
|
|
491
632
|
profile_raw = _embedded_bytes(
|
|
492
|
-
profile_evidence.get("artifact"),
|
|
633
|
+
profile_evidence.get("artifact"),
|
|
634
|
+
"receipt.profileActivation.profilePlan.artifact",
|
|
493
635
|
)
|
|
494
636
|
if sha256_bytes(profile_raw) != profile_digest:
|
|
495
637
|
raise CandidateContractError("profile artifact does not match its digest")
|
|
@@ -498,11 +640,50 @@ def load_prepared_candidate_contract(
|
|
|
498
640
|
except (UnicodeDecodeError, json.JSONDecodeError) as exc:
|
|
499
641
|
raise CandidateContractError("profile artifact is not UTF-8 JSON") from exc
|
|
500
642
|
profile_material = _contract_object(
|
|
501
|
-
profile_evidence.get("material"),
|
|
643
|
+
profile_evidence.get("material"),
|
|
644
|
+
"receipt.profileActivation.profilePlan.material",
|
|
502
645
|
)
|
|
503
646
|
if profile_artifact_material != profile_material:
|
|
504
647
|
raise CandidateContractError("profile artifact and material differ")
|
|
648
|
+
if profile_material.get("harness") != plan.get("harness"):
|
|
649
|
+
raise CandidateContractError(
|
|
650
|
+
"profile plan harness differs from the execution plan"
|
|
651
|
+
)
|
|
505
652
|
profile_files = _profile_files(profile_material)
|
|
653
|
+
activation_files = profile_activation.get("files")
|
|
654
|
+
if not isinstance(activation_files, list):
|
|
655
|
+
raise CandidateContractError(
|
|
656
|
+
"receipt.profileActivation.files must be an array"
|
|
657
|
+
)
|
|
658
|
+
activated: list[ProfileFile] = []
|
|
659
|
+
for index, value in enumerate(activation_files):
|
|
660
|
+
item = _object(value, f"receipt.profileActivation.files[{index}]")
|
|
661
|
+
path = _safe_relative(
|
|
662
|
+
item.get("path"), f"receipt.profileActivation.files[{index}].path"
|
|
663
|
+
)
|
|
664
|
+
mode = _integer(
|
|
665
|
+
item.get("mode"), f"receipt.profileActivation.files[{index}].mode"
|
|
666
|
+
)
|
|
667
|
+
content = item.get("content")
|
|
668
|
+
if (
|
|
669
|
+
mode > 0o777
|
|
670
|
+
or not isinstance(content, str)
|
|
671
|
+
or "\0" in content
|
|
672
|
+
):
|
|
673
|
+
raise CandidateContractError(
|
|
674
|
+
f"receipt.profileActivation.files[{index}] is invalid"
|
|
675
|
+
)
|
|
676
|
+
activated.append(
|
|
677
|
+
ProfileFile(
|
|
678
|
+
path=path,
|
|
679
|
+
mode=mode,
|
|
680
|
+
sha256=sha256_bytes(content.encode("utf-8")),
|
|
681
|
+
)
|
|
682
|
+
)
|
|
683
|
+
if activated != list(profile_files):
|
|
684
|
+
raise CandidateContractError(
|
|
685
|
+
"profile activation files differ from the signed profile plan"
|
|
686
|
+
)
|
|
506
687
|
profile_application = _object(plan.get("profile"), "plan.profile")
|
|
507
688
|
if profile_application.get("planDigest") != profile_digest:
|
|
508
689
|
raise CandidateContractError("plan does not bind the profile digest")
|
|
@@ -563,6 +744,10 @@ def load_prepared_candidate_contract(
|
|
|
563
744
|
raise CandidateContractError("launch cwd names an unavailable workspace")
|
|
564
745
|
cwd_path = _safe_relative(cwd.get("path"), "plan.launch.cwd.path", allow_dot=True)
|
|
565
746
|
limits = _object(plan.get("limits"), "plan.limits")
|
|
747
|
+
if limits != task.get("limits"):
|
|
748
|
+
raise CandidateContractError(
|
|
749
|
+
"execution plan limits differ from the benchmark task"
|
|
750
|
+
)
|
|
566
751
|
timeout_ms = _integer(limits.get("timeoutMs"), "plan.limits.timeoutMs", minimum=1)
|
|
567
752
|
_integer(limits.get("maxSteps"), "plan.limits.maxSteps", minimum=1)
|
|
568
753
|
max_model_calls = _integer(
|
|
@@ -571,11 +756,27 @@ def load_prepared_candidate_contract(
|
|
|
571
756
|
_integer(limits.get("maxInputTokens"), "plan.limits.maxInputTokens")
|
|
572
757
|
_integer(limits.get("maxOutputTokens"), "plan.limits.maxOutputTokens")
|
|
573
758
|
_number(limits.get("maxCostUsd"), "plan.limits.maxCostUsd")
|
|
574
|
-
attempt = _object(
|
|
575
|
-
_integer(
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
759
|
+
attempt = _object(task.get("attempt"), "benchmark task attempt")
|
|
760
|
+
attempt_number = _integer(
|
|
761
|
+
run_cell.get("attempt"), "plan.runCell.attempt", minimum=1
|
|
762
|
+
)
|
|
763
|
+
max_attempts = _integer(
|
|
764
|
+
attempt.get("maxAttempts"), "benchmark task attempt.maxAttempts", minimum=1
|
|
765
|
+
)
|
|
766
|
+
if attempt_number > max_attempts:
|
|
767
|
+
raise CandidateContractError(
|
|
768
|
+
"run cell attempt exceeds the benchmark task maximum"
|
|
769
|
+
)
|
|
770
|
+
retry_policy = attempt.get("retryPolicy")
|
|
771
|
+
if retry_policy not in {
|
|
772
|
+
"none",
|
|
773
|
+
"pre-model-infrastructure-only",
|
|
774
|
+
}:
|
|
775
|
+
raise CandidateContractError("benchmark task retry policy is unsupported")
|
|
776
|
+
if retry_policy == "none" and max_attempts != 1:
|
|
777
|
+
raise CandidateContractError(
|
|
778
|
+
"a no-retry benchmark task must allow exactly one attempt"
|
|
779
|
+
)
|
|
579
780
|
container = _object(plan.get("container"), "plan.container")
|
|
580
781
|
if receipt.get("container") != container:
|
|
581
782
|
raise CandidateContractError("receipt and plan container identities differ")
|
|
@@ -586,11 +787,21 @@ def load_prepared_candidate_contract(
|
|
|
586
787
|
raise CandidateContractError(
|
|
587
788
|
"container image must be an unpinned OCI reference without credentials"
|
|
588
789
|
)
|
|
589
|
-
|
|
790
|
+
container_source = container.get("source")
|
|
791
|
+
if container_source not in {
|
|
590
792
|
"pinned-container",
|
|
591
793
|
"evaluator-task-container",
|
|
592
794
|
}:
|
|
593
795
|
raise CandidateContractError("plan container source is unsupported")
|
|
796
|
+
task_container = task.get("evaluatorTaskContainer")
|
|
797
|
+
if container_source == "evaluator-task-container" and task_container != container:
|
|
798
|
+
raise CandidateContractError(
|
|
799
|
+
"execution plan container differs from the benchmark task container"
|
|
800
|
+
)
|
|
801
|
+
if container_source == "pinned-container" and task_container is not None:
|
|
802
|
+
raise CandidateContractError(
|
|
803
|
+
"pinned execution cannot override a benchmark task container"
|
|
804
|
+
)
|
|
594
805
|
platform = _object(container.get("platform"), "plan.container.platform")
|
|
595
806
|
if platform.get("os") != "linux" or platform.get("architecture") not in {
|
|
596
807
|
"amd64",
|
|
@@ -605,7 +816,10 @@ def load_prepared_candidate_contract(
|
|
|
605
816
|
_string(resolved_model.get("provider"), "plan.model.resolved.provider")
|
|
606
817
|
_string(resolved_model.get("model"), "plan.model.resolved.model")
|
|
607
818
|
_string(resolved_model.get("snapshot"), "plan.model.resolved.snapshot")
|
|
608
|
-
if
|
|
819
|
+
if (
|
|
820
|
+
receipt.get("resolvedModel") != resolved_model
|
|
821
|
+
or task.get("model") != resolved_model
|
|
822
|
+
):
|
|
609
823
|
raise CandidateContractError("receipt and plan resolved models differ")
|
|
610
824
|
model_access = _object(model.get("access"), "plan.model.access")
|
|
611
825
|
if set(model_access) != {"kind", "grantDigest", "network"}:
|
|
@@ -55,6 +55,7 @@ _CANDIDATE_HOME = "/tangle/home"
|
|
|
55
55
|
_CANDIDATE_TMP = "/tangle/tmp"
|
|
56
56
|
_CONTROL_ROOT = "/tangle/control"
|
|
57
57
|
_FIXED_PATH = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
|
|
58
|
+
_FIXED_PATH_ENTRIES = frozenset(_FIXED_PATH.split(":"))
|
|
58
59
|
_RESERVED_PROCESS_ENV = {
|
|
59
60
|
"GIT_CONFIG_COUNT",
|
|
60
61
|
"GIT_CONFIG_KEY_0",
|
|
@@ -124,6 +125,33 @@ def _trace_env(
|
|
|
124
125
|
}
|
|
125
126
|
|
|
126
127
|
|
|
128
|
+
def _evaluator_git(root: str) -> str:
|
|
129
|
+
return shlex.join(
|
|
130
|
+
[
|
|
131
|
+
"git",
|
|
132
|
+
"-c",
|
|
133
|
+
"core.hooksPath=/dev/null",
|
|
134
|
+
"-c",
|
|
135
|
+
"core.fsmonitor=false",
|
|
136
|
+
"-c",
|
|
137
|
+
"core.untrackedCache=false",
|
|
138
|
+
"-c",
|
|
139
|
+
"protocol.file.allow=never",
|
|
140
|
+
"-C",
|
|
141
|
+
root,
|
|
142
|
+
]
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _evaluator_git_config(repository_base_commit: str) -> bytes:
|
|
147
|
+
if len(repository_base_commit) == 64:
|
|
148
|
+
return (
|
|
149
|
+
b"[core]\n\trepositoryformatversion = 1\n"
|
|
150
|
+
b"[extensions]\n\tobjectformat = sha256\n"
|
|
151
|
+
)
|
|
152
|
+
return b"[core]\n\trepositoryformatversion = 0\n"
|
|
153
|
+
|
|
154
|
+
|
|
127
155
|
class TangleCandidateAgent(BaseAgent):
|
|
128
156
|
"""Pier adapter for a branded ``PreparedAgentCandidateExecution``."""
|
|
129
157
|
|
|
@@ -241,12 +269,25 @@ class TangleCandidateAgent(BaseAgent):
|
|
|
241
269
|
) -> dict[str, str]:
|
|
242
270
|
protected: dict[str, str] = {}
|
|
243
271
|
public_names = set(self._contract.env)
|
|
244
|
-
reserved_public = sorted(
|
|
272
|
+
reserved_public = sorted(
|
|
273
|
+
(public_names & _RESERVED_PROCESS_ENV) - {"PATH"}
|
|
274
|
+
)
|
|
245
275
|
if reserved_public:
|
|
246
276
|
raise PierCandidateError(
|
|
247
277
|
"signed public env collides with evaluator Git safety fields: "
|
|
248
278
|
+ ", ".join(reserved_public)
|
|
249
279
|
)
|
|
280
|
+
public_path = self._contract.env.get("PATH")
|
|
281
|
+
if public_path is not None:
|
|
282
|
+
entries = public_path.split(":")
|
|
283
|
+
if (
|
|
284
|
+
not entries
|
|
285
|
+
or len(entries) != len(set(entries))
|
|
286
|
+
or any(entry not in _FIXED_PATH_ENTRIES for entry in entries)
|
|
287
|
+
):
|
|
288
|
+
raise PierCandidateError(
|
|
289
|
+
"signed public PATH contains an empty, duplicate, or untrusted directory"
|
|
290
|
+
)
|
|
250
291
|
expected_trace = _trace_env(self._contract, trace_run_id)
|
|
251
292
|
for name, expected in expected_trace.items():
|
|
252
293
|
if values.get(name) != expected:
|
|
@@ -455,7 +496,7 @@ class TangleCandidateAgent(BaseAgent):
|
|
|
455
496
|
return ()
|
|
456
497
|
originals: list[OriginalProfileFile] = []
|
|
457
498
|
root = self._contract.task_root
|
|
458
|
-
git =
|
|
499
|
+
git = _evaluator_git(root)
|
|
459
500
|
for file in self._contract.profile_files:
|
|
460
501
|
target = f"{root}/{file.path}"
|
|
461
502
|
quoted = shlex.quote(target)
|
|
@@ -674,14 +715,38 @@ class TangleCandidateAgent(BaseAgent):
|
|
|
674
715
|
user="root",
|
|
675
716
|
)
|
|
676
717
|
|
|
718
|
+
async def _install_evaluator_git_config(
|
|
719
|
+
self, environment: BaseEnvironment
|
|
720
|
+
) -> None:
|
|
721
|
+
config = _evaluator_git_config(self._contract.repository_base_commit)
|
|
722
|
+
target = f"{self._contract.task_root}/.git/config"
|
|
723
|
+
with tempfile.NamedTemporaryFile(delete=False) as handle:
|
|
724
|
+
handle.write(config)
|
|
725
|
+
local_path = Path(handle.name)
|
|
726
|
+
try:
|
|
727
|
+
await self._write_verified_file(
|
|
728
|
+
environment,
|
|
729
|
+
local_path,
|
|
730
|
+
target,
|
|
731
|
+
anchor=self._contract.task_root,
|
|
732
|
+
mode=0o600,
|
|
733
|
+
digest=sha256_bytes(config),
|
|
734
|
+
byte_length=len(config),
|
|
735
|
+
)
|
|
736
|
+
finally:
|
|
737
|
+
local_path.unlink(missing_ok=True)
|
|
738
|
+
|
|
677
739
|
async def _restore_profile_and_capture_solution(
|
|
678
740
|
self, environment: BaseEnvironment
|
|
679
741
|
) -> None:
|
|
680
742
|
root = self._contract.task_root
|
|
681
|
-
|
|
682
|
-
git = f"git -c core.hooksPath=/dev/null -C {quoted_root}"
|
|
743
|
+
git = _evaluator_git(root)
|
|
683
744
|
git_env = candidate_git_env(root)
|
|
684
745
|
|
|
746
|
+
# Repository-local config is candidate-owned after execution. Replace it
|
|
747
|
+
# before root invokes Git so arbitrary filters and other programs cannot run.
|
|
748
|
+
await self._install_evaluator_git_config(environment)
|
|
749
|
+
|
|
685
750
|
# Ignore candidate-authored commits and derive one evaluator-owned final
|
|
686
751
|
# tree from the actual working files relative to the signed base commit.
|
|
687
752
|
await self._exec(
|
|
@@ -718,7 +783,12 @@ class TangleCandidateAgent(BaseAgent):
|
|
|
718
783
|
await self._ensure_real_directory(environment, parent, anchor=root)
|
|
719
784
|
await self._exec(environment, f"rm -rf -- {quoted}", user="root")
|
|
720
785
|
|
|
721
|
-
await self._exec(
|
|
786
|
+
await self._exec(
|
|
787
|
+
environment,
|
|
788
|
+
f"{git} add -A -- . {shlex.quote(':(exclude).sidecar')}",
|
|
789
|
+
env=git_env,
|
|
790
|
+
user="root",
|
|
791
|
+
)
|
|
722
792
|
for original in self._original_profile_files:
|
|
723
793
|
if original.tracked:
|
|
724
794
|
continue
|