When an AI agent fails to complete a task, upgrading the model can seem like the obvious next step. Yet an agent’s performance also depends on how it receives instructions, uses tools, and manages its work. Understanding those factors can reveal opportunities to improve reliability with the model already in place.
Google Research’s Harness² examines that surrounding setup and its role in agent performance. I installed and ran the project to explore the idea firsthand. In this article, I look at the research, the implementation, and what developers can learn from it when building their own agents.
A harness shapes how an AI agent behaves through its prompts, tools, memory, and control loop. But different harnesses excel at different tasks. Claude Code performs better on coding and spreadsheets, while Codex leads on web tasks.
The takeaway: A fixed harness leaves performance on the table.
Harness² aims to close that gap by adapting at test time even for tasks its designers never anticipated.
Letting an agent rewrite its own system prompt sounds promising. In practice, it can make performance worse.
The problem? Every added instruction comes at a cost.
System prompts are processed repeatedly throughout a task. As they grow, they consume more tokens, leaving less room for the actual work.
And when the token budget runs out, the agent may fail before finishing.
Harness² exposes eight components, most of which stay dormant until something triggers them. That is the point: they cost nothing when unused, unlike prompt text.
| Group | Components | What it controls |
|---|---|---|
| Context | System prompt, guardrails, memory, skills | What the model sees before acting |
| Tool interface | Tool guidance, scripts | How tools are described and invoked |
| Delegation | Subagents | What work gets handed off to a clean context |
| Middleware | Plugins | Behaviour inside the tool loop |
Why it works: Unlike prompt instructions, these safeguards are enforced by code not just the model’s willingness to follow them.
The hard problem is knowing whether an edit helped. On a benchmark you have an answer key. On a real task you do not.
| Stage | What happens |
|---|---|
| Propose | The agent runs the task once. Two separate editors read the execution log: one looks at workflow mechanics such as broken tool calls, the other acts as a domain specialist and proposes subject-matter rules. |
| Probe | The edited harness runs on the same task, producing a second attempt to compare against the first. |
| Compose | The two runs are compared. An edit is kept only if it fixed a concrete, observable failure from the first run. Everything else is discarded. |
The signal comes from differences between executions, not from a score against a known answer. That is what lets it run on tasks it has never seen.
The probe step is not ceremony. Without it, performance dropped by about 10%.
Letting the agent edit its own harness and simply trusting the edit was tested, and it was worse than not editing at all.
Tested on three professional benchmarks across four model-harness configurations, using GLM-5 and Gemini 3.5 Flash. A single step improved on the base harness in every configuration tried.

| Result | Figure |
|---|---|
| WorkBuddy-Bench | up to 11.5 points |
| JobBench | up to 4.5 points |
| LAB, partial pass rate | up to 11.8 percentage points |
| Five candidates probed in parallel | a further 2.9 to 3.9 points on WorkBuddy-Bench |
| Legal redlining and budget reconciliation | 35 to 50 points in a single step |
| Harness evolved on a strong model, handed to a smaller one | 17 points, immediately |
| ReasoningBank, a memory baseline, on coding | 6.6 points BELOW the base harness |
Linux, Python 3.12, uv. This is what I ran.
Commands
git clone --depth 1 https://github.com/google-research/harness2.git
cd harness2
uv venv --python 3.12 .venv
. .venv/bin/activate
uv pip install -e '.[render]'
Output:
lxml, openpyxl, pillow, python-pptx, typing-extensions, xlsxwriter and dependencies.harness2, harness2-demo, harness2-setup, harness2-check, harness2-judge, harness2-report, harness2-bridge.No API keys, no cloud account. It runs both recursion modes with deterministic editors.
Command
harness2-demo --output runs/demo --k 2
Output:

The demo completing both accounting tasks.
[accounting] 1/2 ok ledger-1
[accounting] 2/2 ok ledger-2
Local demo: 2 tasks completed; 0 failed. runs/demo/demo.json
| Measurement | Value |
|---|---|
| Tasks | 2 completed, 0 failed |
| Executions | 28 |
| Editor calls | 32 |
| Files written | 2,360 |
| Actual content | 265 KB |
| Disk used | varies by filesystem |
A 2-task run writes 2,360 files holding about 265 KB between them. How much disk that actually occupies depends on the filesystem: ext4 gave 13 MB because each tiny file claims a 4 KB block, while APFS on macOS packed the same run into 0.3 MB. Worth knowing before a benchmark run with hundreds of tasks.

Measured from the run. Parallel keeps explicit baselines; sequential does not.
| Mode | Variants per task | Names |
|---|---|---|
| Parallel | 8 | base1, base2, inv1, inv2, dom1, dom2, agg1, agg2 |
| Sequential | 6 | inv1, inv2, dom1, dom2, c1, c2 |
8 and 6 variants across 2 tasks each gives 16 plus 12, which is the 28 the summary reported. The names map onto the algorithm: inv for invariant edits, dom for domain-specific edits, agg or c for compositions.
The research puts the cost at 4 to 20 full agent runs per task. My run landed at 6 to 8 with k=2, which sits at the bottom of that range.
This is the finding I have not seen stated anywhere else, and it matters if you plan to evaluate the project by running the demo.

Measured byte counts from the demo run.
The demo proves the plumbing works. It does not demonstrate the method.
The recursion loop runs, probes fire, compositions are produced, the library is written and experience is recorded. But the deterministic editors never generate real content, so no harness is meaningfully rewritten. To see that you need model credentials and a real benchmark.
Each task writes an EXPERIENCE.md, and its headings show what the second loop keeps:
# ledger-2
## Why this was chosen
## What each edit did
## Transferable lesson
That third heading is the whole point of harnessing-level recursion. My run produced 16 of these across modes and tasks.
Run directories are large and deeply nested. This pulls out the numbers that matter. Not part of the repository.
inspect_run.py
"""
Inspect a harness2 run directory. Written for this article, not part of the repo.
Usage: python inspect_run.py runs/demo
"""
import json
import pathlib
import sys
import collections
root = pathlib.Path(
sys.argv[1] if len(sys.argv) > 1 else "runs/demo"
)
# --- headline numbers from the run summary -------------------------
summary = root / "demo.json"
if summary.exists():
d = json.loads(summary.read_text())
print("RUN SUMMARY")
for k in (
"domain",
"n_tasks",
"done",
"failed",
"execution_count",
"editor_calls"
):
if k in d:
print(f" {k:<16} {d[k]}")
print()
# --- how many artefacts did a run actually produce? ----------------
files = [
p for p in root.rglob("*")
if p.is_file()
]
total_mb = sum(
p.stat().st_size for p in files
) / 1024**2
print(
f"ARTEFACTS: {len(files)} files, {total_mb:.1f} MB\n"
)
# --- execution variants, grouped by mode ---------------------------
print("EXECUTIONS PER TASK")
for mode_dir in sorted((root / "executions").glob("*")):
tasks = sorted(
p for p in mode_dir.glob("*")
if p.is_dir()
)
if not tasks:
continue
variants = sorted(
p.name for p in tasks[0].glob("*")
if p.is_dir()
)
print(
f" {mode_dir.name:<16} "
f"{len(variants)} variants: {', '.join(variants)}"
)
print()
# --- which harness components actually got content? ----------------
print("HARNESS COMPONENTS (bytes written)")
sizes = collections.defaultdict(set)
for p in root.rglob("harness/**/*"):
if p.is_file():
key = (
str(p.relative_to(p.parents[1]))
if p.parent.name != "harness"
else p.name
)
sizes[key].add(p.stat().st_size)
for name, vals in sorted(sizes.items()):
v = sorted(vals)
flag = " <- EMPTY" if v == [0] else ""
size_display = (
v[0]
if len(v) == 1
else f"{v[0]}-{v[-1]}"
)
print(
f" {name:<34} {size_display:>8}{flag}"
)
print()
# --- the harnessing-level memory -----------------------------------
exp = list(root.rglob("EXPERIENCE.md"))
if exp:
print(
f"EXPERIENCE.md x{len(exp)}, section headings:"
)
for line in exp[0].read_text().splitlines():
if line.startswith("#"):
print(" ", line)
Output:

One gotcha worth knowing. Re-running the same output directory skips tasks that already finished, which is the intended resume behaviour, but it also rewrites demo.json with execution_count and editor_calls at zero. The artefacts from the first run survive, so the variant counts and component sizes above are still accurate. Point –output at a fresh directory if you want the counts.
RUN SUMMARY domain accounting n_tasks 2 done 2 failed 0 execution_count 28 editor_calls 32 EXECUTIONS PER TASK demo-oc-agg2 8 variants: agg1, agg2, base1, base2, dom1, dom2, inv1, inv2 demo-oc-seq2 6 variants: c1, c2, dom1, dom2, inv1, inv2 HARNESS COMPONENTS (bytes written) LongTermMEMORY.md 0 <- EMPTY guardrails.md 0 <- EMPTY plugin/README.md 154 scripts/README.md 111 scripts/summarize.py 243 skills/README.md 175 sub_agents/README.md 177 systemprompt.md 56 tool_descriptions/tool_guidance.md 0 <- EMPTY
Model-backed runs need Node.js 20+, the Google Cloud CLI and Vertex AI credentials. LAB also needs Pandoc and Poppler; WorkBuddy needs Docker.
Commands
cp .env.example .env # add your Vertex AI project and model IDs
gcloud auth application-default login
harness2-setup lab
harness2-check --bench lab
harness2 --bench lab --k 1 --mode parallel \
--only-domains antitrust-competition --max-tasks 1
harness2-judge --bench lab --k 1 --mode parallel
harness2-report --bench lab --k 1 --mode parallel
--dry-run to preview task selection without spending anything.--max-tasks 1. Each task fires multiple executions.--substrate cx for the Codex executor instead of the default OpenCode.Harness² is a working implementation of a clear argument. Keep the weights fixed, optimise the scaffolding, and use contrasts between runs instead of ground-truth labels to decide what to keep.
The research behind it is more interesting than the headline numbers. Prompt-stuffing made things worse. Skipping validation made things worse. A competing memory baseline made coding worse. Those negative results are what make the positive ones credible.
The install is clean and the demo runs in seconds without credentials, which makes it cheap to verify the machinery. Just do not mistake that demo for the method: the components it fills in are stubs, and the part the paper is about only shows up once a model is wired in.
Read more: How to Use Jev AI: A Hands-On Guide to TypeSafe’s System One Model
A. Excessive system prompts consume valuable token budgets and increase processing costs, which can cause the agent to fail before completing its assigned tasks.
A. It uses a Propose, Probe, and Compose cycle, keeping only those edits that demonstrate a concrete, observable improvement between two execution attempts.
A. Yes, research shows that a harness evolved using an expensive, high-capacity model can successfully improve performance when applied to a cheaper, smaller model.