Skip to content

Harness² CLI: a general-purpose version of Harness² - #11

Merged
3ae3ae merged 21 commits into
mainfrom
general-cli
Oct 19, 2026
Merged

3ae3ae merged 21 commits into
mainfrom
general-cli

Conversation

@3ae3ae

@3ae3ae 3ae3ae commented Oct 19, 2026 •

Copy link
Copy Markdown
Owner

Summary

This PR turns the fork into Harness² CLI, a general-purpose version of google-research/harness2. Its main use is to refine the harness of the coding agent you already use (Claude Code's CLAUDE.md/.claude/, or Codex's AGENTS.md/.agents/) on tasks from your own project.

  • It runs from any directory.
  • It reaches models through your Claude or ChatGPT subscription, or any model provider.
  • A run ends with one final harness that you review and apply.

The method and its prompts are the paper's; everything around them is new. The work came in through these PRs into general-cli:

PR Change
#1 Improver prompts for Claude Code and command surfaces; Codex scripts move to .agents/scripts
#2 Layered settings (defaults < global < project < profile < env < flags, with origins), provider profiles, suite registry
#3 JobBench / LAB / WorkBuddy moved into the harness2-benchmarks plugin
#4 Provider-aware improver channels, events.jsonl, --budget-usd/--max-calls, resume, relocatable runs
#5 Claude Code and command executors; native harness import/export; system CLIs by default
#6 One harness2 command with subcommands, run names, dry-run call estimates
#7 Built-in tasks suite: git-worktree and files tasks, optional check/rubric grading
#8 Offline end-to-end test of the CLI
#9 init wizard, status --watch, three-way apply, chat, --selector human
#10, #18 Documentation: README, how-it-works, tasks, configuration, cli, CONTRIBUTING, CHANGELOG
#12, #15 The final harness: built at the end of run from every task's adopted harness and lesson; eval compares it with the starting harness
#13 Distribution renamed harness2-cli; CI (Linux/macOS × Python 3.12/3.13 + ruff); runs rm
#14 claude-login / chatgpt-login: subscriptions through the installed CLIs; usage limits stop runs cleanly
#16, #17 Use by AI agents: guide, --json errors, run --detach, stop, no blocking prompts
#19–#21 Fixes from a live run and from the first CI runs
#22 Codex subscription runs use a login home kept for harness2 (harness2 login codex), because Codex runs a personal ~/.codex/hooks.json even with --ignore-user-config; Claude Code's user hooks were verified not to run

Deliberately left out: sandboxing and HTML reports.

Test plan

  • CI is green on Linux and macOS with Python 3.12 and 3.13: 155 core tests, 13 plugin tests and ruff, all offline with fake agent CLIs. The offline tests include an end-to-end CLI test.
  • Live run with Codex on a ChatGPT subscription: one task, k=1.
    • It made exactly the 9 model calls the dry run estimated. A wrapper confirmed that no codex exec call saw an API key.
    • It went through run --detach, status, diff, judge (100%), eval and apply --dry-run.
    • The final harness added a rule that the trajectories justified.
  • Checked against the installed CLIs, with a local capture server and no model calls: personal ~/.claude/CLAUDE.md, user skills and the Codex config.toml are not sent in runs.
  • A wheel installed outside the repo on Python 3.13 runs harness2 demo.
  • Hook isolation, checked with marker-writing hooks and real CLIs, no model calls: Claude Code runs no user hooks with harness2's flags; Codex now runs on its own home.
  • A live run with claude-login was not done; only Codex was tested live.

🤖 Generated with Claude Code

3ae3ae and others added 20 commits October 19, 2026 20:19
Describe the editable surface of the new Claude Code executor (CLAUDE.md,
.claude/skills, .claude/scripts, sub_agents installed as .claude/agents,
.claude/hooks.json merged into .claude/settings.json) and of the generic
command executor, whose surface is a list of path patterns.

Codex harness scripts move to .agents/scripts so they cannot collide with
a project's own scripts/ directory. The shared INV template now takes the
script and skill levers per surface instead of hardcoding scripts/ and
trigger lines.

Co-authored-by: Claude Opus 5.5 <[email protected]>
settings.py resolves configuration from defaults, the global
~/.config/harness2/config.toml, the project's .harness2/config.toml, a
named profile, HARNESS2_<SECTION>__<KEY> environment variables and CLI
overrides, and records where every value came from. It also finds the
project root, loads .harness2/.env, and edits TOML files in place
without dropping comments.

providers.py turns a provider profile (anthropic, openai, vertex,
bedrock) into the environment or config each agent CLI expects, so no
code path assumes Vertex AI any more. Secrets are only ever referenced
through api_key_env.

registry.py finds task suites: the built-in "tasks" suite plus any
installed through the harness2.suites entry-point group.

Co-authored-by: Claude Opus 5.5 <[email protected]>
…#3)

JobBench, LAB and WorkBuddy-Bench now live in their own distribution
under plugins/harness2-benchmarks/ (package harness2_benchmarks) and
register through the harness2.suites entry-point group as the jb, lab,
wb-office, wb-web and wb-code suites. The core package keeps only the
method, the executors and the CLI.

Moved with history: the benchmark adapters and renderers, the
installer, preflight and setup, the vendored runners and patches
(third_party/), the dependency metadata, the benchmark scripts and the
benchmark docs. Downloaded benchmark dependencies now go to the user
cache (~/.cache/harness2/benchmarks, or HARNESS2_BENCH_DEPS) instead of
the repository, and each run's root comes from the suite context.

The plugin keeps its env-driven configuration (HARNESS2_SOLVER_MODEL and
friends) so the paper setup keeps working; runner assets ship as package
data, so a wheel install works outside a checkout.

Co-authored-by: Claude Opus 5.5 <[email protected]>
Improver, selector and reflector sessions now run on the claude, codex
or opencode CLI found on PATH (or a configured binary), each with the
environment its provider profile produces and an isolated config dir,
so neither Vertex AI nor the user's global agent setup is assumed. A
"human" channel lets the CLI answer selections in the terminal, and
the reflector can run on its own channel.

core/events.py appends one JSON line per rollout, session, selection
and task to the run's events.jsonl and enforces --budget-usd and
--max-calls across resumes: work stops before the next call once a
limit is reached, and Ctrl-C stops the same way. core.configure() sets
phase width, transport attempts and session concurrency at run time.

Persisted run-directory paths are stored relative to the run root and
the session cache keys on paths relative to the audit dir, so a run
directory can be moved and still resumes without repeating work.

The benchmarks plugin passes its pinned OpenCode command and Vertex
provider through the new ChannelSpec fields directly.

Co-authored-by: Claude Opus 5.5 <[email protected]>
…#5)

Adds two executors next to Codex and OpenCode:

- claude: runs the Claude Code CLI with an isolated CLAUDE_CONFIG_DIR.
  Its harness is CLAUDE.md, .claude/skills, .claude/scripts, subagents
  installed as .claude/agents/<name>.md and hooks merged into
  .claude/settings.json; stream-json output renders into the shared
  trajectory grammar.
- command: runs any agent through a user-supplied command template; its
  editable surface is a list of glob patterns.

Each executor can now import a harness from a project's native layout,
strip it from a workspace, install a candidate into one, and export an
adopted harness back, touching only the files its surface manages (and
refusing symlinked paths). Codex harness scripts move to .agents/scripts
so they never collide with a project's scripts/ directory.

Executors resolve their CLI from PATH by default (or a configured
binary) instead of pinned downloads, and the bridge serves any provider
profile as a Responses endpoint instead of Vertex only.

Co-authored-by: Claude Opus 5.5 <[email protected]>
Replaces the seven harness2-* scripts with one `harness2` command
(`python -m harness2` works too) whose global options -C, --profile,
-c KEY=VALUE, -q, -v and --json apply to every subcommand: run, doctor,
setup, judge, report, runs, diff, task, config, demo, bridge,
completion, plus init, status, chat and apply (stubs for now). The old
script names stay as aliases.

A run lives in <project>/.harness2/runs/<name>/ and records run.json:
the identity settings, the task stream and the schedule. Resuming with
different identity settings is refused with the differing keys, and
judge, report, status, diff and apply find everything from the run
name (default: the most recent run) instead of repeating flags.

`run --dry-run` prints the task stream and the number of rollouts and
model sessions the run will make; --budget-usd and --max-calls stop a
run cleanly (exit 4) and Ctrl-C stops it for a later resume (exit 130).
judged.json records the judge model it was graded with.

harness2/config.py is gone: its generic parts were replaced by
settings.py, its benchmark parts moved to the plugin.

Co-authored-by: Claude Opus 5.5 <[email protected]>
harness2.suite reads tasks from .harness2/tasks/<id>/ (task.md plus an
optional task.toml, files/, check/ and rubric.md) and runs them on the
claude, codex or command executor:

- project tasks run in a throwaway git worktree at `ref`, with the
  project's own harness and .harness2/ removed and the candidate harness
  installed; the result is the patch the agent made, harness files
  excluded;
- files tasks run in a scratch directory seeded from files/, and the
  result is what the agent saved in output/.

The base harness is imported from the project once per run and kept, so
a resumed run compares like with like. Rollouts are idempotent by run
id and record exit code, time and reported cost.

Grading is optional and never used for refinement: a `check` command is
run against the result in a fresh workspace (exit status, or a JSON
{n_passed, n_criteria} line), or an LLM judge applies rubric.md. Cached
grades are reused only while the check, rubric and judge model are
unchanged. Also adds task listing, scaffolding and JSONL/CSV import,
and the improver prompt profiles for repository and files work.

Co-authored-by: Claude Opus 5.5 <[email protected]>
Drives `harness2` in-process against a temporary git project with a
CLAUDE.md, a skill and a .claude/settings.json, using a fake `claude`
that plays both executor and improver. Covers a project task graded by
a check command and a files task, runs at k=1 and k=2, judge, report,
diff and adoption back into the project (the unrelated settings key and
the skill survive), and a resumed run that makes no new model calls.

Co-authored-by: Claude Opus 5.5 <[email protected]>
- `harness2 init` asks which agent CLI runs the tasks (detecting claude
  and codex on PATH), which models to use and where the harness lives,
  writes a commented .harness2/config.toml, a .gitignore and the tasks
  directory, and says whether the needed API key is visible. --yes
  takes the defaults.
- `harness2 status [--watch]` shows each task's state, the harness it
  picked, rollouts and spend, from run.json and events.jsonl only, so it
  works during a run, after it stopped and from another terminal.
- `harness2 apply` applies only what the run changed relative to the
  harness it started from, so edits made to the project since are kept;
  a file changed on both sides is skipped unless --force. It lists the
  changes first and asks, or reviews file by file with -i, refuses to
  write over uncommitted harness files, and can create a branch.
- `harness2 chat` starts the user's own Claude Code or Codex in the
  project with a guide to harness2 and the project's tasks and runs.
- `harness2 run --selector human` hands the final pick to the person at
  the terminal, showing the anonymised candidates.

Every subcommand now has help text, and run, doctor, config, task and
judge print readable results (doctor also flags a model whose provider
its CLI cannot use). Untracks the bytecode and egg-info that slipped
into the repository and adds a .gitignore.

Co-authored-by: Claude Opus 5.5 <[email protected]>
Rewrites the README around tuning your own agent's harness: what it
does, install with uv/pipx, a quick start from `harness2 init` to
`harness2 apply`, the command list, configuration, other agents through
the command executor, and where the paper's benchmarks went.

Adds docs/how-it-works.md (the method, what a run costs and stores),
docs/tasks.md (task layout, workspaces, deliverables, grading, import),
docs/configuration.md (settings layers, every key, providers per CLI,
profiles, secrets) and docs/cli.md (every command and flag, exit codes,
the run directory). CONTRIBUTING.md now describes this fork's setup,
layout and conventions; the benchmarks plugin gets a README, and its
setup guide no longer points at a missing .env.example or the old
Vertex-only bridge.

Also orders `harness2 --help` the way the commands are used, makes
`task new` say what to do next, and fills in the package metadata.

Co-authored-by: Claude Opus 5.5 <[email protected]>
Harness² adopts one harness per task. `harness2 consolidate` gives the
improver the run's starting harness, every task's adopted harness, its
request and the lesson the run recorded, and asks for one harness for
the project's next tasks; the result is validated against the
executor's surface (one retry with the problems listed) and stored in
<run>/consolidated/<mode>/ with the improver's notes.

`harness2 eval` runs the starting and the consolidated harness on the
run's gradable tasks (optionally repeated), grades both and prints the
per-task and mean pass rates with the difference.

`apply` and `diff` now default to the consolidated harness when there
is one; `--task` still picks a single task's adopted harness.

Co-authored-by: Claude Opus 5.5 <[email protected]>
…#13)

- The distribution is now harness2-cli (the command and the import
  package stay `harness2`); the benchmarks plugin depends on it, and the
  project URLs point at 3ae3ae/harness2-cli with the upstream linked.
- GitHub Actions runs the core and plugin tests on Linux and macOS with
  Python 3.12 and 3.13, plus ruff check and ruff format --check.
- `harness2 init` asks whether the models are reached through the agent
  CLI's own login (a Claude or ChatGPT subscription, the default) or an
  API key, and says whether that CLI is logged in; --auth answers it.
- `harness2 runs rm` deletes a run after asking (--yes to skip), and
  `runs ls` / `runs show` print tables instead of raw values.

Co-authored-by: Claude Opus 5.5 <[email protected]>
…LIs (#14)

Two built-in providers, claude-login and chatgpt-login, use the login
the installed `claude` / `codex` already has, so a Claude or ChatGPT
subscription works without an API key:

- claude-login keeps the user's own config dir (where the login lives)
  and adds --setting-sources project,local, --strict-mcp-config and
  --no-session-persistence, so personal CLAUDE.md, skills, plugins, MCP
  servers and session history stay out of the runs;
- chatgpt-login keeps the user's own CODEX_HOME and adds
  --ignore-user-config and --ignore-rules (doctor notes when a global
  AGENTS.md is still part of every Codex run).

API-key providers keep their fully isolated per-run config dirs. Doctor
checks `claude auth status` / `codex login status` and rejects a login
provider on a CLI that cannot use it.

Hitting a provider's usage limit now stops the run cleanly instead of
recording failed work: the interrupted rollout is discarded and the
same command resumes later. Project worktrees left by killed runs are
pruned before new ones are created.

Co-authored-by: Claude Opus 5.5 <[email protected]>
A run now ends by combining the harnesses it adopted per task, with the
lessons it recorded, into one final harness per recursion mode; that is
what `apply`, `diff` and `eval` use, so there is a single result to
review and apply. The separate `consolidate` command is gone. If the
final step fails, the run's exit code is unchanged and rerunning the
same command retries just that step (every earlier step is reused).

The dry-run estimate counts the extra session, and a run stopped by a
provider's usage limit says to rerun once the limit resets.

Co-authored-by: Claude Opus 5.5 <[email protected]>
`harness2 guide` prints how an AI agent should drive harness2 on a
person's behalf: ask before spending, bound runs with --max-calls, read
--json, answer prompts with flags, never edit runs or harness files
directly, the init/task/doctor/run --detach/status/stop/diff/eval/apply
workflow, where things are stored and what the exit codes mean, followed
by the current project's settings, tasks and runs. `guide --install
claude` installs the same text as a user-level Claude Code skill, which
harness2's own runs never load. `harness2 chat` now builds on it.

`init --json` and `apply --json` print a result object on stdout and
keep their human-readable text on stderr; `apply --json` refuses to ask
for confirmation and needs --yes or --dry-run.

Co-authored-by: Claude Opus 5.5 <[email protected]>
- `harness2 run --detach` starts the same run as a background process,
  logging to <run>/run.log, and returns its name, pid and log path at
  once (an immediate failure is reported with the log's tail).
- A running run records itself in <run>/run.pid; a second start of the
  same run is refused, and `harness2 stop [NAME]` asks it to stop: the
  calls in flight finish, the run exits, and rerunning resumes it. Only
  a detached run's own process group is signalled, never the shell or
  agent that launched a foreground run.
- With --json, a failing command prints {"error", "exit_code"} on
  stdout instead of an error line; `diff --json` returns the changes
  and per-file diffs.
- `apply -i` and `chat` refuse to start without a terminal instead of
  waiting for input, and the agent guide says how to change init's
  choices with `config set`.

Co-authored-by: Claude Opus 5.5 <[email protected]>
* docs: describe Harness² CLI as the general-purpose version of Harness²

The README now presents the fork as Harness² CLI: what it adds to the
original release (a comparison table), the run -> diff -> eval -> apply
flow around one final harness, subscriptions as the default way to
reach models, and how AI agents drive it (--json, --detach, stop,
guide). The configuration guide documents claude-login and
chatgpt-login, what is and is not isolated, and usage limits; the
command reference covers stop, eval, guide, runs rm, --detach, JSON
errors and the new run files; How it works explains the final harness
step that the CLI adds to the paper's method. Adds a changelog.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* feat(status): say whether a run is still going

`harness2 status` (and its --json) now reports whether the run's
process is alive and its pid, and says when a run stopped without
finishing, for example after being killed, so the user knows to resume
it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
A live run with Codex on a ChatGPT subscription (one task, k=1, nine
model calls, all without an API key) went through run, status, diff,
judge, eval and apply. Fixes from reading its output:

- Codex and subscriptions report no money, so run and status say
  "spend not reported" instead of "$0.00 spent", which read as free.
- eval and apply call the run's result the final harness, as the docs
  do, and eval's table is aligned.
- init --yes no longer prints stray blank lines.

Co-authored-by: Claude Opus 5.5 <[email protected]>
The previous commit renamed eval's column to FINAL and aligned the
table, but three assertions in tests/test_consolidate.py still expected
the old text, so the suite failed on general-cli. They now match the
current output, with the column spacing left flexible.

Co-authored-by: Claude Opus 5.5 <[email protected]>
GitHub's Ubuntu runners have no zsh, so the completion test failed
there on /bin/zsh. It now finds bash and zsh on PATH and only checks
the generated script's text for a shell that is missing.

Co-authored-by: Claude Opus 5.5 <[email protected]>
@3ae3ae 3ae3ae changed the title Make harness2 a general-purpose CLI for tuning your own agent's harness Harness² CLI: a general-purpose version of Harness² Oct 19, 2026
…#22)

Codex has no flag to skip a personal ~/.codex/hooks.json or
~/.codex/AGENTS.md: with --ignore-user-config it still runs and reads
them (verified with marker-writing hooks), so every chatgpt-login
rollout and improver session fired the user's own hooks. Claude Code's
flags, by contrast, do keep user hooks and plugins out (verified the
same way).

chatgpt-login now runs Codex on a home kept for harness2,
~/.local/state/harness2/codex-home, which the user signs in to ChatGPT
once with the new `harness2 login codex`; that is a separate login, so
the user's own ~/.codex is never read or changed and its refresh token
is never used. `providers.chatgpt-login.home` points runs at another
home instead, and doctor then lists the personal files every run will
include. `harness2 login` shows whether both subscription logins are
ready, and init and the agent guide point at it.

Co-authored-by: Claude Opus 5.5 <[email protected]>
@3ae3ae
3ae3ae merged commit 1771c8b into main Oct 19, 2026
5 checks passed
@3ae3ae
3ae3ae deleted the general-cli branch October 19, 2026 13:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant