Local checks
Is the change mechanically correct?
- Tests
- Lint
- Types
When a coding agent disappoints is usually to wait for a better model. But the model is only one part of the system. LangChain raised a fixed model's Terminal Bench 2.0 score from 52.8 to 66.5 by changing only the harness around it. The weights stayed the same; the environment, tools, and feedback loop changed.
That matters because agentic code quality cannot be prompted into existence. It has to be engineered through the context the model receives and the machinery that plans, constrains, and verifies its work. Together, I think of those disciplines as agentic engineering.
Both disciplines support the same way of working. Strong practitioners do not point an agent at a large task and watch it type. They spend time on the plan, usually through several rounds with the model, then divide the work into small pieces and verify each checkpoint.
Many practices that make software engineers effective also help coding agents.
This is not tied to one vendor. GitHub recommends roughly the same sequence: prototype cheaply, plan carefully, let a loop run to completion, then review the result. The same discipline applies further upstream as the work gets larger.
The loop becomes useful when the agent can check its own work. Tests, linters, and type-checkers turn a plausible change into an evidence-backed one. This is one of the highest-return capabilities you can give an agent.
Context engineering decides what task-specific information the agent can use while it plans and works. Harness engineering provides the loop, checks, and boundaries that pull it back when it goes astray.
If the model is a processor, context is its working set. It remembers nothing between turns unless that information is placed in the current window. Context engineering is the work of deciding what enters that window, in which order, and when to discard it.
Imagine the best software engineer in the world, who wakes up each morning having forgotten every architectural decision and every line of code from the day before. Would you hire them?
Errors get more expensive the further upstream they occur. A bad line of code may cost one line. A bad plan can send the whole task towards the wrong solution. Faulty research means the plan rests on a misunderstanding; a faulty specification means the team is solving the wrong problem. Worst of all is a bad instruction in a context file that steers every session, potentially for every engineer in the organisation.
The result is a quality ceiling: research limits the plan, and the plan limits the implementation. Careful coding cannot rescue a misunderstood problem. I would rather spend human attention on the research, specification, and shared context than on polishing code the agent was always happy to generate.
All of that material competes for space in working memory: system instructions you do not control, tool descriptions loaded at startup, context files you wrote, memories the agent saved, and the running transcript of the task. You can shape only part of it, but all of it consumes the same fixed budget. The job is to spend that budget deliberately.
In long coding sessions, I find reliability tends to fall as the window fills. The model's own earlier output can be especially costly because errors and stale assumptions remain in view. A larger context window does not remove this problem; it can also give you a larger haystack. Isolation often matters more than raw capacity.
A useful rhythm follows: expand to plan, then reset to execute. Let the context grow while gathering the material needed for research and planning. Once the plan is sound, begin implementation in a clean window with only that plan and the necessary project context.
There is a security consequence here. A poisoned AGENTS.md is both a high-leverage context artifact and an attack surface. The same instruction can steer many sessions, so the machinery around the model has to constrain what those instructions are able to cause.
Vivek Trivedy at LangChain expresses the basic anatomy neatly. The model is one fixed component; the harness is everything around it.
Agent=Model+Harness
At minimum, that runtime needs a system prompt, tools, an agentic loop, and a translation layer between the loop and model providers. A coding harness extends well beyond that core: it also supplies the context, persistence, containment, and feedback that make the loop useful and governable.
How much of that system you control depends on the provider. Claude Code keeps its system prompt, core tools, and sub-agent implementation out of reach. It exposes configuration such as permissions, MCP tools, and sandbox settings, while leaving you to author AGENTS.md, hooks, skills, tests, and linters. Pi makes a different trade. Its core omits MCP, sub-agents, and permission prompts, leaving those capabilities to extensions or user-built orchestration.
Switch harness to see selected pieces land in different tiers - which tier a piece falls in is provider-dependent. Pi, a minimal harness, omits MCP, sub-agents, and permission popups by design, so most pieces are yours to author.
So "engineer the harness" does not mean the same thing in every tool. The first consequential choice is the harness itself because that choice determines which other parts you can touch.
That choice is also an agency decision. A neutral, locally owned harness can keep sessions, instructions, tools, and workflows under the user's control while allowing proprietary and open-weight models to be compared or replaced. You may not own the weights, but you can own the interaction layer instead of rebuilding your working method inside each provider's application.
This is not just a naming exercise. Some apparent model failures are failures of the system around it.
A harness is concrete, not an abstract layer in a diagram. The reason for each component becomes clearer if you start with what a raw model cannot do.
A raw model invocation is text in, text out, with no persistent state. A filesystem and Git provide a workspace, memory, and history; tools let it act. When the work is deterministic, have it invoke an inspectable, testable program rather than produce the answer probabilistically.
Code execution needs containment and feedback. Put it in a sandbox with test runners, a browser, logs, and screenshots. Add memory and search for knowledge outside its training, and isolated sub-agents for investigations that would otherwise clutter the main context. The filesystem and Git come first; most of the other capabilities depend on them.
A component should earn its place. If you cannot name the behaviour it exists to produce, leave it out.
Once the substrate is in place, the remaining question is how to steer the work running on it. Control theory offers a useful vocabulary. Guides add knowledge before the agent acts; Sensors measure what happened afterwards. In control terms, those are feedforward and feedback. Sensors alone catch mistakes but force each new session to rediscover them. Guides alone encode rules without showing whether those rules worked. A complete loop needs both. Across that loop, I find seven useful control surfaces, each tied to a particular kind of failure.
The seven controls play distinct roles. Templates define the reference: the shape of a plan, file, or report that good output should approach. Guides supply standing knowledge before action, while Controllers turn recurring work into procedures using skills, prompts, and subagents. Rails constrain what the agent can do; Throttles cap how far a run may escalate. Sensors return deterministic evidence from tests and other checks. Observers estimate the less visible state of the work by inspecting assumptions, plans, and diffs from a fresh context.
Do not try to design the whole harness up front. Grow it from observed failures. When the agent makes a repeatable mistake, add the smallest constraint that prevents it: a line in AGENTS.md before a hook, and a hook before a hard deny rule. Stronger controls are harder to unwind. Each corrective rule should point back to something that actually went wrong. Ratchet from evidence instead of brainstorming imaginary failures. Configuration is often much shorter than the prose used to explain it:
{
"permissions": {
"deny": ["Bash(rm -rf *)", "Bash(git push --force*)"]
},
"hooks": {
"PostToolUse": [{ "matcher": "Edit", "command": "npm run lint --silent" }]
}
}
Not every instruction comes from a failure. Some encode personal working preferences: how much explanation you want, when the agent should pause, or how much it may change before asking. These preferences make the tool fit the person using it.
Controllers deserve a closer look because this is where teams tend to systematise their working methods. In practice, a controller is often a skill. A skill is a folder rather than a single file: a short entry point beside scripts, references, and templates that load only when needed. The description triggers it, so the full material does not consume context in every session.
Two authoring habits matter. Give the agent scripts to run instead of repeatedly describing mechanical steps in prose. That prevents it from rebuilding the same boilerplate and keeps its attention on the decision at hand. Store recurring gotchas in the skill too.
As the harness accumulates corrections, a skill file can become a poor substitute for its own history. It tells the agent what to do, but not which failures justified each rule, which alternatives were tried, or whether they worked. Keep the active procedure separate from the evidence behind it: raw traces, recurring patterns, and a record of proposed changes and their outcomes. That keeps the skill short and usable while retaining rejected ideas for the next revision.
This is an emerging pattern, not a finished recipe. WikiSkill used small validation splits; its strict immediate-improvement gate can discard useful stepping stones, and its knowledge base still needs a way to prune itself as it grows.
1---2name: release-check3description: Verify a release before it ships.4---5 6# Release check7 81. Run `scripts/check-release.sh`.92. Investigate any failure before continuing.103. Report the checks run and their results.11 12## Gotchas13 14- A clean build does not prove the working tree is clean.Rails contain the OS-level security model and answer the poisoned-AGENTS.md problem. A coding agent usually inherits the permissions of the person who launched it. Cloud credentials, SSH keys, and registry tokens may all be one command away. Hostile instructions can arrive through a repository, pull request, dependency, or context file, and an apparently safe command can start a subprocess the application no longer sees.
The hard boundary therefore belongs below the application. A sandbox is not advice about which commands to avoid. It is a limit the process cannot negotiate with: writes confined to the project and a scratch directory, network access denied by default, and individual hosts approved when needed. The operating system enforces those rules across subprocesses in a way command-string matching cannot.
The two layers do different jobs. The sandbox controls what the agent can do. Application rules flag or constrain intent visible at the tool boundary. Deny rules and pre-tool hooks remain useful for destructive actions whose intent is visible, such as force-pushing or rewriting history.
Read access remains a gap. Claude Code's sandbox confines writes and network access by default, but sandboxed commands can still read much of the disk unless sensitive paths are denied explicitly. Exceptions are sometimes necessary, but each one is a hole in the boundary. The payoff is a safer fast path: an agent can run with fewer approval interruptions inside a sandbox, but should not run that way on a bare machine.
Deletion is the neglected part of harness engineering. As models improve, old scaffolding becomes drag. A constraint that once prevented failure may later obstruct a more capable model. Make this a routine audit instead of waiting until the setup feels unbearable. Claude Code's /checkup command looks for this kind of rot: unused skills, slow hooks, and an overgrown AGENTS.md. You can also write your own pruning skill.
These principles shape my own setup: durable planning artifacts, isolated worktrees, and a shared layer of instructions, skills, agents, and prompts that is independent of any one harness. I document the concrete workflow and its public cross-harness repository separately in My Agentic Engineering Setup.
So far this is a single-player setup: one engineer, one agent, one repository. Most of these practices work well at that scale. Multiplayer is less settled: organisations have to support many engineers, agents, and repositories without prematurely forcing everyone onto one tool. Coordinating concurrent changes to the same codebase remains harder still.
An organisation does not need every team to use the same agent. Zalando's experience across more than 250 teams points to a narrower standard: centralise model access, authentication, protected MCP ingress, reference configurations, and observability. Let teams choose their own tools, and share the skills that prove useful.
Anthropic is already seeing why the shared layer matters. Claude authored more than 80% of its merged code in May 2026, while merged code volume per engineer reached eight times its 2024 level. Anthropic cautions that lines of code overstate productivity, but human review still became a bottleneck. It now runs an automated Claude reviewer before merge.
Faros found higher throughput alongside more bugs, incidents, churn, and review delay across 22,000 developers. Its data is not directly comparable with Anthropic's. The narrower point is that code volume says little about whether an organisation can review and absorb the work.
Throughput is up, but what survives? When agents write most of the code, human review stops being one step among many and becomes the rate-limiter for the whole system.
Verification is therefore part of the organisational harness, not just a repository-level check.
The broader the possible failure, the wider the check needs to be.
Is the change mechanically correct?
Does it behave correctly in context?
Does another agent challenge it?
Does it fit the system?
Start close to the change. Tests, linters, and type-checkers catch mechanical errors. Hooks, browsers, logs, and screenshots check how the change behaves in context. Passing checks can stay quiet; failures should return only the evidence needed to act.
Broader questions benefit from a separate evaluator. Anthropic's long-running application experiments split the harness into a planner, generator, and skeptical evaluator. GitHub Copilot's "rubber duck" review similarly uses a model from a different family to diversify the blind spots brought to the review.
A generator reviewing its own work in the same context can repeat its original blind spots and stop too early. In practice, I find tuning a skeptical evaluator more tractable than teaching a generator to distrust itself.
Human attention can then follow the risk. Zalando uses stack-specific incident rules to auto-approve low-risk pull requests while sending compatibility and configuration hazards to people. The fast path shortens lead time and encourages smaller, independently shippable changes.
OpenAI applied these layers to a million-line internal product built through roughly 1,500 agent-executed pull requests. A short AGENTS.md points to structured repository documentation, linters enforce invariants, and background agents detect drift.
People still take the widest view. Code can pass every local check and fail where it meets the rest of the system. The agent should show what it verified and how the change fits, so reviewers can focus on the seams rather than rereading every line.
This is why I am less interested in waiting for the next model than I used to be. I cannot change its weights. I can change what it sees, what it may do, and what evidence it must produce before I trust the result. That is the part worth engineering.