verifiers 0.3.2.dev45__py3-none-any.whl → 0.3.2.dev47__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/agent.py +26 -24
- verifiers/v1/configs/harness.py +4 -0
- verifiers/v1/envs/isolated_verifier/__init__.py +7 -0
- verifiers/v1/envs/isolated_verifier/env.py +182 -0
- verifiers/v1/harnesses/claude_code/harness.py +11 -25
- verifiers/v1/harnesses/codex/harness.py +11 -23
- verifiers/v1/harnesses/hermes_agent/harness.py +4 -9
- verifiers/v1/harnesses/kimi_code/harness.py +9 -13
- verifiers/v1/harnesses/mini_swe_agent/harness.py +2 -4
- verifiers/v1/harnesses/node.py +9 -14
- verifiers/v1/harnesses/openclaw/harness.py +14 -30
- verifiers/v1/harnesses/pi/harness.py +9 -20
- verifiers/v1/harnesses/pool/harness.py +11 -14
- verifiers/v1/harnesses/prime_agent/harness.py +8 -18
- verifiers/v1/harnesses/rlm/harness.py +9 -7
- verifiers/v1/harnesses/terminus_2/harness.py +2 -4
- verifiers/v1/harnesses/utils/install.py +65 -0
- verifiers/v1/rollout.py +1 -0
- verifiers/v1/task.py +21 -0
- verifiers/v1/tasksets/harbor/env.py +28 -73
- verifiers/v1/tasksets/harbor/taskset.py +22 -20
- {verifiers-0.3.2.dev45.dist-info → verifiers-0.3.2.dev47.dist-info}/METADATA +1 -1
- {verifiers-0.3.2.dev45.dist-info → verifiers-0.3.2.dev47.dist-info}/RECORD +26 -23
- {verifiers-0.3.2.dev45.dist-info → verifiers-0.3.2.dev47.dist-info}/WHEEL +0 -0
- {verifiers-0.3.2.dev45.dist-info → verifiers-0.3.2.dev47.dist-info}/entry_points.txt +0 -0
- {verifiers-0.3.2.dev45.dist-info → verifiers-0.3.2.dev47.dist-info}/licenses/LICENSE +0 -0
verifiers/v1/agent.py
CHANGED
|
@@ -11,7 +11,7 @@ import asyncio
|
|
|
11
11
|
import logging
|
|
12
12
|
from collections.abc import AsyncIterator, Callable, Mapping
|
|
13
13
|
from contextlib import asynccontextmanager, nullcontext
|
|
14
|
-
from dataclasses import dataclass
|
|
14
|
+
from dataclasses import dataclass, replace
|
|
15
15
|
from typing import Self
|
|
16
16
|
|
|
17
17
|
from verifiers.v1.clients import (
|
|
@@ -54,6 +54,27 @@ __all__ = ["Agent", "AgentConfig", "Agents", "TimeoutConfig", "make_agent"]
|
|
|
54
54
|
logger = logging.getLogger(__name__)
|
|
55
55
|
|
|
56
56
|
|
|
57
|
+
def resolve_rollout_timeouts(timeout: TimeoutConfig, task: Task) -> RolloutTimeouts:
|
|
58
|
+
"""Apply an agent's stage-timeout precedence to one task."""
|
|
59
|
+
agent_timeout = (
|
|
60
|
+
timeout.rollout if timeout.rollout is not None else task.data.timeout.agent
|
|
61
|
+
)
|
|
62
|
+
return RolloutTimeouts(
|
|
63
|
+
setup=timeout.setup if timeout.setup is not None else task.data.timeout.setup,
|
|
64
|
+
agent=agent_timeout,
|
|
65
|
+
finalize=(
|
|
66
|
+
timeout.finalize
|
|
67
|
+
if timeout.finalize is not None
|
|
68
|
+
else task.data.timeout.finalize
|
|
69
|
+
),
|
|
70
|
+
scoring=(
|
|
71
|
+
timeout.scoring
|
|
72
|
+
if timeout.scoring is not None
|
|
73
|
+
else task.data.timeout.scoring
|
|
74
|
+
),
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
|
|
57
78
|
def _check_borrowed_placement(
|
|
58
79
|
task: Task, runtime: Runtime, base_config: RuntimeConfig
|
|
59
80
|
) -> None:
|
|
@@ -526,34 +547,15 @@ class Agent:
|
|
|
526
547
|
runtime_config,
|
|
527
548
|
tools=[*task.toolsets(task.config), *shared_tools.values()],
|
|
528
549
|
)
|
|
529
|
-
|
|
530
|
-
agent_timeout = (
|
|
531
|
-
self.timeout.rollout
|
|
532
|
-
if self.timeout.rollout is not None
|
|
533
|
-
else task.data.timeout.agent
|
|
534
|
-
)
|
|
550
|
+
timeouts = resolve_rollout_timeouts(self.timeout, task)
|
|
535
551
|
return {
|
|
536
552
|
"agent_config": self.config,
|
|
537
553
|
"harness": self.harness,
|
|
538
554
|
"ctx": self.ctx,
|
|
539
555
|
"runtime_config": runtime_config,
|
|
540
|
-
"timeouts":
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
if self.timeout.setup is not None
|
|
544
|
-
else task.data.timeout.setup
|
|
545
|
-
),
|
|
546
|
-
agent=cap_remote_agent_timeout(agent_timeout, runtime_config, task),
|
|
547
|
-
finalize=(
|
|
548
|
-
self.timeout.finalize
|
|
549
|
-
if self.timeout.finalize is not None
|
|
550
|
-
else task.data.timeout.finalize
|
|
551
|
-
),
|
|
552
|
-
scoring=(
|
|
553
|
-
self.timeout.scoring
|
|
554
|
-
if self.timeout.scoring is not None
|
|
555
|
-
else task.data.timeout.scoring
|
|
556
|
-
),
|
|
556
|
+
"timeouts": replace(
|
|
557
|
+
timeouts,
|
|
558
|
+
agent=cap_remote_agent_timeout(timeouts.agent, runtime_config, task),
|
|
557
559
|
),
|
|
558
560
|
"limits": self.limits,
|
|
559
561
|
"shared_tools": shared_tools,
|
verifiers/v1/configs/harness.py
CHANGED
|
@@ -4,12 +4,16 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import os
|
|
6
6
|
from pathlib import Path
|
|
7
|
+
from typing import Annotated
|
|
7
8
|
|
|
8
9
|
from pydantic import ConfigDict, Field, FiniteFloat
|
|
9
10
|
from pydantic_config import BaseConfig
|
|
10
11
|
|
|
11
12
|
from verifiers.v1.types import ID
|
|
12
13
|
|
|
14
|
+
PinnedVersion = Annotated[str, Field(pattern=r"^[A-Za-z0-9._+-]+$")]
|
|
15
|
+
"""A release/tag a harness pins its program install to."""
|
|
16
|
+
|
|
13
17
|
|
|
14
18
|
class HarnessConfig(BaseConfig):
|
|
15
19
|
id: ID = "bash"
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"""Deterministic task verification in a fresh runtime.
|
|
2
|
+
|
|
3
|
+
One solver agent runs the task. Its task scoring is deferred, declared artifacts
|
|
4
|
+
are collected after normal task finalization, and the solver runtime is destroyed.
|
|
5
|
+
The task is then set up with a fresh controller in a fresh runtime, its artifacts
|
|
6
|
+
are restored, and its ordinary metrics and rewards run there onto the solver's trace.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import asyncio
|
|
10
|
+
import copy
|
|
11
|
+
import logging
|
|
12
|
+
from contextlib import AsyncExitStack
|
|
13
|
+
from pathlib import PurePosixPath
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
16
|
+
from pydantic import Field, SerializationInfo, field_serializer
|
|
17
|
+
|
|
18
|
+
import verifiers.v1 as vf
|
|
19
|
+
from verifiers.v1.agent import resolve_rollout_timeouts
|
|
20
|
+
from verifiers.v1.errors import TaskError, boundary
|
|
21
|
+
from verifiers.v1.runtimes import Runtime, RuntimeConfig, provision_runtime
|
|
22
|
+
from verifiers.v1.utils.compile import resolve_runtime_config
|
|
23
|
+
from verifiers.v1.utils.decorators import invoke
|
|
24
|
+
from verifiers.v1.utils.retries import backoff
|
|
25
|
+
|
|
26
|
+
logger = logging.getLogger(__name__)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class VerifierConfig(vf.BaseConfig):
|
|
30
|
+
runtime: RuntimeConfig | None = None
|
|
31
|
+
"""Independent verifier placement and policy. None provisions a fresh runtime
|
|
32
|
+
equivalent to the solver's resolved task runtime."""
|
|
33
|
+
env: dict[str, str] | None = None
|
|
34
|
+
"""Process environment for verifier setup and scoring. None uses the task's
|
|
35
|
+
normal runtime environment."""
|
|
36
|
+
retries: int = Field(2, ge=0)
|
|
37
|
+
"""Extra fresh-runtime attempts after setup, restoration, staging, or scoring
|
|
38
|
+
failures."""
|
|
39
|
+
|
|
40
|
+
@field_serializer("runtime")
|
|
41
|
+
def serialize_runtime(
|
|
42
|
+
self, runtime: RuntimeConfig | None, info: SerializationInfo
|
|
43
|
+
) -> dict | None:
|
|
44
|
+
"""Keep an omitted image omitted across a resolved-config round trip."""
|
|
45
|
+
if runtime is None:
|
|
46
|
+
return None
|
|
47
|
+
values = runtime.model_dump(mode=info.mode)
|
|
48
|
+
if "image" not in runtime.model_fields_set:
|
|
49
|
+
values.pop("image", None)
|
|
50
|
+
return values
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class IsolatedVerifierEnvConfig(vf.EnvConfig):
|
|
54
|
+
agent: vf.AgentConfig = vf.AgentConfig()
|
|
55
|
+
"""The one solver seat."""
|
|
56
|
+
verifier: VerifierConfig = VerifierConfig()
|
|
57
|
+
"""The fresh verifier's runtime, process environment, and retry policy."""
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class IsolatedVerifierEnv(vf.Env[IsolatedVerifierEnvConfig]):
|
|
61
|
+
"""Run one solver, then its deterministic task scoring in a fresh runtime."""
|
|
62
|
+
|
|
63
|
+
async def run(self, task: vf.Task, agents: vf.Agents) -> None:
|
|
64
|
+
if task.config.judges:
|
|
65
|
+
raise ValueError(
|
|
66
|
+
"isolated-verifier runs deterministic task metrics and rewards; "
|
|
67
|
+
"model-backed task judges are not supported"
|
|
68
|
+
)
|
|
69
|
+
self.verifier_config(task) # Refuse an impossible verifier before solving.
|
|
70
|
+
await agents.agent.run(task.defer_scoring(), collect_artifacts=True)
|
|
71
|
+
|
|
72
|
+
def verifier_config(self, task: vf.Task) -> RuntimeConfig:
|
|
73
|
+
base = self.config.verifier.runtime or self.config.agent.runtime
|
|
74
|
+
config = resolve_runtime_config(base, task)
|
|
75
|
+
image_spec = type(base).model_fields.get("image")
|
|
76
|
+
if (
|
|
77
|
+
self.config.verifier.runtime is not None
|
|
78
|
+
and image_spec is not None
|
|
79
|
+
and "image" in base.model_fields_set
|
|
80
|
+
):
|
|
81
|
+
config = config.model_copy(update={"image": base.image})
|
|
82
|
+
if isinstance(config, vf.SubprocessConfig):
|
|
83
|
+
raise TypeError(
|
|
84
|
+
"isolated-verifier requires a container runtime so artifacts can be "
|
|
85
|
+
"restored safely; configure the agent or verifier runtime as docker, "
|
|
86
|
+
"prime, or modal"
|
|
87
|
+
)
|
|
88
|
+
relative = [
|
|
89
|
+
artifact.source
|
|
90
|
+
for artifact in task.data.artifacts
|
|
91
|
+
if not PurePosixPath(artifact.source).is_absolute()
|
|
92
|
+
]
|
|
93
|
+
solver = resolve_runtime_config(self.config.agent.runtime, task)
|
|
94
|
+
solver_workdir = PurePosixPath(getattr(solver, "workdir", "") or "/")
|
|
95
|
+
verifier_workdir = PurePosixPath(config.workdir)
|
|
96
|
+
if relative and solver_workdir != verifier_workdir:
|
|
97
|
+
raise ValueError(
|
|
98
|
+
"isolated-verifier cannot transfer relative artifacts "
|
|
99
|
+
f"{relative!r} between solver workdir {str(solver_workdir)!r} and "
|
|
100
|
+
f"verifier workdir {str(verifier_workdir)!r}; use matching workdirs "
|
|
101
|
+
"or absolute artifact paths"
|
|
102
|
+
)
|
|
103
|
+
return config
|
|
104
|
+
|
|
105
|
+
async def finalize(self, task: vf.Task, episode: vf.Episode) -> None:
|
|
106
|
+
solution = episode.traces[0]
|
|
107
|
+
if solution.ok:
|
|
108
|
+
graded = await self.grade(self.verifier_config(task), task, solution)
|
|
109
|
+
episode.traces[0] = graded[1]
|
|
110
|
+
|
|
111
|
+
async def stage_verifier(
|
|
112
|
+
self, task: vf.Task, solution: vf.Trace, runtime: Runtime
|
|
113
|
+
) -> None:
|
|
114
|
+
artifacts = dict(solution.state.artifacts)
|
|
115
|
+
async with boundary(TaskError, "verifier task setup"):
|
|
116
|
+
await invoke(task.setup, {"trace": solution, "runtime": runtime})
|
|
117
|
+
await vf.restore(runtime, artifacts)
|
|
118
|
+
async with boundary(TaskError, "verifier staging"):
|
|
119
|
+
await invoke(task.stage_verifier, {"trace": solution, "runtime": runtime})
|
|
120
|
+
|
|
121
|
+
async def verify(self, task: vf.Task, solution: vf.Trace, runtime: Runtime) -> Any:
|
|
122
|
+
await task.score(solution, runtime)
|
|
123
|
+
|
|
124
|
+
async def grade(
|
|
125
|
+
self,
|
|
126
|
+
config: RuntimeConfig,
|
|
127
|
+
task: vf.Task,
|
|
128
|
+
solution: vf.Trace,
|
|
129
|
+
*,
|
|
130
|
+
scoring_timeout_covers_attempt: bool = False,
|
|
131
|
+
) -> tuple[Any, vf.Trace]:
|
|
132
|
+
timeouts = resolve_rollout_timeouts(self.config.agent.timeout, task)
|
|
133
|
+
last: Exception | None = None
|
|
134
|
+
for attempt in range(self.config.verifier.retries + 1):
|
|
135
|
+
if attempt:
|
|
136
|
+
delay = backoff(attempt - 1)
|
|
137
|
+
logger.warning(
|
|
138
|
+
"isolated verifier attempt %d/%d failed (%s); retrying in %.1fs",
|
|
139
|
+
attempt,
|
|
140
|
+
self.config.verifier.retries + 1,
|
|
141
|
+
last,
|
|
142
|
+
delay,
|
|
143
|
+
)
|
|
144
|
+
await asyncio.sleep(delay)
|
|
145
|
+
try:
|
|
146
|
+
# Teardown is outside the stage deadlines: a completed score must
|
|
147
|
+
# survive a slow cleanup of the verifier runtime.
|
|
148
|
+
async with (
|
|
149
|
+
AsyncExitStack() as boxes,
|
|
150
|
+
asyncio.timeout(
|
|
151
|
+
timeouts.scoring if scoring_timeout_covers_attempt else None
|
|
152
|
+
),
|
|
153
|
+
):
|
|
154
|
+
async with asyncio.timeout(timeouts.setup):
|
|
155
|
+
# Failed setup or scoring must not alter the next attempt.
|
|
156
|
+
# Only the successful controller and trace leave this scope.
|
|
157
|
+
verifier_task = copy.deepcopy(task)
|
|
158
|
+
verifier_solution = copy.deepcopy(solution)
|
|
159
|
+
runtime = await boxes.enter_async_context(
|
|
160
|
+
provision_runtime(
|
|
161
|
+
config,
|
|
162
|
+
env=(
|
|
163
|
+
verifier_task.runtime_env()
|
|
164
|
+
if self.config.verifier.env is None
|
|
165
|
+
else self.config.verifier.env
|
|
166
|
+
),
|
|
167
|
+
)
|
|
168
|
+
)
|
|
169
|
+
await runtime.prepare_setup()
|
|
170
|
+
await self.stage_verifier(
|
|
171
|
+
verifier_task, verifier_solution, runtime
|
|
172
|
+
)
|
|
173
|
+
await runtime.prepare_execution([])
|
|
174
|
+
async with asyncio.timeout(timeouts.scoring):
|
|
175
|
+
result = await self.verify(
|
|
176
|
+
verifier_task, verifier_solution, runtime
|
|
177
|
+
)
|
|
178
|
+
return result, verifier_solution
|
|
179
|
+
except Exception as error: # noqa: BLE001 - retry the whole fresh box
|
|
180
|
+
last = error
|
|
181
|
+
assert last is not None
|
|
182
|
+
raise last
|
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
"""Run Claude Code through the Claude Agent SDK ACP adapter."""
|
|
2
2
|
|
|
3
|
-
import shlex
|
|
4
|
-
|
|
5
|
-
from pydantic import Field
|
|
6
|
-
|
|
7
3
|
from verifiers.v1.acp import ACPConfig, ACPHarness
|
|
8
4
|
from verifiers.v1.clients import ModelContext
|
|
9
|
-
from verifiers.v1.configs.harness import HarnessConfig
|
|
5
|
+
from verifiers.v1.configs.harness import HarnessConfig, PinnedVersion
|
|
10
6
|
from verifiers.v1.harnesses.node import NODE_BIN_DIR, ensure_node
|
|
7
|
+
from verifiers.v1.harnesses.utils.install import ensure_installed, remove_dir
|
|
11
8
|
from verifiers.v1.runtimes import Runtime
|
|
12
9
|
from verifiers.v1.task import TaskData
|
|
13
10
|
from verifiers.v1.trace import Trace
|
|
@@ -32,7 +29,7 @@ touch {ready}
|
|
|
32
29
|
|
|
33
30
|
|
|
34
31
|
class ClaudeCodeHarnessConfig(HarnessConfig):
|
|
35
|
-
version:
|
|
32
|
+
version: PinnedVersion = "2.1.232"
|
|
36
33
|
"""Claude Code release to install, pinned for reproducibility."""
|
|
37
34
|
|
|
38
35
|
|
|
@@ -51,25 +48,18 @@ class ClaudeCodeHarness(ACPHarness[ClaudeCodeHarnessConfig]):
|
|
|
51
48
|
acp_bin = ACP_BIN.format(**versions)
|
|
52
49
|
ready = f"{directory}/.ready"
|
|
53
50
|
script = ACP_INSTALL.replace("{packages}", packages).replace("{ready}", ready)
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
f"sh -c {ensure}"
|
|
61
|
-
)
|
|
62
|
-
acp_result = await runtime.run(
|
|
63
|
-
["sh", "-c", acp_guarded],
|
|
64
|
-
{
|
|
51
|
+
await ensure_installed(
|
|
52
|
+
runtime,
|
|
53
|
+
directory=directory,
|
|
54
|
+
ready=f"[ -f {ready} ] && [ -x {claude_bin} ] && [ -x {acp_bin} ]",
|
|
55
|
+
install=script,
|
|
56
|
+
env={
|
|
65
57
|
**self.config.resolved_env,
|
|
66
58
|
"VF_CLAUDE_CODE_VERSION": self.config.version,
|
|
67
59
|
"VF_CLAUDE_ACP_VERSION": ACP_VERSION,
|
|
68
60
|
},
|
|
61
|
+
label="Claude Agent ACP",
|
|
69
62
|
)
|
|
70
|
-
if acp_result.exit_code != 0:
|
|
71
|
-
detail = (acp_result.stderr or acp_result.stdout).strip()[-500:]
|
|
72
|
-
raise RuntimeError(f"Claude Agent ACP install failed: {detail}")
|
|
73
63
|
await super().setup(runtime)
|
|
74
64
|
|
|
75
65
|
async def prepare_acp(
|
|
@@ -113,11 +103,7 @@ class ClaudeCodeHarness(ACPHarness[ClaudeCodeHarnessConfig]):
|
|
|
113
103
|
)
|
|
114
104
|
|
|
115
105
|
async def cleanup(self, trace: Trace, runtime: Runtime) -> None:
|
|
116
|
-
|
|
117
|
-
if result.exit_code != 0:
|
|
118
|
-
raise RuntimeError(
|
|
119
|
-
f"failed to clean up Claude config: {result.stderr.strip()[-500:]}"
|
|
120
|
-
)
|
|
106
|
+
await remove_dir(runtime, self.config_dir(trace), "Claude config")
|
|
121
107
|
|
|
122
108
|
@staticmethod
|
|
123
109
|
def config_dir(trace: Trace) -> str:
|
|
@@ -4,15 +4,13 @@ import hashlib
|
|
|
4
4
|
import json
|
|
5
5
|
import logging
|
|
6
6
|
import re
|
|
7
|
-
import shlex
|
|
8
7
|
from collections import Counter
|
|
9
8
|
|
|
10
|
-
from pydantic import Field
|
|
11
|
-
|
|
12
9
|
from verifiers.v1.acp import ACPConfig, ACPHarness
|
|
13
10
|
from verifiers.v1.clients import ModelContext
|
|
14
|
-
from verifiers.v1.configs.harness import HarnessConfig
|
|
11
|
+
from verifiers.v1.configs.harness import HarnessConfig, PinnedVersion
|
|
15
12
|
from verifiers.v1.harnesses.node import NODE_BIN_DIR, ensure_node
|
|
13
|
+
from verifiers.v1.harnesses.utils.install import ensure_installed, remove_dir
|
|
16
14
|
from verifiers.v1.runtimes import Runtime
|
|
17
15
|
from verifiers.v1.task import TaskData
|
|
18
16
|
from verifiers.v1.trace import Trace
|
|
@@ -38,7 +36,7 @@ touch {ready}
|
|
|
38
36
|
|
|
39
37
|
|
|
40
38
|
class CodexHarnessConfig(HarnessConfig):
|
|
41
|
-
version:
|
|
39
|
+
version: PinnedVersion = "0.147.0"
|
|
42
40
|
"""Codex release to install, pinned for reproducibility."""
|
|
43
41
|
multi_agent: bool = False
|
|
44
42
|
"""Enable Codex's native multi-agent v2 tools."""
|
|
@@ -64,24 +62,18 @@ class CodexHarness(ACPHarness[CodexHarnessConfig]):
|
|
|
64
62
|
acp_bin = ACP_BIN.format(**versions)
|
|
65
63
|
ready = f"{directory}/.ready"
|
|
66
64
|
script = INSTALL.replace("{packages}", packages).replace("{ready}", ready)
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
f"sh -c {ensure}"
|
|
74
|
-
)
|
|
75
|
-
install = await runtime.run(
|
|
76
|
-
["sh", "-c", guarded],
|
|
77
|
-
{
|
|
65
|
+
await ensure_installed(
|
|
66
|
+
runtime,
|
|
67
|
+
directory=directory,
|
|
68
|
+
ready=f"[ -f {ready} ] && [ -x {codex_bin} ] && [ -x {acp_bin} ]",
|
|
69
|
+
install=script,
|
|
70
|
+
env={
|
|
78
71
|
**self.config.resolved_env,
|
|
79
72
|
"VF_CODEX_VERSION": self.config.version,
|
|
80
73
|
"VF_CODEX_ACP_VERSION": ACP_VERSION,
|
|
81
74
|
},
|
|
75
|
+
label="codex",
|
|
82
76
|
)
|
|
83
|
-
if install.exit_code != 0:
|
|
84
|
-
raise RuntimeError(f"codex install failed: {install.stderr.strip()[-500:]}")
|
|
85
77
|
await super().setup(runtime)
|
|
86
78
|
|
|
87
79
|
async def prepare_acp(
|
|
@@ -112,11 +104,7 @@ class CodexHarness(ACPHarness[CodexHarnessConfig]):
|
|
|
112
104
|
)
|
|
113
105
|
|
|
114
106
|
async def cleanup(self, trace: Trace, runtime: Runtime) -> None:
|
|
115
|
-
|
|
116
|
-
if result.exit_code != 0:
|
|
117
|
-
raise RuntimeError(
|
|
118
|
-
f"failed to clean up Codex home: {result.stderr.strip()[-500:]}"
|
|
119
|
-
)
|
|
107
|
+
await remove_dir(runtime, self.trace_home(trace), "Codex home")
|
|
120
108
|
|
|
121
109
|
@staticmethod
|
|
122
110
|
def trace_home(trace: Trace) -> str:
|
|
@@ -3,11 +3,10 @@
|
|
|
3
3
|
import json
|
|
4
4
|
from pathlib import Path
|
|
5
5
|
|
|
6
|
-
from pydantic import Field
|
|
7
|
-
|
|
8
6
|
from verifiers.v1.acp import ACPConfig, ACPHarness
|
|
9
7
|
from verifiers.v1.clients import ModelContext
|
|
10
|
-
from verifiers.v1.configs.harness import HarnessConfig
|
|
8
|
+
from verifiers.v1.configs.harness import HarnessConfig, PinnedVersion
|
|
9
|
+
from verifiers.v1.harnesses.utils.install import remove_dir
|
|
11
10
|
from verifiers.v1.runtimes import Runtime
|
|
12
11
|
from verifiers.v1.task import TaskData
|
|
13
12
|
from verifiers.v1.trace import Trace
|
|
@@ -16,7 +15,7 @@ PROGRAM_SOURCE = (Path(__file__).resolve().parent / "program.py").read_text()
|
|
|
16
15
|
|
|
17
16
|
|
|
18
17
|
class HermesAgentHarnessConfig(HarnessConfig):
|
|
19
|
-
version:
|
|
18
|
+
version: PinnedVersion = "0.19.0"
|
|
20
19
|
"""Hermes Agent release to install, pinned for reproducibility."""
|
|
21
20
|
use_bundled_skill: bool = False
|
|
22
21
|
"""Enable Hermes Agent's bundled skill catalog in addition to uploaded skills."""
|
|
@@ -96,8 +95,4 @@ class HermesAgentHarness(ACPHarness[HermesAgentHarnessConfig]):
|
|
|
96
95
|
)
|
|
97
96
|
|
|
98
97
|
async def cleanup(self, trace: Trace, runtime: Runtime) -> None:
|
|
99
|
-
|
|
100
|
-
if result.exit_code:
|
|
101
|
-
raise RuntimeError(
|
|
102
|
-
f"failed to clean up Hermes home: {result.stderr.strip()[-500:]}"
|
|
103
|
-
)
|
|
98
|
+
await remove_dir(runtime, f"/tmp/vf-hermes/{trace.id}", "Hermes home")
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
"""Run Kimi Code's native ACP server against interception."""
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
|
-
import shlex
|
|
5
4
|
from typing import Literal
|
|
6
5
|
|
|
7
6
|
import tomli_w
|
|
8
|
-
from pydantic import Field
|
|
9
7
|
|
|
10
8
|
from verifiers.v1.acp import ACPConfig, ACPHarness
|
|
11
9
|
from verifiers.v1.clients import ModelContext
|
|
12
|
-
from verifiers.v1.configs.harness import HarnessConfig
|
|
10
|
+
from verifiers.v1.configs.harness import HarnessConfig, PinnedVersion
|
|
11
|
+
from verifiers.v1.harnesses.utils.install import ensure_installed
|
|
13
12
|
from verifiers.v1.runtimes import Runtime
|
|
14
13
|
from verifiers.v1.task import TaskData
|
|
15
14
|
from verifiers.v1.trace import Trace
|
|
@@ -39,7 +38,7 @@ env \
|
|
|
39
38
|
|
|
40
39
|
|
|
41
40
|
class KimiCodeHarnessConfig(HarnessConfig):
|
|
42
|
-
version:
|
|
41
|
+
version: PinnedVersion = "0.36.0"
|
|
43
42
|
"""Kimi Code release to install, pinned for reproducibility."""
|
|
44
43
|
transport: Literal["chat_completions", "responses", "anthropic_messages"] = (
|
|
45
44
|
"chat_completions"
|
|
@@ -58,16 +57,13 @@ class KimiCodeHarness(ACPHarness[KimiCodeHarnessConfig]):
|
|
|
58
57
|
"kimi-code: ensuring Kimi Code %s is installed", self.config.version
|
|
59
58
|
)
|
|
60
59
|
script = INSTALL.replace("{version}", self.config.version)
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
60
|
+
await ensure_installed(
|
|
61
|
+
runtime,
|
|
62
|
+
directory="/tmp/vf-kimi-code",
|
|
63
|
+
install=script,
|
|
64
|
+
env={},
|
|
65
|
+
label="Kimi Code",
|
|
65
66
|
)
|
|
66
|
-
install = await runtime.run(["sh", "-c", guarded], {})
|
|
67
|
-
if install.exit_code != 0:
|
|
68
|
-
raise RuntimeError(
|
|
69
|
-
f"Kimi Code install failed: {install.stderr.strip()[-500:]}"
|
|
70
|
-
)
|
|
71
67
|
await super().setup(runtime)
|
|
72
68
|
|
|
73
69
|
async def prepare_acp(
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
2
|
|
|
3
|
-
from pydantic import Field
|
|
4
|
-
|
|
5
3
|
from verifiers.v1.clients import ModelContext
|
|
6
|
-
from verifiers.v1.configs.harness import HarnessConfig
|
|
4
|
+
from verifiers.v1.configs.harness import HarnessConfig, PinnedVersion
|
|
7
5
|
from verifiers.v1.harness import Harness
|
|
8
6
|
from verifiers.v1.runtimes import ProgramResult, Runtime
|
|
9
7
|
from verifiers.v1.task import TaskData
|
|
@@ -13,7 +11,7 @@ PROGRAM_SOURCE = (Path(__file__).resolve().parent / "program.py").read_text()
|
|
|
13
11
|
|
|
14
12
|
|
|
15
13
|
class MiniSWEAgentHarnessConfig(HarnessConfig):
|
|
16
|
-
version:
|
|
14
|
+
version: PinnedVersion = "2.4.6"
|
|
17
15
|
"""mini-swe-agent release to install, pinned for reproducibility."""
|
|
18
16
|
|
|
19
17
|
|
verifiers/v1/harnesses/node.py
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
1
|
+
from verifiers.v1.harnesses.utils.install import ensure_installed
|
|
3
2
|
from verifiers.v1.runtimes import Runtime
|
|
4
3
|
|
|
5
4
|
NODE_DIR = "/var/tmp/vf-node"
|
|
@@ -39,16 +38,12 @@ node_ok || { echo "ACP adapters require Node.js 22.19 or newer" >&2; exit 1; }
|
|
|
39
38
|
|
|
40
39
|
async def ensure_node(runtime: Runtime) -> None:
|
|
41
40
|
"""Install the shared Node runtime used by ACP adapter harnesses."""
|
|
42
|
-
lock
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
f
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
f"sh -c {shlex.quote(INSTALL)}"
|
|
41
|
+
# The install replaces NODE_DIR wholesale, so the lock lives beside it.
|
|
42
|
+
await ensure_installed(
|
|
43
|
+
runtime,
|
|
44
|
+
directory=NODE_DIR,
|
|
45
|
+
lock=f"{NODE_DIR}.install.lock",
|
|
46
|
+
install=INSTALL,
|
|
47
|
+
env={"VF_NODE_VERSION": NODE_VERSION},
|
|
48
|
+
label="Node.js",
|
|
51
49
|
)
|
|
52
|
-
result = await runtime.run(["sh", "-c", guarded], {"VF_NODE_VERSION": NODE_VERSION})
|
|
53
|
-
if result.exit_code != 0:
|
|
54
|
-
raise RuntimeError(f"Node.js install failed: {result.stderr.strip()[-500:]}")
|
|
@@ -4,13 +4,11 @@ import asyncio
|
|
|
4
4
|
import json
|
|
5
5
|
import logging
|
|
6
6
|
import secrets
|
|
7
|
-
import shlex
|
|
8
|
-
|
|
9
|
-
from pydantic import Field
|
|
10
7
|
|
|
11
8
|
from verifiers.v1.acp import ACPConfig, ACPHarness
|
|
12
9
|
from verifiers.v1.clients import ModelContext
|
|
13
|
-
from verifiers.v1.configs.harness import HarnessConfig
|
|
10
|
+
from verifiers.v1.configs.harness import HarnessConfig, PinnedVersion
|
|
11
|
+
from verifiers.v1.harnesses.utils.install import ensure_installed, remove_dir
|
|
14
12
|
from verifiers.v1.runtimes import Runtime
|
|
15
13
|
from verifiers.v1.task import TaskData
|
|
16
14
|
from verifiers.v1.trace import Trace
|
|
@@ -120,7 +118,7 @@ wait "$acp_pid"
|
|
|
120
118
|
|
|
121
119
|
|
|
122
120
|
class OpenClawHarnessConfig(HarnessConfig):
|
|
123
|
-
version:
|
|
121
|
+
version: PinnedVersion = "2026.8.1"
|
|
124
122
|
"""OpenClaw release to install, pinned for reproducibility."""
|
|
125
123
|
use_bundled_skill: bool = True
|
|
126
124
|
"""Enable OpenClaw's bundled skill catalog in addition to uploaded harness skills."""
|
|
@@ -143,39 +141,29 @@ class OpenClawHarness(ACPHarness[OpenClawHarnessConfig]):
|
|
|
143
141
|
ready_path = f"{self._staged_skills_dir}/.ready"
|
|
144
142
|
ready = await runtime.run(["test", "-f", ready_path], {})
|
|
145
143
|
if ready.exit_code != 0:
|
|
146
|
-
|
|
147
|
-
|
|
144
|
+
await remove_dir(
|
|
145
|
+
runtime, self._staged_skills_dir, "OpenClaw skills"
|
|
148
146
|
)
|
|
149
|
-
if cleared.exit_code != 0:
|
|
150
|
-
raise RuntimeError(
|
|
151
|
-
"failed to clear OpenClaw skills: "
|
|
152
|
-
f"{cleared.stderr.strip()[-500:]}"
|
|
153
|
-
)
|
|
154
147
|
await self.install_skills(runtime, self._staged_skills_dir)
|
|
155
148
|
await runtime.write(ready_path, b"")
|
|
156
149
|
directory = OPENCLAW_DIR.format(version=self.config.version)
|
|
157
150
|
binary = OPENCLAW_BIN.format(version=self.config.version)
|
|
151
|
+
logger.info("openclaw: ensuring OpenClaw %s is installed", self.config.version)
|
|
158
152
|
# Borrowed runtimes can share this cache, so one filesystem lock owns both
|
|
159
153
|
# installation and the transcript adjustment.
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
)
|
|
166
|
-
logger.info("openclaw: ensuring OpenClaw %s is installed", self.config.version)
|
|
167
|
-
result = await runtime.run(
|
|
168
|
-
["sh", "-c", guarded],
|
|
169
|
-
{
|
|
154
|
+
await ensure_installed(
|
|
155
|
+
runtime,
|
|
156
|
+
directory=directory,
|
|
157
|
+
install=SETUP,
|
|
158
|
+
env={
|
|
170
159
|
**self.config.resolved_env,
|
|
171
160
|
"VF_OPENCLAW_BIN": binary,
|
|
172
161
|
"VF_OPENCLAW_DIR": directory,
|
|
173
162
|
"VF_OPENCLAW_VERSION": self.config.version,
|
|
174
163
|
},
|
|
164
|
+
label="OpenClaw",
|
|
165
|
+
shell=("bash", "-o", "pipefail", "-c"),
|
|
175
166
|
)
|
|
176
|
-
if result.exit_code != 0:
|
|
177
|
-
detail = (result.stderr or result.stdout).strip()[-500:]
|
|
178
|
-
raise RuntimeError(f"OpenClaw setup failed: {detail}")
|
|
179
167
|
await super().setup(runtime)
|
|
180
168
|
|
|
181
169
|
async def prepare_acp(
|
|
@@ -277,8 +265,4 @@ class OpenClawHarness(ACPHarness[OpenClawHarnessConfig]):
|
|
|
277
265
|
|
|
278
266
|
async def cleanup(self, trace: Trace, runtime: Runtime) -> None:
|
|
279
267
|
state_dir = f".vf-openclaw/{trace.id}"
|
|
280
|
-
|
|
281
|
-
if result.exit_code != 0:
|
|
282
|
-
raise RuntimeError(
|
|
283
|
-
f"failed to clean up OpenClaw state: {result.stderr.strip()[-500:]}"
|
|
284
|
-
)
|
|
268
|
+
await remove_dir(runtime, state_dir, "OpenClaw state")
|