Understanding Harness Engineering: A Simple Yet Deep Dive into Context-Layer Architecture for Agentic Development
For developers navigating between AGENTS.md/CLAUDE.md, skills, hooks, MCP, and everything in between.
Why this matters
You’ve set up Claude Code, added a few MCP servers, launched an /init command to generate a CLAUDE.md, and maybe dropped in some skills. With the latest generation of state-of-the-art LLMs now genuinely capable of producing high-quality production code, it mostly works.
But over time (more features, more repos, more edge cases), things can still get messy: the agent ignores skills, context fills up fast, and output quality degrades across long sessions.
The usual reaction is to add more: more skills, more rules, more docs. Counterintuitively, that often decreases code quality. Most agent failures are context-management failures, and stuffing more content into the window usually makes things worse.
Every rule added to
CLAUDE.md, every skill, every hook, is a patch.
It compensates for something the codebase fails to communicate on its own. A well-structured module with consistent conventions and enforced boundaries does not need a paragraph of unwritten conventions explaining it: the agent can read it.
That reframe matters because it changes what harness engineering is actually for. The goal is not to accumulate layers, but to make each one unnecessary, one decision at a time, by moving that decision into the codebase itself, where it becomes permanent, visible, and impossible to ignore.
Analyzing the context layers is precisely what reveals where those gaps are.
The core problem
Context is a signal-quality problem, not a capacity problem
The core execution model of an agent is an iterative loop:
At every step, the agent draws from its context window: a fixed-size buffer holding everything it currently “knows” about the session, including instructions, conversation history, file contents, and tool call results. When that buffer gets noisy or overloaded, the agent doesn’t degrade cleanly, it starts making subtle mistakes.
Wrong information in context is worse than missing information. Useful signal gets buried under irrelevant content, and the agent stops separating the two reliably.
The layered answer
The answer is not more context, but a harness designed around how each tool interacts with the context window.
Your harness is the set of tools, constraints, and feedback loops that make those layers work together.
A simple mental model
Context-Layer Architecture
In practice:
- Permanent: what belongs in every turn.
- On-demand: what should load only when needed.
- System: what must be enforced without trusting the model.
- Feedback: what checks the result after execution.
Layer 1: Permanent context (AGENTS.md/CLAUDE.md)
This is the Markdown file at the root of your project that loads into the agent’s context on every turn, without being explicitly invoked.
The first instinct when setting one up is to write everything: architecture overview, folder structure, team conventions, library choices, onboarding notes. That instinct is worth resisting. A permanent context file should be small, strict, and operational. If a rule is not worth enforcing on every single task, it probably does not belong here.
Keep it short
AGENTS.md/CLAUDE.md tend to reduce task-success rates compared to providing no AGENTS.md/CLAUDE.md at all, while simultaneously increasing inference cost by over 20%. Auto-generated files (via /init or similar) are primary culprits: they force the agent to spend reasoning tokens on information it could infer directly from reading the code. Bloated, contradictory, or over-specified files turn useful signal into noise.
What belongs here
The agent can already read your codebase. What helps is the stuff it cannot infer from code: tribal knowledge, non-obvious constraints, reasoning directives, and traps that have already caused real issues. Think of it as the short list of things you would tell a senior engineer on day one.
Three kinds of content are worth keeping:
Hard technical obligations
Constraints that apply unconditionally and that the agent might not pick up from context alone.
- “Always use pnpm, not npm.”
Gotchas
Non-obvious traps specific to this codebase.
- “We need to keep folder
/pointOfSaleOldfor backward compatibility. We will remove it once we’ll turn the feature flag on.” - “The auth token lifecycle is per-session, not per-request. Storing it in a closure or
WeakMapwill cause stale-token bugs on long connections.”
Retrieval nudges
Help the agent get relevant context when API docs are not in training data by routing to relevant skills, using web search, or looking into sibling repos.
- “Prefer retrieval-led reasoning over pre-training-led reasoning when using the Expo SDK: always use WebSearch to get docs matching the specific version.”
- “
business-logicis a sibling repo you may need to navigate and edit when necessary (cd ../business-logic).”
Note: in upcoming sections, you will see that some of these can be moved to the on-demand or system layer to improve context engineering further.
Layer 2: On-Demand tools (Skills, MCP, WebSearch, CLI, Subagents)
This layer covers everything the agent can reach for when needed, but that does not load automatically. These tools do different jobs, and treating them as interchangeable is a good way to get a messy setup.
| Tool | What it does | When to use it |
|---|---|---|
| Skills | Portable packages of instructions, scripts, and resources | When the agent needs domain knowledge, best practices, or procedural steps |
| MCP | External service integrations with persistent state | Structured tool access to authenticated or stateful external systems |
| WebFetch / WebSearch | Real-time web access | When the agent needs up-to-date and precise info not in training data |
| CLI | Direct execution through shell commands and installed command-line tools | When the task is best handled through local commands, scripts, or developer tooling |
| Subagents | Spawned helper agents for scoped exploration or execution | When the task can be decomposed into bounded subtasks or parallelized |
Skills
Done well, skills are one of the most effective levers in a harness. They move specialized knowledge out of permanent context and into a retrieval model: the agent reaches for what it needs, when it needs it. That keeps the context window clean, and you only pay the cost of expertise when you actually need it. This is the fundamental argument against a CLAUDE.md that keeps growing forever: permanent context is a fixed overhead while skills are a variable cost. In practice, move as many AGENTS.md / CLAUDE.md rules as possible into dedicated skills.
A skill is not always just a .md file. It is a directory with three parts:
SKILL.md(required): Contains YAML frontmatter (metadata) and Markdown instructions. The agent only loads the name and description from the frontmatter into its context. If it judges that the description matches the user’s request, it then opens the entire file to follow the instructions.scripts/(optional): Executable code (Bash, JS/TS, Python) that lets the agent perform actions on the LLM.references/(optional): In-depth documentation loaded only if the agent needs to look something up mid-task. This is an additional sub-layer of on-demand context.
The three core skill types
To keep a harness usable, categorize skills by intent.
1. Documentation and knowledge skills
Even the most advanced models have a knowledge cutoff, a knowledge date linked to the end of their training.
- Purpose: Provide information the agent doesn’t know or might misremember.
- Example: If you use Expo SDK 55, the agent might not know the API details simply because this specific API version may not have been in its training data.
- Solution: Expo Skills
2. Behaviors and best practices
LLMs tend to generate “average” code.
- Purpose: Drive project-specific, expert-level implementation quality.
- Example: A skill based on the React team’s You Might Not Need an Effect best-practices article.
- Solution: React useEffect Skill
3. Tooling skills
This is probably the most underused skill type.
- Purpose: Give the agent capabilities it doesn’t have natively, by bundling scripts that produce output the model alone cannot.
- Example: A codebase-visualizer skill that runs a bundled script to generate an interactive HTML tree of your project
- Why it matters: Without the script, this is a prompt. With the script, it is a tool.
Risks: bloat and security
It is tempting to install every best-practice skill you can find, but it is usually a mistake.
- Context bloat: even with lazy loading, the agent still scans every installed skill description on every turn. If you have 50 skills, you have added 2,000+ tokens of routing noise to each prompt.
- Prompt-injection risk: a skill is an executable prompt. A malicious third-party skill can embed hidden instructions that alter agent behavior. Always audit
SKILL.mdand any associated scripts before adding a skill to your harness.
How to: install skills for your agent
MCP for stateful integrations
MCP (Model Context Protocol) is an open standard for structured communication between an agent and external systems. In practice, an MCP server is a small Node.js or Python service that exposes typed tools the agent can call. The agent discovers tools, invokes one, and gets a structured response back.
That matters in two main cases:
1. Authenticated integrations
Some systems need a persistent, credentialed connection: Atlassian, GitHub, Context7, and others.
Manually managing tokens in shell environment variables or passing credentials as CLI flags is fragile and error-prone. MCP solves authentication once and exposes structured actions on top.
2. External state manipulation
MCP is also the right tool when the agent needs to operate inside another system, not just query it.
A good example is Chrome DevTools MCP: the agent can open Chrome, inspect the live DOM and CSS, read console and network activity, simulate user flows, and record a performance trace through DevTools. It is not just fetching documentation about the page. It is operating inside a running browser session and reading the resulting state back. The state lives in Chrome, not in the context window, and MCP is the bridge.
MCP tools are usually not very token efficient. If you do not need authentication or persistent external state to operate inside another system, you probably do not need MCP. A skill usually solves the same problem with less overhead and less complexity.
WebSearch and WebFetch for retrieval
These tools are native to most modern agents. They solve two problems:
- Knowledge cutoff: a language model trains on a snapshot of the world at a specific date. For anything that changes, such as a new Next.js release, a revised Expo SDK, or a breaking change, the model does not know.
- Precision errors: even for stable APIs in training data, the model may generate plausible but incorrect details, such as wrong method signatures or invented edge-case behavior.
WebSearch and WebFetch are the answer to both. Architecturally, they provide retrieval on demand: instead of trusting pre-training weights, the agent fetches factual data from up-to-date sources and reasons from there.
- “Upgrade Storybook from v8 to v10.33 (latest). Don’t just upgrade version, make necessary corresponding API changes in the codebase. Use WebSearch to get up to date docs”
It is often worth making WebSearch usage explicit in your prompts, AGENTS.md, CLAUDE.md, or skills to replace the LLM’s default behavior:
“Prefer retrieval-led reasoning over pre-training-led reasoning whenever precision matters”
That shifts the default from “the model probably knows” to “check first before acting.”
CLI as the execution surface
CLI is the natural execution surface for agents, and it falls into two categories:
Native tools
Unix fundamentals (find, grep, sed, awk, jq, curl) and core git commands are deeply embedded in most agents’ training. They need no introduction and carry almost no context cost. The agent can chain them and adapt them to novel situations without explicit instructions.
Augmented CLIs
These are CLIs you can install to extend your agent’s capabilities, tools that are not part of the base toolchain but become available as soon as they are installed on the machine. In practice, if you want the agent to use them reliably, you also need to explicitly tell it they exist in AGENTS.md or in a skill.
A good example is gh, the official GitHub CLI. It unlocks direct access to GitHub operations from the shell.
The same logic applies across a broader tool set:
agent-browsergives the agent the ability to control a headless browser from the command line, which is useful for testing, debugging, or navigating the web UI during execution.- Cloud-provider CLIs such as
AWS CLIandAzure CLIexpose hundreds of operations the agent can chain directly, using syntax it already knows from training. - Custom CLIs built specifically for your infrastructure can expose internal operations behind an interface the agent can discover on demand via
--help.
When should you use the CLI? If a tool has a mature CLI and the agent can use it from its own training as a starting point, prefer the CLI. MCP wins when the tool has no CLI, when authentication is too awkward to manage cleanly in shell, or when the workflow requires persistent state in an external system.
Subagents as isolated workers
A subagent is an agent spawned by the main agent to handle a bounded subtask. It gets:
- its own context window
- its own tool access
- its own scope
- then returns a result to the parent
From a context-architecture point of view, this matters because it moves work out of the main context entirely.
Instead of loading a large codebase analysis or a long diagnostic sequence into the primary window, you delegate it. The parent agent sees a clean result, not all intermediate reasoning and file reads that produced it.
The practical gains are:
- Isolation: A subagent that goes wrong does not corrupt the main session’s context.
- Parallelism: Subagents can run concurrently on independent tasks, such as writing tests for module A while refactoring module B.
In practice, most agents handle this automatically. Claude Code, Codex, Kiro, and similar tools spawn subagents when tasks warrant it. You usually do not configure this, but if you want finer control, you can explicitly spawn custom subagents for well-defined subtasks.
Layer 3: the System layer (hooks and permissions)
This is the enforcement layer. Unlike the permanent and on-demand layers, it does not rely on the model’s judgment at all. It intercepts execution at lifecycle events and allows, blocks, or transforms actions before they reach the filesystem or external systems. Permissions and hooks run deterministically. They do not forget rules when the context gets crowded, which is why they are the most reliable enforcement surface in the harness.
Permissions
Permissions define what the agent is allowed to attempt: file-system access, network access, and whitelisted CLI commands. There is usually little to tweak here, but avoid whitelisting destructive commands you would never want executed without approval.
Hooks: deterministic enforcement
Where an AGENTS.md/CLAUDE.md rule can be ignored, a hook is a hard gate.
Unlike AGENTS.md/CLAUDE.md, hooks do not live in the prompt. They only inject content into context when they fail. That makes hooks ideal for rules you never want violated, without paying an ongoing context cost.
Note: the hook implementation described in this section corresponds to Claude Code. Other agents that implement hooks may expose a different model, event set, or handler system, since this layer is not yet truly standardized.
Handler types
Claude Code supports three handler types:
| Type | What it does | When to use it |
|---|---|---|
command | Runs a shell script | Structural checks, enforcement, formatting |
prompt | Sends context to a model for judgment calls | When the decision requires interpretation, not a hard rule |
agent | Spawns a subagent with tool access | Deep verification that needs codebase exploration |
Focus on command first. It is deterministic, fast, has no inference cost, and covers most enforcement needs.
Lifecycle events
Hooks attach to specific points in the agent’s execution cycle. Claude Code exposes many; two matter most:
PreToolUse fires before any tool executes. It is the only event that can block actions. Every tool call, Bash, Edit, Write, Read, WebFetch, Task, or any MCP tool, passes through here first. Your hook receives a JSON payload on stdin with the tool name, its full input, and session context.
Exit 0 and execution proceeds. Exit 2 with a message on stderr and the action is blocked, with that message fed directly back to the agent.
INPUT=$(cat)
command=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
if echo "$command" | grep -qE "(^|[\\s&|;])npm "; then
echo "Blocked: use pnpm, not npm." >&2
exit 2
fi
That makes PreToolUse the right place for policy enforcement and human-in-the-loop gates on irreversible operations like production deploys, database migrations, and git writes.
PostToolUse fires after a tool completes successfully. It cannot block, but it can inject structured feedback via additionalContext. The pattern is straightforward: run a quality check, capture output, return it to the agent. A linter catches an error; the error description flows back into context; the agent resolves it in its next action. This closes the loop without any human intervention.
FILE=$(echo "$(cat)" | jq -r '.tool_input.file_path // empty')
[[ "$FILE" =~ \.(ts|tsx)$ ]] || exit 0
npx prettier --write "$FILE" 2>/dev/null
if ! npx tsc --noEmit 2>&1 | head -20; then
echo "Type errors introduced - resolve before proceeding." >&2
fi
Use PreToolUse for policy guards and PostToolUse for cleanup and feedback.
How to: install hooks for Claude Code
Move hard rules out of AGENTS.md context
Many rules that clutter AGENTS.md/CLAUDE.md are actually enforcement candidates, not context candidates:
- “Always use pnpm, not npm or yarn.”
- “Never manually edit files in the
__generated__directory.” - “All commits must follow conventional commit format.”
These are hard constraints, not implicit knowledge. The use-pnpm rule becomes a PreToolUse hook inspecting every Bash command. The __generated__ protection becomes a file-path check on Write operations. Commit-format enforcement runs on Bash tools invoking git commit.
Moving enforcement rules out of permanent context and into hooks is one of the highest-leverage cleanups you can make. It keeps AGENTS.md/CLAUDE.md focused on what genuinely needs reasoning context and reserves the system layer for what requires absolute guarantees.
Layer 4: the Feedback layer (tests, build, lint, type checker)
This verification loop closes the agent action cycle. It is one of the most underbuilt layers in agentic setups, and one of the most important to get right.
The agent can produce something, report success, and still be wrong. The feature might work, but the code quality can be low. The feedback layer exists to catch that. Tests validate functional correctness, type checking catches structural breakage early, and linting enforces consistency without needing a human to step in every time. Together, these checks keep the codebase maintainable and high quality and allow the agent to evolve more autonomously.
Type checking
tsc --noEmit is usually the fastest deterministic signal in a TypeScript stack. It knows your interfaces, exports, and function signatures. When the agent refactors a shared utility or changes the shape of a DTO, tsc reports the downstream breakage before tests or builds even start.
Stricter rules are free signal
With a human developer, a strict type config can feel like friction. It slows you down, forces explicit decisions, and surfaces errors you meant to clean up later. In agentic development, that logic flips. The agent has no real concept of “later.” It produces code, gets a signal, and reacts immediately.
The stricter the compiler, the richer the signal. A strict tsconfig is not a constraint on the agent. It is a free quality multiplier applied to everything it produces.
The rules worth enabling:
strict: trueintsconfig.jsonis non-negotiable in an agentic context.noUnusedLocalsandnoUnusedParameterscatch the debris of refactoring. The agent reorganizes logic and leaves behind variables and parameters that no longer serve a purpose.allowUnreachableCode: falseandallowUnusedLabels: falsesurface dead code the moment it is introduced.noUncheckedSideEffectImports: trueblocks side-effect-only imports where the module cannot be verified to exist.noFallthroughCasesInSwitch: trueforces explicit intent on every switch case.paths: { "@/*": ["./src/*"] }is not a validation rule, but a structural contract. It forces imports through resolved aliases rather than relative paths.
A stricter compiler does not slow the agent down. It gives it better signal on every turn.
Linting
The linter is an architectural contract
The same logic that applies to a strict tsconfig applies here too. Every lint rule you add is a zero-token sensor that fires on every change the agent makes without hoping the model remembered the right paragraph in CLAUDE.md, without waiting for review, without a human spotting the issue later. The difference is that a type checker enforces structural correctness. A linter enforces intent: architectural decisions, team conventions, deprecated patterns, and domain-specific rules the type system cannot express.
An agent that writes “average” code is often an agent operating without enough constraints. The linter is one way to raise the floor.
The philosophy of strict baselines
Before writing custom rules, start with a strict baseline that treats lint errors as failures, not warnings. A strict baseline catches a whole class of LLM-shaped mistakes like unnecessary assertions, overly broad error handling, sloppy generics, barrel imports, missing exhaustive checks, etc… right when they appear. Quality then becomes a property of the environment, not something you have to ask for in a new prompt.
Ultracite is a good example of this philosophy. It is a highly opinionated lint preset that bundles hundreds of rules across TypeScript, React, accessibility, imports, and code quality, pre-tuned to be strict without being noisy. Whether you adopt Ultracite itself or assemble your own equivalent, the principle is the same: a strict baseline replaces tedious back-and-forth with the agent and gives you high signal-to-noise enforcement out of the box.
File and function size limits as architectural guardrails
LLMs tend to produce large, monolithic files. A 200-line utility quickly becomes an 800-line file as the agent iterates. The problem is not just readability: performance degrades as context within a file grows. The model spends more tokens tracking internal references, local state, and nested logic, and less on the actual task which makes it harder to test, review, and maintain.
You can solve this deterministically with built-in ESLint/OxLint rules that enforce size limits:
{
"rules": {
"max-lines": ["error", { "max": 600, "skipBlankLines": true, "skipComments": true }],
"max-lines-per-function": ["error", { "max": 250, "skipBlankLines": true, "skipComments": true }]
}
}
These constraints encode principles you would enforce as a developer anyway if you care about clean code architecture and patterns: composability, separation of concerns, and testable units. The difference is that a lint rule applies them automatically and immediately, enforcing deterministically what would otherwise require constant vigilance—without waiting for review, without relying on the LLM’s judgment in the moment. The agent adapts by producing smaller, more focused units from the start, and the codebase stays navigable as it grows.
Project-specific rules are the real leverage
The highest-leverage linting work is the rules you write yourself, specific to your codebase, your domain, and your team’s accumulated knowledge.
Every architectural decision that currently lives as tacit team knowledge is a lint rule that only waits to exist:
- “Do not import the database layer from UI components.”
- “Use the internal `httpClient` wrapper, not raw `fetch`.”
- “The payments module cannot import from analytics.”
- “We deprecated `moment`, use `date-fns`.”
Each of these exists as a comment in a PR, a section in a wiki, or tribal knowledge in someone’s head, all of which the agent will never reliably reach, and none of which survive team turnover. Turn them into rules, and they become part of the environment the agent operates inside.
no-restricted-imports is the simplest governance primitive:
"no-restricted-imports": ["error", {
"paths": [
{ "name": "axios", "message": "Use the internal httpClient wrapper instead." },
{ "name": "moment", "message": "Use date-fns. moment is deprecated." }
]
}]
For architectural boundaries, eslint-plugin-boundaries goes further. It lets you declare which layers can import from which: UI, domain, infrastructure, shared, and turns every violation into an immediate, local error before it reaches review, before it reaches CI, before it propagates across the codebase.
Every time a pattern appears more than twice in code review, ask whether it can become a lint rule. If yes, it probably should. A recurring review comment is a lint rule waiting to exist, and in an agentic workflow, a lint rule is considerably more reliable than a comment.
The more project-specific rules you encode, the more the agent’s output reflects your actual codebase instead of statistical averages from training data. Each rule is another sensor. More sensors means better signal. Better signal usually means better output.
Tests
Tests as behavioral signal
Tests are the most direct feedback signal in your harness. A type checker tells the agent the code is structurally valid, a linter tells it the code follows the rules, and tests tell it whether the code does what it’s supposed to do.
Writing tests used to be expensive and tedious, so teams sometimes settled for thin coverage and happy-path-only suites. The feedback loop was limited by how much pain the team was willing to absorb.
That cost structure has changed. Describe the behavior, point the agent at the module, and it can draft a test suite quickly. The practical implication is that coverage gaps are now feedback-loop gaps, and weak tests are bad signals. The agent will keep moving either way. If the suite does not clearly define correct behavior, nothing reliably catches drift when it happens.
A strict baseline and high-quality tests create a virtuous circle: they become tangible anchors that guide the agent’s next changes and let it evolve in the codebase with confidence.
Your codebase is the highest signal
A tight CLAUDE.md and quality skills is simply good documentation. A strict TypeScript configuration is what good engineers try to enforce on every codebase. Lint rules that encode architectural decisions are written institutional knowledge. Tests as a “feedback loop” are not a new insight—it is one of the oldest ideas in software quality.
Harness engineering is just good engineering
What is new is the cost of not doing it. When a human developer skips documentation or writes a weak test, the gap is often compensated by the team’s judgment and memory that is capable of navigating those gaps. The system is imperfect, but it can generally hold together.
An agent has none of that. Every gap in your harness is a gap the agent may fall into.
The paradox is that a well-engineered codebase barely needs CLAUDE.md at all.
Agents are strong pattern matchers. If architectural decisions and code patterns show up consistently, the agent does not need the rules spelled out every time because it can read them from the environment.
Manual context layers exist to compensate for gaps. Eliminate the gaps and you eliminate most of what those AGENTS and skills files needed to say.
The discipline harness engineering asks for is the same discipline good engineering has always asked for: encode decisions so they outlive the people who made them, prefer deterministic enforcement over tribal knowledge, and close feedback loops early.
What has changed is where your attention goes: the agent writes the code, and your job is to review and improve the environment it operates in. The underrated promise of agentic development is that a well-designed codebase, under constant automated pressure, converges toward optimal quality faster than any team ever could manually.
Sources
- AGENTS.md outperforms skills in our agent evals
- Lessons from Building Claude Code: How We Use Skills
- Evaluating AGENTS.md: Are Repository-Level Context Files Helpful for Coding Agents?
- Your AGENTS.md Is Just Band-Aid
- You Don’t Know Claude Code: Architecture, Governance, and Engineering Practices
- You Don’t Know AI Agents: Principles, Architecture, and Engineering Practices
- Claude Code Documentation
Harness Engineering : la Context-Layer Architecture au service du développement agentique
Pour les développeurs qui naviguent entre AGENTS.md/CLAUDE.md, les skills, les hooks, MCP, et tout le reste.
Pourquoi c’est important
Vous avez configuré Claude Code, ajouté quelques serveurs MCP, lancé une commande /init pour générer un CLAUDE.md, et peut-être même ajouté quelques skills. Avec la dernière génération de LLM, ça fonctionne globalement bien la plupart du temps. Mais malgré cela, petit à petit, plus de nouvelles fonctionnalités sont implémentées, plus les choses peuvent devenir bancales : l’agent ignore certains skills, le contexte se remplit trop vite, et la qualité des réponses et du code se dégrade.
Le réflexe habituel est d’en ajouter davantage : plus de skills, plus de règles, plus de documentation. Et pourtant, même si cela peut sembler contre-intuitif, cela dégrade souvent la qualité du code. La plupart des ratés de l’agent sont des échecs de “context management”, et entasser du contenu dans la fenêtre de contexte aggrave généralement la situation.
Chaque règle ajoutée au
CLAUDE.md, chaque skill, chaque hook, est un patch.
Ces règles compensent quelque chose que la codebase ne parvient pas à communiquer d’elle-même. Un module bien structuré, avec des conventions cohérentes, n’a pas besoin d’un paragraphe de règles implicites pour être compris : l’agent peut le lire directement. Ce changement de paradigme est important car il redéfinit le rôle réel du harness engineering. Le but n’est pas d’empiler des couches de règles, mais de rendre chacune superflue, une décision à la fois, en l’encodant dans la codebase elle-même, là où elle devient permanente, visible et impossible à ignorer. Analyser les couches du contexte nous permet justement de révéler précisément où se situent les lacunes.
Le problème central
Le contexte est un problème de qualité de l’information, pas de capacité
Le modèle d’exécution central d’un agent est une boucle itérative :
À chaque étape, l’agent puise dans sa “context window” : un buffer de taille fixe qui contient tout ce qu’il “sait” à un instant donné sur la session, y compris les instructions, l’historique de conversation, le contenu des fichiers et les résultats d’appels d’outils. Quand ce buffer devient surchargé, l’agent ne se dégrade pas proprement : il commence à faire des erreurs subtiles.
Une mauvaise information dans le contexte est pire qu’une information absente. Le signal utile s’enfouit sous du contenu non pertinent, et l’agent cesse de distinguer les deux de manière fiable.
La réponse : les context-layers (ou “couches de contexte”)
La réponse n’est pas d’ajouter plus de contexte, mais de construire un harness (un cadre) pensé autour de la façon dont chaque outil interagit avec la fenêtre de contexte.
Le harness est l'ensemble des outils, contraintes et feedbacks qui font fonctionner ces couches de contexte ensemble.
Un modèle mental simple
Context-Layer Architecture
En pratique :
- Permanent : ce qui doit être présent systématiquement.
- On-demand : ce qui ne doit être fourni que quand l’agent en a besoin.
- System : ce qui doit être imposé par le système (ou l’OS).
- Feedback : ce qui vérifie le résultat après exécution.
Couche 1 : Permanent context (AGENTS.md/CLAUDE.md)
Il s’agit du fichier Markdown à la racine du projet qui est systématiquement chargé dans le contexte de l’agent, sans être explicitement invoqué.
Le premier réflexe, quand on en met un en place, est d’écrire tout ce qui décrit le repo : vue d’ensemble de l’architecture, structure des dossiers, conventions d’équipe, choix des librairies, notes d’onboarding, etc… Il faut résister à cette envie. Un fichier de contexte permanent doit être court, strict et opérationnel. Si une règle ne vaut pas la peine d’être appliquée à chaque tâche, elle n’a probablement pas sa place ici.
Keep it short
Les fichiers AGENTS.md/CLAUDE.md ont tendance à diminuer le taux de réussite des tâches par rapport à l’absence totale de fichiers AGENTS.md/CLAUDE.md, tout en augmentant le coût d’inférence de plus de 20 %. Les fichiers auto-générés (via /init ou équivalent) sont souvent les premiers coupables : ils forcent l’agent à dépenser des tokens de raisonnement sur des informations qu’il pourrait déduire simplement en lisant le code. Des fichiers .md trop volumineux, contradictoires ou sur-spécifiés transforment l’info utile en bruit.
Ce qui doit être dans CLAUDE.md/AGENTS.md
L’agent sait déjà lire votre codebase. Ce qui l’aide, c’est ce qu’il ne peut pas déduire du code : de la connaissance tacite, des contraintes non évidentes, des pièges subtils qui ont déjà provoqué de vrais problèmes. Il faut penser ce fichier comme la courte liste de choses que vous diriez à un dev qui arrive sur le projet.
Trois types de contenu y ont leur place :
Contraintes que l’agent pourrait ne pas déduire du contexte seul.
- “Always use pnpm, not npm.”
Gotchas
Pièges subtils propres à la codebase.
- “We need to keep folder
/pointOfSaleOldfor backward compatibility. We will remove it once we’ll turn the feature flag on.” - “The auth token lifecycle is per-session, not per-request. Storing it in a closure or
WeakMapwill cause stale-token bugs on long connections.”
Retrieval nudges
Aidez l’agent à obtenir le contexte pertinent lorsque la documentation d’API ne fait pas partie de ses données d’entraînement, en l’orientant vers les skills adaptés, en utilisant la recherche web ou en explorant d’autres repos
- “Prefer retrieval-led reasoning over pre-training-led reasoning when using the Expo SDK: always use WebSearch to get docs matching the specific version.”
- “
business-logicis a sibling repo you may need to navigate and edit when necessary (cd ../business-logic).”
Note : dans les sections suivantes, vous verrez qu'une partie de ces éléments peut souvent être déplacée vers la couche "on-demand" ou "system" pour améliorer encore la gestion du contexte.
Couche 2 : On-Demand Layer (Skills, MCP, WebSearch, CLI, Subagents)
Tout ce qui n’a pas besoin de résider dans le contexte permanent doit, par défaut, vivre ici.
Le principe est simple : si l’information est spécialisée ou pertinente seulement dans certains contextes, elle doit être récupérée au moment où on en a besoin. Cela réduit le bruit, économise la “context window” et améliore la précision.
Skills
Quand ils sont bien conçus, les skills sont l’un des leviers les plus efficaces d’un harness. Ils déplacent les connaissances spécialisées hors du contexte permanent vers un modèle de récupération à la demande : l’agent va chercher ce dont il a besoin, quand il en a besoin. La fenêtre de contexte reste propre, et vous ne payez le coût de l’expertise que lorsque vous en avez réellement besoin. C’est la solution au problème du CLAUDE.md qui grossit sans fin : le contexte permanent est un coût fixe tandis que les skills sont un coût variable. En pratique, migrez autant que possible les règles de votre AGENTS.md / CLAUDE.md vers des skills dédiées.
Un skill n’est pas toujours seulement un fichier .md. C’est un répertoire en trois parties :
SKILL.md(obligatoire) : contient un frontmatter YAML (métadonnées) et des instructions en Markdown. L’agent ne charge que le nom et la description du frontmatter dans son contexte. S’il juge que la description correspond à la demande de l’utilisateur, il ouvre ensuite le fichier en entier pour suivre les instructions.scripts/(optionnel) : du code exécutable (Bash, JS/TS, Python) qui permet à l’agent d’effectuer des actions au LLM.references/(optionnel) : de la documentation plus approfondie, chargée uniquement si l’agent doit vérifier un point bien précis en cours de tâche. C’est une sous-couche supplémentaire du contexte à la demande.
Les trois grands types de skills
1. Skills de documentation et de connaissance
Même les modèles les plus avancés ont une “knowledge cutoff”, une date de coupure des connaissances liée à la fin de leur entrainement.
- But : fournir une information que l’agent ne connaît pas, ou qu’il risque de mal se rappeler.
- Exemple : si vous utilisez Expo SDK 55, l’agent peut ne pas connaître les détails de l’API, simplement parce que cette version en particulier n’était peut-être pas dans ses données d’entraînement.
- Solution : Expo Skills
2. Skills de “best practices”
Les LLM ont tendance à produire du code de “moyenne” qualité.
- But : pousser l’implémentation vers un niveau de qualité expert, aligné avec les standards du projet.
- Exemple : un skill construit à partir de l’article des best practices de l’équipe React You Might Not Need an Effect.
- Solution : React useEffect Skill
3. Skills de “tooling”
C’est probablement le type de skill le plus sous-utilisé.
- But : donner à l’agent des capacités qu’il n’a pas nativement, en embarquant des scripts qui produisent un résultat que le modèle seul ne peut pas produire.
- Exemple : un skill codebase-visualizer qui exécute un script embarqué pour générer un arbre HTML interactif du projet.
- Pourquoi c’est important : sans le script, ce n’est qu’un prompt. Avec le script, c’est un outil.
Risques : “bloat” et sécurité
Il est tentant d’installer tous les skills de bonnes pratiques que vous trouvez, mais c’est généralement une erreur :
- Bloat de contexte : même avec du lazy loading, l’agent parcourt la description de chaque skill installé à chaque tour. Avec 50 skills, vous ajoutez plus de 2 000 tokens de bruit à chaque prompt.
- Risque de prompt injection : un skill est un prompt exécutable. Un skill tiers malveillant peut embarquer des instructions cachées qui modifient le comportement de l’agent. Auditez toujours le
SKILL.mdet les scripts associés avant d’ajouter un skill à votre harness.
How to : installer des skills pour votre agent
MCP pour les intégrations “stateful”
MCP (Model Context Protocol) est un standard ouvert pour la communication structurée entre un agent et des systèmes externes. Concrètement, un serveur MCP est un petit service Node.js ou Python qui expose des outils typés que l’agent peut appeler. L’agent découvre les outils, en invoque un, et reçoit une réponse structurée en retour. Cela devient pertinent dans deux cas principaux :
1. Intégrations authentifiées
Certains systèmes nécessitent une connexion persistante avec credentials : Atlassian, GitHub, Context7, et autres.
2. Manipulation d’état externe
MCP est aussi le bon outil quand l’agent doit opérer à l’intérieur d’un autre système, pas seulement le requêter.
Un bon exemple est Chrome DevTools MCP : l’agent peut ouvrir Chrome, inspecter le DOM et le CSS en direct, lire l’activité console et réseau, simuler des flux utilisateur, et enregistrer une trace de performance via DevTools. Il ne se contente pas de récupérer de la documentation sur la page. Il opère à l’intérieur d’une session navigateur active et lit le state résultant. Le state réside dans Chrome, pas dans la fenêtre de contexte, et MCP est le pont.
Les outils MCP ne sont généralement pas très efficaces en termes de tokens. Si vous n’avez pas besoin d’authentification ou d’état externe persistant pour opérer à l’intérieur d’un autre système, vous n’avez probablement pas besoin de MCP. Un skill résout généralement le même problème avec moins d’overhead et moins de complexité.
WebSearch et WebFetch pour la récupération d’information
Ces outils sont natifs à la plupart des agents modernes. Ils résolvent deux problèmes :
- Knowledge cutoff: un modèle de langage s’entraîne sur un instantané du monde à une date donnée. Pour tout ce qui évolue, une nouvelle version de Next.js, d’Expo SDK, un breaking change, le modèle ne sait pas.
- Erreurs de précision: même pour des APIs stables présentes dans les données d’entraînement, le modèle peut générer des détails plausibles mais incorrects : mauvaises signatures de méthodes, comportements de cas limites inventés, etc…
WebSearch et WebFetch répondent aux deux. Architecturalement, ils fournissent de la récupération à la demande : au lieu de se fier aux poids du pré-entraînement, l’agent récupère la donnée factuelle depuis des sources à jour et raisonne à partir de celle-ci.
- “Upgrade Storybook from v8 to v10.33 (latest). Don’t just upgrade version, make necessary corresponding API changes in the codebase. Use WebSearch to get up to date docs”
Il est souvent très payant de rendre explicite dans vos prompts, votre AGENTS.md, CLAUDE.md, ou vos skills le fait d’utiliser le tool WebSearch afin de remplacer le comportement par défaut du LLM :
“Prefer retrieval-led reasoning over pre-training-led reasoning whenever precision matters”
Cela déplace le comportement par défaut de “le modèle sait probablement” vers “vérifier d’abord avant d’agir.”
La CLI comme surface d’exécution
La CLI est une surface d’exécution naturelle pour les agents, et elle se divise en deux catégories :
Outils natifs
Les fondamentaux Unix (find, grep, sed, awk, jq, curl) et les commandes git de base sont profondément ancrés dans l’entraînement de la plupart des LLM. Ils ne nécessitent aucune introduction et ont un coût de contexte quasi nul. L’agent peut les enchaîner et les adapter à des situations nouvelles sans instructions explicites.
CLI augmentées
Ce sont les CLIs que vous pouvez installer pour étendre les capacités de votre agent, des outils qui ne font pas partie de la chaîne d’outils de base mais deviennent disponibles dès qu’ils sont installés sur la machine. En pratique, pour qu’un agent pense à les utiliser de manière fiable, il faut aussi lui signaler explicitement leur existence dans AGENTS.md ou dans un skill.
Un bon exemple est gh, la GitHub CLI officielle. Elle débloque un accès direct aux opérations GitHub depuis le shell.
La même logique s’applique à un ensemble d’outils plus large :
agent-browserdonne à l’agent la capacité de contrôler un navigateur headless depuis la ligne de commande, utile pour tester, débugger ou naviger dans l’UI d’une app.- Les CLIs de cloud providers comme
AWS CLIetAzure CLIexposent des centaines d’opérations que l’agent peut enchaîner directement, avec une syntaxe qu’il connaît déjà depuis l’entraînement. - Des CLI custom construites spécifiquement pour vos projets
Quand utiliser la CLI ? Si un outil dispose d’une CLI mature et que l’agent peut s’appuyer sur ses connaissances d’entraînement comme point de départ, préférez la CLI. MCP s’impose quand l’outil n’a pas de CLI, quand l’authentification est trop délicate à gérer proprement en shell, ou quand le workflow nécessite un état persistant dans un système externe.
Les subagents comme workers isolés
Un subagent est un agent “spawné” par l’agent principal pour gérer une sous-tâche délimitée. Il dispose :
- de sa propre fenêtre de contexte
- de son propre accès aux outils
- de son propre scope
- puis retourne un résultat au parent
Du point de vue de la Context-Layer Architecture, cela compte car cela déplace le travail hors du contexte principal. Au lieu de charger une analyse approfondie de la codebase (ou une longue séquence de diagnostic) dans la fenêtre principale, vous déléguez. L’agent parent voit simplement le résultat, pas tout le raisonnement intermédiaire ni les lectures de fichiers qui l’ont produit. Les bénéfices concrets sont :
- Isolation : chaque subagent travaille avec sa propre fenêtre de contexte et évite ainsi qu’une tâche latérale pollue le contexte principal.
- Parallélisme : ils peuvent avancer en même temps sur des tâches indépendantes, comme écrire des tests pour le module A pendant qu’un refactor a lieu sur le module B.
En pratique, la plupart des agents gèrent ça automatiquement. Claude Code, Codex, Kiro et des outils similaires spawnent des subagents quand les tâches le justifient. Vous ne configurez généralement pas cela, mais vous pouvez, si vous souhaitez un contrôle plus fin, spawner des subagents personnalisés pour des sous-tâches bien définies.
Couche 3 : System Layer (hooks et permissions)
C’est la couche d’enforcement, qui permet de forcer les choses au niveau system. Contrairement à la couche permanente et à la couche on-demand, elle ne repose pas du tout sur le jugement probabiliste du LLM. Elle intercepte l’exécution à des événements de cycle de vie et autorise, bloque, ou transforme les actions avant qu’elles n’atteignent le filesystem ou des systèmes externes. Les permissions et les hooks s’exécutent de manière déterministe. Ils n’oublient jamais les règles quand le contexte est saturé, c’est pourquoi ils constituent la surface d’enforcement la plus fiable du harness.
Permissions
Les permissions définissent ce que l’agent est autorisé à faire : accès au filesystem, accès réseau, et des commandes CLI autorisées. Il y a généralement peu à ajuster ici, mais évitez d’autoriser des commandes destructives que vous ne voudriez jamais voir s’exécuter sans approbation.
Hooks : enforcement déterministe
Là où une règle dans AGENTS.md/CLAUDE.md peut être ignorée, un hook est une barrière stricte.
Contrairement aux AGENTS.md/CLAUDE.md, les hooks ne vivent pas dans le prompt. Ils n’injectent du contenu dans le contexte que quand ils échouent. Cela rend les hooks idéaux pour les règles que vous ne voulez jamais voir violées, sans payer un coût contextuel permanent.
Note : l'implémentation des hooks décrite dans cette section correspond à celle de Claude Code. Les autres agents qui implémentent les hooks peuvent exposer un modèle, des événements ou des handlers différents, car cette couche n'est pas encore réellement standardisée.
Types de handlers
Claude Code supporte trois types de handlers :
| Type | What it does | When to use it |
|---|---|---|
command | Exécute un script shell | Vérifications structurelles, enforcement, formatage |
prompt | Envoie le contexte au LLM | Quand la décision nécessite une interprétation, pas une règle dure |
agent | Spawn un subagent avec accès aux outils | Vérification approfondie nécessitant l’exploration de la codebase |
Concentrez-vous d’abord sur command. Il est déterministe, rapide, n’a aucun coût d’inférence, et couvre la plupart des besoins.
PreToolUsepour valider ou refuser une action avant exécution.PostToolUsepour le “cleanup”, les vérifications ou le ” feedback ” après exécution.
Utilisez PreToolUse pour les “policy guards” et PostToolUse pour le “cleanup” et le “feedback”.
Lifecycle events
Les hooks s’attachent à des points spécifiques du cycle d’exécution de l’agent. Claude Code en expose beaucoup, mais deux couvrent la plupart des cas qui nous intéressent :
PreToolUse se déclenche avant que tout outil ne s’exécute. C’est le seul événement qui peut bloquer des actions. Chaque appel d’outil, Bash, Edit, Write, Read, WebFetch, Task, ou tout outil MCP, passe d’abord forcément par ici. Le hook reçoit une payload JSON sur stdin avec le nom de l’outil, son input complet, et le contexte de session.
Exit 0 et l’exécution continue. Exit 2 avec un message sur stderr et l’action est bloquée, avec un message retourné directement à l’agent.
INPUT=$(cat)
command=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
if echo "$command" | grep -qE "(^|[\\s&|;])npm "; then
echo "Blocked: use pnpm, not npm." >&2
exit 2
fi
PreToolUse est donc le parfait candidat pour forcer des politiques strictes sur les opérations irréversibles comme les déploiements en production, les migrations de base de données, et les écritures git.
PostToolUse se déclenche après qu’un outil s’est exécuté avec succès. Il ne peut pas bloquer, mais peut injecter du feedback structuré via additionalContext. Le pattern est simple : lancer un quality check, capturer l’output, le retourner à l’agent. Un linter détecte une erreur, la description de l’erreur revient dans le contexte, l’agent la corrige à sa prochaine action. Cela ferme la feedback loop sans aucune intervention humaine.
FILE=$(echo "$(cat)" | jq -r '.tool_input.file_path // empty')
[[ "$FILE" =~ \.(ts|tsx)$ ]] || exit 0
npx prettier --write "$FILE" 2>/dev/null
if ! npx tsc --noEmit 2>&1 | head -20; then
echo "Type errors introduced - resolve before proceeding." >&2
fi
How to : installer des hooks pour Claude Code
Déplacer les règles strictes hors du AGENTS.md
Beaucoup de règles qui encombrent AGENTS.md/CLAUDE.md sont en réalité des candidats à l’enforcement, pas au contexte :
- “Toujours utiliser pnpm, jamais npm ni yarn.”
- “Ne jamais modifier manuellement les fichiers du dossier
__generated__.” - “Tous les commits doivent suivre le format conventional commit.”
Ce sont des contraintes strictes, pas de la connaissance implicite. La règle use-pnpm devient un hook PreToolUse qui inspecte chaque commande shell. La protection de __generated__ devient un contrôle de chemin sur les opérations d’écriture. Le format des commits s’impose sur les outils shell qui invoquent git commit.
Déplacer les règles d‘“enforcement” hors du contexte permanent et dans les hooks est l’un des nettoyages de contexte les plus effciaces que vous puissiez faire. Cela permet de garder AGENTS.md/CLAUDE.md centré sur ce qui a réellement besoin de contexte, et de réserver la couche système à ce qui exige des garanties absolues.
Couche 4 : Feedback Layer (tests, lint, type check)
Cette boucle de vérification referme le cycle d’action de l’agent. C’est l’une des couches les moins développées dans beaucoup de setups agentiques, et pourtant l’une des plus importantes à bien construire.
L’agent peut produire quelque chose, annoncer que c’est réussi, et pourtant se tromper. La fonctionnalité peut fonctionner, mais la qualité du code peut rester médiocre. La couche feedback est là pour capter cela. Les tests valident la correction fonctionnelle, le type checking attrape tôt les erreurs structurelles, et le lint impose de la cohérence sans dépendre d’un humain à chaque étape. Ensemble, ces vérifications maintiennent une codebase de qualité et permettent à l’agent d’évoluer de manière plus autonome.
Type checking
tsc --noEmit est souvent le signal déterministe le plus rapide dans une stack TypeScript. Il connaît vos interfaces, vos exports et les signatures de fonctions. Quand l’agent refactor un utilitaire partagé ou change la forme d’un DTO, tsc remonte les ruptures en aval avant même que les tests ou le build ne démarrent.
Des règles plus strictes = du signal gratuit
Pour un développeur humain, une config TypeScript très stricte peut ressembler à de la friction. Elle ralentit, force des décisions explicites, et fait remonter des erreurs qu’on comptait nettoyer plus tard. Dans le développement agentique, cette logique s’inverse. L’agent n’a pas vraiment de notion de “plus tard”. Il produit du code, reçoit un signal, et réagit immédiatement.
Plus le compilateur est strict, plus le signal est riche. Une config tsconfig stricte n’est pas une contrainte pour l’agent. C’est un multiplicateur de qualité gratuit appliqué à tout ce qu’il produit.
Les règles qui valent la peine d’être activées :
strict: truedanstsconfig.jsonest non négociable dans un contexte agentique.noUnusedLocalsetnoUnusedParametersattrapent les débris de refactor.allowUnreachableCode: falseetallowUnusedLabels: falsefont remonter le code mort instantanément.noUncheckedSideEffectImports: truebloque les imports à effets de bord quand l’existence du module n’est pas vérifiable.noFallthroughCasesInSwitch: trueimpose une intention explicite pour chaqueswitch.paths: { "@/*": ["./src/*"] }n’est pas une règle de validation, mais un contrat structurel.
Un compilateur plus strict ne ralentit pas l’agent. Il lui donne un meilleur signal à chaque fois.
Linting
Le linter est un contrat d’architecture
La même logique qu’avec un tsconfig strict s’applique ici. Chaque règle de lint ajoutée est un capteur à zéro token qui se déclenche sur chaque changement produit par l’agent, sans espérer que le modèle se souvienne du bon paragraphe dans CLAUDE.md, sans attendre la review, sans compter sur un humain pour repérer le problème plus tard. La différence, c’est que le type checker impose la correction structurelle. Le linter impose l’intention : décisions d’architecture, conventions d’équipe, patterns dépréciés et règles métier que le système de types ne sait pas exprimer.
Un agent qui produit du code moyen est souvent un agent qui travaille sans assez de contraintes. Le linter est une manière de relever le niveau.
La philosophie des “baselines” strictes
Avant d’écrire des règles custom, commencez par une base stricte qui traite les erreurs de lint en échec et non en warnings. Une baseline stricte capte toute une classe d’erreurs typiques des LLM, comme les assertions inutiles, une gestion d’erreurs exagérément large, des generics brouillons, des “barrel imports”, des “exhaustive checks” manquants, etc., au moment même où elles apparaissent. La qualité devient alors une propriété de l’environnement, pas quelque chose qu’il faut redemander dans un nouveau prompt.
Ultracite est un bon exemple de cette philosophie. C’est un preset de lint très “opinionated ” qui embarque des centaines de règles sur TypeScript, React, l’accessibilité, les imports et la qualité de code, calibrées pour être strictes sans créer trop de “bruit”. Que vous adoptiez Ultracite ou que vous composiez votre propre équivalent, le principe reste le même : une base stricte remplace une grande partie des allers-retours fastidieux avec l’agent et fournit, dès le départ, un “enforcement” avec un fort “signal-to-noise ratio”.
Les limites de taille des fichiers et des fonctions comme garde-fous architecturaux
Les LLM ont tendance à produire des fichiers volumineux et monolithiques. Un utilitaire de 200 lignes devient vite un fichier de 800 lignes au fil des itérations de l’agent. Le problème ne se limite pas à la lisibilité : les performances se dégradent à mesure que le contexte interne d’un fichier grossit. Le modèle dépense davantage de tokens à suivre les références internes, l’état local et la logique imbriquée, et moins sur la tâche elle-même, ce qui rend l’ensemble plus difficile à tester, relire et maintenir.
On peut résoudre cela de manière déterministe avec des règles ESLint/OxLint intégrées qui imposent des limites de taille :
{
"rules": {
"max-lines": ["error", { "max": 600, "skipBlankLines": true, "skipComments": true }],
"max-lines-per-function": ["error", { "max": 250, "skipBlankLines": true, "skipComments": true }]
}
}
Ces contraintes encodent des principes que vous appliqueriez déjà en tant que développeur pour peu que vous teniez à garder une architecture propre et à garantir de bons patterns : composabilité, séparation des responsabilités et unités testables. La différence, c’est qu’une règle de lint les applique automatiquement et immédiatement, en imposant de manière déterministe ce qui demanderait sinon une vigilance constante, sans attendre la review et sans dépendre du jugement du LLM. L’agent s’adapte en produisant dès le départ des unités plus petites et plus ciblées, et la codebase reste navigable à mesure qu’elle grandit.
Les règles spécifiques au projet sont le vrai levier
Les meilleures règles de linting sont les règles que vous écrivez vous-même, spécifiques à votre codebase, à votre domaine et à la connaissance accumulée par votre équipe.
Chaque décision d’architecture qui vit aujourd’hui comme règle tacite d’équipe est une règle de lint qui n’attend qu’à exister :
- “Do not import the database layer from UI components.”
- “Use the internal `httpClient` wrapper, not raw `fetch`.”
- “The payments module cannot import from analytics.”
- “We deprecated `moment`, use `date-fns`.”
Chacune de ces règles existe aujourd’hui sous forme de commentaire de PR, de section dans un wiki, ou de connaissance tacite dans la tête de quelqu’un. L’agent n’y accèdera jamais de manière fiable, et rien de tout cela ne résiste correctement au turnover d’équipe. Transformez-les en règles, et elles deviennent une partie de l’environnement dans lequel l’agent opère.
no-restricted-imports est la “primitive” de gouvernance la plus simple :
"no-restricted-imports": ["error", {
"paths": [
{ "name": "axios", "message": "Use the internal httpClient wrapper instead." },
{ "name": "moment", "message": "Use date-fns. moment is deprecated." }
]
}]
Pour l’architecture, eslint-plugin-boundaries va plus loin. Il permet de déclarer quelles couches peuvent importer depuis quelles autres : UI, domain, infra, shared, et transforme chaque violation en erreur locale immédiate.
Chaque fois qu’un pattern apparaît plus de deux fois en review, demandez-vous s’il peut devenir une règle de lint. Si oui, il devrait probablement le devenir. Un commentaire de review récurrent est une règle de lint qui attend d’exister, et dans un workflow agentique, une règle de lint est bien plus fiable qu’un commentaire de code review.
Plus vous encodez de règles spécifiques au projet, plus la sortie de l’agent reflète les vraies exigences de votre codebase plutôt que des moyennes statistiques issues des données d’entraînement. Chaque règle est un capteur supplémentaire. Plus de capteurs veut dire un meilleur signal. Et un meilleur signal permet à votre agent de vous donner un meilleur output.
Tests
Les tests comme signal comportemental
Les tests sont le signal de feedback le plus direct dans votre “harness”. Le type checker indique à l’agent que le code est structurellement valide, le linter lui dit qu’il respecte les règles, et les tests lui disent si le code fait réellement ce qu’il est censé faire.
Écrire des tests était autrefois coûteux et pénible, si bien que certaines équipes se contentaient parfois d’une couverture moyenne ou/et de tests passables. La boucle de feedback était limitée par la quantité jugée acceptable de “pain points” que l’équipe était prête à absorber. Aujourd’hui, le coût de la couverture et de la qualité des tests a changé. Vous décrivez le comportement, vous pointez l’agent vers le composant, et il peut proposer rapidement une suite de tests. La conséquence pratique : les trous dans la couverture de tests sont désormais des trous dans la “feedback loop”.
Inversement, exiger une couverture complète et des tests de qualité crée un cercle vertueux. Ces tests deviennent des repères tangibles : ils guident l’agent dans ses prochaines modifications et lui permettent d’évoluer avec confiance dans la codebase.
La codebase est le meilleur contexte
Une idée centrale traverse tout l’article :
Un CLAUDE.md et des skills de qualité, c’est simplement de la bonne documentation. Une configuration TypeScript stricte, c’est ce que les développeurs essaient déjà d’imposer sur chaque codebase. Des règles de lint qui encodent les décisions d’architecture, c’est de la connaissance institutionnelle écrite. Les tests comme “feedback loop” ne sont pas une idée nouvelle, c’est l’une des idées les plus anciennes de la qualité logicielle.
Le “harness engineering”, c’est simplement du bon engineering
Ce qui est nouveau, c’est le coût de ne pas le faire. Quand un développeur humain passe à côté d’une doc ou écrit un test passable, le manque est souvent compensé par le jugement et la mémoire de l’équipe qui est capable de naviguer malgré ces lacunes. Le système est imparfait, mais il peut généralement tenir.
Un agent n’a rien de tout cela. Chaque trou dans votre harness est un trou dans lequel l’agent risque de tomber.
Le paradoxe, c’est qu’une codebase bien conçue n’a presque plus besoin de CLAUDE.md.
Les agents sont d’excellents “pattern matchers”. Si les décisions d’architecture et les patterns de code apparaissent de manière cohérente, l’agent n’a pas besoin qu’on lui réécrive les règles à chaque fois car il peut les lire dans l’environnement.
Les couches de contexte manuelles existent pour compenser les manques. Éliminez les manques, et vous éliminerez l’essentiel de ce que ces fichiers d’AGENTS et de skills avaient besoin de dire.
La discipline que demande le “harness engineering” est la même que celle qu’a toujours demandée le bon engineering : encoder les décisions pour qu’elles survivent à ceux qui les ont prises, préférer des règles déterministe à la connaissance tacite, et refermer les “feedback loops” tôt.
Ce qui change, c’est là où porte votre attention : l’agent écrit le code, et votre travail consiste à reviewer et améliorer l’environnement dans lequel il évolue. La promesse sous-estimée du développement agentique, c’est qu’une codebase bien conçue, soumise à une pression automatisée constante, converge vers la qualité optimale plus vite qu’aucune équipe n’aurait pu le faire manuellement auparavant.
Sources
- AGENTS.md outperforms skills in our agent evals
- Lessons from Building Claude Code: How We Use Skills
- Evaluating AGENTS.md: Are Repository-Level Context Files Helpful for Coding Agents?
- Your AGENTS.md Is Just Band-Aid
- You Don’t Know Claude Code: Architecture, Governance, and Engineering Practices
- You Don’t Know AI Agents: Principles, Architecture, and Engineering Practices
- Claude Code Documentation