{"slug": "sensors-the-other-half-of-the-harness", "title": "Sensors: The Other Half of the Harness", "summary": "An engineer argues that agent harnesses for AI coding agents need more than just rules; they require 'sensors'—automated checks that enforce rules. The engineer describes a four-line shell script that caught a dangerous migration by checking for a required backfill comment, illustrating how sensors prevent failures that rules alone miss. The post outlines five workflow positions for sensors, from pre-edit to post-deploy, emphasizing that every critical rule should have a corresponding sensor.", "body_md": "The pre-commit hook caught a migration last Tuesday that would have shipped to staging green and broken production on the next deploy. The migration dropped a foreign key constraint without naming a backfill plan, and the rule that said *every constraint change needs a backfill plan* had been sitting in migrations.md for four months. The rule didn’t stop the agent. The rule didn’t stop the human reviewer either. The diff was 38 files and the constraint drop was a single line. What stopped it was a four-line shell script wired to pre-commit that grepped the staged migration for DROP CONSTRAINT and exited non-zero if no--backfill: comment followed it.\n\nThat four-line script is what I want to talk about. Most of what gets written about agent harnesses is about rules — what CLAUDE.md should say, where to put it, how to scope it, when to version it. Rules are half the harness. The other half is the set of checks that fire when a rule gets broken. I call them sensors, and they get talked about a lot less than they should.\n\nRead any of the well-circulated posts on agent harnesses and count the words spent on each side. Rules are the centerpiece. Sensors are glanced over, as if they’re so obvious they don’t deserve their own treatment. They aren’t obvious, and the assumption that they’re already there is the most expensive assumption in the field.\n\nThe asymmetry has a reason. Rules are easier to write. A rule is a paragraph. A sensor is a script, a hook, a CI step, a pre-commit config, a custom check. Rules sit at the cognitive level a writer naturally operates at; sensors sit at the level of plumbing. The first one is a lot more fun to write than the second.\n\nThe asymmetry also has a cost. *Rules without sensors are vibes.* The agent reads them, claims to follow them, and gets graded on whether the resulting code *looks* like it followed them. *Looks like* is the failure mode. The agent is a probabilistic system. It will obey a rule most of the time and skip it some of the time, and the times it skips are the times the rule mattered most: the awkward branch, the migration nobody likes to think about, or the edge case the rule was written to catch.\n\nA sensor changes the contract. The rule is no longer a hope. It’s a falsifiable check.\n\nA sensor is anything that can detect whether a rule was followed and signal that detection in a way the workflow respects. The shape is narrow:\n\nMost of the sensors in a working harness aren’t fancy. A grep with set -e. A line in a linter config. A pytest fixture that asserts the database fixture hasn’t drifted. A pre-commit hook that runs mypy on changed files. A GitHub Actions job that fails the PR if the migration directory has a file without a paired rollback. None of these is impressive on its own. The point isn’t the individual sensor; *it’s the discipline of having one for every rule that matters.*\n\nThere are five workflow positions where sensors earn their pay. Each catches a different class of failure. A working harness has sensors at most of them.\n\nThe earliest sensor is the one the agent encounters before it writes code. The rule is loaded into context; the sensor is something the agent can run to check its own work before producing output. A type stub generator, a schema dump, a --dry-run command that simulates the change. The agent doesn’t need approval; it just needs to see what its proposed change *would* do.\n\nPre-edit sensors are the rarest of the five, because they require giving the agent tools, not just rules. The pay-off is that they catch errors before any bytes hit disk. The agent that can run prisma validate on a proposed schema change is going to make fewer broken commits than the agent that can only read the rule that says *make sure your schema is valid*.\n\nPre-commit is where most teams put their first sensor, because Git makes it easy. A .pre-commit.config.yaml or a husky hook fires the moment the agent (or human) tries to land a change. The check has the staged diff to look at, the rest of the repo for context, and a hard exit code that the workflow respects.\n\nThis is the sensor layer for rules that constrain the *artifact* — what the code looks like, not what it does. Format, lint, type, dead code, banned imports, naming patterns, the migration backfill check above. Cheap, fast, local. The agent that breaks a pre-commit hook either fixes the violation or doesn’t commit. Either outcome beats *the agent commits and nobody notices*.\n\nA pre-PR sensor is one that runs after the commits exist but before the diff goes up for review. The check has more to work with: a full branch, a base ref, a set of changed files that’s no longer one commit at a time. It’s the right layer for cross-file checks. *Did you change this API without updating its consumers? Did you add a new migration without bumping the schema version? Does the test coverage diff drop more than two points?*\n\nPre-PR runs cheap on CI and slow locally. Most teams run it on the branch push, which catches the agent’s work right as it’s offering the work for human attention. That timing matters. A check that fires after the human has started reviewing is a check that’s been skipped.\n\nPost-merge sensors are the ones that run against main. They aren’t strictly part of the agent’s loop, but they’re part of the harness because they detect when the loop produced something that broke once integrated. Smoke tests, end-to-end suites, schema-diff jobs that compare staging to production, query-plan monitors that fire when a slow query joins the rotation.\n\nThe instinct is to think of post-merge as *CI* and not as part of the harness. That’s a mistake. The agent’s behavior is shaped by what fails *after* it shipped. A post-merge sensor that catches a regression and gets traced back to a missing rule is the most valuable kind of sensor in the whole stack. It tells you something the inner sensors missed, and it tells you what to add upstream.\n\nThe fifth and most-skipped sensor is the one that checks whether the rules themselves are still true. The codebase moves. The framework version bumps. The pattern the rule used to describe gets replaced by a new pattern. The rule is now wrong, and nobody notices because nothing fires.\n\nA drift sensor is the check that wakes up periodically and asks: does this rule still match reality? Sometimes that’s a literal grep for the pattern the rule references. Sometimes it’s a count of how many files in the codebase still match the rule’s example. Sometimes it’s a last-modified audit that flags any rule older than six months for review. The cadence is monthly or quarterly; the goal is to keep the harness honest as the code beneath it changes.\n\nI’ll write a whole post about rule-rot soon. The short version: *a drift sensor is the difference between a harness that’s six months old and helpful and a harness that’s six months old and lying.*\n\nIf a rule matters, it has a sensor. That’s the principle. It sounds rigid, and it is, on purpose.\n\nThe discipline works because it forces a real question every time a rule gets written: *how would I know if this got violated?* If the answer is *I’d see it in code review*, the rule is on probation. Code review is slow, expensive, and inconsistent. It is also the wrong layer for rules that produce mechanical violations.\n\nIf the answer is *the linter would catch it*, the rule probably doesn’t need to be a rule. **The linter is the rule. Just configure the linter and skip the prose.**\n\nThe interesting cases are the rules where the answer is *nothing currently checks for this, but I could write a check*. Those are the rules that earn a sensor. The sensor doesn’t have to ship the same day the rule ships, but it should be on the list and have a date.\n\nThe rules that aren’t worth a sensor are the rules that aren’t worth writing down. *Be thoughtful* is not a rule. *Bullet points should be sentence fragments unless they form a list of full sentences* is also not a rule, because nothing catches a violation and the cost of writing it down is more than the cost of fixing the rare slip in editing.\n\nA rule with no sensor is either too vague to matter or important enough that someone should pair it within the month. *Either way, the absence of a sensor is information.*\n\nThe sensors that survive in production look alike. They share five traits.\n\n**They run in seconds.** A pre-commit hook that takes 90 seconds gets bypassed within two weeks. A pre-PR check that takes 12 minutes gets the --skip-checks shortcut added to the team Slack. The check has to be fast enough that running it is cheaper than figuring out how to avoid running it.\n\n**They produce clear messages.** A failed sensor that prints EXIT 1 is a failed sensor that’s about to get ignored. A failed sensor that prints migrations/20260620_drop_users_index.sql dropped an index without a paired backfill comment. Add a comment starting with --backfill: explaining the plan, or annotate the migrations with --no-backfill-needed: <reason> is a sensor that teaches the agent (or the human) how to fix the violation.\n\n**They run locally.** A check that only exists in CI is a check that the agent finds out about ten minutes after it pushed. *Running the same check locally before push closes the loop.* The pre-commit framework and npm run check-style scripts both make this easy; the discipline is to make sure CI runs the *same* scripts the developer can run.\n\n**They cite the rule they enforce.** Every sensor message that earns its weight names the rule. *Failed: rule MIGRATIONS-04 (foreign-key changes require a backfill plan).* The agent reads the message; if the rule is wrong, the agent can find it and propose a change. The sensor is the gate, but the rule is the explanation.\n\n**They fail loud and pass quiet.** A sensor that prints OK checked 47 migration files in 1.2s every commit is a sensor whose output everyone learns to ignore. Pass silently. Fail with a paragraph. Save the human’s attention for the cases that need it.\n\nA working sensor layout has four physical homes. Each holds the sensors for a different workflow position.\n\n```\n.husky/ # or .pre-commit-config.yaml  pre-commit # fast, staged-files only  commit-msg # commit message linting.github/workflows/  pr-checks.yml # pre-PR, runs on push  post-merge.yml # post-merge, runs on main.claude/  hooks/ # agent-facing pre-edit checks  sensors/ # one shell script per drift checkscripts/check-*.sh # the actual checks, invoked from above\n```\n\nThe shape isn’t sacred. *What matters is that each sensor has a single home and the workflow knows where to call it.* The anti-pattern is the same check running in three places with slightly different logic and the team unable to remember which one is authoritative.\n\nThe sensors themselves go in scripts/ (or wherever your project keeps tooling) and get called by whichever runner needs them. That separation means the same check can fire from pre-commit, from CI, and from a drift-audit cron without three copies. One script, three invocations.\n\nPersonally, I prefer to wrap these scripts in make tasks. The benefit that I receive is that the agent always knows to use make. And that makes everything easier to find and more consistent. I use make in CI too. Usually, I work with docker and so this is a natural pair for me. *Isolate the environment with docker; isolate the commands with make. *That makes it easier for the human *and* the agent to work with the repository.\n\nThe two highest-value sensors for a team starting from zero are both small.\n\n**The migration safeguard.** Whatever your migration tool is, the team’s hardest-to-catch bugs ship through it. A pre-commit script that knows the patterns your migrations are supposed to avoid — DROP COLUMN without paired backfill, ADD COLUMN NOT NULL without a default, schema-breaking renames without a two-phase plan — catches more production issues than any rule on its own. The agent will write migrations that look right and miss the constraint. The sensor sees what the human reviewer doesn’t.\n\n**The diff-budget gate.** A pre-PR check that fails if the diff exceeds N files or M lines. The number is yours to pick. I use 15 files and 400 lines, which roughly maps to a PR a human can review in one sitting. The rule that says *keep PRs small* is exactly the kind of rule that gets nodded at and then drowned in the next month. The sensor that fails the build at file 16 is the rule with teeth.\n\nBoth are under fifty lines of shell. Both pay for themselves the first week.\n\nA few sensor failure modes recur often enough to name.\n\n**The unrunnable sensor.** It lives in CI, takes 14 minutes, and depends on a secret only the deploy bot has. Nobody on the team can run it locally. By the time it fires, the human reviewer has already mentally signed off. The fix: factor out the local-runnable piece and run *that* on every commit.\n\n**The sensor that always passes.** Someone wrote it months ago, the inputs changed, the assertions still hold, the failure case is no longer reachable. The sensor is a green light glued to the dashboard. The fix: every sensor needs at least one *known-bad* fixture that the test suite runs against it to confirm it still detects the violation it was written for.\n\n**The flaky sensor.** It fails sometimes and passes the rest. The team learns to rerun. Within a month, the rerun habit has spread to the other checks, and a real failure gets bypassed because it *looked* flaky. The fix: **a flaky sensor is broken.** Pull it from the gate, fix it, or delete it. Do not leave it firing.\n\n**The rule with no sensor.** The whole point of this post. A rule that lives in CLAUDE.md and has no check is a rule that gets followed when the agent feels like it. The fix is the pairing rule above: if the rule matters, pair it within a month.\n\n**The sensor with no rule.** Less common but worth naming. A check fires, a developer doesn’t know why, the rule it’s enforcing isn’t written down. The check is correct on its own terms but unteachable. The fix: cite the rule in the failure message. If you can’t, write the rule.\n\nPick one rule from your CLAUDE.md — the one you find yourself reminding the agent about most. Open a file. Write a check. Wire it to pre-commit. Ship it.\n\nThe right rule for the exercise is one with a clear violation pattern. *Always use the **Result<T,E> type for fallible operations* is a fine candidate; you can grep for throw in the changed files and warn. *Match the existing code style* is a poor candidate; the violation pattern is too diffuse for a small sensor to catch.\n\nWhen the sensor catches its first violation, two things happen. The rule becomes load-bearing. The agent now has to satisfy it instead of just acknowledge it. Additionally, you find out whether the rule was right. Sensors that fail a lot on changes that look reasonable in review are sensors enforcing rules that need to change. *The sensor is the feedback loop; the rule is the hypothesis.*\n\nAdd a second one next month. By the end of the quarter your harness will feel different. Not because the rules are smarter, but because the rules can be checked.\n\nThe rules are the part everyone writes. The sensors are the part that makes them true.", "url": "https://wpnews.pro/news/sensors-the-other-half-of-the-harness", "canonical_source": "https://tacoda.medium.com/sensors-the-other-half-of-the-harness-43d3781a722e?source=rss-bf474619cf47------2", "published_at": "2026-06-20 15:15:50+00:00", "updated_at": "2026-06-25 03:47:27.241132+00:00", "lang": "en", "topics": ["ai-agents", "developer-tools", "mlops", "ai-safety", "ai-infrastructure"], "entities": ["Git", "GitHub Actions", "CLAUDE.md", "prisma", "mypy", "pytest", "husky", "pre-commit"], "alternates": {"html": "https://wpnews.pro/news/sensors-the-other-half-of-the-harness", "markdown": "https://wpnews.pro/news/sensors-the-other-half-of-the-harness.md", "text": "https://wpnews.pro/news/sensors-the-other-half-of-the-harness.txt", "jsonld": "https://wpnews.pro/news/sensors-the-other-half-of-the-harness.jsonld"}}