{"slug": "harness-engineering-101", "title": "Harness Engineering 101", "summary": "Software engineering is entering a new era where code generation by AI agents requires a new discipline called harness engineering. The gap between an agent saying 'done' and actual completion erodes trust, and engineers must shift from writing code to supervising the systems that write code. The key to advancing through stages of AI-assisted development is investing in preparation, execution, and review loops to build trust.", "body_md": "[← Back](/)\n\nJuly 7, 2026 · 23 min read\n\n# Harness Engineering 101\n\n## “Done.”\n\nYou handed the agent a big task, watched it disappear for an hour, and it came back with the most dangerous word in modern software engineering: Done.\n\n“*Done, you can now verify it’s running.*”, “*Done: the form now matches the reference.*”, “*Done. The PR now contains only the production fix.*”\n\nI pulled those from my own session transcripts, and you have identical ones in yours.\n\nIt is not done. Either it built something that isn’t what you asked for, or what it built doesn’t actually work. And now you are digging through code you didn’t write, spending more time verifying “done” work than the work took to produce.\n\nThat gap between “done” and done has a name: trust. You didn’t trust the agent, and you were right not to. Every hour you spend re-checking its output is an hour paid to that missing trust.\n\nTrust also decides how far you can climb, because code generation moves in stages, and each stage renames your job. Pre-AI, you wrote code. With AI-assisted generation, the Copilot and Cursor era, you review code. With human-supervised generation, the stage we are in now, you review features. And in the end state, what I call the software factory, where a Linear ticket turns into written, tested, verified code without anyone prompting anything, you review the system itself.\n\nThe stages compound, but they also gate. Switching from Copilot to Codex does not move you up a stage. Each level opens only once you have earned enough trust in the one below. Today, most of the industry sits mid-transition between the second and third stage. Only a handful of companies (OpenAI, Anthropic, Cursor, maybe a few more) are genuinely at the frontier, and even they are figuring it out by failing. The rest of us, Delphi where I work included, should have the modesty to admit we are students.\n\nAnd we are students of a new job. Now that everyone can code through an agent, code itself is cheap. If your value is translating product requirements into prompts, you are a translator, and translation is exactly what the software factory automates away. The durable job is building and supervising the system that writes the code. That job is harness engineering, and this post is the playbook for it.\n\n## The loop\n\nThe whole playbook fits in one loop, the same loop you already follow as an engineer:\n\n- Prepare\n- Do\n- Review\n\nThe difference between you and OpenAI is that they automated the loop and you still run it by hand. Each stage on the timeline is this same loop with more of it automated. Climbing to the next stage means handing the next piece of it to the system.\n\nEvery practice below invests in one of these three steps or protects an investment already made, and each one buys a specific piece of trust. Skip them, and no gate opens. Pay them, and the loop runs with less and less of you in it.\n\n## Prepare\n\nPrepare covers everything the agent will find when a session starts, and what it finds is all it knows. [Anthropic describes](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) coding agents perfectly:\n\n“\n\nImagine a software project staffed by engineers working in shifts, where each new engineer arrives with no memory of what happened on the previous shift.”\n\nThat is your agent. Every session starts blank. If something is not written down or enforced, it does not exist.\n\nYou already know how to handle capable people with zero context, because every company onboards new hires. Every hard question about “*codebases for AI*” answers itself when you swap “*AI*” for “*a new engineer.*” A great codebase for agents is a great codebase for humans. The whole Prepare step is that same onboarding, applied to agents.\n\n### Document the product\n\nA new engineer’s first need is to understand what the project is, who it serves, how to launch it, and what to expect once it runs. An agent’s first need is the same. Write that overview and keep it short.\n\nSkip documentation about the code. The code is the one thing the agent can already read. Well-named functions and types explain the how, and prose about implementation rots the moment the implementation changes. Save the writing for what the product does and why it exists.\n\nTo test it, land in the repository, read the README file, and get the app running locally, all without opening the source. If a fresh agent can do that, your documentation is finished.\n\n### Turn the rules into a map\n\nThe overview gives a new engineer the product. Rules give them your standards, what separates a good function from a bad one, and a good strategy from a bad one. Between humans, culture does this job. You tell a colleague “this could be cleaner,” they remember, and the team converges. An agent has no team memory to converge into. So you write the rules down.\n\nThe naive version is one big rules file at the repository root, a `AGENTS.md`\n\nor `CLAUDE.md`\n\n, listing every do and don’t. It fails predictably. The file bloats the context until the agent follows none of it, and humans can’t maintain it either. You end up with stale rules, missing rules, and no structure.\n\nWhat you need instead is a map. The root `AGENTS.md`\n\nstays short, carries one section per topic, and points to where the depth lives, so the agent reads only what the task requires. Here is a layout inspired by our own repository at Delphi:\n\n```\nREADME.md            the product overview\nAGENTS.md            the map: one short section per topic, pointing to depth\nCLAUDE.md            symlink to AGENTS.md, for Claude\nSTRATEGY.md          the why: who we serve, what we sell, the constraints\ndocs/\n├── design/\n│   ├── index.md\n│   ├── colors.md\n│   ├── logos.md\n│   ├── ux.md\n│   └── ...\n├── architecture/\n│   ├── index.md\n│   ├── boundaries.md\n│   └── ...\n├── testing/\n│   ├── index.md\n│   ├── agent-browser.md\n│   ├── browser-use.md\n│   └── ...\n└── ...\n```\n\nThe root `AGENTS.md`\n\nworks as a table of contents. The design principles live in `docs/design/`\n\n, the testing guides in `docs/testing/`\n\n, and finding them is the agent’s job. An agent asked to change a color opens `colors.md`\n\nand nothing else. Its context stays small, and everything in it is relevant to the task.\n\nHarnesses also let you package know-how into skills, how-to guides loaded on demand, a browser runbook, a database playbook. Skills obey the same economics. The body costs nothing until a task needs it, while the description is the expensive part, sitting permanently in the agent’s context and taxing every task. Keep the description short. Say what the skill contains and when to load it.\n\nExample:\n\n“PostgreSQL best practices, query optimization, connection troubleshooting, and performance improvement. Load when working with Postgres databases.”\n\nThe agent knows when to open it, and everything else lives inside. Keep the always-loaded surface tiny, and trust the agent to fetch depth when the task calls for it.\n\n### Rules must be gradable\n\nA rule only works if the agent can check its work against it. “Make it pretty” gives it nothing to check, so it falls back on the average of every interface it has ever seen and ships that.\n\nDesign is the extreme example, since nothing sounds more subjective than taste. Sit down with your designer and translate that taste into written principles for logos, colors, pages, and UX. A principle like “every screen has one primary action, and only that action uses the accent color” is gradable. An agent can count the accented elements on a screen and know whether it complied, while nothing in “keep the interface clean” can be counted.\n\nThe translation changes the question the agent answers. “Is this beautiful?” invites taste the agent does not have. “Does this follow the principles?” has a yes or a no. Apply the same test to every rule you write, design or not. If the agent cannot grade its own work against it, rewrite it until it can.\n\n### Enforce, don’t ask\n\nThere are two ways to make an agent respect a rule. You ask, or you enforce. Asking means writing markdown and hoping the agent follows it. Enforcing means a pre-commit hook that makes the wrong thing impossible. Moving a single rule from asked to enforced does more for your harness than a page of new guidelines.\n\nTake a rule of mine as an example. I ban runtime `typeof`\n\nin TypeScript, because if your data is parsed and your types are strong, you should never need to guess a type at runtime. The rule is written in the rules file. The agent ignores it anyway. No matter how hard you phrase it, some session eventually reaches for `typeof`\n\nto do a dirty string-or-array check.\n\nA static lint in the pre-commit hook fixes it. The agent still writes `typeof`\n\n, but the commit fails. I make the failure message more verbose than a normal lint error. It states that `typeof`\n\nis banned and why it is a bad signal. It reminds the agent that under “[parse, don’t validate](https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate/),” properly parsed input makes `typeof`\n\nunnecessary in the first place.\n\nAnd the agent corrects itself. It reads the failure, admits the mistake, rewrites the check into a small Zod boundary parser, and commits fresh:\n\nNo human intervened at any point.\n\nEnforcement gives you two more things:\n\n- An enforced rule holds one hundred percent of the time, mechanically, which is trust you never spend attention on again.\n- Anything enforced can be deleted from the rules file, freeing context budget for the rules that cannot be automated.\n\nTreat your error messages as prompts, and write them for the agent that actually reads them.\n\n### Enforce the architecture too\n\nThe same enforcement works one level up, on the architecture itself. An agent cannot keep your architecture in its head across sessions, and no human reviewer will spot architectural drift across hundreds of agent pull requests. So write the architecture down as invariants, rules a machine can check, like which layer may depend on which, and enforce them exactly like the `typeof`\n\nban.\n\nOpenAI’s [agent-built repo](https://openai.com/index/harness-engineering/) shows the full version. Every business domain is a fixed set of layers, and dependencies only point forward: Types → Config → Repo → Service → Runtime → UI. Forward-only is what bounds the blast radius. A change in one layer can only ripple toward the UI, never backward into the foundations, so an agent edit stays predictable no matter which session makes it. Cross-cutting concerns (auth, telemetry, feature flags, connectors) enter through a single Providers interface. Everything else is disallowed by custom linters, generated code, and structured tests.\n\n### One set of rules, every harness\n\nRules also have to physically reach every agent. Engineers on one team run different harnesses. Cursor and Codex read `.agents`\n\nand `AGENTS.md`\n\n, while Claude reads `.claude`\n\nand `CLAUDE.md`\n\n. Maintain those copies by hand and they silently drift. One half of the team’s agents keep following rules the other half’s agents no longer see, and the codebase splits into two cultures, which is what the rules existed to prevent. Write everything once and symlink the rest:\n\n```\nln -s ~/{project}/.agents/skills ~/{project}/.claude/skills\n```\n\n### Centralize the context\n\nThe codebase is only half the onboarding. Before anyone writes code for a ticket, someone has to collect its context. The requirements sit in a Slack thread, the acceptance criteria in the Linear ticket, the spec in Notion, and the metrics in a dashboard. A human spends real time walking through all four.\n\nWith an agent, there are two ways to handle that. You can tell it to check every source on every task, and all that checking costs time, which is far more expensive than tokens. Or you can hope it finds what matters on its own, and it will regularly miss a source and build the wrong thing without noticing.\n\nYou need to centralize the context ahead of time. I personally make everything converge on Linear. A ticket created from Slack carries the thread with it, or at least the URLs back to it. Notion docs are linked inside the ticket. The agent starts from the ticket, follows the links it finds there, and writes back what it learns, so the next session starts richer. Any tool can play this role, as long as every task has one place where its full context lives.\n\n### Force a plan\n\nWith the context in one place, the plan is the last thing to prepare.\n\nFor a small task, plan mode is enough. Describe the intention in `/plan`\n\nand go. For a large one, maintain a `task.md`\n\nholding the goal and its context, a table of contents, the full to-do list, the decision log (what was done and why that way), and the live status of every item. Anthropic’s harnesses work the same way, with a progress file the agent updates at the end of every session.\n\nThe task file is the workflow’s save point. If the session dies or the context gets cleared, the next agent reads `task.md`\n\nand resumes without re-exploring anything.\n\nThat is the trust Prepare buys. The product, the standards, and the ticket no longer need you to explain them, because every session onboards itself.\n\n## Do\n\nThe one rule of the Do step is to iterate over one-shotting. The hour-long disappearance that ends in “Done” is what one-shotting looks like from the outside. And the bigger the tasks agents can take, the worse the failure gets, because the pile of unverified work grows with the capability.\n\nSplit the work instead. Chunk the task, have the agent verify each chunk before starting the next, and make every verified chunk a commit.\n\nThe commits are load-bearing:\n\n- Every commit fires the pre-commit hooks, so your linters keep the work green at every step.\n- The history becomes the documentation. Code shouldn’t carry docs but commits can. They record what was done, in what order, and where the work stands, and any session, human or agent, can resume from them.\n\n### Clear, don’t compact\n\nEven with commits as checkpoints, a long session eventually fills its context window, and the harness offers two ways forward, compact the conversation into a summary or clear it entirely. I first saw the answer in an [Anthropic post](https://www.anthropic.com/engineering/harness-design-long-running-apps) and have followed it since. Don’t compact it, clear it.\n\nA fresh session is the agent at its best. Anthropic wrote Claude’s system prompt, OpenAI wrote Codex’s, and every word was tuned for peak performance. As a session grows, you drift away from that tuned state, and compaction accelerates the drift by replacing your context with a lossy summary of it.\n\nThink gradient descent. Fresh sessions are at the top of the hill, and each compaction is one step downhill. Enough steps and you are in a dead valley the model cannot climb out of. You have felt this in the session that gets dumber with every compaction until it is unrecoverable.\n\nClearing the context puts you back at the top of the hill, and it costs almost nothing when the work is checkpointed. The task file holds the state, the commits hold the history, and the next session reads both and resumes as if nothing happened.\n\nThat is the trust Do buys. You stop digging through a dead session to learn where the work stands. The commits and the task file already hold the answer.\n\n## Review\n\nAn agent that iterates and commits still produces work someone has to judge. The goal is to spend as little of your own attention on that judgment as possible. Push the checking onto boundaries, other agents, and telemetry, and drop the checks that were never worth your attention.\n\n### Wrap liberties in boundaries\n\nThe checking starts with boundaries. An agent arrives with full freedom, plus every skill and tool you handed it along the way. Boundaries wrap those liberties, and they come in two strengths.\n\nSoft boundaries are guidance. Think `AGENTS.md`\n\ninstructions, a browser it can drive, logs and metrics and traces it can consult, unit tests it can run. The agent decides when to use them.\n\nHard boundaries cannot be routed around. They are the linters, the strong types, parse-don’t-validate, the end-to-end tests, the build. They double as the agent’s own final review, because the agent runs into them before you ever see the code.\n\nPrepare rested on one fact about agents, that whatever is not written down or enforced does not exist, and nothing in a codebase satisfies both halves like strong types do. The `typeof`\n\nban only worked because strong types made the runtime guess unnecessary. Tests are written down and enforced too, but they only cover the paths someone thought to write, while the compiler re-checks every line on every build, so its coverage grows with the agent’s output. Run the types end to end, and autogenerate them where you can, the repo types from the database schema and the client types from the OpenAPI spec. An agent changing a field does not know what else uses it. With the types running end to end, the build errors point to every place that breaks.\n\nMigrate standards from soft to hard over time, into the types when they can carry the standard and into a lint when they cannot. Each standard you harden frees the attention you spent checking it, and the trust question disappears with it. The machine now guarantees what you previously had to trust the agent on.\n\n### Review with a different agent\n\nGuidelines that cannot be mechanized need a reviewer, and the reviewer must not be the author. An agent that just wrote code has that code in its context and is biased toward approving it, for the same reason you don’t approve your own pull requests. A fresh agent brings a fresh context and a single goal, to check the guidelines.\n\nFrom my internal benchmarks, Codex is the strictest reviewer and flags deviations explicitly, while Claude models are more lenient and under-flag deviations. At Delphi we run Cursor’s Bugbot on pull requests, and on any big PR it always finds something. Obviously some findings are noise but most of the time they are relevant.\n\nReviews on pull requests also work as monitoring, because if your harness genuinely improves, your reviewer should find less over time. Plot the average number of review comments per PR and you can watch what each new rule bought you.\n\nMost review comments should disappear that way, as rules absorb them. One keeps coming back, the complaint that the code reads too verbose. We love a dense one-liner and call it pure beauty (until it fails in a way nobody can read 😅), while agents want code deterministic, verbose, and validated everywhere. Side with the agent, because the next reader of the code is another session with no memory. Verbose and validated is what a blank agent can modify without archaeology, and every explicit check is more surface for your hard boundaries to catch mistakes on.\n\n### Give the agent eyes\n\nBoundaries and reviewer agents judge the code at rest. The running app needs judging too, and an agent cannot review what it cannot observe. As a template, take the observability stack [OpenAI describes](https://openai.com/index/harness-engineering/). The app emits logs, metrics, and traces over OTLP, a local Vector instance fans them out to VictoriaLogs, VictoriaMetrics, and VictoriaTraces, and the agent queries all three through the LogQL, PromQL, and TraceQL APIs.\n\nWith that stack in place, the agent can verify its own work instead of waiting for you to do it. It writes the code, launches the app, pulls the telemetry, compares what it observes against what was expected, and adjusts until they match. The telemetry also makes new kinds of instructions actionable. An agent with access to the metrics can take “this user action takes 400 milliseconds, get it under 200” and treat it like any other task.\n\n### An app per agent\n\nNone of that works if the agent cannot launch the app, so that ability is mandatory, locally or in a sandbox. I run each agent in its own git worktree with its own full stack. The dumb obstacle is ports, since fixed ports collide across worktrees and dynamic ones confuse the agent, and a colleague at Delphi solved it with portless, which prints dynamic URLs with no visible port. Each worktree spawns its own stack, and each stack feeds its own loop.\n\n### Ship the video\n\nAfter the stack and the telemetry, the agent can check its own work. You still need something to look at, and every harness can already produce it, because each can drive a browser. Codex drives Chrome through the DevTools Protocol with skills describing how to use it, Claude uses Puppeteer, and Cursor uses CDP as well. Make every pull request ship with a video of the change working.\n\nThis habit pays off as you move toward the software factory. Imagine an agent watching your customer success channel in Slack and opening a pull request for every ticket. Hundreds arrive, far more than anyone can read, and merging them unread is a gamble. With a thirty-second before-and-after video attached to each one, you can verify a change in seconds and open the code only when the video looks wrong.\n\nThat is the trust Review buys. Opening the code becomes the exception, because a boundary, a reviewer agent, or a wrong-looking video now decides when you look.\n\n## Don’t fix bugs, fix the system\n\nSooner or later a boundary fires, or the reviewer agent finds something real. The reflex is to type “no, not like that” into the session, explain why, and move on. Resist that reflex and ask why the bad code happened instead. Which rule was missing? Which boundary was soft that should have been hard? What could the agent not see? Fix that, and the failure disappears for you, for your coworkers, and for the factory you will eventually automate, because an automated system faithfully reproduces every flaw you leave in it.\n\nThe signals are already lying around:\n\nEvery signal you convert is a check the system runs from then on without you.\n\nFixing the system also has a proactive half, because the harness that earns your trust decays like any other system. Documentation drifts away from reality, rules go stale, and legacy code piles up. That cleanup used to be human work, and agents can now do most of it. A doc gardening agent walks the documentation and fixes whatever no longer matches the code. A code gardening agent checks that the rules are still followed, asks whether each violation is intentional, and flags dead rules for deletion.\n\nUpkeep also means keeping the loop fast. Every boundary you added in Review runs on every commit and every pull request, and an agent idling on CI is your factory standing still. Keep CI fast and minimal, let the agent run tests locally instead of waiting for a sandbox to spawn and `pnpm install`\n\nto finish, and once trust is established, pull verification out of the pull request loop and into the agent loop entirely.\n\nThat is the trust these habits protect. The checks you already bought keep holding, and the loop that runs them keeps its speed.\n\n## Toward the software factory\n\nA session you barely need to watch frees you to run a second one beside it. However many agents you can afford to run, the number you can actually supervise is set by how much attention each one still needs from you. My own ceiling today is five parallel sessions. Five full stacks saturate my laptop’s RAM, and when the machine freezes I lose all five and spend more time restarting everything than the parallelism ever saved. Even with unlimited RAM I would stay near five, because each session is a separate task I have to hold in my monkey brain, and five is the most I can switch between while still supervising each one properly. Cloud sessions change nothing for me for the same reason. They remove the hardware limit and leave the attention limit untouched.\n\nThe only way to raise that ceiling is trust. An agent you trust needs less checking, so every point of trust your harness earns turns directly into more agents running at once.\n\nEvery practice in this post takes a check you used to run by hand and builds it into the system, so each agent needs a little less of your attention. My five-session limit becomes six, then ten, and far enough along that curve sit sessions nobody watches, where a ticket turns into verified code on its own. The software factory is that curve followed to its end.\n\nSomewhere along the way, a session ends again with “Done.” By then the hooks have already rejected the shortcuts, a fresh agent has already checked the guidelines against the diff, the telemetry already matches what the ticket asked for, and a thirty-second video shows the change working. You read one word, watch one clip, and merge.\n\nThe blocker has always been trust, and trust is built lint by lint.\n\nYour job now is to build the system that writes the code. Start with the boring parts.\n\nIf you are building your own harness, I want to hear about it. Find me on [GitHub](https://github.com/Thytu), [X](https://x.com/VDMatos), or by [email](mailto:vltn.dematos@gmail.com).\n\n## Sources\n\nThe research behind this post draws heavily on these:", "url": "https://wpnews.pro/news/harness-engineering-101", "canonical_source": "https://thytu.com/posts/harness-engineering/", "published_at": "2026-07-07 16:16:31+00:00", "updated_at": "2026-07-07 16:30:27.495427+00:00", "lang": "en", "topics": ["ai-agents", "ai-tools", "developer-tools", "ai-safety", "ai-research"], "entities": ["OpenAI", "Anthropic", "Cursor", "Delphi", "Copilot", "Codex", "Linear"], "alternates": {"html": "https://wpnews.pro/news/harness-engineering-101", "markdown": "https://wpnews.pro/news/harness-engineering-101.md", "text": "https://wpnews.pro/news/harness-engineering-101.txt", "jsonld": "https://wpnews.pro/news/harness-engineering-101.jsonld"}}