HEMRA

Harnesses

A harness is the agent under test, described by one TOML file. Its via says how it runs. hemra run -H <name> reads harnesses/<name>.toml, or the file of that name in --harness-dir.

hemra harness add my-agent --via cli --agent claude-code   # writes harnesses/my-agent.toml
hemra harness list
hemra harness show my-agent      # as loaded: digests, referenced files, variable names
hemra harness check my-agent     # one trivial task, observed version against the pinned one

Kinds of harness

viaRunsAgents tested with it
programAny program that speaks the harness protocol itselfYour own, in any language; @hemra/sdk for TypeScript
cliAn agent's command line, through hemra-driverClaude Code, Codex, Pi
sdkAn agent SDK, through a TypeScript driverClaude Agent SDK, Codex SDK
acpAny Agent Client Protocol agent, through hemra-driverClaude Code's and Codex's ACP adapters, Gemini CLI, Goose, OpenCode
computer-useAn agent product through its web UI, in ChromiumChatGPT agent mode
harborA Harbor agent, as one supervised Harbor job per datasetAny Harbor agent, such as oracle, nop or terminus-2

Every field of a harness file is recorded with the run, and its digest is part of the run's spec. Harness files name environment variables, never their values.

Your own program

via = "program"
command = ["./echo.sh"]      # relative to this file; in a container, a path inside it
version = "0.1.0"            # what the harness reports in `ready`
env = ["MY_MODEL_KEY"]       # passed through by name

The harness's environment is cleared except PATH and the variables the file names. To write one, read the harness protocol or start from the TypeScript SDK and its example.

Agents through a driver

A cli, sdk or acp file is an agent profile, handed to the driver unchanged. For Claude Code:

via = "cli"
agent = "claude-code"
version = "2.1.302"          # expected; the observed version is what gets recorded
model = "claude-sonnet-4-5"
max_turns = 20
permission = "allowlist"     # deny | allowlist | auto
allowed_tools = ["Bash", "Read", "Write", "Edit"]
env = ["ANTHROPIC_API_KEY", "ANTHROPIC_BASE_URL"]

The common fields:

FieldMeaning
agentThe agent: claude-code, codex or pi for cli; claude-agent-sdk or codex-sdk for sdk; the agent's own name for acp
versionThe version you expect. The version the agent reports is recorded, and a difference is flagged
modelThe model. Pin it: a run can require every harness to use the same one
permissiondeny refuses every tool request, allowlist allows allowed_tools only, auto approves everything (on this host only with --allow-host-auto-approve)
allowed_toolsTool names, or ACP tool kinds such as execute, edit and read
max_turnsA turn limit, where the agent has one (not Codex, not ACP agents)
envVariables passed to the agent, by name
authoperator uses your own login and configuration instead of an isolated HOME
capturemetadata keeps text and tool payloads out of the event log
[files]Files copied into the agent's isolated HOME, relative to the harness file
bin, extra_argsFor acp: the agent program and its arguments, such as bin = "gemini" and extra_args = ["--acp"]

Each agent runs with its own HOME, so your own configuration and logins are not used unless auth = "operator" says so. The driver reports what the agent did as events and keeps its raw output as the raw-transcript artifact. It never turns the agent's own claim of success into a score.

The repository has a working file for every agent in examples/: driver-claude-code, driver-codex, driver-pi, driver-claude-agent-sdk, driver-codex-sdk, driver-acp and driver-computer-use. ACP agents that approve their own tools by default (Goose and OpenCode) need the configuration file their example copies in, so that permission decides.

The sdk drivers are built from the repository with Node.js 22 (npm ci && npm run build), and found with --driver-bin, for example --driver-bin packages/drivers/claude-agent-sdk/dist/main.js.

Agent products through their UI

A computer-use harness operates a product's web UI with a scripted recipe, in a browser profile you signed in to. It types only the task's instruction and the fixed replies in [ui], and only runs tasks marked metadata.shareable = true. A login wall, a broken recipe or a product outage is an infrastructure error, never a failed task.

Harbor agents

via = "harbor"
agent = "terminus-2"
model = "openai/gpt-5.5"
version = "2.0.0"            # the agent version Harbor installs; an unpinned one is flagged
# configs = ["agent.yaml"]   # Harbor config files, relative to this file
# [kwargs]                   # extra agent arguments, recorded with the run: no secrets

A run gives each dataset to Harbor as one supervised job, keeps Harbor's job directory byte for byte, and imports its official rewards. Harbor is pinned: HEMRA is tested against Harbor 0.24.

MCP servers

A harness file can list MCP servers. The agent reaches them only through HEMRA's proxy, which starts each server beside the runner, probes its tools before the attempt and records every tools/call in the attempt's event log.

[[mcp]]
name = "docs"
command = ["./docs-server.py"]   # relative to this file
version = "1.2.0"                # expected serverInfo.version
env = ["DOCS_TOKEN"]

Allow its tools like any other: allowed_tools = ["mcp__docs"] allows every tool of the docs server. A server that fails its probe fails the attempt before the agent starts, and two runs with different tool sets are never paired silently. computer-use harnesses take no MCP servers.

Edit this page on GitHub