Mechanical constraints for agents
From hoping agents comply to making violations impossible
A repo can have a rich documentation layer — an AGENTS.md as a navigational index, rule files for domain-specific constraints, developer docs that explain how subsystems work. All of this shapes agent behavior through context injection: read the right file, you’ll produce better output.
But agents can read a rule and still violate it. A security guideline about innerHTML doesn’t stop an agent from using it. The gap between “documented constraint” and “mechanically enforced constraint” is the gap between hoping agents do the right thing and making it structurally impossible for them to do the wrong thing.
The OpenAI Harness Engineering team arrived at the same conclusion: “Agents are most effective in environments with strict boundaries and predictable structure.” We don’t always need smarter models. We need custom linters, structural tests, enforced dependency directions, and feedback loops that make the repository correct agent execution in real time.
Every constraint compounds
Every constraint you add pays dividends on every future agent interaction. It’s a silent acceptance criteria expressed on all agent output. A lint rule with a great error message teaches the agent how to fix its mistake at the exact moment the mistake occurs. A TypeScript compiler error is the most reliable form of agent constraint — it is impossible to ignore. An architectural boundary encoded in ESLint prevents an entire class of structural error that would otherwise require expensive unwinding.
This compounds. If 100 agents build features over a quarter, and each encounters an average of 3 constraint violations per session that would otherwise require human correction, that’s 300 correction cycles eliminated. Each constraint you add reduces the correction rate for every subsequent session.
The constraint hierarchy
The hierarchy of constraint reliability, from least to most effective:
Prose in documentation — agent must find, read, and follow it.
Rules in
.cursor/rules/injected into context, but still advisory.Lint rules with remediation messages — blocks merge, teaches the fix.
Compiler errors — impossible to ignore, impossible to merge past.
Structural tests in CI — catches invariant violations that individual rules miss.
The strategic move is to promote constraints up this hierarchy whenever possible. A constraint that lives only in a markdown file can drift, be missed, or be misinterpreted. A constraint that lives in eslint.config.mjs or tsconfig.json is verified on every build, every lint run, every agent interaction.
As we’ll describe in a later post, documentation can be thought of as a materialized view of the codebase. It’s a precomputed projection that trades freshness for accessibility. This initiative extends that metaphor: we are promoting constraints from the materialized view (docs) into the source of truth (code). It’s not extra context to agents, it’s context delivered only when relevant, exactly at the moment needed.
What this looks like in practice
The following sections come from my implementation experience on this epic.
TypeScript strictness
Enabling noUncheckedIndexedAccess forces explicit null handling on all array and object index access. It eliminates a class of runtime errors where agents assume indexed values exist without checking. Low effort, high reliability, every agent benefits immediately. This is the single highest-value example of a compiler flag doing constraint work that documentation cannot.
Import boundary enforcement
Encoding an architectural layer model (foundation → shared utilities → domain engines → integrations → presentation) as ESLint no-restricted-imports rules with directory-scoped config blocks. Each rule includes an agent-oriented error message explaining why the import is forbidden and what to do instead. This prevents the most expensive class of agent error: building features that violate architectural boundaries and then having to unwind them.
Custom lint rules with agent remediation messages
Adding no-restricted-syntax rules that mechanically enforce security patterns, architecture patterns, and other constraints currently expressed only as prose. Each rule’s error message is written for agent consumption — it names the utility to use, points to the relevant source file, and cites the rule document. A lint error message is a form of just-in-time teaching, delivered at the exact moment the agent needs it.
Structural tests
Tests that verify architectural invariants rather than behavior. An import graph test catches transitive boundary violations that per-file lint rules miss. A public API surface test ensures modules expose only their barrel exports. These catch drift that individual rules cannot.
Schema validation at boundaries
Adding schema validation (e.g., Zod) at system boundaries: JSON files loaded at runtime, data from external APIs, content from documentation sources. This gives agents compile-time types derived from runtime validation, eliminating “it type-checks but crashes at runtime” errors. The principle is “parse, don’t validate.”
File size and complexity limits
Enforcing maximum lines per file and cyclomatic complexity via structural tests or lint rules. This prevents agents from creating god-files, forces modular decomposition, and makes future agent work on the same files easier.
What success looks like
lint,typecheck, andtestcollectively enforce all critical architectural constraints.Every lint error message is written for agent consumption: it explains the constraint, names the alternative, and cites the relevant doc.
An agent that violates an architectural boundary gets a compiler or lint error within seconds, not a human code review days later.
The number of human correction cycles per agent session trends toward zero for constraint-related issues.


