AI & Engineering / Guide
Harness Engineering for PHP Developers
A coding agent is a model plus everything around it: the instructions it reads, the commands it may run, and the checks that tell it it is wrong. The model you rent. The rest you build, and in a PHP project most of it already exists in your toolbox. This guide shows how to put it together.
"Harness engineering" became a name in 2026. OpenAI used it in February for a five-month experiment in which agents wrote all the code of an internal product and the engineers' job became the environment around them. In April Birgitta Böckeler published Harness engineering for coding agent users on martinfowler.com, which gave the idea a frame you can design with. This article uses that frame and fills it with PHP tools. Where it describes laravel-ai-verify and Semitexa, keep in mind that we build both.
The short answer
A harness is what you add around a coding agent so that its first attempt is more often right, and its wrong attempts are caught before it says "done". Böckeler quotes the formula "Agent = Model + Harness". Coding tools ship with a harness of their own; the part you build for your codebase sits on top of it.
It has two halves. Guides work before the agent acts: instructions, conventions, a map of the code. Sensors work after it acts: linters, static analysis, tests, review. Guides without sensors never learn whether they worked; sensors without guides catch the same mistake on every run.
For PHP the sensors mostly exist already. PHPStan, Pint, Pest or PHPUnit, Rector, Infection. What is usually missing is the wiring: running the right subset after every change, giving the agent the result in a form it acts on, and not letting it finish while the result is red.
Guides and sensors
Böckeler borrows from control theory. A guide is feedforward: it raises the odds of a good first attempt. A sensor is feedback: it observes the result and lets the agent correct itself. The second axis is how a control decides. A computational control is deterministic and cheap, so it can run on every change: a linter, a type checker, a test. An inferential control uses a model to judge meaning: an AI reviewer, an "LLM as judge". It sees things a script cannot, at a higher cost and with less certainty.
That gives four boxes, and a useful habit: whenever a rule lives in an inferential box, ask whether it could move to a computational one. "Controllers do not query models directly" written in AGENTS.md is a request. The same rule as an architecture test is a constraint the agent cannot talk its way past.
The PHP harness at a glance
| Computational | Inferential | |
|---|---|---|
| Guides (before the edit) | Framework conventions and attributes, generated code maps (routes, models, listeners), Rector rules and codemods, project templates | AGENTS.md and CLAUDE.md, agent skills, architecture notes, examples in the prompt |
| Sensors (after the edit) | php -l, PHPStan or Larastan, Pint or PHP-CS-Fixer, Pest or PHPUnit, Pest arch() tests or Deptrac, Infection, framework boot checks | AI code review on the pull request, an agent reviewing the diff in a fresh context |
Nothing in the left column is new. The work is in making it run at the right moment, on the right files, with output written for the reader that will act on it, which is now often a model.
Guides: what the agent knows before it edits
Instruction files
AGENTS.md is read by Codex, Cursor, Copilot and most other agents. Claude Code reads CLAUDE.md, and since September falls back to AGENTS.md in a folder without one (how to keep one file for both). Keep them short and about this codebase: where things live, which commands to run, what not to touch. They are the cheapest guide you have and the weakest: an agent follows them most of the time, and nothing tells you about the rest. The most valuable line in an instruction file is usually the one that points at a sensor: "run this command before you say you are done".
Conventions the agent can see
Böckeler cites Ned Letcher's term "ambient affordances": properties of the environment that make the right move obvious. In PHP that is a framework with one way to register a route, typed properties, attributes next to the code they describe. A Laravel form request is a guide: an agent that sees one knows where validation goes. Every convention you enforce removes a decision the agent could get wrong.
A map instead of grep
An agent that does not know which tests cover a controller either runs everything or guesses. A generated map of the application (route to controller to request to model to table to view to test) answers that in one call. It is a guide when the agent reads it before editing, and it becomes part of a sensor when the same graph decides which tests to run afterwards.
Sensors: what tells it the edit is wrong
Böckeler groups sensors by what they regulate. Mapped to PHP:
- Maintainability. Pint or PHP-CS-Fixer for style, PHPStan or Larastan for types, Rector in dry-run for deprecated patterns. Computational and fast. They catch structure, not judgment: an overengineered but well-typed class passes all three.
- Architecture fitness. Pest
arch()expectations, Deptrac layers or PHPArkitect rules: "the domain does not depend on the HTTP layer", "jobs do not call controllers". This is the box where rules from your instruction file should end up. - Behaviour. Pest or PHPUnit. Böckeler calls this one the elephant in the room, and the section below explains why.
PHP adds failures that no unit test sees. A Blade view with an unclosed @if compiles into PHP that does not parse. A typo in a migration surfaces at deploy time. A closure in a config file breaks config:cache. A service provider that no longer boots takes down every route. And on a long-running server (Octane, RoadRunner, FrankenPHP worker mode, Swoole) a static property that holds the first request's user is a bug that passes every test run in a fresh process. A PHP harness needs boot checks and template checks next to the test suite, and the agent needs to hear that a green run does not mean the running workers have the new code.
One more rule for every sensor: write its output for the agent. "1 error introduced by this change: PostController.php:24 relation comments not found on App\Models\Post" gets fixed in one step. Four hundred pre-existing PHPStan errors in a legacy app teach the agent to ignore PHPStan.
Run the right checks, not all of them
A sensor the agent skips is not a sensor. The full suite takes minutes, so agents run one test by hand or none. pest --dirty runs the test files that changed, not the tests for the code that changed. Static analysis on a whole legacy app buries the change in old errors.
The fix is selection: read the diff, classify each file (controller, model, migration, view, config), and run the checks that can vouch for that kind of file, with the tests found through the code graph. Report problems on lines the change touched as failures and the rest as pre-existing. Then add one rule that keeps the selection honest: a run only passes if at least one check actually read at least one changed file. "Green" must never mean "nothing looked at it".
The behaviour gap
The weakest sensor is the one that checks what the code is supposed to do, because today the agent often writes those tests itself. Two failure modes follow.
The agent moves the goalposts. A failing test gets deleted, skipped, or loosened until it passes. A computational sensor can catch the crude cases: compare each changed test file with the base commit and fail if tests or assertions disappeared, or skip(), todo() or only() appeared. Mutation testing with Infection catches the subtle one, tests that pass whatever the code does, though it is too slow to run on every change.
The change does the wrong thing correctly. We had this one in October, in our own test app. Claude Code, while we were testing our GitHub Action, committed "Limit contact name to 250 chars". It did a find-and-replace of max:255, but the name rule was max:200, and the only max:255 in the form request was on the email rule. Syntax was fine, the tests were green, our verification passed and stamped the commit. A review bot caught it the next morning. Every check had answered "did the checks pass?"; none asked "is this the change the message describes?".
Part of that question is computational after all. When the subject names something in a changed file, that line is unchanged, and a line of the same shape right next to it did change, the commit is suspect. We added that check; run over 937 commits from five of our own repositories, it flagged 23 of them. It is a heuristic and stays a warning. The rest is what Böckeler says plainly: correctness is outside any sensor's remit if nobody clearly specified what they wanted. Behaviour still needs a person who knows what the change was for.
Close the loop
Sensors that run in CI after the push arrive too late for the agent: it has already reported success. Three wiring choices move them into the loop.
- One command, one verdict. The agent runs a single command after every edit and gets
pass,failorincomplete, with the reproduce command for each failure. One command is easy to put in an instruction file and hard to forget. - The agent cannot finish while it is red. Claude Code supports a Stop hook: a command that runs when the agent tries to end its turn and can send it back to work. A git pre-commit hook does the same at commit time.
- "Verified" can be checked, not believed. Have the verify run write a receipt (which checks ran, on which files, with which result) and stamp it on the commit. A reviewer or CI can then confirm that the claim matches the tree that was committed. That matters most with subagents, whose "all tests pass" you never saw.
A starter harness for Laravel
For a Laravel 12 or 13 application, most of the above fits in two commands with laravel-ai-verify, an open-source package we maintain:
composer require --dev semitexa/laravel-ai-verify
php artisan ai:verify:install --hook --git-hook
The install writes the instruction into the agents' instruction files, such as AGENTS.md and CLAUDE.md (the guide), adds the Claude Code Stop hook (the loop) and a git hook that stamps verified commits (the receipt), and from then on php artisan ai:verify is the sensor: it picks tests through a graph of routes, controllers, requests, models, views and tests, runs Larastan and Pint on what the change introduced, boots the parts of Laravel you touched, pretends your migrations, compiles changed Blade views, guards test integrity, and checks the commit message against the diff. php artisan ai:graph is the map, with impact analysis before an edit. With Laravel Boost installed, Boost's MCP server also offers the checks to the agent as tools.
You can build the same harness from parts: an instruction file, PHPStan with a baseline, Pest arch() tests, a pre-commit hook. The package exists because the selection and the wiring are the parts teams rarely get to.
A framework built as a harness
Retrofitting a harness onto an application works. Semitexa, the PHP framework laravel-ai-verify was ported from, starts from the other end: the framework is designed so that its own structure is a guide and its own checks are the sensors.
- Structure is declared, so it can be queried. Routes, handlers, resources, lifetimes and tenancy are PHP attributes on the classes they describe.
ai:ask route --path=/invoicesreturns the whole chain from payload to template; the agent reads one answer instead of a dozen files. - Verification is part of the workflow. The agent manual says
ai:verifyafter every edit, without exception. It runs the lint, tests and module-structure checks the diff needs, and writes a receipt thatai:verify:receiptcan check later. - Some numbers may only go down. A quality ledger records metrics such as request cost; a regression fails verification, and raising a number takes an explicit, reasoned entry that shows up in the diff.
It is a young framework with a small ecosystem, built on Swoole and aimed at new applications. If you run Laravel, the package above gives you the verification half without moving anywhere.
Common questions
What is harness engineering?
Designing the environment a coding agent works in: the guides that steer it before it acts and the sensors that catch its mistakes after. The term spread in 2026 through OpenAI's write-up of an agent-built product and Birgitta Böckeler's article on martinfowler.com.
Is harness engineering the same as context engineering?
Böckeler treats it as a specific form of context engineering. Context engineering is how information reaches the model; a harness uses it to deliver guides and sensor results, and adds the checks themselves.
Is CI not enough?
CI is a sensor that reports after the push, to a person. An agent needs the result before it reports success, on the files it just changed, in a few seconds. Keep CI as the backstop and run a selected subset inside the agent's loop.
Where does AI code review fit?
It is an inferential sensor, good at what scripts cannot judge: intent, naming, a change in the wrong place. Run it after the computational checks are green so it spends its attention on meaning, not on formatting.
Does a small project need a harness?
It needs the smallest one: an instruction file with the commands to run, static analysis at a level the code passes, and a hook that runs the relevant tests before the agent finishes. That takes an afternoon and pays back the first time an agent says "done" too early.
On the human side of the loop, keeping your own reason for accepting a patch: Your AI Agent Sounds Right. Do You Know Why?