verifiers 0.3.2.dev46__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.
@@ -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"
@@ -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: str = Field(default="2.1.232", pattern=r"^[A-Za-z0-9._+-]+$")
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
- ensure = shlex.quote(
55
- f"[ -f {ready} ] && [ -x {claude_bin} ] && [ -x {acp_bin} ] || ({script})"
56
- )
57
- acp_guarded = (
58
- f"mkdir -p {directory} && "
59
- f'"$(command -v flock || command -v lockf)" {directory}/install.lock '
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
- result = await runtime.run(["rm", "-rf", self.config_dir(trace)], {})
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: str = Field(default="0.147.0", pattern=r"^[A-Za-z0-9._+-]+$")
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
- ensure = shlex.quote(
68
- f"[ -f {ready} ] && [ -x {codex_bin} ] && [ -x {acp_bin} ] || ({script})"
69
- )
70
- guarded = (
71
- f"mkdir -p {directory} && "
72
- f'"$(command -v flock || command -v lockf)" {directory}/install.lock '
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
- result = await runtime.run(["rm", "-rf", self.trace_home(trace)], {})
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: str = Field(default="0.19.0", pattern=r"^[A-Za-z0-9._+-]+$")
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
- result = await runtime.run(["rm", "-rf", f"/tmp/vf-hermes/{trace.id}"], {})
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: str = Field(default="0.36.0", pattern=r"^[A-Za-z0-9._+-]+$")
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
- guarded = (
62
- "mkdir -p /tmp/vf-kimi-code && "
63
- '"$(command -v flock || command -v lockf)" '
64
- f"/tmp/vf-kimi-code/install.lock sh -c {shlex.quote(script)}"
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: str = Field(default="2.4.6", pattern=r"^[A-Za-z0-9._+-]+$")
14
+ version: PinnedVersion = "2.4.6"
17
15
  """mini-swe-agent release to install, pinned for reproducibility."""
18
16
 
19
17
 
@@ -1,5 +1,4 @@
1
- import shlex
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 = f"{NODE_DIR}.install.lock"
43
- guarded = (
44
- f'until ln -s "$$" {lock} 2>/dev/null; do '
45
- f"owner=$(readlink {lock}); "
46
- f'if ! kill -0 "$owner" 2>/dev/null; then '
47
- f'[ "$(readlink {lock})" != "$owner" ] || rm -f {lock}; fi; '
48
- f"sleep 0.1; done; "
49
- f'trap \'[ "$(readlink {lock})" != "$$" ] || rm -f {lock}\' EXIT; '
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: str = Field(default="2026.8.1", pattern=r"^[A-Za-z0-9._+-]+$")
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
- cleared = await runtime.run(
147
- ["rm", "-rf", self._staged_skills_dir], {}
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
- guarded = (
161
- f"mkdir -p {shlex.quote(directory)} && "
162
- f'"$(command -v flock || command -v lockf)" '
163
- f"{shlex.quote(f'{directory}/install.lock')} "
164
- f"bash -o pipefail -c {shlex.quote(SETUP)}"
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
- result = await runtime.run(["rm", "-rf", state_dir], {})
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")
@@ -5,12 +5,11 @@ import logging
5
5
  import shlex
6
6
  from typing import Literal
7
7
 
8
- from pydantic import Field
9
-
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
13
11
  from verifiers.v1.harnesses.node import NODE_BIN_DIR, ensure_node
12
+ from verifiers.v1.harnesses.utils.install import ensure_installed
14
13
  from verifiers.v1.runtimes import Runtime
15
14
  from verifiers.v1.task import TaskData
16
15
  from verifiers.v1.trace import Trace
@@ -46,7 +45,7 @@ fi
46
45
 
47
46
 
48
47
  class PiHarnessConfig(HarnessConfig):
49
- version: str = Field(default="0.84.1", pattern=r"^[A-Za-z0-9._+-]+$")
48
+ version: PinnedVersion = "0.84.1"
50
49
  """Pi release to install, pinned for reproducibility."""
51
50
  transport: Literal["chat_completions", "responses", "anthropic_messages"] = (
52
51
  "chat_completions"
@@ -69,27 +68,17 @@ class PiHarness(ACPHarness[PiHarnessConfig]):
69
68
  self.config.version,
70
69
  ACP_VERSION,
71
70
  )
72
- lock = f"{PI_DIR}/install.lock"
73
- guarded = (
74
- f"mkdir -p {PI_DIR} && "
75
- f'until ln -s "$$" {lock} 2>/dev/null; do '
76
- f"owner=$(readlink {lock}); "
77
- f'if ! kill -0 "$owner" 2>/dev/null; then '
78
- f'[ "$(readlink {lock})" != "$owner" ] || rm -f {lock}; fi; '
79
- f"sleep 0.1; done; "
80
- f'trap \'[ "$(readlink {lock})" != "$$" ] || rm -f {lock}\' EXIT; '
81
- f"sh -c {shlex.quote(INSTALL)}"
82
- )
83
- install = await runtime.run(
84
- ["sh", "-c", guarded],
85
- {
71
+ await ensure_installed(
72
+ runtime,
73
+ directory=PI_DIR,
74
+ install=INSTALL,
75
+ env={
86
76
  "VF_PI_VERSION": self.config.version,
87
77
  "VF_PI_MCP_VERSION": MCP_VERSION,
88
78
  "VF_PI_ACP_VERSION": ACP_VERSION,
89
79
  },
80
+ label="pi",
90
81
  )
91
- if install.exit_code != 0:
92
- raise RuntimeError(f"pi install failed: {install.stderr.strip()[-500:]}")
93
82
  await super().setup(runtime)
94
83
 
95
84
  async def prepare_acp(
@@ -3,11 +3,10 @@
3
3
  import json
4
4
  import shlex
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 ensure_installed
11
10
  from verifiers.v1.runtimes import Runtime
12
11
  from verifiers.v1.task import TaskData
13
12
  from verifiers.v1.trace import Trace
@@ -28,7 +27,7 @@ chmod +x "{dir}/pool"
28
27
 
29
28
 
30
29
  class PoolHarnessConfig(HarnessConfig):
31
- version: str = Field(default="1.0.15", pattern=r"^[A-Za-z0-9._+-]+$")
30
+ version: PinnedVersion = "1.0.15"
32
31
  """Pool release to install, pinned for reproducibility."""
33
32
 
34
33
 
@@ -44,17 +43,15 @@ class PoolHarness(ACPHarness[PoolHarnessConfig]):
44
43
  script = INSTALL.replace("{version}", self.config.version).replace(
45
44
  "{dir}", directory
46
45
  )
47
- ensure = shlex.quote(f"[ -x {binary} ] || ({script})")
48
- # Cache the pinned binary across local rollouts; Linux has flock, macOS has lockf.
49
- guarded = (
50
- f"mkdir -p {directory} && "
51
- f'"$(command -v flock || command -v lockf)" {directory}/install.lock '
52
- f"bash -o pipefail -c {ensure}"
46
+ await ensure_installed(
47
+ runtime,
48
+ directory=directory,
49
+ ready=f"[ -x {binary} ]",
50
+ install=script,
51
+ env=self.config.resolved_env,
52
+ label="Pool",
53
+ shell=("bash", "-o", "pipefail", "-c"),
53
54
  )
54
- result = await runtime.run(["sh", "-c", guarded], self.config.resolved_env)
55
- if result.exit_code != 0:
56
- detail = (result.stderr or result.stdout).strip()[-500:]
57
- raise RuntimeError(f"Pool install failed: {detail}")
58
55
  await super().setup(runtime)
59
56
 
60
57
  async def prepare_acp(
@@ -10,6 +10,7 @@ from verifiers.v1.acp import ACPConfig, ACPHarness, ACPTurn
10
10
  from verifiers.v1.clients import ModelContext
11
11
  from verifiers.v1.configs.harness import HarnessConfig
12
12
  from verifiers.v1.harnesses.node import NODE_BIN_DIR, ensure_node
13
+ from verifiers.v1.harnesses.utils.install import ensure_installed, remove_dir
13
14
  from verifiers.v1.runtimes import Runtime
14
15
  from verifiers.v1.task import TaskData
15
16
  from verifiers.v1.trace import Trace
@@ -157,26 +158,19 @@ class PrimeAgentHarness(ACPHarness[PrimeAgentHarnessConfig]):
157
158
  await self.install_skills(runtime, SKILLS_DIR)
158
159
  await ensure_node(runtime)
159
160
  logger.info("prime-agent: ensuring commit %s is installed", self.config.commit)
160
- lock = f"{PRIME_AGENT_DIR}/install.lock"
161
- guarded = (
162
- f"mkdir -p {PRIME_AGENT_DIR} && "
163
- f'"$(command -v flock || command -v lockf)" {lock} '
164
- f"sh -c {shlex.quote(INSTALL)}"
165
- )
166
- result = await runtime.run(
167
- ["sh", "-c", guarded],
168
- {
161
+ await ensure_installed(
162
+ runtime,
163
+ directory=PRIME_AGENT_DIR,
164
+ install=INSTALL,
165
+ env={
169
166
  **self.config.resolved_env,
170
167
  "VF_PRIME_AGENT_DIR": PRIME_AGENT_DIR,
171
168
  "VF_PRIME_AGENT_GITHUB_RELEASE_URL": GITHUB_RELEASE_URL,
172
169
  "PRIME_AGENT_COMMIT": self.config.commit,
173
170
  "PRIME_AGENT_RELEASE_VERSION": PRIME_AGENT_VERSION,
174
171
  },
172
+ label="prime-agent",
175
173
  )
176
- if result.exit_code != 0:
177
- raise RuntimeError(
178
- f"prime-agent install failed: {result.stderr.strip()[-500:]}"
179
- )
180
174
  await super().setup(runtime)
181
175
 
182
176
  async def prepare_acp(
@@ -285,11 +279,7 @@ class PrimeAgentHarness(ACPHarness[PrimeAgentHarnessConfig]):
285
279
 
286
280
  async def cleanup(self, trace: Trace, runtime: Runtime) -> None:
287
281
  root = self._root(trace)
288
- removed = await runtime.run(["rm", "-rf", root], {})
289
- if removed.exit_code != 0:
290
- raise RuntimeError(
291
- f"prime-agent state cleanup failed: {removed.stderr.strip()[-500:]}"
292
- )
282
+ await remove_dir(runtime, root, "prime-agent state")
293
283
 
294
284
  def _bin(self) -> str:
295
285
  return f"{PRIME_AGENT_DIR}/{self.config.commit}/bin/prime-agent"
@@ -18,6 +18,7 @@ from pydantic_config import BaseConfig
18
18
  from verifiers.v1.acp import ACPConfig, ACPHarness, ACPTurn, JsonObject
19
19
  from verifiers.v1.clients import ModelContext
20
20
  from verifiers.v1.configs.harness import HarnessConfig
21
+ from verifiers.v1.harnesses.utils.install import ensure_installed
21
22
  from verifiers.v1.runtimes import Runtime
22
23
  from verifiers.v1.task import TaskData
23
24
  from verifiers.v1.trace import Trace
@@ -130,16 +131,17 @@ class RLMHarness(ACPHarness[RLMHarnessConfig]):
130
131
  f"touch {ready})"
131
132
  )
132
133
  logger.info("rlm: ensuring rlm is installed (version=%s)", self.config.version)
133
- ensure = shlex.quote(f"[ -f {ready} ] && [ -x {binary} ] || ({install})")
134
- guarded = (
135
- f"mkdir -p {directory} && flock {directory}/install.lock sh -c {ensure}"
136
- )
137
134
  env = self.config.resolved_env.copy()
138
135
  extra_uv_args = env.get("RLM_EXTRA_UV_ARGS", "")
139
136
  env["RLM_EXTRA_UV_ARGS"] = f"{extra_uv_args} --with mcp~=1.28".strip()
140
- result = await runtime.run(["sh", "-c", guarded], env)
141
- if result.exit_code != 0:
142
- raise RuntimeError(f"rlm install failed: {result.stderr.strip()[-500:]}")
137
+ await ensure_installed(
138
+ runtime,
139
+ directory=directory,
140
+ ready=f"[ -f {ready} ] && [ -x {binary} ]",
141
+ install=install,
142
+ env=env,
143
+ label="rlm",
144
+ )
143
145
  await super().setup(runtime)
144
146
 
145
147
  def _runtime_metadata(
@@ -1,10 +1,8 @@
1
1
  import logging
2
2
  from pathlib import Path
3
3
 
4
- from pydantic import Field
5
-
6
4
  from verifiers.v1.clients import ModelContext
7
- from verifiers.v1.configs.harness import HarnessConfig
5
+ from verifiers.v1.configs.harness import HarnessConfig, PinnedVersion
8
6
  from verifiers.v1.harness import Harness
9
7
  from verifiers.v1.runtimes import ProgramResult, Runtime
10
8
  from verifiers.v1.task import TaskData
@@ -15,7 +13,7 @@ logger = logging.getLogger(__name__)
15
13
 
16
14
 
17
15
  class Terminus2HarnessConfig(HarnessConfig):
18
- version: str = Field(default="0.21.0", pattern=r"^[A-Za-z0-9._+-]+$")
16
+ version: PinnedVersion = "0.21.0"
19
17
  """Harbor release to install, pinned for reproducibility."""
20
18
 
21
19
 
@@ -0,0 +1,65 @@
1
+ """Host-side helpers for installing a harness's program into a runtime."""
2
+
3
+ import shlex
4
+
5
+ from verifiers.v1.runtimes import Runtime
6
+
7
+
8
+ async def ensure_installed(
9
+ runtime: Runtime,
10
+ *,
11
+ directory: str,
12
+ install: str,
13
+ env: dict[str, str],
14
+ label: str,
15
+ ready: str | None = None,
16
+ lock: str | None = None,
17
+ shell: tuple[str, ...] = ("sh", "-c"),
18
+ ) -> None:
19
+ """Run `install` in `runtime` under a lock, so concurrent rollouts sharing the runtime
20
+ install once and the rest wait. `ready` is a shell test that skips the install when it
21
+ already holds. The lock is `directory/install.lock` unless the install replaces
22
+ `directory` itself, in which case pass a `lock` path beside it. `shell` runs the script
23
+ (e.g. `bash -o pipefail -c` for pipelines).
24
+
25
+ The lock is `flock` (Linux, busybox) or `lockf` (macOS/BSD), both released when the holder
26
+ dies; an image with neither falls back to a symlink spinlock whose owner is recorded as
27
+ `pid:starttime` so a dead holder is reaped even if its pid was reused."""
28
+ lock = shlex.quote(lock or f"{directory}/install.lock")
29
+ script = f"{ready} || ({install})" if ready else install
30
+ run = f"{shlex.join(shell)} {shlex.quote(script)}"
31
+ # The owner token is pid:starttime (starttime from /proc where readable), so a reused pid
32
+ # does not pass as the live holder. A lock that is not a symlink (left behind by
33
+ # flock/lockf) or whose owner is gone is stale and reaped; `kill -0 ""` succeeds under
34
+ # busybox ash, hence the explicit -z.
35
+ ident = (
36
+ 'ident() { if [ -r "/proc/$1/stat" ] && s=$(sed "s/^.*) //" "/proc/$1/stat" '
37
+ '| cut -d" " -f20) && [ -n "$s" ]; then echo "$1:$s"; else echo "$1"; fi; }; '
38
+ "me=$(ident $$); "
39
+ )
40
+ spinlock = (
41
+ f"{ident}"
42
+ f'until ln -s "$me" {lock} 2>/dev/null; do owner=$(readlink {lock} 2>/dev/null); '
43
+ f'pid=${{owner%%:*}}; if [ -z "$owner" ] || ! kill -0 "$pid" 2>/dev/null '
44
+ f'|| [ "$(ident "$pid")" != "$owner" ]; then '
45
+ f'[ "$(readlink {lock} 2>/dev/null)" != "$owner" ] || rm -f {lock}; fi; '
46
+ f"sleep 0.1; done; "
47
+ f'trap \'[ "$(readlink {lock} 2>/dev/null)" != "$me" ] || rm -f {lock}\' EXIT; {run}'
48
+ )
49
+ guarded = (
50
+ f"mkdir -p {shlex.quote(directory)} && "
51
+ f'if l=$(command -v flock || command -v lockf); then "$l" {lock} {run}; else {spinlock}; fi'
52
+ )
53
+ result = await runtime.run(["sh", "-c", guarded], env)
54
+ if result.exit_code != 0:
55
+ detail = (result.stderr.strip() or result.stdout.strip())[-500:]
56
+ raise RuntimeError(f"{label} install failed: {detail}")
57
+
58
+
59
+ async def remove_dir(runtime: Runtime, path: str, label: str) -> None:
60
+ """Delete `path` in `runtime`; a failure names `label` (what the path held)."""
61
+ result = await runtime.run(["rm", "-rf", path], {})
62
+ if result.exit_code != 0:
63
+ raise RuntimeError(
64
+ f"failed to clean up {label}: {result.stderr.strip()[-500:]}"
65
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: verifiers
3
- Version: 0.3.2.dev46
3
+ Version: 0.3.2.dev47
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
@@ -44,7 +44,7 @@ verifiers/v1/configs/__init__.py,sha256=X7u6X7B3ieD1HZl79Tv1kg-yPHbT0QLvAebaGqxV
44
44
  verifiers/v1/configs/agent.py,sha256=L5P6ombAi4U5wwsspMHAuM8111sB9AVdm8q6B5iV1lw,3251
45
45
  verifiers/v1/configs/client.py,sha256=E7ZxeWBFV_AP8MyfxXncTZs-Yn-QKdDS57HCAzqVTrQ,4437
46
46
  verifiers/v1/configs/env.py,sha256=-Bcdd4MEPfekVc_fnpiwsb9UDwCNdNCKrAHGoQuuXu8,7784
47
- verifiers/v1/configs/harness.py,sha256=nk046YBXjSv_cNQJQeOSDddbwbH9BI4GXylixFvL4I4,1640
47
+ verifiers/v1/configs/harness.py,sha256=Ks5fQD-8DYoDKokw2ejxUFwB9iWpOWDL0o89bd2ipxo,1798
48
48
  verifiers/v1/configs/judge.py,sha256=CBmGgyHXVpcBctvrFD5GlW75wpeFXVFajq_xiMaAVDk,2142
49
49
  verifiers/v1/configs/retries.py,sha256=-nPlgmz7J_NZ4GOYG6oCc3thXo4La2SufBmvQGvvPGo,821
50
50
  verifiers/v1/configs/runtime.py,sha256=IBX3FxiJ2RJaiX2aKghQ15clFHvJpPUXy8GWQOpNhYM,2967
@@ -82,42 +82,43 @@ verifiers/v1/gepa/dataset.py,sha256=hFuR7dou4VSKa0ujb8sFmGm9a19mjrsmTa1MMW1Kv7I,
82
82
  verifiers/v1/gepa/reflection.py,sha256=ptHhx0lcDLcWhWhXuslh9T8gPVNpnX5XLPtXoneW1yA,1054
83
83
  verifiers/v1/gepa/runner.py,sha256=fGEyFzejdQKBzBbvdiTDHV3JtRhXMXMGSBU5y8C8i3w,6054
84
84
  verifiers/v1/harnesses/__init__.py,sha256=0FVi-mPF36fF0vpLY4H2vbK3U_om1Svz3KgJ3i-phs0,1918
85
- verifiers/v1/harnesses/node.py,sha256=_-7NofTl-TNuwgEg8qxrD28shlQuyBWgiVHwCCwbdVo,2410
85
+ verifiers/v1/harnesses/node.py,sha256=-lDPh30p65IETPVLkKn4yhEK49rtEGVQf9TNs7MVQYE,2133
86
86
  verifiers/v1/harnesses/bash/__init__.py,sha256=IAV4eMMqDHSl-9ANb0Q3kxmBZ22SeHZFBM448X4gQio,140
87
87
  verifiers/v1/harnesses/bash/harness.py,sha256=22c2Pn4TX-ftbqeFO1tOTbAij3RT19V2R88ph098jjE,5315
88
88
  verifiers/v1/harnesses/browser_use/__init__.py,sha256=yxm2IG5TDLadxZEkX8SL26XWbcPcODXcrJfPfm1GqwU,171
89
89
  verifiers/v1/harnesses/browser_use/harness.py,sha256=eGdLDRbTF8hg4NUwU1jVJcsUei0Y-gTqbGcj3eFJ7XI,4926
90
90
  verifiers/v1/harnesses/browser_use/program.py,sha256=3dSFHSDIW3R5ZejV23sXReEKnGQjvPAAdzU-N2RrHpw,9822
91
91
  verifiers/v1/harnesses/claude_code/__init__.py,sha256=JkZQylMSA3o1fvBx_NM41y_umuahmOSqFjTrJ2hryTw,171
92
- verifiers/v1/harnesses/claude_code/harness.py,sha256=bkYbQe0tih9tIGTUp-keSjUlx4DFGXYjX7Lisd212do,4620
92
+ verifiers/v1/harnesses/claude_code/harness.py,sha256=Racxbs1EAH3gRwSeUptAIa3kB-EUm_nreUnHMwRUHuE,4119
93
93
  verifiers/v1/harnesses/codex/__init__.py,sha256=ocyBvlpSO8c3pT6D1ebrQohoXYESQcCyPT5kN3j1-N4,132
94
- verifiers/v1/harnesses/codex/harness.py,sha256=BXA1xptf6ilry7i3zgAI7WnUomEbXcD_Ikv8W8xqHI4,7482
94
+ verifiers/v1/harnesses/codex/harness.py,sha256=c8Vht0LOba7xTxCXwFH2W9rRTRdAv8Eypc1e-uSxFvg,7050
95
95
  verifiers/v1/harnesses/hermes_agent/__init__.py,sha256=k84Y8UTTf1KOw5LGqPb2dk-hYFagPevDJBOF7qsR_vo,176
96
- verifiers/v1/harnesses/hermes_agent/harness.py,sha256=AZNjQTwkv7_Dk9P1a20Xoeb-Df7iN60pWWHMw9YJ2eM,3773
96
+ verifiers/v1/harnesses/hermes_agent/harness.py,sha256=dSsE2VZ8jaM-U14QXO3J7P-U6NMpSuOz3DUMtDWpWk0,3622
97
97
  verifiers/v1/harnesses/hermes_agent/program.py,sha256=RaIYm6NFD8KLXERGXp6rOE4QrQjlcTnm1Fu33xYsiHw,570
98
98
  verifiers/v1/harnesses/kimi_code/__init__.py,sha256=1BEotSfuzmVcE7UrxHeAREM5d7J20cGkonHuowlrzu4,161
99
- verifiers/v1/harnesses/kimi_code/harness.py,sha256=3SSIty3JtQmvbItutjXJRGrh86KSAnRAK1g9Koa_5mA,4400
99
+ verifiers/v1/harnesses/kimi_code/harness.py,sha256=NCkNIdOFDNiUrEmLYb-r-SjyunR5Cmu1g4F1lwuhGK0,4163
100
100
  verifiers/v1/harnesses/mini_swe_agent/__init__.py,sha256=1OBu2Y-XqL72-dGhFdgB6sr5uX_peEEXT9vEjPy1ovU,182
101
- verifiers/v1/harnesses/mini_swe_agent/harness.py,sha256=7HHismhmfCtahlLtQdQyp_fTKidHfco-MdeWp7B9xCo,2522
101
+ verifiers/v1/harnesses/mini_swe_agent/harness.py,sha256=HKGtlDyhXDJqNtDFeb3D4JYUerIgXK6HAmyqOm1mkRo,2473
102
102
  verifiers/v1/harnesses/mini_swe_agent/program.py,sha256=Beej-JLIgSqnqIqB746oKU6kJYcynvppIbmwGa3nJJ0,167
103
103
  verifiers/v1/harnesses/null/__init__.py,sha256=XDeKOPeoXUQi9US9_Z3_yH9YaZ4EiOMKJmUtNNdtD7s,127
104
104
  verifiers/v1/harnesses/null/harness.py,sha256=RWj2I7auxYe4dJ885DhqH1MN15iUS-TKT7-gEWNrgmM,1412
105
105
  verifiers/v1/harnesses/openclaw/__init__.py,sha256=kNyfPB1sGwEf4K3mTfKN0VEyxun3SGvNpvArVKvH-x8,160
106
- verifiers/v1/harnesses/openclaw/harness.py,sha256=EREEYr3J0Qn8mK_YShnCSHctMtsiIN07rj00kq1Lxoc,11229
106
+ verifiers/v1/harnesses/openclaw/harness.py,sha256=tucrQb_Osi7KAEtpyO4i55IXI5SFtLnyItB8eV4kjng,10534
107
107
  verifiers/v1/harnesses/pi/__init__.py,sha256=1mSnzOf-RdEchdeg6IHtcdobG5KIpAhuTdUIgsCIn74,117
108
- verifiers/v1/harnesses/pi/harness.py,sha256=M0BRsfJxnBNP-KCrp0vT5gHlaJEDnyti5x_3V5Py40M,7024
108
+ verifiers/v1/harnesses/pi/harness.py,sha256=11V9d6_npjE9vEEgIG7y80kfaX1Ilfi3lbYsqGsN1NE,6496
109
109
  verifiers/v1/harnesses/pool/__init__.py,sha256=HTYsNiWGdiNEsSHj4xhPmshJSNb7SHjQ7d4yZQ4p3HM,127
110
- verifiers/v1/harnesses/pool/harness.py,sha256=HKAogPbyLK5QpvPQ1teHOh_IyRQX-or2JF71nnZ_5Ps,3875
110
+ verifiers/v1/harnesses/pool/harness.py,sha256=TsELYhW4mMOt_laiDN6nAQJxFL3iCcKvckQWYve2XiE,3571
111
111
  verifiers/v1/harnesses/prime_agent/__init__.py,sha256=xfPC4RrlnyYWaf38tPAOZq4YL6M55lMpj5BoRLmaK8I,171
112
- verifiers/v1/harnesses/prime_agent/harness.py,sha256=vswuKVWObWC9rcDBvlCnjjPRq1qCNqCzTknkOTN3cV8,11178
112
+ verifiers/v1/harnesses/prime_agent/harness.py,sha256=VFQ3kOqfR38upCwR8nuBbdc68RYW3EKUjVeOD7cKq1c,10785
113
113
  verifiers/v1/harnesses/rlm/__init__.py,sha256=hwx51xTdEWzzYgMj-p4ETNcptoub3IPMDbEdJ4uB8Ns,122
114
- verifiers/v1/harnesses/rlm/harness.py,sha256=iVdI4dzbuXm5v6Pq28ctLjOmcXWtC2Vmp2aKOniq0ss,10532
114
+ verifiers/v1/harnesses/rlm/harness.py,sha256=DX6xPBdxdXhJz-LClThAj7f1ZJ5tgN03H_WBbYhKce4,10445
115
115
  verifiers/v1/harnesses/terminus_2/__init__.py,sha256=l1pwfg1HyVDEqT9efdnFWcXqaZwtKwe7TrplkyxZSA0,166
116
- verifiers/v1/harnesses/terminus_2/harness.py,sha256=kXKhi-aqqsoXGnvet0Tq_v7LYYpRYEzARyQL2MTEGWs,3101
116
+ verifiers/v1/harnesses/terminus_2/harness.py,sha256=DMTY1bzvydyjHqzmvBSBGKJsmUzYEgxqd215a7cG7tg,3052
117
117
  verifiers/v1/harnesses/terminus_2/program.py,sha256=X8MZdyItU3gbXGXnGOlJ9LWcUTnqIDaxlhkZvoRiqKY,2703
118
118
  verifiers/v1/harnesses/utils/__init__.py,sha256=eTS-Ft2HwoLiYn5u1t_KueLmaZB_W12cdYmcVsE-G5A,364
119
119
  verifiers/v1/harnesses/utils/compaction.py,sha256=DeY6CYLEsv7-vJm26sT2mRaPHu3W2ywiZhvFr_pi6Hg,10495
120
120
  verifiers/v1/harnesses/utils/core.py,sha256=_FYeBvp506JWsya0vKYbka9ZYqUwJC7Gm7fnwammwug,14993
121
+ verifiers/v1/harnesses/utils/install.py,sha256=BDkkUVZ1brSzCrbQOvMO5mVIE2DsV2pE7rTC5yROYUw,2992
121
122
  verifiers/v1/harnesses/utils/launch.py,sha256=XlQezSmgq6X3F7q9L7UROw74BfF4oHFPgQLVbd-m7SI,3039
122
123
  verifiers/v1/harnesses/utils/mcp.py,sha256=Sss-DVMtxdE1eGEfq1ycdi1v7SBsmUU5UcpcIY6dGTY,4572
123
124
  verifiers/v1/interception/__init__.py,sha256=5Kvo7tcBUVaa65s9A1NiOH3CPpzw-P5btXwxB4Nbbvs,4251
@@ -187,8 +188,8 @@ verifiers/v1/utils/prime.py,sha256=UTYRjp9cbjNb6CVmBHda-1wWZAIIfxNOmTyNuT7_wL4,9
187
188
  verifiers/v1/utils/retries.py,sha256=Y2ZgrAjn-qNkRKZP_RVNL_7EK0iaRcZeCa404lpGYi4,5417
188
189
  verifiers/v1/utils/score.py,sha256=493yJVMw8teCu9JxapxMFPFzI0hNUqdo0Y2nGW4kckk,6200
189
190
  verifiers/v1/utils/version.py,sha256=-obEo_-l9-D8FLef4hYxncOe-uJpxrM1g2Hig_37Sgs,1607
190
- verifiers-0.3.2.dev46.dist-info/METADATA,sha256=ywadnUcDH-_ti14xITC_YxDyDv2IvcS5wkyYlcJlqDs,4153
191
- verifiers-0.3.2.dev46.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
192
- verifiers-0.3.2.dev46.dist-info/entry_points.txt,sha256=uqQje0TMsr7k6nXlWxlPs13Si0evb3pVCEDF6c-YL80,241
193
- verifiers-0.3.2.dev46.dist-info/licenses/LICENSE,sha256=v0RrUsdV3IDoZhrRce297IXS3xMHNJ-_LdLpFAUWb9k,1072
194
- verifiers-0.3.2.dev46.dist-info/RECORD,,
191
+ verifiers-0.3.2.dev47.dist-info/METADATA,sha256=hX1Sy4nnoTuxGorYcMqTLPV8USLi2-0AuxOZd04cT7s,4153
192
+ verifiers-0.3.2.dev47.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
193
+ verifiers-0.3.2.dev47.dist-info/entry_points.txt,sha256=uqQje0TMsr7k6nXlWxlPs13Si0evb3pVCEDF6c-YL80,241
194
+ verifiers-0.3.2.dev47.dist-info/licenses/LICENSE,sha256=v0RrUsdV3IDoZhrRce297IXS3xMHNJ-_LdLpFAUWb9k,1072
195
+ verifiers-0.3.2.dev47.dist-info/RECORD,,