verifiers 0.2.2.dev4__py3-none-any.whl → 0.2.2.dev6__py3-none-any.whl

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.
verifiers/v1/__init__.py CHANGED
@@ -72,6 +72,11 @@ from verifiers.v1.scoring import (
72
72
  )
73
73
  from verifiers.v1.retries import RetryConfig, RolloutRetryConfig
74
74
  from verifiers.v1.rollout import Rollout
75
+ from verifiers.v1.utils.git import (
76
+ PATCH_CAP_BYTES as PATCH_CAP_BYTES,
77
+ capture_patch as capture_patch,
78
+ resolve_head as resolve_head,
79
+ )
75
80
  from verifiers.v1.runtimes import (
76
81
  DockerConfig,
77
82
  PrimeConfig,
@@ -260,6 +265,10 @@ __all__ = [
260
265
  "RubricJudge",
261
266
  "RubricJudgeConfig",
262
267
  "Criterion",
268
+ # git patch capture
269
+ "PATCH_CAP_BYTES",
270
+ "capture_patch",
271
+ "resolve_head",
263
272
  # scoring
264
273
  "compare_stdout_results",
265
274
  "extract_boxed_answer",
@@ -0,0 +1,113 @@
1
+ """Persist an agent's final git patch into `trace.info` at finalize time.
2
+
3
+ SWE-style tasksets call `capture_patch` from `Task.finalize` — after the harness
4
+ finishes, while the runtime is live, before scoring mutates the repo (restoring
5
+ test files, switching commits) — so the diff is exactly what the agent produced,
6
+ including edits to test files (intentional: they reveal reward hacking).
7
+
8
+ The diff is taken against `base_commit` when the caller has one — a dataset row
9
+ field, or a SHA recorded with `resolve_head` at setup time and kept in host
10
+ memory (never the sandbox, where an agent could tamper with it) — so commits the
11
+ agent made are included. Bare `HEAD` is the fallback of last resort and misses
12
+ agent commits.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import uuid
18
+ from typing import TYPE_CHECKING
19
+
20
+ if TYPE_CHECKING:
21
+ from verifiers.v1.runtimes import Runtime
22
+ from verifiers.v1.trace import Trace
23
+
24
+ PATCH_CAP_BYTES = 2_000_000
25
+ """Truncate captured patches beyond this size; `info["patch_truncated"]` marks it."""
26
+
27
+ # Temp paths are suffixed per invocation with a host-generated nonce: fixed names
28
+ # would let concurrent rollouts on a shared-filesystem runtime (subprocess) read
29
+ # each other's patches between the write and the read, and would let an agent
30
+ # pre-create the predictable path as a FIFO so the shell's redirect blocks the
31
+ # rollout forever.
32
+ _FULL = "/tmp/vf_agent_patch_full"
33
+ _CAPPED = "/tmp/vf_agent_patch"
34
+
35
+ # `git reset -q` must run even when staging or diffing fails, or the error path
36
+ # leaves the tree staged and can break scoring's later checkouts. Every step
37
+ # reports: a failure in add, diff, reset, or head fails the capture. A failed
38
+ # add (e.g. a stale index.lock from a killed agent git command) leaves a stale
39
+ # index, so letting the diff's clean exit stand would record a silently
40
+ # incomplete patch; a failed reset (e.g. ENOSPC rewriting .git/index) leaves the
41
+ # tree staged, so reporting success would hide a state later scoring may trip
42
+ # on; a failed head leaves an empty {capped} (the redirect truncates it before
43
+ # head runs), which would read back as a silently empty patch.
44
+ _DIFF = (
45
+ "rm -f {full} {capped}; "
46
+ "git add -A; "
47
+ "add_rc=$?; "
48
+ 'git -c core.quotepath=off diff --cached --binary "$VF_DIFF_BASE" > {full}; '
49
+ "diff_rc=$?; "
50
+ "git reset -q; "
51
+ "reset_rc=$?; "
52
+ "head -c {cap} {full} > {capped}; "
53
+ "head_rc=$?; "
54
+ "rm -f {full}; "
55
+ "rc=$head_rc; "
56
+ '[ "$diff_rc" -ne 0 ] && rc=$diff_rc; '
57
+ '[ "$reset_rc" -ne 0 ] && rc=$reset_rc; '
58
+ '[ "$add_rc" -ne 0 ] && rc=$add_rc; '
59
+ 'exit "$rc"'
60
+ )
61
+
62
+
63
+ async def resolve_head(runtime: Runtime, env: dict | None = None) -> str:
64
+ """The repo's current commit SHA, or "" when unresolvable.
65
+
66
+ Call at the end of `setup`, before the agent runs, and keep the result in
67
+ host memory (e.g. a dict on the task keyed by `id(runtime)`) for `finalize`
68
+ to pass as `base_commit` — diffing against a pre-agent SHA is what keeps
69
+ commits the agent makes inside the captured patch.
70
+ """
71
+ result = await runtime.run(["git", "rev-parse", "HEAD"], env or {})
72
+ if result.exit_code != 0:
73
+ return ""
74
+ return (result.stdout or "").strip()
75
+
76
+
77
+ async def capture_patch(
78
+ trace: Trace, runtime: Runtime, base_commit: str = "", env: dict | None = None
79
+ ) -> None:
80
+ """Snapshot the agent's cumulative diff into `trace.info["patch"]`.
81
+
82
+ Best-effort by design: a rollout whose sandbox died or whose repo state is
83
+ broken records `info["patch_error"]` instead of failing the rollout —
84
+ scoring still runs and the error stays visible in results.
85
+ """
86
+ nonce = uuid.uuid4().hex
87
+ full, capped = f"{_FULL}_{nonce}", f"{_CAPPED}_{nonce}"
88
+ cmd = _DIFF.format(full=full, capped=capped, cap=PATCH_CAP_BYTES + 1)
89
+ try:
90
+ result = await runtime.run(
91
+ ["sh", "-c", cmd],
92
+ {**(env or {}), "VF_DIFF_BASE": base_commit or "HEAD"},
93
+ )
94
+ if result.exit_code != 0:
95
+ trace.info["patch_error"] = (
96
+ f"exit={result.exit_code} {(result.stderr or '').strip()[-500:]}"
97
+ )
98
+ return
99
+ raw = await runtime.read(capped)
100
+ except Exception as exc: # noqa: BLE001 - capture must never fail the rollout.
101
+ trace.info["patch_error"] = f"{type(exc).__name__}: {exc}"
102
+ return
103
+ finally:
104
+ # Unique names don't overwrite each other, so leftovers would accumulate
105
+ # on shared-filesystem runtimes; removal is best-effort by design.
106
+ try:
107
+ await runtime.run(["rm", "-f", full, capped], env or {})
108
+ except Exception: # noqa: BLE001,S110 - cleanup must never fail the rollout.
109
+ pass
110
+ if len(raw) > PATCH_CAP_BYTES:
111
+ raw = raw[:PATCH_CAP_BYTES]
112
+ trace.info["patch_truncated"] = True
113
+ trace.info["patch"] = raw.decode("utf-8", errors="replace")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: verifiers
3
- Version: 0.2.2.dev4
3
+ Version: 0.2.2.dev6
4
4
  Summary: Verifiers: Environments for LLM Reinforcement Learning
5
5
  Project-URL: Homepage, https://github.com/primeintellect-ai/verifiers
6
6
  Project-URL: Documentation, https://github.com/primeintellect-ai/verifiers
@@ -168,7 +168,7 @@ verifiers/utils/threaded_sandbox_client.py,sha256=Pbr8MA4FDPEitL6z88S8T1qLJOmtXN
168
168
  verifiers/utils/tool_utils.py,sha256=gInWZODWQUZN4TyEMuuITyOUm9qlHJxtcvr1zZl_zJs,1020
169
169
  verifiers/utils/usage_utils.py,sha256=GPLC0xGY_Obrr8X7huWY-2iODZ7tgX-MtO8WSDN4rXI,3904
170
170
  verifiers/utils/version_utils.py,sha256=am3hZLnlaUWTFdllGZR1VMN91hEhiiE8FDmPf1cOG1k,2642
171
- verifiers/v1/__init__.py,sha256=hjN9xg5SVAqjNHhoCVvqr3qpGfiQQ57FTpiqfwnbfTU,5863
171
+ verifiers/v1/__init__.py,sha256=1crIo9cOEd05cuziZqwYr6CoviuR5tMenb8nYi6GhXk,6100
172
172
  verifiers/v1/decorators.py,sha256=tCgmFCta0gnEqoZArHnvLw97_vlg8AH35NlwBNEqeWM,4073
173
173
  verifiers/v1/env.py,sha256=zU3_zTYaVwJKFeg05mgYCKWEgySPKs4dhLbuIhd2WpQ,19292
174
174
  verifiers/v1/episode.py,sha256=emcXB_yD2KHNFRLyk8VFK2zZAAD_2difuyDeJJ046Yw,2153
@@ -298,6 +298,7 @@ verifiers/v1/utils/__init__.py,sha256=1RDUX9pjAuQHKYEDevehifHd3IB03y9LAUsln3exPQ
298
298
  verifiers/v1/utils/aio.py,sha256=ZKTHeURNbWhTpHMyYuqMLsdPWVHyn5anfG1IJs5y-Zg,1481
299
299
  verifiers/v1/utils/format.py,sha256=NfQo9M5KMzWCZpQaet5YtsJx56vCKAZPfbjZZJLJhhM,2187
300
300
  verifiers/v1/utils/generic.py,sha256=cpiyt_GIhFcHVK3dh7eBDqQJ6tQFXSQyHUuPKRLZfj4,647
301
+ verifiers/v1/utils/git.py,sha256=5VX5B3011yhclPex8Mc79ZmZ5ZT4hGfKaXoqL12QMdc,4872
301
302
  verifiers/v1/utils/image.py,sha256=OFw_wdwVtbdwa8uJ3X3vYfhpUAi0CTH5HMBupjVsRRQ,282
302
303
  verifiers/v1/utils/install.py,sha256=fWNsyKrw_PyhC0Qhqv5Ri0adFm5Ddx4AVy_hbsra1GU,1390
303
304
  verifiers/v1/utils/interrupt.py,sha256=F-KKhc5ndPJJfhd3SuMqyqhXhA32FhCRy5KWFJpEoM4,1179
@@ -305,8 +306,8 @@ verifiers/v1/utils/logging.py,sha256=OcMHA6NsYux3oIzjPuI95rDWmFBHNcHDjZeNIhXTX-Y
305
306
  verifiers/v1/utils/memory.py,sha256=ZkIvGk6uITAH5sKon65LifKPbvZr8mJ__PVc23FZpOQ,1835
306
307
  verifiers/v1/utils/sampling.py,sha256=JczGzBn6s3wsIrSY2Hy3hE8m6NreNgsNzhSjDikQqX0,1037
307
308
  verifiers/v1/utils/version.py,sha256=75ZtI2NHBmlb52KpcKLUpiSXp8q4bASr7uKeXoCKlT8,1582
308
- verifiers-0.2.2.dev4.dist-info/METADATA,sha256=ZmUyYSoNBajAItnypg3IUt4GdNNfS-4T_Wtx5zsKJIU,4540
309
- verifiers-0.2.2.dev4.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
310
- verifiers-0.2.2.dev4.dist-info/entry_points.txt,sha256=dF82JUYEFslR1AOW8LZfuBWeZeQoyX4wCRrduklg8-I,551
311
- verifiers-0.2.2.dev4.dist-info/licenses/LICENSE,sha256=v0RrUsdV3IDoZhrRce297IXS3xMHNJ-_LdLpFAUWb9k,1072
312
- verifiers-0.2.2.dev4.dist-info/RECORD,,
309
+ verifiers-0.2.2.dev6.dist-info/METADATA,sha256=tmKW4Ax2c5YNJDMpMyHodgJuE_x7SNSnp60ozWIOhvo,4540
310
+ verifiers-0.2.2.dev6.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
311
+ verifiers-0.2.2.dev6.dist-info/entry_points.txt,sha256=dF82JUYEFslR1AOW8LZfuBWeZeQoyX4wCRrduklg8-I,551
312
+ verifiers-0.2.2.dev6.dist-info/licenses/LICENSE,sha256=v0RrUsdV3IDoZhrRce297IXS3xMHNJ-_LdLpFAUWb9k,1072
313
+ verifiers-0.2.2.dev6.dist-info/RECORD,,