@tangle-network/agent-bench 0.3.8 → 0.4.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.
Files changed (115) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +7 -0
  3. package/dist/adapters.js +23 -23
  4. package/dist/benchmarks/_harness.d.ts +4 -1
  5. package/dist/benchmarks/_harness.js +3 -1
  6. package/dist/benchmarks/aec-bench.js +2 -2
  7. package/dist/benchmarks/agentbench.js +2 -2
  8. package/dist/benchmarks/appworld.js +2 -2
  9. package/dist/benchmarks/bfcl.js +2 -2
  10. package/dist/benchmarks/commit0.js +2 -2
  11. package/dist/benchmarks/crag.js +2 -2
  12. package/dist/benchmarks/dabstep.js +2 -2
  13. package/dist/benchmarks/enterpriseops-gym.js +2 -2
  14. package/dist/benchmarks/finresearchbench.js +2 -2
  15. package/dist/benchmarks/nomiracl.js +2 -2
  16. package/dist/benchmarks/open-rag-bench.js +2 -2
  17. package/dist/benchmarks/programbench.js +2 -2
  18. package/dist/benchmarks/ragbench.js +2 -2
  19. package/dist/benchmarks/swe-bench.js +2 -2
  20. package/dist/benchmarks/t2-ragbench.js +2 -2
  21. package/dist/benchmarks/tau-bench-shared.js +2 -2
  22. package/dist/benchmarks/tau2-bench.js +3 -3
  23. package/dist/benchmarks/tau3-banking.js +3 -3
  24. package/dist/benchmarks/terminal-bench.js +2 -2
  25. package/dist/benchmarks/toollm.js +2 -2
  26. package/dist/benchmarks/webarena-verified.js +2 -2
  27. package/dist/{chunk-YSMEKBTD.js → chunk-5FEQDSCT.js} +2 -2
  28. package/dist/{chunk-PB64GYIG.js → chunk-67ACKDCX.js} +2 -2
  29. package/dist/{chunk-NQG5XDSB.js → chunk-7FKBWOQT.js} +2 -2
  30. package/dist/{chunk-K3BQGZCT.js → chunk-BEN6IF2X.js} +2 -2
  31. package/dist/{chunk-SHYIRB7I.js → chunk-BZY5QARD.js} +2 -2
  32. package/dist/{chunk-KP5KD6EN.js → chunk-CLIKAXKH.js} +2 -2
  33. package/dist/{chunk-XKEFIFIC.js → chunk-CWIOBFSP.js} +2 -2
  34. package/dist/{chunk-IA2FBTWC.js → chunk-CXDUTWQE.js} +2 -2
  35. package/dist/{chunk-RH5F53JT.js → chunk-DWALFME7.js} +2 -2
  36. package/dist/{chunk-V7AEBY6U.js → chunk-EEOC6QPJ.js} +21 -21
  37. package/dist/{chunk-WSKWVEQB.js → chunk-EIETHPD5.js} +7 -3
  38. package/dist/chunk-EIETHPD5.js.map +1 -0
  39. package/dist/{chunk-SHM6MRRF.js → chunk-GC2EPS6L.js} +2 -2
  40. package/dist/{chunk-IZ5M6OAC.js → chunk-GCHL6XPM.js} +2 -2
  41. package/dist/{chunk-IFVINJ4B.js → chunk-HQ5HCCKF.js} +2 -2
  42. package/dist/{chunk-7GRVHU22.js → chunk-HVW25KSX.js} +2 -2
  43. package/dist/{chunk-3U5TXJZS.js → chunk-J6BU3NTM.js} +2 -2
  44. package/dist/{chunk-XYA4XSNU.js → chunk-JSQOUKXS.js} +4 -3
  45. package/dist/chunk-JSQOUKXS.js.map +1 -0
  46. package/dist/{chunk-SVR2LKYI.js → chunk-NRMGT25X.js} +2 -2
  47. package/dist/{chunk-MQMRLGOG.js → chunk-QZZEAHWJ.js} +2 -2
  48. package/dist/{chunk-RCYQEFNX.js → chunk-WG7TM7UV.js} +3 -3
  49. package/dist/{chunk-HWST3SED.js → chunk-XXFF3RRD.js} +2 -2
  50. package/dist/{chunk-SFLA7OH3.js → chunk-ZFNOM7WR.js} +3 -3
  51. package/dist/{chunk-Z4TZ76N7.js → chunk-ZNCCYTFG.js} +2 -2
  52. package/dist/index.js +23 -23
  53. package/package.json +4 -4
  54. package/pier_agents/candidate_contract.py +238 -24
  55. package/pier_agents/tangle_candidate.py +75 -5
  56. package/scripts/trata-hedge/README.md +6 -5
  57. package/scripts/verify-packed-consumer.mjs +65 -1
  58. package/scripts/verify-pier-agent.mts +6 -4
  59. package/src/benchmarks/_harness.test.mts +16 -1
  60. package/src/benchmarks/_harness.ts +9 -2
  61. package/src/benchmarks/terminal-bench.ts +2 -1
  62. package/src/gate.ts +1 -1
  63. package/src/hev-eval.mts +5 -2
  64. package/src/hev-improve.mts +118 -73
  65. package/src/official-optimizer-config.mts +89 -0
  66. package/src/official-optimizer-config.test.mts +88 -0
  67. package/src/profiles.ts +2 -2
  68. package/src/rollout-ledger/backfill-swe-arena.test.mts +28 -24
  69. package/src/smoke-structural-rollout.mts +15 -9
  70. package/src/swe-arena/activation.mts +1 -4
  71. package/src/swe-arena/activation.test.mts +10 -13
  72. package/src/swe-arena/gepa-seat.mts +425 -131
  73. package/src/swe-arena/gepa-seat.test.mts +524 -100
  74. package/src/swe-arena/implementation-ref.test.mts +64 -0
  75. package/src/swe-arena/implementation-ref.ts +62 -0
  76. package/src/swe-arena/outer-loop.mts +103 -76
  77. package/src/swe-arena/proposer-fanout.mts +51 -36
  78. package/src/swe-arena/proposer-fanout.test.mts +0 -1
  79. package/src/swe-arena/proposer-provenance.mts +11 -16
  80. package/src/swe-arena/scratch-worktree.test.mts +55 -0
  81. package/src/swe-arena/scratch-worktree.ts +34 -0
  82. package/src/swe-code-improve.mts +24 -25
  83. package/src/swe-improve.mts +129 -96
  84. package/src/swe-local-proof.mts +6 -1
  85. package/src/swe-stream.mts +4 -2
  86. package/src/tb-container-executor.test.mts +30 -6
  87. package/src/tb-supervisor-sidecar.mts +2 -1
  88. package/src/trata-gepa.mts +182 -245
  89. package/dist/chunk-WSKWVEQB.js.map +0 -1
  90. package/dist/chunk-XYA4XSNU.js.map +0 -1
  91. package/src/live-improve-campaign-mbpp.mts +0 -641
  92. package/src/live-improve-campaign.mts +0 -500
  93. package/src/swe-arena/lineage-record.mts +0 -164
  94. package/src/swe-arena/lineage-record.test.mts +0 -115
  95. /package/dist/{chunk-YSMEKBTD.js.map → chunk-5FEQDSCT.js.map} +0 -0
  96. /package/dist/{chunk-PB64GYIG.js.map → chunk-67ACKDCX.js.map} +0 -0
  97. /package/dist/{chunk-NQG5XDSB.js.map → chunk-7FKBWOQT.js.map} +0 -0
  98. /package/dist/{chunk-K3BQGZCT.js.map → chunk-BEN6IF2X.js.map} +0 -0
  99. /package/dist/{chunk-SHYIRB7I.js.map → chunk-BZY5QARD.js.map} +0 -0
  100. /package/dist/{chunk-KP5KD6EN.js.map → chunk-CLIKAXKH.js.map} +0 -0
  101. /package/dist/{chunk-XKEFIFIC.js.map → chunk-CWIOBFSP.js.map} +0 -0
  102. /package/dist/{chunk-IA2FBTWC.js.map → chunk-CXDUTWQE.js.map} +0 -0
  103. /package/dist/{chunk-RH5F53JT.js.map → chunk-DWALFME7.js.map} +0 -0
  104. /package/dist/{chunk-V7AEBY6U.js.map → chunk-EEOC6QPJ.js.map} +0 -0
  105. /package/dist/{chunk-SHM6MRRF.js.map → chunk-GC2EPS6L.js.map} +0 -0
  106. /package/dist/{chunk-IZ5M6OAC.js.map → chunk-GCHL6XPM.js.map} +0 -0
  107. /package/dist/{chunk-IFVINJ4B.js.map → chunk-HQ5HCCKF.js.map} +0 -0
  108. /package/dist/{chunk-7GRVHU22.js.map → chunk-HVW25KSX.js.map} +0 -0
  109. /package/dist/{chunk-3U5TXJZS.js.map → chunk-J6BU3NTM.js.map} +0 -0
  110. /package/dist/{chunk-SVR2LKYI.js.map → chunk-NRMGT25X.js.map} +0 -0
  111. /package/dist/{chunk-MQMRLGOG.js.map → chunk-QZZEAHWJ.js.map} +0 -0
  112. /package/dist/{chunk-RCYQEFNX.js.map → chunk-WG7TM7UV.js.map} +0 -0
  113. /package/dist/{chunk-HWST3SED.js.map → chunk-XXFF3RRD.js.map} +0 -0
  114. /package/dist/{chunk-SFLA7OH3.js.map → chunk-ZFNOM7WR.js.map} +0 -0
  115. /package/dist/{chunk-Z4TZ76N7.js.map → chunk-ZNCCYTFG.js.map} +0 -0
@@ -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
- obj = _object(value, "task.instruction")
323
- if obj.get("encoding") != "utf8":
324
- raise CandidateContractError("task.instruction.encoding must equal utf8")
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=_digest(obj.get("sha256"), "task.instruction.sha256"),
346
- byte_length=_integer(
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
- bundle_digest = _digest(plan.get("bundleDigest"), "plan.bundleDigest")
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
- task = _object(plan.get("task"), "plan.task")
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(task.get("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
- receipt.get("profilePlan"),
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"), "receipt.profilePlan.digest"
629
+ profile_evidence.get("digest"),
630
+ "receipt.profileActivation.profilePlan.digest",
490
631
  )
491
632
  profile_raw = _embedded_bytes(
492
- profile_evidence.get("artifact"), "receipt.profilePlan.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"), "receipt.profilePlan.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(plan.get("attempt"), "plan.attempt")
575
- _integer(attempt.get("number"), "plan.attempt.number", minimum=1)
576
- _integer(attempt.get("maxAttempts"), "plan.attempt.maxAttempts", minimum=1)
577
- if attempt.get("retryPolicy") != "none":
578
- raise CandidateContractError("Pier execution requires runtime-owned retries")
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
- if container.get("source") not in {
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 receipt.get("resolvedModel") != resolved_model:
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(public_names & _RESERVED_PROCESS_ENV)
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 = f"git -c core.hooksPath=/dev/null -C {shlex.quote(root)}"
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
- quoted_root = shlex.quote(root)
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(environment, f"{git} add -A -- .", env=git_env, user="root")
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
@@ -13,9 +13,9 @@ sparse (1 iff all themes). No deployable ground-truth checker — it's an **orac
13
13
  - **NOT** the verifier-grounded selector gate (our HumanEval/commit0 headline). The only
14
14
  checker is the judge itself, so selecting by it = selecting by the eval metric (an
15
15
  oracle, a Goodhart trap). See `docs/results.md`.
16
- - **IS** admissible: (1) more-compute headroom (pass@k), (2) the **improvement loop**
17
- (`selfImprove`/GEPA optimizing the analyst directive *against this judge*, held-out
18
- gated the legitimate use of a judge domain), with the honest caveat that an
16
+ - **IS** admissible: (1) more-compute headroom (pass@k), (2) prompt optimization
17
+ (`improve` with agent-eval's official GEPA method, using disjoint train, selection,
18
+ and final-test partitions), with the honest caveat that an
19
19
  LLM-judge can be *gamed*, so a held-out lift means "higher judge score," which without
20
20
  ground truth we can't independently certify as "better analysis."
21
21
 
@@ -52,5 +52,6 @@ dotenvx run -f ~/company/devops/secrets/.env.keys -f ~/company/devops/secrets/ag
52
52
  ## Next steps
53
53
 
54
54
  1. Agentic solver: our sandbox runtime browses the corpus + cites (fair baseline).
55
- 2. The improvement loop: `selfImprove` on the analyst directive vs this judge, held-out
56
- gated, across the 102 tasks with the judge-gaming caveat stated.
55
+ 2. Prompt optimization: run `bench/src/trata-gepa.mts` across all 102 tasks with
56
+ explicit train, selection, and final-test partitions, and state the judge-gaming
57
+ caveat with every result.
@@ -54,9 +54,54 @@ try {
54
54
  const packDir = path.join(scratch, 'pack')
55
55
  const runtimePackDir = path.join(scratch, 'runtime-pack')
56
56
  const consumerDir = path.join(scratch, 'consumer')
57
+ const terminalBenchVenv = path.join(scratch, 'terminal-bench-venv')
58
+ const terminalBenchBinDir = path.join(terminalBenchVenv, 'bin')
57
59
  await mkdir(packDir)
58
60
  await mkdir(runtimePackDir)
59
61
  await mkdir(consumerDir)
62
+ await mkdir(terminalBenchBinDir, { recursive: true })
63
+ await writeFile(path.join(terminalBenchVenv, 'package.json'), '{"type":"module"}\n')
64
+ await writeFile(
65
+ path.join(terminalBenchBinDir, 'python'),
66
+ String.raw`#!/usr/bin/env node
67
+ const rows = [{
68
+ id: 'installed-absolute-venv',
69
+ instruction: 'prove the installed path',
70
+ task_dir: '/tmp/terminal-bench-task',
71
+ solution: 'echo ok\n',
72
+ }]
73
+ process.stdout.write(JSON.stringify(rows) + '\n')
74
+ `,
75
+ { mode: 0o755 },
76
+ )
77
+ await writeFile(
78
+ path.join(terminalBenchBinDir, 'tb'),
79
+ String.raw`#!/usr/bin/env node
80
+ import { mkdirSync, writeFileSync } from 'node:fs'
81
+ import { join } from 'node:path'
82
+
83
+ const args = process.argv.slice(2)
84
+ function value(flag) {
85
+ const index = args.indexOf(flag)
86
+ if (index === -1 || index + 1 >= args.length) throw new Error('missing ' + flag)
87
+ return args[index + 1]
88
+ }
89
+
90
+ const outputPath = value('--output-path')
91
+ const runId = value('--run-id')
92
+ const taskId = value('-t')
93
+ const reportDir = join(outputPath, runId)
94
+ mkdirSync(reportDir, { recursive: true })
95
+ writeFileSync(
96
+ join(reportDir, 'results.json'),
97
+ JSON.stringify({
98
+ resolved_ids: [taskId],
99
+ results: [{ task_id: taskId, is_resolved: true, parser_results: { installedVenv: true } }],
100
+ }),
101
+ )
102
+ `,
103
+ { mode: 0o755 },
104
+ )
60
105
 
61
106
  // Build explicitly so verification cannot inherit a machine-level
62
107
  // ignore-scripts setting and accidentally pack stale or missing output.
@@ -122,6 +167,21 @@ try {
122
167
  path.join(consumerDir, 'index.mjs'),
123
168
  "import { resolveAdapter, runBenchmarks } from '@tangle-network/agent-bench'\nimport { ADAPTERS } from '@tangle-network/agent-bench/adapters'\nimport { createCragAdapter } from '@tangle-network/agent-bench/benchmarks/crag'\n\nif (typeof resolveAdapter !== 'function' || typeof runBenchmarks !== 'function') throw new Error('root exports are not executable')\nif (typeof ADAPTERS !== 'object' || typeof createCragAdapter !== 'function') throw new Error('subpath exports are not executable')\nif (resolveAdapter('crag').name !== 'crag') throw new Error('compiled adapter registry returned the wrong adapter')\nprocess.env.TOOLLM_FIXTURES = '1'\nconst toolLlmTasks = await resolveAdapter('toollm').loadTasks({ limit: 1 })\nif (toolLlmTasks.length !== 1 || toolLlmTasks[0]?.id !== '1') throw new Error('packed ToolLLM fixture loading failed')\n",
124
169
  )
170
+ await writeFile(
171
+ path.join(consumerDir, 'terminal-bench-absolute-venv.mjs'),
172
+ `import { createTerminalBenchAdapter } from '@tangle-network/agent-bench/benchmarks/terminal-bench'
173
+
174
+ const adapter = createTerminalBenchAdapter()
175
+ const tasks = await adapter.loadTasks({ ids: ['installed-absolute-venv'] })
176
+ if (tasks.length !== 1 || tasks[0]?.id !== 'installed-absolute-venv') {
177
+ throw new Error('packed Terminal-Bench adapter did not load through the absolute venv')
178
+ }
179
+ const score = await adapter.judge(tasks[0], 'echo installed-package-path')
180
+ if (!score.resolved || score.score !== 1) {
181
+ throw new Error(\`packed Terminal-Bench adapter did not judge through the absolute venv: \${JSON.stringify(score)}\`)
182
+ }
183
+ `,
184
+ )
125
185
  await writeFile(
126
186
  path.join(consumerDir, 'tsconfig.json'),
127
187
  `${JSON.stringify(
@@ -165,6 +225,10 @@ for name in sorted(expected):
165
225
  consumerDir,
166
226
  )
167
227
  await run('node', ['index.mjs'], consumerDir)
228
+ await run('node', ['terminal-bench-absolute-venv.mjs'], consumerDir, {
229
+ ...process.env,
230
+ TERMINAL_BENCH_VENV: terminalBenchVenv,
231
+ })
168
232
  await run('npm', ['exec', '--', 'tsc', '-p', 'tsconfig.json'], consumerDir)
169
233
  const typescript5 = await run('npm', ['exec', '--', 'tsc', '--version'], consumerDir)
170
234
  if (typescript5.stdout.trim() !== `Version ${TYPESCRIPT_5}`) {
@@ -228,7 +292,7 @@ for name in sorted(expected):
228
292
  })
229
293
  }
230
294
  console.log(
231
- `packed consumer verified: ${manifest.name}@${manifest.version} with @tangle-network/agent-runtime@${runtimeManifest.version}, TypeScript ${TYPESCRIPT_5} and ${TYPESCRIPT_6}; prepared ${prepareProof.executionPlanDigest}`,
295
+ `packed consumer verified: ${manifest.name}@${manifest.version} with @tangle-network/agent-runtime@${runtimeManifest.version}, TypeScript ${TYPESCRIPT_5} and ${TYPESCRIPT_6}, absolute Terminal-Bench venv; prepared ${prepareProof.executionPlanDigest}`,
232
296
  )
233
297
  } finally {
234
298
  await rm(scratch, { recursive: true, force: true })
@@ -435,7 +435,7 @@ ${proofArm === 'success' ? "(task / 'src/status.txt').write_text('ready\\nowner=
435
435
  workspace: taskWorkspace,
436
436
  evaluatorTaskContainer: container,
437
437
  limits: {
438
- timeoutMs: 60_000,
438
+ timeoutMs: 180_000,
439
439
  maxSteps: 8,
440
440
  maxModelCalls: 0,
441
441
  maxInputTokens: 0,
@@ -671,12 +671,14 @@ ${proofArm === 'success' ? "(task / 'src/status.txt').write_text('ready\\nowner=
671
671
  )
672
672
  }
673
673
  assertTreeOmits(scratch, 'zero-model-proof')
674
- const usage = finalized.receipt.value.usage
674
+ const usage = finalized.receipt.value.modelSettlement.material.usage
675
675
  if (
676
676
  usage.modelCalls !== 0 ||
677
677
  usage.inputTokens !== 0 ||
678
678
  usage.outputTokens !== 0 ||
679
- usage.costUsd !== 0 ||
679
+ usage.cachedInputTokens !== 0 ||
680
+ usage.reasoningTokens !== 0 ||
681
+ usage.costUsdNanos !== 0 ||
680
682
  finalized.receipt.value.trace.modelCallCount !== 0
681
683
  ) {
682
684
  throw new Error(`runtime minted nonzero usage for a zero-model run: ${JSON.stringify(usage)}`)
@@ -691,7 +693,7 @@ ${proofArm === 'success' ? "(task / 'src/status.txt').write_text('ready\\nowner=
691
693
  modelCalls: usage.modelCalls,
692
694
  inputTokens: usage.inputTokens,
693
695
  outputTokens: usage.outputTokens,
694
- costUsd: usage.costUsd,
696
+ costUsd: usage.costUsdNanos / 1_000_000_000,
695
697
  pierContextUsage: null,
696
698
  profileExcludedByVerifier: true,
697
699
  container: {