Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions verifiers/v1/configs/harness.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,16 @@

import os
from pathlib import Path
from typing import Annotated

from pydantic import ConfigDict, Field, FiniteFloat
from pydantic_config import BaseConfig

from verifiers.v1.types import ID

PinnedVersion = Annotated[str, Field(pattern=r"^[A-Za-z0-9._+-]+$")]
"""A release/tag a harness pins its program install to."""


class HarnessConfig(BaseConfig):
id: ID = "bash"
Expand Down
36 changes: 11 additions & 25 deletions verifiers/v1/harnesses/claude_code/harness.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
"""Run Claude Code through the Claude Agent SDK ACP adapter."""

import shlex

from pydantic import Field

from verifiers.v1.acp import ACPConfig, ACPHarness
from verifiers.v1.clients import ModelContext
from verifiers.v1.configs.harness import HarnessConfig
from verifiers.v1.configs.harness import HarnessConfig, PinnedVersion
from verifiers.v1.harnesses.node import NODE_BIN_DIR, ensure_node
from verifiers.v1.harnesses.utils.install import ensure_installed, remove_dir
from verifiers.v1.runtimes import Runtime
from verifiers.v1.task import TaskData
from verifiers.v1.trace import Trace
Expand All @@ -32,7 +29,7 @@


class ClaudeCodeHarnessConfig(HarnessConfig):
version: str = Field(default="2.1.232", pattern=r"^[A-Za-z0-9._+-]+$")
version: PinnedVersion = "2.1.232"
"""Claude Code release to install, pinned for reproducibility."""


Expand All @@ -51,25 +48,18 @@ async def setup(self, runtime: Runtime) -> None:
acp_bin = ACP_BIN.format(**versions)
ready = f"{directory}/.ready"
script = ACP_INSTALL.replace("{packages}", packages).replace("{ready}", ready)
ensure = shlex.quote(
f"[ -f {ready} ] && [ -x {claude_bin} ] && [ -x {acp_bin} ] || ({script})"
)
acp_guarded = (
f"mkdir -p {directory} && "
f'"$(command -v flock || command -v lockf)" {directory}/install.lock '
f"sh -c {ensure}"
)
acp_result = await runtime.run(
["sh", "-c", acp_guarded],
{
await ensure_installed(
runtime,
directory=directory,
ready=f"[ -f {ready} ] && [ -x {claude_bin} ] && [ -x {acp_bin} ]",
install=script,
env={
**self.config.resolved_env,
"VF_CLAUDE_CODE_VERSION": self.config.version,
"VF_CLAUDE_ACP_VERSION": ACP_VERSION,
},
label="Claude Agent ACP",
)
if acp_result.exit_code != 0:
detail = (acp_result.stderr or acp_result.stdout).strip()[-500:]
raise RuntimeError(f"Claude Agent ACP install failed: {detail}")
await super().setup(runtime)

async def prepare_acp(
Expand Down Expand Up @@ -113,11 +103,7 @@ async def prepare_acp(
)

async def cleanup(self, trace: Trace, runtime: Runtime) -> None:
result = await runtime.run(["rm", "-rf", self.config_dir(trace)], {})
if result.exit_code != 0:
raise RuntimeError(
f"failed to clean up Claude config: {result.stderr.strip()[-500:]}"
)
await remove_dir(runtime, self.config_dir(trace), "Claude config")

@staticmethod
def config_dir(trace: Trace) -> str:
Expand Down
34 changes: 11 additions & 23 deletions verifiers/v1/harnesses/codex/harness.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,13 @@
import json
import logging
import re
import shlex
from collections import Counter

from pydantic import Field

from verifiers.v1.acp import ACPConfig, ACPHarness
from verifiers.v1.clients import ModelContext
from verifiers.v1.configs.harness import HarnessConfig
from verifiers.v1.configs.harness import HarnessConfig, PinnedVersion
from verifiers.v1.harnesses.node import NODE_BIN_DIR, ensure_node
from verifiers.v1.harnesses.utils.install import ensure_installed, remove_dir
from verifiers.v1.runtimes import Runtime
from verifiers.v1.task import TaskData
from verifiers.v1.trace import Trace
Expand All @@ -38,7 +36,7 @@


class CodexHarnessConfig(HarnessConfig):
version: str = Field(default="0.147.0", pattern=r"^[A-Za-z0-9._+-]+$")
version: PinnedVersion = "0.147.0"
"""Codex release to install, pinned for reproducibility."""
multi_agent: bool = False
"""Enable Codex's native multi-agent v2 tools."""
Expand All @@ -64,24 +62,18 @@ async def setup(self, runtime: Runtime) -> None:
acp_bin = ACP_BIN.format(**versions)
ready = f"{directory}/.ready"
script = INSTALL.replace("{packages}", packages).replace("{ready}", ready)
ensure = shlex.quote(
f"[ -f {ready} ] && [ -x {codex_bin} ] && [ -x {acp_bin} ] || ({script})"
)
guarded = (
f"mkdir -p {directory} && "
f'"$(command -v flock || command -v lockf)" {directory}/install.lock '
f"sh -c {ensure}"
)
install = await runtime.run(
["sh", "-c", guarded],
{
await ensure_installed(
runtime,
directory=directory,
ready=f"[ -f {ready} ] && [ -x {codex_bin} ] && [ -x {acp_bin} ]",
install=script,
env={
**self.config.resolved_env,
"VF_CODEX_VERSION": self.config.version,
"VF_CODEX_ACP_VERSION": ACP_VERSION,
},
label="codex",
)
if install.exit_code != 0:
raise RuntimeError(f"codex install failed: {install.stderr.strip()[-500:]}")
await super().setup(runtime)

async def prepare_acp(
Expand Down Expand Up @@ -112,11 +104,7 @@ async def prepare_acp(
)

async def cleanup(self, trace: Trace, runtime: Runtime) -> None:
result = await runtime.run(["rm", "-rf", self.trace_home(trace)], {})
if result.exit_code != 0:
raise RuntimeError(
f"failed to clean up Codex home: {result.stderr.strip()[-500:]}"
)
await remove_dir(runtime, self.trace_home(trace), "Codex home")

@staticmethod
def trace_home(trace: Trace) -> str:
Expand Down
13 changes: 4 additions & 9 deletions verifiers/v1/harnesses/hermes_agent/harness.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,10 @@
import json
from pathlib import Path

from pydantic import Field

from verifiers.v1.acp import ACPConfig, ACPHarness
from verifiers.v1.clients import ModelContext
from verifiers.v1.configs.harness import HarnessConfig
from verifiers.v1.configs.harness import HarnessConfig, PinnedVersion
from verifiers.v1.harnesses.utils.install import remove_dir
from verifiers.v1.runtimes import Runtime
from verifiers.v1.task import TaskData
from verifiers.v1.trace import Trace
Expand All @@ -16,7 +15,7 @@


class HermesAgentHarnessConfig(HarnessConfig):
version: str = Field(default="0.19.0", pattern=r"^[A-Za-z0-9._+-]+$")
version: PinnedVersion = "0.19.0"
"""Hermes Agent release to install, pinned for reproducibility."""
use_bundled_skill: bool = False
"""Enable Hermes Agent's bundled skill catalog in addition to uploaded skills."""
Expand Down Expand Up @@ -96,8 +95,4 @@ async def prepare_acp(
)

async def cleanup(self, trace: Trace, runtime: Runtime) -> None:
result = await runtime.run(["rm", "-rf", f"/tmp/vf-hermes/{trace.id}"], {})
if result.exit_code:
raise RuntimeError(
f"failed to clean up Hermes home: {result.stderr.strip()[-500:]}"
)
await remove_dir(runtime, f"/tmp/vf-hermes/{trace.id}", "Hermes home")
22 changes: 9 additions & 13 deletions verifiers/v1/harnesses/kimi_code/harness.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
"""Run Kimi Code's native ACP server against interception."""

import logging
import shlex
from typing import Literal

import tomli_w
from pydantic import Field

from verifiers.v1.acp import ACPConfig, ACPHarness
from verifiers.v1.clients import ModelContext
from verifiers.v1.configs.harness import HarnessConfig
from verifiers.v1.configs.harness import HarnessConfig, PinnedVersion
from verifiers.v1.harnesses.utils.install import ensure_installed
from verifiers.v1.runtimes import Runtime
from verifiers.v1.task import TaskData
from verifiers.v1.trace import Trace
Expand Down Expand Up @@ -39,7 +38,7 @@


class KimiCodeHarnessConfig(HarnessConfig):
version: str = Field(default="0.36.0", pattern=r"^[A-Za-z0-9._+-]+$")
version: PinnedVersion = "0.36.0"
"""Kimi Code release to install, pinned for reproducibility."""
transport: Literal["chat_completions", "responses", "anthropic_messages"] = (
"chat_completions"
Expand All @@ -58,16 +57,13 @@ async def setup(self, runtime: Runtime) -> None:
"kimi-code: ensuring Kimi Code %s is installed", self.config.version
)
script = INSTALL.replace("{version}", self.config.version)
guarded = (
"mkdir -p /tmp/vf-kimi-code && "
'"$(command -v flock || command -v lockf)" '
f"/tmp/vf-kimi-code/install.lock sh -c {shlex.quote(script)}"
await ensure_installed(
runtime,
directory="/tmp/vf-kimi-code",
install=script,
env={},
label="Kimi Code",
)
install = await runtime.run(["sh", "-c", guarded], {})
if install.exit_code != 0:
raise RuntimeError(
f"Kimi Code install failed: {install.stderr.strip()[-500:]}"
)
await super().setup(runtime)

async def prepare_acp(
Expand Down
6 changes: 2 additions & 4 deletions verifiers/v1/harnesses/mini_swe_agent/harness.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
from pathlib import Path

from pydantic import Field

from verifiers.v1.clients import ModelContext
from verifiers.v1.configs.harness import HarnessConfig
from verifiers.v1.configs.harness import HarnessConfig, PinnedVersion
from verifiers.v1.harness import Harness
from verifiers.v1.runtimes import ProgramResult, Runtime
from verifiers.v1.task import TaskData
Expand All @@ -13,7 +11,7 @@


class MiniSWEAgentHarnessConfig(HarnessConfig):
version: str = Field(default="2.4.6", pattern=r"^[A-Za-z0-9._+-]+$")
version: PinnedVersion = "2.4.6"
"""mini-swe-agent release to install, pinned for reproducibility."""


Expand Down
23 changes: 9 additions & 14 deletions verifiers/v1/harnesses/node.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import shlex

from verifiers.v1.harnesses.utils.install import ensure_installed
from verifiers.v1.runtimes import Runtime

NODE_DIR = "/var/tmp/vf-node"
Expand Down Expand Up @@ -39,16 +38,12 @@

async def ensure_node(runtime: Runtime) -> None:
"""Install the shared Node runtime used by ACP adapter harnesses."""
lock = f"{NODE_DIR}.install.lock"
guarded = (
f'until ln -s "$$" {lock} 2>/dev/null; do '
f"owner=$(readlink {lock}); "
f'if ! kill -0 "$owner" 2>/dev/null; then '
f'[ "$(readlink {lock})" != "$owner" ] || rm -f {lock}; fi; '
f"sleep 0.1; done; "
f'trap \'[ "$(readlink {lock})" != "$$" ] || rm -f {lock}\' EXIT; '
f"sh -c {shlex.quote(INSTALL)}"
# The install replaces NODE_DIR wholesale, so the lock lives beside it.
await ensure_installed(
Comment thread
macroscopeapp[bot] marked this conversation as resolved.
runtime,
directory=NODE_DIR,
lock=f"{NODE_DIR}.install.lock",
install=INSTALL,
env={"VF_NODE_VERSION": NODE_VERSION},
label="Node.js",
)
result = await runtime.run(["sh", "-c", guarded], {"VF_NODE_VERSION": NODE_VERSION})
if result.exit_code != 0:
raise RuntimeError(f"Node.js install failed: {result.stderr.strip()[-500:]}")
44 changes: 14 additions & 30 deletions verifiers/v1/harnesses/openclaw/harness.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,11 @@
import json
import logging
import secrets
import shlex

from pydantic import Field

from verifiers.v1.acp import ACPConfig, ACPHarness
from verifiers.v1.clients import ModelContext
from verifiers.v1.configs.harness import HarnessConfig
from verifiers.v1.configs.harness import HarnessConfig, PinnedVersion
from verifiers.v1.harnesses.utils.install import ensure_installed, remove_dir
from verifiers.v1.runtimes import Runtime
from verifiers.v1.task import TaskData
from verifiers.v1.trace import Trace
Expand Down Expand Up @@ -120,7 +118,7 @@


class OpenClawHarnessConfig(HarnessConfig):
version: str = Field(default="2026.8.1", pattern=r"^[A-Za-z0-9._+-]+$")
version: PinnedVersion = "2026.8.1"
"""OpenClaw release to install, pinned for reproducibility."""
use_bundled_skill: bool = True
"""Enable OpenClaw's bundled skill catalog in addition to uploaded harness skills."""
Expand All @@ -143,39 +141,29 @@ async def setup(self, runtime: Runtime) -> None:
ready_path = f"{self._staged_skills_dir}/.ready"
ready = await runtime.run(["test", "-f", ready_path], {})
if ready.exit_code != 0:
cleared = await runtime.run(
["rm", "-rf", self._staged_skills_dir], {}
await remove_dir(
runtime, self._staged_skills_dir, "OpenClaw skills"
)
if cleared.exit_code != 0:
raise RuntimeError(
"failed to clear OpenClaw skills: "
f"{cleared.stderr.strip()[-500:]}"
)
await self.install_skills(runtime, self._staged_skills_dir)
await runtime.write(ready_path, b"")
directory = OPENCLAW_DIR.format(version=self.config.version)
binary = OPENCLAW_BIN.format(version=self.config.version)
logger.info("openclaw: ensuring OpenClaw %s is installed", self.config.version)
# Borrowed runtimes can share this cache, so one filesystem lock owns both
# installation and the transcript adjustment.
guarded = (
f"mkdir -p {shlex.quote(directory)} && "
f'"$(command -v flock || command -v lockf)" '
f"{shlex.quote(f'{directory}/install.lock')} "
f"bash -o pipefail -c {shlex.quote(SETUP)}"
)
logger.info("openclaw: ensuring OpenClaw %s is installed", self.config.version)
result = await runtime.run(
["sh", "-c", guarded],
{
await ensure_installed(
runtime,
directory=directory,
install=SETUP,
env={
**self.config.resolved_env,
"VF_OPENCLAW_BIN": binary,
"VF_OPENCLAW_DIR": directory,
"VF_OPENCLAW_VERSION": self.config.version,
},
label="OpenClaw",
shell=("bash", "-o", "pipefail", "-c"),
)
if result.exit_code != 0:
detail = (result.stderr or result.stdout).strip()[-500:]
raise RuntimeError(f"OpenClaw setup failed: {detail}")
await super().setup(runtime)

async def prepare_acp(
Expand Down Expand Up @@ -277,8 +265,4 @@ async def prepare_acp(

async def cleanup(self, trace: Trace, runtime: Runtime) -> None:
state_dir = f".vf-openclaw/{trace.id}"
result = await runtime.run(["rm", "-rf", state_dir], {})
if result.exit_code != 0:
raise RuntimeError(
f"failed to clean up OpenClaw state: {result.stderr.strip()[-500:]}"
)
await remove_dir(runtime, state_dir, "OpenClaw state")
Loading