The Memory Problem
You've seen this before. User management gets a service class. Orders get helper functions. Payments get a singleton. Same agent, same codebase, three incompatible patterns.
This is what statelessness looks like in practice. AI agents have no memory between sessions, limited context within them. Every response generated fresh. The result isn't just inconsistency: it's technical debt that compounds with every task you delegate.
The obvious fix is rules files. CLAUDE.md, cursor rules, system prompts full of instructions. But rules without reasoning don't stick. "Use service classes" tells the agent what to do, not when or why. Edge cases get handled inconsistently. The agent follows the letter, misses the intent.
Rules also lack enforcement. Nothing stops the agent from ignoring them when pattern-matching pulls elsewhere. Suggestions aren't constraints.
Why Agents Need Decisions
Without documented decisions, agents pattern-match from whatever code they can see. They infer conventions from examples. Sometimes correctly. Often not.
The longer the session, the worse this gets. Structural choice in hour one based on one part of the codebase. By hour four, different code, incompatible choice. By hour six, inconsistent in ways that take hours to untangle.
Documented decisions prevent this. The agent doesn't infer, it reads. When encountering an edge case, it reasons from documented principles rather than guessing from patterns.
The result: consistency that compounds. Every decision aligns with every previous decision, because they all flow from the same documented reasoning.
Decision Records
A decision record documents one architectural choice: what was decided, why, and how to verify compliance.
# Decision: Pure Logic Separation
**Context:** Complex modules mix database access with calculations,
making unit testing difficult.
**Decision:** Separate pure functions into `_logic/` directories.
These files must have no database access and no framework imports.
**Reasoning:** Pure functions are trivially testable. No mocking
required. Fast test execution. Clear separation of concerns.
**Compliance:** Files in `_logic/` must not import MutationCtx,
QueryCtx, or @/_generated/server. No ctx.db.* calls.
The reasoning section is what matters. Rules tell the AI what to do. Reasoning tells it why, so it can handle cases the rule doesn't cover.
When the AI understands that _logic/ exists for testability, it knows a function doing date arithmetic belongs there. But a function that fetches data then does arithmetic? Needs to be split. The fetch stays in functions/, the calculation moves to _logic/.
Without reasoning, the AI pattern-matches. It sees "calculation" and puts everything in _logic/, database calls included.
Enforcement: Validators as Constraints
Decision records without enforcement are suggestions. The AI might follow them. It might not.
Validators turn decisions into constraints. Every time the AI edits a file, a validator checks whether the decision was followed. If not, the error message includes the decision details. The AI sees what it did wrong and how to fix it in a single response.
{
"hooks": {
"PostToolUse": [{
"matcher": "Edit|Write",
"hooks": [{
"type": "command",
"command": "pnpm lint --quick --file \"$file_path\""
}]
}]
}
}
This runs after every file edit. Each error type has a templated message: the decision, reasoning, and fix are pre-written for that violation category:
The AI doesn't need to look anything up. The error message contains everything: what decision was violated, why it exists, and the standard fix. The validator matches error patterns to pre-written remediation templates.
Self-correction at scale. The AI never drifts because every mistake comes with its own explanation and remedy.
For unattended refactoring using this pattern, see Stop Planning, Start Looping.
Context Injection
The AI can only reason about what it can see. Decision records work because you can inject them into context.
Skill files do this automatically. When you invoke a skill, Claude Code loads the skill's files into context. Your decisions, templates, and examples become available for the AI to reference.
.claude/skills/convex-module/
├── SKILL.md # Entry point
├── decisions/ # Decision records
│ ├── INDEX.md # Quick lookup
│ ├── logic-separation.md
│ └── schema-patterns.md
├── templates/ # Code patterns
└── validators/ # Enforcement scripts
The AI doesn't memorise your conventions. It reads them when needed. The skill file says "read these decisions before working on modules." The AI loads them, applies them, and the validators confirm compliance.
You can also inject context explicitly: "Read the decisions in .claude/skills/convex-module/decisions/ before restructuring this module." The AI loads the files, understands the reasoning, and works accordingly.
Plans as Executable Specifications
For larger work, decisions combine with plans. A plan is a structured document describing what to build, referencing decisions that apply.
# Plan: Restructure Hostaway Integration
## Decisions to Follow
- Read: decisions/api-entry-points.md
- Read: decisions/submodule-depth.md
- Read: decisions/workflow-patterns.md
## Steps
1. Create api.ts facade for public entry points
2. Move webhooks to webhooks/ submodule
3. Move rate sync to rates/ submodule
4. Ensure max 3 levels of nesting
5. Add CLAUDE.md documentation
## Validation
- Run: pnpm health-check --module hostaway
- All validators must pass before completion
The AI reads the plan, loads the referenced decisions, executes step by step, and validates after each change. The plan is an executable specification. Not instructions for a human to interpret, but a program for the AI to run.
The Collaborative Loop
Decision records aren't just documentation you write for the AI. The AI participates in creating them.
schema.ts, others from types.ts"The AI becomes a participant in architecture, not just an executor. It spots patterns across more code than you read. It suggests decisions based on what it's seen work.
Why This Works
AI agents are stateless. They can't remember. They can't learn from experience across sessions. Every limitation people complain about (inconsistency, drift, forgetting context) stems from this.
Decisions + validators work around the limitation entirely:
You're not making the agent smarter. You're giving it external memory and external reasoning it can reference. The agent's capability stays the same, but its outputs become consistent because the inputs (decisions with clear reasoning) are consistent.
This compounds over time. Each decision narrows the space of valid choices. The agent doesn't have to figure out the right approach from infinite possibilities. It reads the decisions and the right approach is specified. Better inputs, better outputs.
Getting Started
If you keep telling the AI "we do it this way because...", that's a decision record waiting to be written.
If you can't describe how to verify the decision, it's too vague. Validators need clear criteria.
Start with structure checks: does the file exist, is it in the right place. Add pattern checks as decisions accumulate.
When a template shows a pattern, link to the decision explaining why.
The first few decisions take effort. After that, the agent suggests new ones. It hits an edge case, asks how to handle it, and you realise there's a missing decision. Write it together, add the validator, continue.
Each decision makes the agent more capable. Not smarter, more informed. The reasoning accumulates. The constraints tighten. Until the agent can't easily make a wrong choice, because the decisions specify what right looks like.
This is how you get long-term coherent reasoning from a stateless system. You externalise the reasoning.
Decisions are all you need.