Rules, Scopes, Validators, Gates, and Context
I want to share a mental model of how I work with coding agents. I believe that succesful usage of agents comes down to understanding their structure, and then deliberately designing around that structure.
What Is an Agent, Really?
After years of the term "agent" meaning a hundred different things to a hundred different people, we've finally converged on something useful. Simon Willison coined itl:
An LLM agent runs tools in a loop to achieve a goal.
This definition looks simple, but it contains everything we need to understand how to make agents work well. Let me break it down, because each component operates on fundamentally different principles:
"Runs tools" — This is deterministic. When an agent calls git commit or writes to a file, the outcome is predictable. The tool succeeds or fails. There's no probability involved.
"In a loop" — This is where learning happens. Each iteration feeds back into the next. The agent sees the result of its previous action—including any errors—and that information becomes part of its context for deciding what to do next. This is few-shot learning in action: every tool result is an example that shapes subsequent behavior.
"To achieve a goal" — This is probabilistic. The LLM decides what to do next based on what's in its context window. It's predicting which action will move it closer to the goal. This is where things can go wrong.
Key insight: we can use the deterministic parts (tool outcomes) to steer the probabilistic part (the LLM's decision-making). The loop architecture gives us few-shot learning for free: every tool result teaches the model something about this specific task. The question is: what do we want it to learn?
Two Approaches to Guiding an Agent
There are fundamentally two ways to prevent an agent from making mistakes:
The front-loading approach: Stuff the context window with instructions about everything that could go wrong. "Don't use deprecated APIs." "Follow our naming conventions." "Remember to handle edge cases." You're trying to prevent errors by telling the agent about them in advance.
The fail-fast approach: Let the agent try things, but set up deterministic checkpoints that catch problems immediately. When something goes wrong, the error becomes part of the context. The agent learns from the failure and adjusts.
The front-loading approach seems intuitive, but it has serious problems. You're burning context window tokens on potential issues that may never come up. You're relying on the probabilistic LLM to correctly apply instructions it read thousands of tokens ago. And you're not leveraging the loop at all—you're treating the agent like a one-shot prompt.
The fail-fast approach is fundamentally different. Instead of hoping the model remembers your instructions, you let it hit a wall and learn from the impact. A failed linter check is worth a thousand words of style guidelines. A type error is a precise, unambiguous signal. The deterministic tool tells the probabilistic model exactly what went wrong.
This is safer (errors get caught), more efficient (you only spend tokens on problems that actually occur), and more scalable (you can automate the checkpoints). The loop becomes a genuine learning mechanism instead of just a retry counter.
RSVG: A Mental Model for Validation
This brings me to what I call RSVG: Rules, Scopes, Validators, and Gates. It's a simpel mental model for thinking about where to put your deterministic checkpoints.
The logic flows naturally from what we just discussed. If we want to use deterministic signals to guide a probabilistic agent, we need to think clearly about:
- What should be checked (Rules)
- Where those checks apply (Scopes)
- How to check them (Validators)
- When to run the checks (Gates)
Rules: What Should Be Checked
Every codebase operates on conventions. Most are tribal knowledge, things you "just know" when you have worked on the project for a while. Agents have no access to this implicit knowledge unless you make it checkable.
Rules come in two flavors. General rules concern how code should be written: no circular dependencies, imports must be absolute, stuff like that. Specific rules concern what the code should do: users cannot withdraw more than their balance, API responses must complete within 200ms.
The key principle I try to follow when I work with agents: every disapproval is a missing rule. When you reject agent output and think "that's not how we do it here," you've discovered implicit knowledge. The question is: can you turn it into a check?
Scopes: Defining Boundaries
Scopes define the boundaries in which an agent can operate autonomously with full context. They therefore also define what rules apply where.
Think about it: if an agent needs to understand your entire 500k-line monorepo to make a change safely, it is far more likely that it will either burn massive amounts of context gathering that understanding, or it will make assumptions and break things. But if the relevant boundary is well-defined (a feature module, a service, a file type), the agent can have complete context about its working area without needing the whole picture.
In bullets, scopes are:
- Where validators run: A Python linter doesn't need to check TypeScript files
- Where an agent operates: The boundary of a single agent's autonomous work
- What context is needed: The minimal viable context window for a task
Just as rules should be defined clearly enough to produce pass/fail checks, scopes should be defined clearly by their boundaries. Those boundaries can be structural (directory-based, feature modules) or categorical (file types, layers of the architecture). What matters is that they're unambiguous.
Good codebase structure makes scoping easier. Colocated features with clear boundaries naturally create scopes. A tangled monolith where everything depends on everything makes scoping nearly impossible, and consequently makes autonomous agent work much harder.
Clear scopes also enable parallel execution. Agents working in different scopes are less likely to step on each other's files or generate merge conflicts.
Feature based scoping and structure is therefore, I think, a better suitable structure for working with agents.
Validators: How to Check
A validator is anything that checks whether a rule is being followed. The crucial distinction:
Hard validators are deterministic. Linters, type checkers, and test suites produce binary pass/fail. No judgment required. When they fail, the agent gets an unambiguous signal.
Soft validators require judgment. Sometimes you need an LLM to evaluate whether code follows a pattern. These are more expensive and less reliable, but sometimes necessary.
The goal is always to graduate soft validators to hard validators whenever possible. If you can write a static analysis rule that catches what a soft validator was catching, you've made progress. Deterministic validation is cheaper, more reliable, faster, and provides clearer learning signals.
Gates: When to Check
Gates are temporal checkpoints; often defined as hooks. Moments in the workflow where you demand validators pass before proceeding. Common gates:
onSave: Format, lint (immediate feedback)onCommit: Type check, unit tests (structural correctness)onPR: Integration tests, review (full verification)
The principle is to push failure left (catch errors at the earliest possible gate). An agent that gets immediate feedback from a failed lint check learns faster than one that only discovers problems at PR review.
Why does pushing left matter? Two reasons:
- Faster feedback loops mean more learning opportunities within the same context window. The agent can try, fail, learn, and retry immediatly.
- Cheaper corrections. Catching a naming convention violation at save-time costs almost nothing. Discovering it requires a refactor at PR review wastes significant compute.
This maps directly to how modern agent tooling works. Claude Code hooks let you wire up validators to lifecycle events like PreToolUse and PostToolUse. Cursor hooks offer similar capabilities with beforeShellExecution and afterFileModify. The pattern is the same: deterministic checks that run automatically at defined points, providing clear signals back to the agent.
The more you push validation left and make it deterministic, the more autonomy you can safely grant the agent. Good validators make good agents.
Autonomy Requires Context
Rules, scopes, validators and gates define boundaries, they are our fences. But the whole point of setting up validation infrastructure is to enable autonomy withini those fences. We want agents that can work on our codebase for extended periods without constant human intervention. The longer an agent can work autonomously while producing quality output, the more valuable it becomes.
The problem is that autonomous work requires two things that compete for the same limited resource:
Context about the task: What files are relevant? What's the current state? What has been tried?
Working memory for the loop: Tool results, error messages, intermediate outputs—all the learning signals that make the loop effective.
Both of these live in the context window. And the context window is finite. Andrej Karpathy describes it well:
Context engineering is the delicate art and science of filling the context window with just the right information for the next step.
This is the core problem. You want the context window to contain:
- Enough information to make good decisions
- Enough space for the feedback loop to operate
- Little enough noise that the signal comes through clearly
These requirements create tension. More context means better-informed decisions, but also less room for learning and higher risk of the model losing focus on what matters. Context engineering is all about optimizing this problem.
Patterns for Context Optimization
Over the past year or so, several patterns have emerged that help manage this tension. Let me briefly go through some of them...
Gradual Disclosure
Instead of front-loading all possible context, structure your codebase so context loads incrementally. The simplest version: a CLAUDE.md or AGENTS.md file in the repository root with high-level orientation, then more specific files in subdirectories. The agent loads only what's relevant to its current scope.
Anthropic's Agent Skills take this further. Skills are folders containing instructions, scripts, and resources that agents discover and load dynamically. A skill has a SKILL.md file with a small Yaml frontmatter, that contains instructions that Claude reads when relevant, if so, Claude can read the entire Markdown, and finally it can go even deeper to all the files in the folder.
Skills are model-invoked, not user-invoked. Claude decides when to load them based on the task. This is gradual disclosure at the architectural level, the agent loads specialized knowledge only when it determines that knowledge is relevant.
Think of skills as procedural memory. Each skill represents knowledge about how to accomplish something specific. At the start of a task, the agent just knows the catalogus index of these skills.
Sub-agents for Context Isolation
A second pattern that is implemented a lot is the pattern of sub-agents. When the main agent needs to do extensive searching, reading, or reasoning, it delegates to a sub-agent that starts with a fresh context window.
The sub-agent does all the token-expensive discovery work in its own space. Then it returns only the relevant results to the parent. The parent gets the answer without paying the token cost of the discovery process.
This is context compression through delegation. Anthropic's research on effective agent harnesses emphasizes this pattern: specialized sub-agents do focused work without polluting the main context. I also really like how AMP Code has a set of very opiniated sub agent definitions in their coding agent.
A particularly useful pattern is coordinator-executor: the coordinator plans and delegates, while executors do the actual work. When an executor fails and retries, those failed attempts consume the executor's context window—not the coordinator's. The coordinator stays clean, receiving only final results.
Common sub-agent types:
- Search agents: Find relevant files quickly with minimal context overhead
- Reasoning agents: Handle complex problems that need deep thought
- Verification agents: Run tests and validation in isolation
The way I see it, sub-agents are for controlling context, not for anthropomorphizing roles. Don't create a "Designer agent" and a "Coder agent" that chat with each other. Create isolated contexts for specific types of work.
Research-Plan-Execute
This third pattern structures workflow around intentional compaction. Instead of letting context grow until it collapses, you actively compress at strategic points. There are several set ups around this idea, one common example is research, plan, execute.
Research phase: Understand how the system works, find relevant files, stay objective. Output: a compressed research document with exact files and line numbers.
Planning phase: Outline exact steps, including code snippets and testing criteria. Output: a reviewable plan that even a less capable model could execute.
Execute phase: Implement the plan. Keep context low by not re-discovering things already known.
Each phase produces compressed artifacts that carry forward only essential information. You're constantly refreshing the context window with high-signal content.
The Agent Harness
All of these patterns come together in what's called an agent harness: the infrastructure that wraps around the core agent loop to make it effective.
The harness includes:
- Validation hooks that provide immediate deterministic feedback
- Context management that keeps the window focused
- Memory systems that persist knowledge across sessions
- Sub-agent orchestration for delegating specialized work
The harness is where you implement RSVG. It's where you put the gates, wire up the validators, enforce the scopes. The harness transforms a general-purpose LLM into an agent that can operate effectively on your specific codebase.
Core Principles
If I had to distill everything into three principles:
Make expectations checkable. Rules should be enforced by deterministic validators, not hoped for via instructions. Every time you reject agent output, ask: can I turn this into a check? Push validation left. Build high fences.
Use the loop for learning. The agent runs tools in a loop—that's few-shot learning waiting to happen. Let it try things and learn from failures. Deterministic error messages are worth more than pages of instructions.
Treat context as precious. The context window is not a dumping ground. It's the only input that determines output quality. Use sub-agents to isolate work. Use gradual disclosure to load only what's needed. Compress aggressively.
The models will keep getting better. The tooling will keep improving. But these principles (checkable rules, learning loops, managed context) aren't workarounds for current limitations. They're how you build robust systems around probabilistic components.
Jan Willem Altink