Harbor is the eval framework
behind terminal-bench. clawcodex_agent.py is a Harbor custom agent that
installs clawcodex-cli from PyPI inside each task container and runs it
headless (--print --dangerously-skip-permissions).
# Harbor CLI on the host (needs a running Docker daemon)
uv tool install harbor
# DeepSeek API key (or pass any other provider key clawcodex supports)
export DEEPSEEK_API_KEY=sk-...If docker pull hangs on your machine (Docker Desktop's
"credsStore": "desktop" credential helper can wedge, stalling even
anonymous pulls of public images), the durable fix is to switch
~/.docker/config.json to the direct keychain helper — with empty
auths there is nothing to migrate:
# in ~/.docker/config.json: "credsStore": "desktop" -> "osxkeychain"Non-invasive alternative (leaves your Docker config untouched): point
DOCKER_CONFIG at a helper-free config for the eval only:
mkdir -p ~/.docker-nocreds
echo '{}' > ~/.docker-nocreds/config.json
ln -sfn ~/.docker/cli-plugins ~/.docker-nocreds/cli-plugins # keep compose v2
export DOCKER_CONFIG=~/.docker-nocredsTerminal-bench 2.1 (harbor-framework/terminal-bench-2-1)
is the verified iteration of 2.0 — same 89 tasks, 26 of them fixed for
bugs, timeouts/resources, and reward-hacking robustness. It resolves from
Harbor Hub under an org-qualified name (no @version). From the repo root:
PYTHONPATH=$PWD/eval/harbor harbor run \
--dataset terminal-bench/terminal-bench-2-1 \
--agent clawcodex_agent:Clawcodex \
--model deepseek/deepseek-v4-flash \
--jobs-dir eval/harbor/jobs \
--n-concurrent 4NOTE: hub datasets namespace task names — filters must match the full
name: -i 'terminal-bench/fix-git' (or use a glob: -i '*fix-git*').
Uses your Claude Pro/Max subscription (OAuth) instead of an API key.
One-time prerequisite on the host: clawcodex login (writes
~/.clawcodex/anthropic-oauth.json). Before each trial the adapter
checks that file and refreshes it when under 30 minutes of runway;
containers receive a refresh-token-free copy on a path outside the
bind-mounted /logs tree, so no credential ever enters the jobs
directory. When an effort kwarg is set it is also seeded into the
container's settings so subagents inherit it (the --effort flag alone
covers only the main loop).
PYTHONPATH=$PWD/eval/harbor harbor run \
--dataset terminal-bench/terminal-bench-2-1 \
--agent clawcodex_agent:Clawcodex \
--model anthropic/claude-opus-5 \
--ak subscription=true \
--ak effort=high \
--ak source=git+https://github.com/agentforce314/clawcodex@main \
--jobs-dir eval/harbor/jobs \
--n-concurrent 2Notes:
- Official Terminal-Bench submissions may not modify task timeouts or resources. Keep Harbor's default timeout policy for leaderboard-comparable runs; use timeout multipliers only for explicitly labeled diagnostics.
effort=highmaps toclawcodex --effort high→output_config.efforton effort-capable models (Opus 5, Opus 4.6/4.8, Sonnet 4.6, Fable 5). Requires clawcodex > 1.2.1 in the container — until the next PyPI release, keep the--ak source=git+https://github.com/agentforce314/clawcodex@mainabove.- claude-opus-5 needs a clawcodex that registers it (the model tables
in
src/models/configs.py,src/services/pricing.py, and the three gates insrc/query/query.py). On a build without it, opus-5 falls off the adaptive-thinking allowlist and the request carriesthinking={"type": "enabled", "budget_tokens": …}— removed on Opus 5, so every request 400s — while--effortis silently dropped and the context window is assumed to be 200K instead of 1M (early compaction, the handicap #730 removed for opus-4-8). PyPI 1.2.1 predates this, sosource=is required; verify the branch/SHA you point it at actually carries the registration before starting a full run, and prefer a commit SHA over@mainfor anything you plan to compare later. - Subscription rate limits are shared with your interactive Claude
usage — keep
--n-concurrentlow (2-4) and consider--max-retries 2 --retry-include ApiRateLimitError. - In subscription mode the adapter does NOT forward
ANTHROPIC_API_KEY(inside clawcodex an API key would take precedence and bill the API).
claude_code_subscription.py wraps Harbor's own claude-code agent
(which bootstrap-installs the latest official CLI inside each container)
with the same per-trial subscription-token handling the other adapters
use: every trial starts with ≥30 min of access-token runway (above the
900s task timeouts; a single trial >30 min could still outlive its
token), so multi-hour jobs work without a manually exported token:
PYTHONPATH=$PWD/eval/harbor harbor run \
--dataset terminal-bench/terminal-bench-2-1 \
--agent claude_code_subscription:ClaudeCodeSubscription \
--model anthropic/claude-opus-5 \
--ak reasoning_effort=high \
--jobs-dir eval/harbor/jobs \
--n-concurrent 2Notes:
- Effort uses the parent agent's kwarg name:
--ak reasoning_effort=(low|medium|high|xhigh|max). - The model is forwarded as the
ANTHROPIC_MODELenv var with the provider prefix stripped (harbor/agents/installed/claude_code.py:1372-1386 — so don't expect a--modelon the exec line when debugging), and effort as the--effortCLI flag. Neither is gated on a model table anywhere in the adapter, and the bootstrap installs the latest official CLI in every container, so moving this arm to a new model is the model string alone — no adapter or clawcodex change. - Pin the CLI to a leaderboard row's version with
--ak version=2.1.205(default: latest). A pin predating a model's support will fail on that model, so re-check the pin when changing--model. CLAUDE_FORCE_OAUTHis set by the wrapper, so a hostANTHROPIC_API_KEYcan never silently take over and bill the API.--ak subprocess_env_scrub=trueenables the CLI's subprocess-env scrub — only on images with bubblewrap (modern claude-code hard-fails withoutbwrap; default off matches stock harbor/leaderboard runs).
openclaude_agent.py runs the old TypeScript implementation at
<repo>/typescript through the same harness, for apples-to-apples
comparisons with clawcodex. It uploads the host-built bundle into each
container plus an npm install of its 7 unbundled runtime externals
(native sharp/ripgrep binaries must match the container platform; the
ripgrep postinstall downloads from GitHub, so containers need egress).
Build the bundle once first:
cd typescript && bun run build # produces dist/cli.mjsThen (same subscription + effort semantics as the clawcodex adapter;
--provider anthropic is always pinned because the any-LLM fork would
otherwise auto-route to whatever provider credentials it detects):
PYTHONPATH=$PWD/eval/harbor harbor run \
--dataset terminal-bench/terminal-bench-2-1 \
--agent openclaude_agent:OpenClaude \
--model anthropic/claude-opus-4-8 \
--ak subscription=true \
--ak effort=high \
--jobs-dir eval/harbor/jobs \
--n-concurrent 2Notes:
typescript/is gitignored and absent in worktrees — run from the main checkout, or pointOPENCLAUDE_DIST/--ak dist=at a builtcli.mjs.- Subscription auth is env-only (
CLAUDE_CODE_OAUTH_TOKENaccess token, no refresh token, no credential file in the container); the same 30-min-runway host refresh as the clawcodex adapter applies. - This openclaude snapshot (0.24.0) knows claude-opus-4-8 but not
claude-opus-5, and on opus-5 it does not merely degrade — it breaks.
modelSupportsAdaptiveThinking(typescript/src/utils/thinking.ts:159) allowlists only opus-4-8/4-7/4-6 and sonnet-4-6, then excludes anything else matchingopus, so opus-5 falls to thebudget_tokensbranch attypescript/src/services/api/claude.ts:1731— andbudget_tokensis removed on Opus 5, i.e. HTTP 400 on every request (the same failure the clawcodex adapter'ssource=note describes). Model metadata is missing too, so the context window falls back to the 200K default (typescript/src/utils/context.ts:17). Keep this arm onanthropic/claude-opus-4-8untiltypescript/is refreshed to a snapshot carrying opus-5. - Its effort ladder is low|medium|high|xhigh|max as of 0.24.0.
PYTHONPATH=$PWD/eval/harbor harbor run \
--dataset terminal-bench@2.0 \
--agent clawcodex_agent:Clawcodex \
--model deepseek/deepseek-v4-flash \
--jobs-dir eval/harbor/jobs \
--n-concurrent 4Results land in eval/harbor/jobs/<job-name>/ (result.json has the
aggregate accuracy; each trial dir has the agent's stream-json log under
agent/clawcodex.txt plus session JSONLs under agent/sessions/).
# A subset of tasks (repeatable glob filter) — good for smoke tests
-i fix-git -i openssl-selfsigned-cert # terminal-bench@2.0
-i 'terminal-bench/fix-git' # hub datasets (2.1)
# First N tasks only
--n-tasks 5
# Retry provider blips
--max-retries 2 --retry-include ApiRateLimitError
# Agent kwargs
--ak max_turns=100 # clawcodex --max-turns (default 300)
--ak effort=high # clawcodex --effort (low|medium|high|xhigh|max)
# xhigh is model-dependent (opus-5/opus-4-8 yes,
# sonnet-4-6/opus-4-6 no → degraded to high)
--ak version=1.2.1 # pin the clawcodex-cli PyPI version
--ak source=git+https://github.com/agentforce314/clawcodex@main
# install from git instead of PyPI (unreleased code)
--ak source=dist/clawcodex_cli-1.4.0-py3-none-any.whl
# a local wheel: uploaded into each container and
# installed there, so a working tree can be
# benchmarked without pushing (uv build --wheel)
--ak subscription=true # Claude Pro/Max OAuth instead of ANTHROPIC_API_KEY
# Pass the key explicitly instead of exporting it
--ae DEEPSEEK_API_KEY="${DEEPSEEK_API_KEY}"
# Other models/providers (Harbor convention: provider/model)
--model deepseek/deepseek-v4-pro
--model anthropic/claude-opus-4-8 # the previous tb2.1 baseline
--model anthropic/claude-opus-4-5 # needs ANTHROPIC_API_KEYprefix_cache_probe.py answers "how many tokens is each request re-sending?",
which is the number that actually moves cost on DeepSeek. Aggregate hit rate
hides the failure mode: a harness can sit at 90% while re-billing the same
multi-thousand-token block every single turn.
# 1. Record — wraps any clawcodex invocation, capturing every wire payload
python eval/harbor/prefix_cache_probe.py record --out /tmp/pl -- \
--print --dangerously-skip-permissions \
--model deepseek-v4-flash --provider deepseek -- "your task"
# 2. Analyse — diff consecutive requests, attribute the misses
python eval/harbor/prefix_cache_probe.py analyse --out /tmp/planalyse prints, per consecutive pair, the longest common message prefix and
the bytes that had to be recomputed, next to the provider's own
cached_tokens. A healthy session diverges only at the append point. Anything
re-sent every turn (the DeepSeek REQUEST-scope tail) shows up immediately.
Reference points, terminal-bench 2.1 on deepseek-v4-flash: Reasonix 98.24% hit / ~1,295 miss tokens per request; clawcodex ~1,600-3,400 after the tail split (~6,764 before it).
- The model name uses Harbor's
provider/modelform; the adapter splits it into clawcodex's--provider/--modelflags. - Task containers run as root; the adapter sets
IS_SANDBOX=1, which is clawcodex's sanctioned way to allow--dangerously-skip-permissionsunder root inside sandboxes (same pattern Harbor uses for Claude Code). - The adapter bootstraps
uv+ a managed CPython 3.13 in each container, so task images need no preinstalled Python. harbor view eval/harbor/jobs/<job-name>serves a local results browser (the trajectory pane stays empty — this adapter doesn't emit ATIF; readagent/clawcodex.txtfor the full stream-json trajectory).