Back to articles

Decisions Are All You Need

How to give agents consistent behaviour across sessions

17 Jan 2025 9 min read 1,719 words
claude-codearchitecturedecisions

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.

without decisions
Task 1: Agent creates UserService class
Task 2: Agent creates orderHelpers functions
Task 3: Agent creates PaymentManager singleton
Three different patterns. No consistency.
... hours of cleanup later ...

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.

The Real Fix
Document architectural decisions with clear reasoning. Enforce them with validators that run after every edit. The agent reads the decision, understands the why, and can't make choices that violate it. Reasoning + enforcement = consistency.

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.

decisions/INDEX.md
# Decision Index
ADR-001 Pure logic separation
ADR-002 API entry points via api.ts facade
ADR-003 Max 3 levels submodule depth
ADR-004 Validators export from schema.ts
ADR-005 Workflow functions in workflows/

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.

markdown
# 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.

json
{
  "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:

module-lint
ERROR: _logic/calculations.ts violates pure logic separation
Decision: Files in _logic/ must have no framework dependencies.
Reasoning: Pure functions are testable without mocking.
Violation: Found import of MutationCtx on line 3.
Fix: Move database operations to functions/internal.ts

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.

text
.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.

markdown
# 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.

Identify
AI spots inconsistency: "Some modules export from schema.ts, others from types.ts"
Collaborate
AI drafts decision record. You discuss trade-offs, refine reasoning, agree on compliance criteria.
Build
AI writes validation rules enforcing the decision. These go into the linting scripts.
Apply
AI updates codebase to match. The validators it just wrote confirm each file is correct.
repeat

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:

Can't remember
Reasoning is written down
Can't learn
Conventions are documented
Drifts over time
Validators catch deviations

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

1
Start with what you repeat

If you keep telling the AI "we do it this way because...", that's a decision record waiting to be written.

2
Write compliance first

If you can't describe how to verify the decision, it's too vague. Validators need clear criteria.

3
Build validators incrementally

Start with structure checks: does the file exist, is it in the right place. Add pattern checks as decisions accumulate.

4
Link templates to decisions

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.