ADRs for Agent Context

James Phoenix
James Phoenix

Architecture Decision Records document WHY decisions were made. This context is invaluable for agents navigating your codebase.


What Are ADRs?

Architecture Decision Records capture important decisions and their context:

  • What was decided
  • Why it was decided
  • What alternatives were considered
  • What consequences follow

Without ADRs, agents (and future humans) must reverse-engineer intent from code.


Why ADRs Matter for Agents

Agents face a fundamental problem: code shows WHAT, not WHY.

# Agent sees this code:
class RateLimiter:
    def __init__(self):
        self._locks = {}  # Why not Redis?
        self._state = {}  # Why in-memory?

# Agent thinks:
# "This should use Redis for distributed rate limiting"
# Agent refactors to Redis
# Production breaks (latency spikes)

With an ADR:

# ADR-0001: In-Memory Rate Limiting

## Decision
Use in-memory rate limiting instead of Redis.

## Context
Redis single-threaded execution + Lua scripts caused 50+ second latency spikes
under high concurrency.

## Consequences
- ✅ No latency spikes
- ❌ Not suitable for multi-worker deployments

Agent now knows: don’t touch this without understanding the latency history.


ADR Template

# ADR-XXXX: [Title]

## Status
[Proposed | Accepted | Deprecated | Superseded by ADR-YYYY]

## Date
YYYY-MM-DD

## Context
What is the issue? Why are we making this decision?

## Decision
What is the change being proposed/made?

## Alternatives Considered
1. [Alternative 1] - Why rejected
2. [Alternative 2] - Why rejected
3. [Alternative 3] - Why rejected

## Consequences
### Positive
- ✅ Benefit 1
- ✅ Benefit 2

### Negative
- ❌ Tradeoff 1
- ❌ Tradeoff 2

### Neutral
- ⚖️ Note 1

## References
- [Link to discussion]
- [Link to related code]

Example ADRs

ADR-0001: In-Memory Rate Limiting

# ADR-0001: In-Memory Rate Limiting (Deprecating Redis)

## Status
Accepted (Nov 2025)

## Context
Our Redis-based rate limiter was experiencing 50+ second latency spikes:
- Redis single-threaded execution serializes all Lua script calls
- Under high concurrency (1000+ concurrent requests), queue depth exploded
- P99 latency became unusable

We operate single-worker deployments, so distributed coordination isn't required.

## Decision
Replace Redis rate limiting with in-memory implementation using `asyncio.Lock()`.

## Alternatives Considered
1. **Redis Cluster** - Adds complexity, doesn't solve single-threaded bottleneck
2. **Rate limiting at load balancer** - Loses application-level context
3. **Keep Redis with tuning** - Fundamental architecture issue, not tunable

## Consequences
### Positive
- ✅ Latency reduced from 50s to <1ms
- ✅ Simpler architecture (no external dependency)
- ✅ More predictable performance

### Negative
- ❌ State lost on restart
- ❌ Can't scale to multiple workers without redesign

### Neutral
- ⚖️ Requires architecture change if we ever need multiple workers

## References
- `api/app/services/rate_limiting/` - Implementation
- `api/CLAUDE.md` - Redis deprecation details

ADR-0002: Effect-TS for Core Domain

# ADR-0002: Effect-TS for Core Domain Logic

## Status
Accepted (Oct 2025)

## Context
Our TypeScript codebase had:
- Inconsistent error handling (throw vs return)
- Hidden side effects in pure-looking functions
- Difficult testing due to implicit dependencies
- No clear distinction between sync and async

## Decision
Adopt Effect-TS for all core domain logic:
- Explicit error channels in types
- Dependency injection via Context
- Composable, testable effects

## Alternatives Considered
1. **fp-ts** - Less batteries-included, steeper learning curve
2. **neverthrow** - Only handles errors, not effects
3. **Keep current** - Problems would compound

## Consequences
### Positive
- ✅ Errors visible in type signatures
- ✅ Dependencies explicit and injectable
- ✅ Better testability
- ✅ Improved LLM comprehension (explicit effects)

### Negative
- ❌ Learning curve for team
- ❌ More verbose code initially
- ❌ Some library interop friction

## References
- <a href="/posts/functional-programming-signal/">functional programming signal</a> - Why FP increases LLM signal
- `src/domain/` - Effect-TS patterns

ADR-0003: OTEL for All Observability

# ADR-0003: OpenTelemetry as Sole Observability Standard

## Status
Accepted (Sep 2025)

## Context
We had fragmented observability:
- Logs to CloudWatch
- Metrics to custom Prometheus
- Traces to Jaeger
- No correlation between signals

## Decision
Standardize on OpenTelemetry (OTEL) for all observability:
- Single SDK for traces, metrics, logs
- Correlation via trace context
- Vendor-neutral export

## Alternatives Considered
1. **Keep fragmented** - Debugging nightmare
2. **Datadog SDK** - Vendor lock-in
3. **Custom solution** - Maintenance burden

## Consequences
### Positive
- ✅ Unified instrumentation
- ✅ Trace-log correlation
- ✅ Vendor flexibility
- ✅ Industry standard

### Negative
- ❌ Migration effort
- ❌ OTEL SDK maturity varies by language

## References
- `docker-compose.yml` - OTEL collector config
- <a href="/posts/closed-loop-telemetry-driven-optimization/">closed loop telemetry driven optimization</a> - Using OTEL for optimization

ADR Location Strategy

docs/
└── adr/
    ├── 0001-in-memory-rate-limiting.md
    ├── 0002-effect-ts-for-domain.md
    ├── 0003-otel-observability.md
    ├── 0004-ddd-bounded-contexts.md
    └── index.md  # Links to all ADRs

Reference ADRs in CLAUDE.md:

# CLAUDE.md

## Architecture Decisions
Key decisions are documented in `docs/adr/`. Read relevant ADRs before
modifying:
- Rate limiting: ADR-0001 (explains why NOT Redis)
- Error handling: ADR-0002 (Effect-TS patterns)
- Observability: ADR-0003 (OTEL requirements)

ADRs as Agent Context

In CLAUDE.md

## Critical ADRs
Before making changes, read these:

- **ADR-0001**: We use in-memory rate limiting because Redis caused latency
  spikes. Do NOT refactor to Redis without understanding the history.

- **ADR-0002**: All domain logic uses Effect-TS. Errors must be in the type
  signature, not thrown.

- **ADR-0003**: All observability through OTEL. No direct logging to stdout.

In Code Comments

// See ADR-0001: In-memory rate limiting due to Redis latency issues
// Do NOT refactor to Redis without understanding the tradeoffs
class InMemoryRateLimiter {
  // ...
}

In Slash Commands

# .claude/commands/understand-architecture.md
Before making changes, review relevant ADRs:

1. List files that will be modified
2. For each area, check if there's an ADR in `docs/adr/`
3. Read any relevant ADRs
4. Respect the documented constraints and tradeoffs
5. If proposing to change an ADR-documented decision, explain why the
   original reasoning no longer applies

When to Write ADRs

Write an ADR when:

  • Choosing between significant alternatives
  • Making a decision that’s hard to reverse
  • Making a decision others might question
  • Deprecating a previous approach
  • Adopting a new pattern or library

Don’t write an ADR for:

  • Obvious choices
  • Easily reversible decisions
  • Standard patterns

ADR Anti-Patterns

No alternatives – Just says “we chose X”
No consequences – Doesn’t acknowledge tradeoffs
Too much detail – Implementation docs, not decision docs
Never updated – Decisions change, ADRs should too
Not discoverable – Hidden in random locations


Key Insight

Code tells agents WHAT exists. ADRs tell agents WHY it exists and WHAT NOT to change.

Udemy Bestseller

Learn Prompt Engineering

My O'Reilly book adapted for hands-on learning. Build production-ready prompts with practical exercises.

4.5/5 rating
306,000+ learners
View Course

ADRs are context that prevents agents from undoing carefully considered decisions.


Related

Topics
Ai AgentsArchitecture Decision RecordsCode DocumentationContext EngineeringDesign Patterns

More Insights

Cover Image for Thought Leaders

Thought Leaders

People to follow for compound engineering, context engineering, and AI agent development.

James Phoenix
James Phoenix
Cover Image for Systems Thinking & Observability

Systems Thinking & Observability

Software should be treated as a measurable dynamical system, not as a collection of features.

James Phoenix
James Phoenix