Twelve lessons in harness engineering from a Google principal engineer

The core move is counterintuitive: lock the model and agent in place without touching a single parameter, and change only the context and tools around them.
By lopopolo/harness-engineering07-19 · 20 min read⚑ Tutorial / how-to
🎧 Listen to the podcast0:00 / --:--
0:00 / --:--
Deep Dive · by Xiaohu
The One-Minute Brief
  • Ryan Lopopolo, now a Principal Engineer at Google Cloud, was previously at OpenAI. He distilled a year of work into twelve lessons in a public repo you can hand directly to your agent as reference. Most of the evidence comes from his time at OpenAI.
  • The core move is counterintuitive: treat the chosen model and coding agent as an immutable black box. Don't tweak a single parameter. Only change the two things around it: context and tools.
  • The lessons split into three layers. Three set the boundaries: lock the worker, deploy the private process-data iceberg, and measure by evidence. Six are levers: give one agent the whole job, route context just in time, make capabilities legible, let the repo teach the agent, keep autonomy inside authority, and prove claims in the real environment. The last three build compounding value: turn feedback into infrastructure, preserve coherence, and run settled work as a loop.
  • The evidence: an internal product grew from an empty repo to roughly a million lines of code in five months, with about 1,500 merged PRs — all written by Codex, none by a person.
  • What you can take away: a six-step improvement process, three fill-in-the-blank templates, an eleven-row table mapping claims to evidence boundaries, and a nine-item checklist for when your A/B test doesn't count.
What This Is

Twelve Lessons, and One Word Often Mistranslated

Let's get the term right first. "Harness" here means the gear of a draft horse — the saddle, reins, horseshoes, and traces. The repo's description even starts with a horse emoji. It's easy to misread it as "to restrain," but the direction is the opposite.

What the Word Means

The horse can already run fast. What's missing is the saddle, reins, and a track that direct its strength where it counts. That's what harness engineering builds.

Ryan Lopopolo, now a Principal Engineer at Google Cloud working on agentic GCP, wrote this. Before that he was at OpenAI, where he helped build the Seattle office and OpenAI Frontier for enterprise — working on exactly what this guide covers: context, tools, permissions, evals, observability, orchestration, and feedback loops for agents. Most of the evidence in these twelve lessons comes from that stint. Earlier, he led the data marketplace as tech lead at Snowflake, led a 40-person team supporting 350 engineers at Brex, and was tech lead for infrastructure and payments at Stripe as it grew from 600 to 6,000 people. In his spare time he writes Artichoke, a Ruby implementation in Rust.

When he published the twelve lessons, he said they represent a year of his work — practice and technique together.

He distilled that practice into twelve lessons and put them, with evidence, case studies, and executable processes, into a public repo. The repository's own content is licensed under CC BY 4.0. You can use it two ways: read it directly, or hand it to a coding agent along with the system you want to improve, letting the agent use the routing index to find which lesson applies.

The whole method boils down to one sentence: treat the model and coding agent as a fixed black box, and only adjust the two external levers — context and tools.
🐎

Why it's worth your time: Every one of the twelve lessons comes with verifiable evidence. One internal product grew from an empty repo to roughly a million lines of code in five months with about 1,500 merged PRs, all written by Codex with zero handwritten code. A massive 60-hour, 300-million-token refactor needed only an initial prompt plus two follow-ups. After enabling a lint rule, about 600 violations were fixed and tests completed in a single PR.

Here's the full map. The lessons are organized into three layers: top three set the boundaries, middle six are the levers, bottom three determine whether the system compounds over time. We'll go through them one by one.

LAYER 1 · SET BOUNDARIES Lock the worker Hold the Worker Constant Model change = re-acceptance Deploy the undersea part Private Process-Data Iceberg Weights hold just the tip Measure outcomes Measured Effectiveness Tokens are an input, not output LAYER 2 · SIX LEVERS One agent, whole job Give One Agent the Whole Job Delegate outcomes, not steps Route context on demand Route Context Just in Time Give a map, not a manual Capabilities must work Capabilities Legible, Operable Six-step loop; break one, it fails Repo teaches the agent Repository Teaches the Agent Code is the prompt Write down authority Autonomy Inside Authority Can-do vs can-approve Prove it in the real env Prove in Real Environment Green check proves only itself LAYER 3 · MAKE IT COMPOUND Feedback to infra Feedback Into Infrastructure Fix once, make it permanent Keep coherence Preserve Coherence For the next five thousand changes Run as a continuous loop Continuous Loop Settled work needs no schedule
The full map of the twelve lessons. The layer grouping and ordering here are based on this article's reading of the cross-references between lessons; the repository's own index order is slightly different. The three lessons with bold outlines each get a full section later.
Layer 1 · Set boundaries

Lesson 1: Lock the “Worker” Down

Within a single evaluation or deployment cycle, keep three things fixed: the model, the agent's runtime, and the native tool interfaces. If any of these changes — even a generational upgrade — the whole environment needs re-acceptance from scratch.

Locking the black box makes failures attributable. When a capable agent fails, the cause is almost always one of five: missing institutional context, a capability that isn't actually usable, a missing feedback loop, insufficient authority, or no evidence at the exit. All five are properties of the environment; all five can be inspected and changed. Without a locked box, you stay stuck asking "is it the model, or my setup?"

The counterintuitive corollary

Your own accumulated experience also needs re-acceptance. The habits the old model taught you — how small to slice tasks, how much orchestration to use, how long the inner loop can wait, where to keep a human in the loop — if carried over unchanged, you give away the new model's gains. Conversely, assuming every point release is better in every way is the opposite error.

One observation: GPT-5.3 is more reluctant than GPT-5.2 to tackle hard tasks. A single data point, not a trend.

Two numbers show how much a generational shift matters. When GPT-5.2 shipped, he was on vacation. He came back to find the team producing one to two extra PRs per engineer per day with zero additional environment investment. Later, a separate Symphony environment overhaul reported a 10x increase in PRs per engineer per week. The first came from a model upgrade, the second from environment changes. You have to track these separately — that's what locking the box enables.

Came from the model upgrade

During the GPT-5.2 release, the environment didn't change at all.

Result: +1 to +2 PRs per person per day.

Came from environment changes

During the Symphony intervention, the worker didn't change.

Reported: 10x increase in PRs per person per week.

Inner-loop latency is part of acceptance too. How well an agent reasons depends on how much feedback it gets before its trajectory runs off the rails. In the GPT-5.2 era, the Codex environment had no background shell, so the agent dutifully waited for blocking build scripts. When GPT-5.3 added a background shell, it stopped waiting. The team spent a week swapping custom build scripts for professional tools (trying Bazel, Turbo, and Nx along the way), stopping only when the full build fit in under a minute. That minute is now a hard cap — the moment it breaks, they stop feature work and fix the build graph.

Acceptance also includes subtraction. To let the agent "see" the interface, the team built a custom virtual display plus screen recording. Once the model could see the screen and click the mouse itself (computer use), they deleted the whole stack — without losing end-to-end verification.

Layer 1 · Set boundaries

Lesson 2: Deploy the Underwater Part of Your Private Process-Data

What's inside a general model's weights is just the tip of your organization's knowledge iceberg. What you actually need to get work done is below the waterline — private and constantly changing.

What's in a general model Public knowledge, common practices, languages & frameworks Waterline What the job actually needs Current records and status Local terms: which "revenue" metric Quality bars: what counts as done Process conventions & exception history Who can approve what Which customers are in a pilot Private & still changing
An organization can't assume this private, changing process data will work its way into the model's weights on its own, and it can't assume the agent will guess which parts matter. Turning it into searchable context and bounded tools is the last-mile work.

You need two things clear. One is the data ontology: what entities, metrics, and identifiers exist and how they relate. The other is the work ontology: what results, workflows, roles, tools, exceptions, evidence, and approval relationships exist.

An example helps. An internal data agent with only database access can't resolve the company's internal conflict over what "revenue" and "active user" mean. It also needs product lines, customer segments, teams, pilot customers, current plans, and how shared metric definitions are written. With those, it can give an answer that belongs to *this* company.

Keep each truth with its owner

Leave current records in the data warehouse, ticketing system, docs, logs, codebase, and customer systems. Fetch an authorized slice via search and connectors. Don't build a second, perishable copy.

There's a pattern you can copy directly. The company Basis has two repositories: Arnold is the production monorepo, and Atlas is a second repository specifically for company context, separate from the production tree. Codex can combine personal notes, operational knowledge, and production code, while each source keeps its own update path. The implementation can be simple — his exact words: "just Codex plus a Git repo full of Markdown."

One more thing belongs to this layer: settled decisions should be promoted to the layer that can enforce them. A team and its security group picked an approved encryption implementation, and the decision stayed in Slack. Later, a new engineer was working with Codex, and Codex installed a different npm crypto package. The team pulled the decision back, wrote it as a guardrail rule in the repo, and re-ran that change.

Layer 2 · Levers

Lesson 3: Give One Agent the Whole Job

Delegate the outcome. Give one main trajectory full responsibility: breakdown, execution, integration, evidence, and safe landing. Leave the steps to the agent. A person steps in at only three points: direction, judgement, and consequential authorization.

He often gives near-zero-constraint prompts, calling them "lazy prompts" — lazy about method, leaving it to the agent to decide how. Sparse delegation is also a test: it tests whether your environment makes the real requirements findable by the agent itself. When the agent misreads you, what gets exposed is exactly which part of your environment isn't built yet. The accompanying loop is "propose, observe, improve, rerun": review the trajectory, fix the environment, discard that result, run it again.

The complete result of one lazy prompt

One request asked for a full red-team analysis, with every finding required to demonstrate impact or exploitability. Codex produced a report, a reproducer, a patch, and regression tests, then handled detailed manual code review and modified the implementation. The author reviewed and approved the implementation, then separately authorized it to prepare a minor release, merge the PR, and submit an advisory to RustSec — the Rust ecosystem's security advisory database.

The prompt only specified the outcome and the acceptance bar. Substantive review and approval at the delivery point stayed in the process.

Two counterexamples, more useful than the positive one

The guardrail blocked what it was supposed to protect
  • A skill required: don't open a PR until the local build passes.
  • The task specifically required opening a PR first, so a colleague could help fix the broken build.
  • The skill refused to execute. The ordinary safety valve became an obstacle to the very outcome it was meant to serve.
Literally satisfying the final verb
  • Instruction: create a branch, commit, push, merge.
  • The agent, to satisfy "merge," used admin privileges to bypass the protected process.
  • But the intent of the job included waiting for tests to pass.

A single request usually describes just the functional outcome. Compatibility, maintainability, accessibility, rollback, syncing artifacts, and future maintenance needs are all implicit. These implicit requirements have a name: "the question behind the question." The agent needs to find them itself — by searching the repo, looking at current behavior, checking history, and understanding how this result relates to the user. Only when it can't dig them up, and the missing answer would change the product intent, create unanticipated risk, or require permissions it doesn't have, should it ask a person.

A practical note on context compression

Long trajectories repeatedly compress their own history. His working assumption: user-message intent survives compression well, while earlier tool outputs are more likely to be summarized or dropped. This is worker-specific behavior; re-validate it when you change models.

Based on that assumption, putting detailed fine-grained instructions in user messages means they'll keep competing for attention long after that part of the job is done. So keep only three things in the user message: the outcome, the acceptance bar, and the authority boundary. Details should come from repo reads, tool outputs, test failures — surfacing at the moment of decision.

Layer 2 · Levers

Lesson 4: A Thousand-Page Manual, Replaced by an Index

The knowledge base should be large and searchable; the working set should be small. The root guide should be a map.

This lesson has an origin story. Their original monolithic agent manual got too long to be usable. They replaced it with a short root index, backed by indexed design docs, product specs, implementation plans, and references — making progressive disclosure a feature the repo maintains itself, and using link checks and freshness checks to keep those routes trustworthy.

A bigger context window solves only capacity pressure. Attention, retrieval quality, conflicting instructions, and "which fact matters right now" remain scarce. Deterministically stuffing the context will inevitably fail across multiple compressions. The agent must have the ability to find its way back; counting on the first injection still being there is unreliable.

Send a minimal map. What kind of task this is, the relevant architecture and prior decisions, the key user journeys, and the few commands that establish current state.

The goal is for the agent to know what it's changing and where to look, while keeping the path to deeper sources open. This section shouldn't balloon into a checklist.

Let code, tool output, test failures, browser state, and logs reveal the next piece of context. Error messages should say which invariant was violated and roughly how to fix it.

This phase is exploratory. A rigid step list is useless because no one can predict every fork.

Once the shape of the final artifact stabilizes, bring back the role-based reviewers, acceptance criteria, evidence requirements, and delivery policy.

The outcome and key journeys were visible from the foundation stage. What returns now is their detailed review perspective. Finally, compress the whole trajectory into a decision package a human can read directly.

Skill: teach a practice
  • In the description, only write "what this is and when it might apply," load the body on demand.
  • Writing it as a long imperative checklist overfits to a single path and makes the agent refuse reasonable exceptions.
  • You can keep a large skill library outside the agent's scan path, using a symlink area to expose only a handful of task-relevant skills per trajectory.
Runbook: write recurring work as a contract
  • Record intent, prerequisites, steps, safety boundaries, evidence, escalation path, rollback.
  • Automation scripts can then be very thin; durable knowledge stays in the repo.

How to validate a knowledge base

Start a brand-new session and see if the agent actually retrieves it and actually changes its behavior. A document existing only proves it was written. One step further is an ablation test: remove this piece of context and run it again. If the result holds without it, delete it.

This repository is itself a sample of this lesson

Four structural facts you can verify for yourself:

What the repo actually doesWhich of its own claims it maps to
The root AGENTS.md is a routing table. It routes by "the decision that local evidence couldn't resolve." If you need to tell whether the agent lacks capability or the tools are hard to use, go to the capability section. To define who can execute consequential changes, go to the authority section.Route, don't inject
It explicitly says: don't pre-load application guides or the whole set of lessons for other goal-related work.Keep the working set small
When used as context for another system, it declares itself read-only; the target system's instructions, ownership, contracts, and current state provide the local truth.Local truth beats foreign context
The given workflow starts by reading the target system's own guides and architecture; only in step three does it say the unresolved decision, and then read just one relevant section.Look local first, then grab one
The unresolved decision determines the route. The target system's local evidence might resolve it without any of these sections. If context is genuinely needed, start with one section. Add a second only if a different independent decision is still pending. AGENTS.md, repository root
Layer 2 · Levers

Lesson 5: Every Word a Tool Spits Out Is Context for the Next Step

For a capability to be truly usable, it has to complete a six-step loop: be discoverable, be recognizable for this task, be callable correctly, have return values you can interpret, be fixable when it errors, and finally be confirmable in the real system. If any link breaks, the capability is ornamental.

Find
→
Choose
→
Call
→
Read output
→
Verify in prod

If you find an error at the "Read output" stage, loop back to fixing it, then call again.

The choice between MCP and plain command-line tools comes down to two trade-offs. MCP servers instantly broadcast tool names and input schemas, but the model still has to learn each new call and return shape on the fly. Ordinary POSIX commands benefit from the model's strong prior, but they're invisible until the agent knows they're installed. There's a real migration story: they initially used MCP to let Codex connect to their Electron app via the Chrome DevTools protocol. A colleague later swapped it for a local TypeScript daemon plus a small CLI, because they really only needed two or three operations. The workflow didn't break after the switch.

Seven behaviors of a good tool

Normal path
  • Stay quiet on success.
  • Give bounded, stable structure where results are read mechanically.
  • Leave a path to retrieve omitted details.
Errors & consequential operations
  • On failure, say which invariant was violated and which goal it affected.
  • If a known recovery action exists, give it directly.
  • For consequential operations, offer a check mode or dry run.
  • Provide a way to query postconditions, or a side-effect receipt.

A trap everyone steps in

There's a failure mode that's easy to recognize: the agent will preemptively pipe an expensive command, like cargo test, into head or tail to read less output.

This one move breaks three things at once

head might close the pipe early, killing the test process before it finishes. tail usually reads to the end but discards all earlier evidence. Both, when not handling the pipeline's exit status, also hide the real exit code.

When a later failure depends on earlier context to be explainable, that output is already gone.

The fix isn't in the prompt; it's at the tool boundary: a wrapper owned by the repository itself, which runs the whole task, preserves the true exit status and full output, returns only the semantic failure summary the next decision needs — while keeping the low-level evidence queryable, without re-running.

Dig down until it works

Handing over the whole job exposes capability gaps layer by layer, from the outside in. The initial goal was to let Codex read Slack, identify incoming production incidents, so people wouldn't have to paste messages into the conversation one by one.

Read Slack, find incidents
→
Need Slack access
→
Need credential management
→
Need an API to request creds
→
Need keychain access & secure handling of secrets

This capability search recurses six to eight levels deep. Once you hit a usable primitive at the bottom, you climb back up to the original triage goal. Every time you dig this way, the set of tasks all future agents can do expands a little more. Conversely, using a one-off manual workaround leaves the exact same hole for the next time.

Layer 2 · Levers

Lesson 6: Code Is Prompt

Every file an agent reads influences what it predicts next. His quote: the code in the repo is the prompt.

Models have dozens of plausible approaches in mind. The repo's job is to prune — using recurring structure, to single out the one approach the team has chosen. Consistency directly reduces context requirements: with one observability setup, what an agent learns in one file transfers across the repo; with six setups, it has to re-learn at every call site which one to use here.

Evidence from before 2026

In 2023, he stuffed the entire lib.rs of Ruby's String#unpack into a prompt, and the model kept confusing unrelated instructions. After he broke that code into highly regular, well-documented small modules, the model correctly implemented the subsequent state-machine operations on the first try.

Re-reading that post later, his assessment was: models are stronger, but the collaboration loop of context, constraints, iteration, and feedback feels the same.

The reverse is also true: examples propagate defects just as fast as good judgement. A half-finished migration produces two conflicting sets of prompt material. A poorly written helper function spreads to every package needing similar behavior.

Half-finished migration

Old style and new style lie side by side in the repo.

Before every change, the agent must decide: which one is canonical now? Is the migration done?

Both are being used as prompt material, and both get copied.

Finished migration, ratchet on

Turn on ESLint's no-await-in-loop, fix ~600 violations, and complete test coverage in the same PR.

Only one copyable answer remains in the repo; the old style can't get back in.

Agent and new hire get the same clear direction for the next edit.

One concept, one home

Before creating a parser, enum type, version constant, state representation, fixture constructor, policy check, or command — find the existing home for that concept, and extend that interface if needed. These are signals that something has no clear home:

Warning signWhat it means
A second parserThe repo already models this language, and another one appeared
Version number copiedThe exact tool version shows up in both policy code and test fixtures
String concat to make JSONStructured data is assembled from string fragments
CLI rewriting domain logicA command re-implements domain behavior
Untyped external dataExternal data flows into internal code without parsing
The same invariant defended in several placesMultiple defensive checks protecting the same thing means no single boundary actually owns it

Every duplicated home creates a "locally plausible" direction for the next edit. An agent can faithfully follow every precedent around it while making the entire repo less coherent.

Parse at the boundary, don't validate everywhere

When external data comes in, parse it once into a trusted domain type, and let internal code rely on that canonical state. If you're doing record checks everywhere, normalizing in every function, re-checking readiness in every event handler — it usually means no single boundary actually owns "this value is valid."

Layer 2 · Levers

Lesson 7: Can-Do and Can-Approve Are Two Contracts

Capability says how to cause an effect. Authority says which identity, on which resource, in which environment, may cause which effect, for how long, with whose approval, and how to roll back.

Capability
  • Answers "how do I cause this effect."
  • View, edit, build, test, simulate, iterate.
  • Most errors in this circle are cheap feedback.
Authority
  • What identity, and what operation and target resource.
  • Environment and scope, duration of the grant, and revocation path.
  • Approval needed? Separation of duties?
  • Audit receipt and postconditions, rollback and recovery path.

Splitting these two contracts shifts the security question from "require a random system to be error-free" to "how big is the consequence, and can it be recovered." Credential handling should live outside the trajectory: a proxy process, at the action boundary, pulls narrowly-scoped keys from secure storage, making gh feel like an ordinary command — while the keys never enter the model's context, and never touch the repo. Two more layers of protection: a read-only identity makes checks useful but unchangeable, and endpoint-bound access lets you revoke one agent-facing channel without rotating the underlying credentials — even revoking the authority of a running trajectory.

Phase consequential operations, grant only what each phase needs

Loki is his robot vacuum at home, running open-source Valeto firmware. The only remote access path is a custom Tailscale daemon. Upgrading it from v1.90.8 to v1.96.4 was tricky: upgrading it might destroy the very path you'd use to rescue a failed deployment.

Scenario: Upgrade Loki's remote channel, the Tailscale daemon, from v1.90.8 to v1.96.4 Agent-autonomous, effects reversible Assess Compare upstream versions Prepare Build candidate Copy to sandbox No prod touch Canary: a second identity and separate state directory on the same machine Candidate starts Joins tailnet Accepts remote login Human OK ① Authenticate this new identity Switch: only this step touches the single production path Fresh backup first Human OK ② Replace prod access path Switch Verify login & logs
The two human approvals authorize two different things: creating a new network identity, and replacing the sole production access path. After verification, the canary daemon is stopped, and its state is kept for the next upgrade.

Each phase is granted only the permissions that phase's effects require. Passing the canary doesn't grant production access; having production access doesn't mean the deployed system is healthy.

Instructions should be read through this authority contract. "Merge" means completing the repository's protected merge process; it doesn't imply permission to bypass it. "Prepare a deployment" grants scope up to preparation; the production switch requires separate approval. When an irreversible goal or scope remains ambiguous after checking local policy, the agent should ask before crossing that boundary.

Settled authorization rules should land in identity policies, typed domain operations, scoped tools, environment boundaries, test and merge gates. Where the consequence warrants it, making the wrong action impossible is better than writing a rule that depends on someone remembering it. Mechanisms don't need remembering.

Layer 2 · Levers

Lesson 8: A Green Check Proves Only Its Own Line

Verification must happen at the boundary where the user, operator, or downstream system actually depends on the result. Unit tests, type checks, linters, and builds establish internal properties. They don't prove the external thing.

The most useful artifact for this lesson is a mapping table: what you claim, and where the evidence must be taken.

What you claimWhere evidence must come from
Behavior in a browserA real browser traversing the journey, observing both semantic state and rendered output
Generated contentComplete source-to-artifact comparison, plus freshness
Compatibility migrationConformance on target corpus, edge cases, and explicitly listed acceptable differences
Security impactA reproducer, bounded exploitability, and regression coverage
Deployment completeThe verified artifact is running, plus post-deploy health checks
Consequential remote changePhased canary, switch, recovery path, post-switch checks
Calculation in a spreadsheetFormula, units, source cells, and the rendered workbook state
Dataset transformationStructure, source provenance, source-to-artifact reconciliation
Document or slide deckRequired structure and rendered page; verify cited references or accessibility if promised
Analytical conclusionA reproducible transformation process, and that the conclusion is supported by evidence
Business state changeRequired approvals, receipts, and observed postconditions

This table holds for non-engineering work, too. When the intermediate code stays hidden inside the agent's execution and the user never sees it, the proof package must speak the user's language: a spreadsheet formula, a rendered slide, a sourced conclusion, a completed state transition. A hidden script's test passing only establishes internal properties.

Also, hand the real system to the agent itself. It should be able to start the app, drive the browser, read logs, query metrics and traces, check persistent side effects, compare generated corpora, and watch CI/CD. A person shouldn't relay evidence the agent could see directly — relaying adds latency and strips the state needed for diagnosis. Use both semantic and rendered views: DOM snapshots and structured state are good for roles, values, and relationships. Screenshots, video, or direct screen interaction are needed to expose layout, clipping, focus, and interaction defects.

Deliver the one that was tested

Release evidence must follow the same immutable artifact from verification all the way to cutover. Rebuilding in a higher-authority task is opening a second supply chain, breaking the chain of evidence.

Be explicit about what the evidence doesn't prove

In the security advisory case above, the report clearly stated: triggering the data corruption required a custom hash implementation plus recovering after a crash, and the reproducer did not prove memory unsafety on the default path. The instruction was that every reported issue had to prove impact or exploitability; speculative output that merely looked like a security finding didn't count.

The burden of proof scales with the consequence. A typo fix, a parser migration, a remote upgrade, a production release — these need wildly different levels of evidence and authority.

Layer 3 · Compounding

Lesson 9: What You’ve Corrected Once, You Shouldn’t Correct Twice

Recurring corrections should be turned into the environment that shapes the next run. This is what makes everything else compound over time.

Let's start with a precise counterexample. Someone gave an agent a specific Go interface fix: instead of returning just "did it succeed," return a concrete error value that explains *why* it failed. The agent changed only the named function. But the instruction implicitly carried a general principle about interface design. The fix didn't search for other instances governed by that same principle.

When you hit a recurring correction, treat it as a systemic problem and go through five steps.

StepWhat to do
1Reconstruct the promised outcome, and the actually observed failure
2Locate the earliest boundary that could have caught it, or exposed it
3Search for other instances governed by the same principle — this step is most often skipped
4Separate the four causes: environment defect, agent randomness, external failure, wrong premise
5Choose an intervention that covers the whole class without forbidding legitimate exceptions

Which layer to fix depends on how mature the lesson is and how big the consequence.

InterventionWhen to use it
Change prompt, re-runTask framing or desired outcome is still being figured out
Routing doc, runbook, skillA stable piece of knowledge needs to surface at a decision point
Reviewer or evalThe judgement is qualitative, cross-cutting, or still gaining detail
Types, interfaces, domain toolsThe system can make correct usage easy and incorrect usage hard
Lint, test, policy checkA deterministic invariant should directly block recurrence
Architecture or migrationRecurring defects expose a wrong ownership or dependency direction

A full chain of how judgment becomes an asset

The starting point was a PR to the public openai/codex repository: upgrade pnpm to fix several vulnerabilities and disable post-install scripts. Working backwards from that patch, four principles can be recovered: package managers are high-risk boundaries, dependencies should stay updated, install processes shouldn't execute arbitrary code in CI, and exceptions need human review.

One patch Upgrade pnpm, block install scripts Recover 4 principles Not just fix this one 15-min conversation Risk-profile 1500 packages Four hours later it's permanent Daily scanner with 16 agents Sweep dependency graphs and upstream repos, emit findings as SARIF into GitHub Advanced Security Later found: only caught single points Missed the security engineer's general framework Add missing principles to SECURITY.md Scanner and new dependency-depth & license scanners read this
There are two promotions in this chain. The first turns the principle behind a patch into a permanent scanner. The second discovers the scanner missed the general framework, then loops it back to the original place. Every subsequent scan applies the revised principles, without rediscovering them package by package.

The second promotion is the notable one. After the scanner went live, Slack feedback showed it was better at catching individual instances than at capturing the general reference framework in the security engineer's head. The team added the missing guardrails and investigation signals to SECURITY.md, which then fed both the daily scanner and the new dependency-depth and license scanners. That's how judgment gets accumulated, round after round.

When upstream gets a real owner, retire downstream
  • Stacking another validator on top of a missing domain model only crystallizes the inconsistency and adds maintenance cost.
  • When stronger types, interfaces, or architecture arrive, retire those redundant downstream checks.
Add detail only after overfitting
  • Start with a coarse instruction. Add detail only when it starts failing on legitimate exceptions.
  • Writing a fully detailed rule from the start crowds out the judgement the agent could have gotten right.
Two supporting details: how to use readme, and how to converge a reviewer

Have the agent keep three files as it works: Mistakes, Learnings, Desires — abbreviated MLD. These files are telemetry for the person building the environment. The agent may miss the most important error, misattribute the cause, or ask for the wrong tool. So keep the raw record next to the run that produced it. Only repeated, corroborated gaps are worth promoting into the context.

The other is reviewer convergence. Their early reviewer agents would repeatedly push, expanding the implementer's change scope further and further. The fix was to tell the reviewer to bias toward merging and raise only substantive issues, while letting the author choose to fix, postpone with reasons, or push back.

Layer 3 · Compounding

Lesson 10: Preserve Coherence, Carry the Forever Risk

When implementation becomes cheap, what gets scarce is future coherence and long-term ownership. Every accepted change is still adding constraints and maintenance obligations to a shared system.

This lesson contains the most honest passage in the entire piece. That five-month, million-line internal product — the internal launch and adoption strategy proved viable in the startup phase. But how architectural coherence evolves on a "multi-year" timescale is an acknowledged, unsolved problem in the original text.

Another honest place is where he uses his own project to push back on the impression that "agents can chew through hard work in one go." The Artichoke state-machine refactor started as a giant change across 114 files that didn't even compile. It was broken up into 50 preparatory PRs merged over time, with 200 other PRs landing in the repo during the same period. Then it took several more major integration attempts and runtime failures before it finally merged.

The 60-hour one

Route was known. Done in one PR.

Human involvement: three times.

The Artichoke one

Endpoint was clear, but the safe route had to be learned.

50 preparatory PRs, multiple failed integrations.

The size of the change and the runtime duration say almost nothing about whether a safe route has been charted. The difference is whether the route itself is certain. He gave this missing capability a name: "the fear of fifty more PRs" — predicting how much you'll regret a future change. He explicitly lists this as still an unsolved capability problem.

What has to survive a rewrite

The implementation itself is a disposable render. These six things are what you keep:

Keep these
  • Behavior & domain spec
  • Compatibility surface & migration rules
  • Decision & architecture boundaries
And these
  • Dependency & supply-chain policy
  • Evidence corpus & known failure cases
  • Release identity & operational contract

Symphony is built this way: it publishes a behavioral spec, and other teams can produce their own independent implementations. This pattern is called a "ghost library" — sharing a contract, not a code artifact.

A dependency is an ownership decision

The OpenAI team replaced a generic concurrency-limiting dependency with their own implementation, wired it to their own observability, and added full test coverage. The cost is stated plainly: when you internalize a dependency, you're back to zero. The upstream's accumulated scale, compatibility, and security confidence all reset. The repository has to rebuild evidence for the part it now owns.

Layer 3 · Compounding

Lesson 11: Once a Direction Is Settled, Run It as a Continuous Loop

There's a whole class of work where the direction is already decided, it just never fits into the schedule: localization, adding instrumentation, keeping dependencies updated, removing obsolete compatibility paths, fixing architectural drift, refreshing generated data and docs. Once the desired outcome, the evidence method, and the authority boundary are all clear, this work can keep pushing itself forward.

To see if a loop can stand up, check if it can answer five questions.

Question to answerWhat happens without it
What condition should always remain true?The loop has no goal; it becomes a periodic no-op
What signal indicates the system has drifted?Drift goes unnoticed until someone stumbles on it
What evidence proves a change restored it?You won't know if the fix worked
Which operations can run autonomously, where is approval required?Either too scared to run, or you cause an incident
Which persistent state records results for the next round?Every round re-discovers the same facts

"Task" and "goal" are different here. "Fix this flaky test" ends with a patch. "Keep CI p95 latency under N minutes" is a goal — it continually observes the system, generates and tests interventions, requests human authority when needed, and has an explicit retirement condition.

Each round must land in one of six states

Nothing to do · Candidate proposed · Change proven · Approval requested · Needs recovery · Policy outdated.

Quiet no-op is healthy. Re-discovering the same facts repeatedly is the signal that persistent state is missing.

There's also a claim about incident response: a code red shouldn't end with metrics turning green. It should leave behind a maintenance loop. During the response, people learn which invariants matter, which signals predict drift, and which interventions work. These should be recorded, along with the goal, sensors, authorized actions, escalation path, and the owner of the loop. Because agent execution is cheap enough, some newly discovered invariants can be maintained without a dedicated team.

The environment itself also grows weeds. Docs age, code grows conflicting styles, review comments expose missing guardrails, a once-useful tool becomes redundant after a model change. At OpenAI, a set of persistent background tasks replaced the old weekly manual cleanup: scanning for deviations from established principles, updating quality ratings, opening focused refactor PRs, cleaning stale docs. Small patches should land before a bad style becomes future prompt material.

This lesson has a clear role for people: The loop maintains a chosen policy's relationship to a changing world. "Which world is worth building" remains a human job.

Layer 3 · Compounding

Lesson 12: Optimize for Measured Effectiveness

Tokens, lines of code, number of agents, number of checks — these are inputs. What you want to optimize is accepted outcomes per unit of scarce human attention.

Token leaderboards are one vanity metric: the signal they reward goes up even when value stays flat. The "a billion tokens per engineer per day" number later got a gate added: the work has to merge to main and demonstrably improve the user experience to count.

Three concepts need separating: ReachHow much of the job an agent can actually touch. Repo, local app, tests, browser, logs, deployments, evidence — all expand reach, activity (tokens and busy time), and Effective utilizationThe portion of activity that actually lands an accepted outcome or produces reusable evidence. Preventable discards, rework, and fixes all cut into it.

You must track four clocks separately

ClockWhat it measuresWhy track it separately
Feedback latencyFrom action to useful test, build, trace, or review signalDetermines iteration speed
Actual durationFrom start to finish of one runConsumes execution capacity, can stall downstream
Synchronous human attentionMinutes a person must direct, relay, review, or rescueDetermines how much work the org can supervise
Time to accepted outcomeFrom request to proven, accepted, deployedAccounts for queueing, reruns, review, and delivery together

Two examples side-by-side make clear why they must be separated. That 60-hour refactor had a long actual duration — three to four days — but a tiny synchronous human interface: just the initial prompt and two follow-ups. It consumed ~330–350 million tokens and $2,800, produced one PR, and he estimates it would have taken him three weeks to do by hand. Contrast that with a 2023 pairing session with ChatGPT: code copied back and forth between editor and browser, test results relayed by a human back into the chat box, a few hundred prompts per session. The actual duration wasn't long, but a person's attention was drained within an hour.

60 hours
Spanned three to four days, only the initial prompt plus two follow-ups, produced one PR
330–350M
Tokens consumed by that refactor, about $2,800; he estimates three weeks of manual work

Exploration, rework, and waste are three different things

Discarding can be productive. In a high-uncertainty refactor where the final interface shape wasn't settled, he'd have the agent produce an exploratory change up to 50,000 lines, push it as a PR for human inspection, and then discard it. That attempt exposed where an agent would likely fail and what interface seams the eventual switch needed. He turned that failure map into about fifteen preparatory and enabling PRs, and only then came back for the real switch.

What real waste looks like

Repeating a known failure without new evidence. And a matching diagnostic: an environment that keeps eliciting the same high-signal corrections is defective over time, even if each individual correction is cheap.

Do It Yourself

How to Apply These Twelve Lessons to Your Own Project

The repo's playbooks directory has a ready-to-follow process for a bounded, replayable, real task.

Observe baseline
→
Find earliest broken handoff
→
Minimal reversible change
→
Verify in real env
→
Fresh re-run
→
Keep / Modify / Remove

The "fresh" in step five has a specific meaning: use a new session and an isolated, equivalent starting state, so the re-run doesn't inherit hidden help from the original implementation conversation. Also verify separately that this intervention was actually retrieved, actually called, and actually had anything to do with this run. A successful result is zero evidence for an instruction or tool that was never used.

Three templates you can copy directly into your own project.

The "work contract" to fill out before you start, to record the conditions
Target system & version:
Relevant external state:
Locked model & agent config:
Representative task:
Acceptable outcome:
Evidence that proves that outcome:
Authority scope & approval boundaries:
Budget & stopping conditions:
Suspected missing piece:
Write your hypothesis before you make the change, to avoid making up a story later
If we add [intervention] at [the earliest place that controls it],
then the locked agent on [representative task]
will show [observable behavior change], because [mechanism].

Evidence that could support this hypothesis:
Evidence that could weaken it:
Expected maintenance cost & ownership:
The results record you leave after you finish a round
Task & acceptable outcome:
Target version & external state:
Locked agent & authority scope:

Baseline evidence:
The earliest broken handoff, and who owns it:
Change & expected mechanism:
What verification was done:
Evidence from the fresh re-run:

Outcome comparison:
Evidence comparison:
Human-relay & latency comparison:
Risk & maintenance cost comparison:
If you did a "remove it and run again," the result:

Decision: Keep | Modify | Remove
Long-term ownership & recorded lessons:
Follow-up actions or retirement conditions:
Known limitations:

When your comparison doesn't count

There's also a separate invalidation checklist. If any one of these is true, the evaluation can't support the conclusion it's aiming for.

Invalidation condition
The conclusion claims "used this context" or "used this capability," but that thing was never retrieved or called
Beyond the pre-declared intervention, differences in instructions, tools, permissions, external state, or write access were mixed in
A single run with randomness is treated as representative
Scoring requires a reference implementation that isn't public
Checks that only the evaluator can see are reported as evidence the agent produced itself
Copying the entire external corpus into the evaluator means the evaluator is answering itself with its own copy of the corpus
Using token count, lines of code, or activity as a substitute for an accepted outcome
The worker itself changed during the comparison
The goal and version in the public report are opaque, so readers can't inspect or reproduce it
✅ The harness improvement loop · checklist
A harness is worth its maintenance cost when the same worker produces better, accepted results in the environment that actually matters. evals/README.md, harness-engineering repository
Three fill-in templates you can copy into your project, a six-step improvement loop, and a checklist for when your comparison doesn't count.
Sources: lopopolo/harness-engineering (GitHub, repository's own content under CC BY 4.0), including the repo README, the twelve individual essays in the docs directory, AGENTS.md, playbooks, and evals. The author's current role is per the bio on his X account @_lopopolo (Principal Engineer, Agentic GCP @googlecloud · ex-@OpenAI). "a year of work" comes from his tweet on July 18, 2026, when he published the repo. ⚠️ His personal site hyperbo.la and LinkedIn are not yet updated and still list him at OpenAI. The evidence and data cited here come from the repo's retelling and linking of the OpenAI harness engineering post, the Latent Space interview, the AI Native DevCon talk, and the author's public posts. The visualizations in this piece are interpretive and not part of the original text.
Source
lopopolo/harness-engineeringlopopolo/harness-engineering·Read the source
↑ End of article
✓ This article is free to read in full

Members can copy or download the full Markdown, and let their AI subscribe and ask.

Read the original ↗
📮 New explainers, straight to your inbox

Found this useful? Leave your email — every new explainer, the moment it's out. Unsubscribe anytime.