Most of harness engineering is giving good negative feedback: the model generates, and the harness rejects what's wrong, fast and automatically
A coding agent will generate for as long as you let it. Generation is the cheap part: the model supplies as much plausible-looking code as you can pay for. What’s scarce is automatic rejection. Backpressure is the set of checks that reject bad output, and configuring it is most of harness engineering.
That observation comes from Simon Willison, who states it as a principle of its own: writing code is cheap now. Generating code is no longer the expensive part, so practices designed around scarce generation need revision.
“Negative feedback” is meant here in its control-theory sense: the error-correcting loop that stabilises a system, which is cybernetics in its original meaning (steering). It’s also meant in the everyday sense, the critique you give and receive at the crit. A good failing test and a good crit comment must both be specific, early, and aimed at the artefact rather than the author.
Guides and sensors#
The corrective signal runs through two kinds of channel (vocabulary borrowed in part from Learn Harness Engineering; Birgitta Böckeler splits it the same way):
- Guides constrain the agent before it generates: the CLAUDE.md in your repo, a skill it consults, the published spec and the plan you agreed. Feedforward — they constrain what gets produced.
- Sensors measure what it produced after: exit codes, lint output, type errors, test results, the rendered page. Feedback — they reject what’s wrong.
This topic is mostly about sensors; the big guides get their own treatment (CLAUDE.md in week 2, skills in week 4). Use each sensor to answer one question: does the artefact meet the requirement? Testing and linting are the everyday instances, but any automatic comparison between the artefact and its requirements can provide this feedback. A useful sensor is:
- specific — a file, a line, an expected-versus-actual. “Something’s off” is not a signal the agent can act on.
- fast — the correction appears in the same loop turn as the mistake, while the agent still has the context that produced it. Shortening the loop does the same work from the other end, which is what a dev server that rebuilds on save buys you.
- automatic — it fires on every change without anyone remembering to run it.
- trustworthy — a flaky sensor is worse than none, because a red check that’s sometimes wrong leads you and the agent to ignore red checks.
Once configured, sensors can run on every change at negligible marginal cost. Your attention is the scarcest resource in the loop, so automating a correction saves you from repeating the same check.
Four sensors worth wiring in#
The rest of this page covers four concrete sensors, from code checks to the rendered page itself.
Linting and types#
Lint output gives an agent a machine-readable correction: a rule name and a location. The agent can act on that output in the next turn without further instruction or interrupting a human.
Type checkers catch errors with greater consequences. Agents often generate plausible code that reads correctly but is wrong; a type checker converts many of those errors into diagnostics with exact locations before anything runs. Agentic practice has therefore strengthened the case for types and strict lint configs: every enabled rule automates another correction.
The sharpest sensor is a failing test#
Lint and types reject malformed code. A test can also reject code whose behaviour differs from the expected result. A failing test is backpressure you author yourself: it encodes your intent as a check the harness can run and gives the agent a specific failure to fix.
The resulting habit is short enough to keep: only push on green. This section does not require the full test-driven ceremony. Red-green-refactor is a fine discipline, but here we are concentrating on the feedback channel. Write a test when you know something the code should do, and keep the suite green as the gate on every push.
stylelint: grounding for CSS#
Agents produce particularly unreliable CSS. The cascade has no type checker, a selector that matches nothing raises no exception, and the model’s training data contains plenty of copied rules with conflicting declarations. An agent can therefore produce plausible CSS while few automated checks detect the visual failure. stylelint reports invalid values, duplicate or conflicting declarations, and selectors that cannot match. It cannot judge the design, but it prevents invalid and contradictory CSS from accumulating.
agent-browser: the rendered page as ground truth#
Everything above checks the code. None of it shows the agent what the page looks like. Without rendered output, an agent may report that the layout is fixed because the CSS it wrote appears correct. Vercel’s agent-browser CLI closes that loop: the agent drives a real browser, takes screenshots, reads the accessibility tree and the console errors, and compares what actually rendered against what it believes it built. A contradictory screenshot provides evidence that the claim was wrong.
Used defensively like that, it is a sensor. Used deliberately — driving the artefact the way a user would, to find what nothing else reaches — it is a named practice called agentic manual testing, which is week 5’s business.
On Ubuntu 23.10 and later the browser won’t start under the defaults: AppArmor
restricts the unprivileged user namespaces Chrome’s sandbox needs, and the run
exits with No usable sandbox! before any page loads. Pass
--args "--no-sandbox" on whichever command launches the browser:
agent-browser --args "--no-sandbox" open http://localhost:4321/
That turns off Chrome’s own process isolation, which is a fair trade for a browser pointed at your own dev server — just don’t reuse that session for general web browsing.
The roster, wired into CI#
Your starter repo runs these sensors through one pnpm check: the type checker,
oxlint, stylelint, the build, and a test suite that includes the invariant
checks. The crit 7 starter adds
axe-core to those
invariants, an accessibility floor run in jsdom; the final project’s template
ships only two checks, that the app answers and that /readme/ publishes your
README, and leaves the rest of the roster to you. While your repo is private
they run locally via the starter’s scripts (the faster loop anyway); from the
moment it goes public at your cutoff, CI runs them on every push. The repo’s
CLAUDE.md describes each check and how to interpret a failure. Two habits
complete the loop:
- read the checks back. A failed CI check reports a specific problem. Give that output to the agent so it can act on the same information.
- remember what green means. Passing checks cover only the machine-checkable requirements, not the work’s overall quality.
The task list is another sensor: an external record for checking the agent’s completion reports.
When no sensor catches it#
Some changes are wrong in ways no sensor catches until too late, and then the
question is which correction to reach for. That judgement is mostly about how
early you are. A wrong direction caught mid-response is cheapest to interrupt; a
few turns later you may need to rewind the conversation; once the change has
reached the repository, use git revert. The
cheatsheet has the
mechanics, including an important boundary: Claude Code’s checkpoints cover the
conversation and its own edits. Use git history after any other tool has changed
your files.
All of that depends on knowing what happened rather than what the agent said
happened. Use the diff, check output and rendered page to verify any claim of
“done”. Because git revert is cheap, you can let the agent work in bounded
stretches without watching every turn.
One signal, three channels#
This page concentrates on the automated channel: corrections that run without anyone in the loop. You provide another channel by reading the diff, asking the agent to explain itself and checking the task list. Your peers provide a third at the weekly crit. At the crit you practise giving specific feedback; during the rest of the week you automate as much of it as possible.