Skip to content

Architecture Decision Records (ADRs)

Architecture Decision Records (ADRs) document the significant architectural choices made during software development. They capture the reasoning behind decisions, the context in which they were made, and the consequences of those choices.

ADRs create a searchable, version-controlled history of architectural evolution that helps current team members understand the system and guides future developers who join the project.

Philosophy

ADRs are lightweight documentation that answers one question: "Why did we make this choice?" They preserve context that would otherwise be lost in Slack conversations, meeting notes, and institutional memory.

Why Use ADRs?

The Problem

Without ADRs, architectural knowledge exists in:

  • Scattered conversations: Slack threads, email chains, hallway discussions
  • Meeting notes: Buried in documents, different formats, hard to find
  • Individual memories: Team members leave, context is lost
  • Code comments: Limited space, becomes outdated, not searchable
  • Tribal knowledge: "That's just how we've always done it"

This creates problems:

graph TB
    A[Architectural Decision] --> B[Discussed in Slack]
    A --> C[Mentioned in Meeting]
    A --> D[Implemented in Code]

    B --> E[Thread Lost in History]
    C --> F[Notes Scattered]
    D --> G[Context Missing]

    E --> H[New Developer: Why?]
    F --> H
    G --> H

    H --> I[Repeat Same Discussion]
    H --> J[Make Conflicting Choice]
    H --> K[Cargo Cult Programming]

    style A fill:#e1f5ff
    style H fill:#ffebee
    style I fill:#fff9e1
    style J fill:#fff9e1
    style K fill:#fff9e1

The Solution

ADRs provide:

  • Context preservation: Future developers understand the "why" behind decisions
  • Team alignment: All stakeholders aware of important choices
  • Knowledge management: Searchable history of decisions
  • Onboarding: New team members understand system evolution
  • Decision quality: Writing forces clear thinking about tradeoffs

ADR Format

Michael Nygard Template

We use Michael Nygard's lightweight template, which balances simplicity with completeness:

# [Timestamp] [Verb Phrase Title]

Date: YYYY-MM-DD

Status: Proposed | Accepted | Deprecated | Superseded by [xxx]

Author: [Git username]

## Context

What is the issue we're seeing that motivates this decision?

## Decision

What are we going to do about it?

## Consequences

What becomes easier or more difficult as a result?

## Notes

[Any updates or learnings added after the decision]

Section Purposes

Title: Verb phrase describing the decision (e.g., "Adopt Redis for Caching")

Date: When the decision was made or proposed

Status: Current state of the decision - Proposed: Being considered, not yet implemented - Accepted: Approved and implemented/being implemented - Deprecated: No longer relevant but kept for history - Superseded: Replaced by a newer decision (reference it)

Author: Who made or proposed the decision

Context: The situation that led to this decision - Current problems or constraints - Requirements that must be satisfied - Alternatives that were considered - Stakeholders involved

Decision: The choice being made - What technology/approach/pattern - Key implementation details affecting architecture - Scope and boundaries

Consequences: What changes as a result - Positive: Benefits gained, problems solved - Negative: Tradeoffs accepted, increased complexity - Neutral: Other changes, new considerations

Notes: Living section for updates - Implementation learnings - Real-world results - Links to related decisions - Changes in context

File Naming Convention

We use a hybrid approach combining timestamps with descriptive verb phrases:

Format: YYYYMMDD_HHMMSS_verb-phrase.md

Examples: - 20250904_143022_adopt-redis-caching.md - 20250904_151245_switch-to-htmx.md - 20250905_093011_implement-tenant-isolation.md

Benefits of this format:

graph LR
    A[Timestamp Prefix] --> B[Chronological Sorting]
    A --> C[No Collision Issues]
    A --> D[Easy to Track Evolution]

    E[Verb Phrase Suffix] --> F[Human Readable]
    E --> G[Searchable]
    E --> H[Descriptive]

    style A fill:#e1f5ff
    style E fill:#e8f5e9

Timestamp benefits: - Files sort chronologically automatically - No naming conflicts (precise to the second) - Can trace decision timeline

Verb phrase benefits: - Meaningful names in search results - Clear purpose when browsing directory - Easy to reference in discussions

When to Create an ADR

Create an ADR When:

Technology decisions: - Choosing frameworks (Django over Flask) - Selecting databases (MySQL over PostgreSQL) - Picking deployment platforms (ECS over Kubernetes)

Architecture patterns: - API design approach (REST over GraphQL) - Authentication strategy (Auth0 vs. self-hosted) - Multi-tenancy implementation (separate databases vs. shared)

Integration approaches: - External service integration patterns - Data synchronization strategies - Event-driven vs. request/response

Security and compliance: - Authentication/authorization approach - Data encryption strategy - Audit logging requirements

Development practices: - Testing strategy changes - CI/CD pipeline design - Deployment workflow

Cross-cutting concerns: - Logging and monitoring approach - Error handling strategy - Caching architecture

Don't Create an ADR For:

Implementation details: Already covered by code comments

# ❌ Don't create ADR for this
# ADR: "Use List Comprehension for Filtering Users"

# ✅ Just use a code comment
# Filter active users using list comprehension for readability
active_users = [u for u in users if u.is_active]

Temporary workarounds: Short-term fixes that will be replaced

# ❌ Don't create ADR for temporary fix
# ADR: "Workaround for Third-Party API Bug"

# ✅ Just add a TODO comment
# TODO: Remove this workaround when API v2 is released
if api_version == "v1":
    result = workaround_function()

Decisions already documented: Covered by standards, policies, or external docs

Minor configuration changes: Not architecturally significant

Reversible decisions: Low-impact changes that can be easily changed

Decision Significance Test

Ask these questions to determine if an ADR is warranted:

  1. Does this decision affect system architecture? (Yes → ADR)
  2. Will future developers need to understand why we made this choice? (Yes → ADR)
  3. Does this have long-term implications or affect multiple teams? (Yes → ADR)
  4. Were there multiple viable options with different tradeoffs? (Yes → ADR)
  5. Does this involve external dependencies, technologies, or vendors? (Yes → ADR)

If you answered "yes" to 2+ questions, create an ADR.

Creating ADRs

For Django projects, use the management command:

# Create new ADR
python manage.py create_decision "adopt-redis-caching"

# Output:
# Created decision record: documentation/decisions/20250904_143022_adopt-redis-caching.md
# Edit the file to add context, decision, and consequences.

The command automatically: - Generates timestamped filename - Fills in date and author from git config - Creates file from template - Shows path for editing

Method 2: Manual Creation

If you don't have the management command:

# Copy template
cp documentation/decisions/template.md \
   documentation/decisions/20250904_143022_adopt-redis-caching.md

# Edit the file
vim documentation/decisions/20250904_143022_adopt-redis-caching.md

Directory Structure

project/
├── documentation/
│   └── decisions/
│       ├── README.md                                    # Process documentation
│       ├── template.md                                  # ADR template
│       ├── 20250904_143022_adopt-redis-caching.md
│       ├── 20250904_151245_switch-to-htmx.md
│       └── 20250905_093011_implement-tenant-isolation.md

ADR Lifecycle

Statuses and Transitions

stateDiagram-v2
    [*] --> Proposed: Create ADR
    Proposed --> Accepted: Team approves and implements
    Proposed --> [*]: Rejected (delete ADR)

    Accepted --> Deprecated: No longer relevant
    Accepted --> Superseded: Replaced by new decision

    Deprecated --> [*]: Archived
    Superseded --> [*]: Archived

    note right of Accepted
        Most ADRs remain
        in this state
    end note

Proposed: Decision being considered - Still evaluating options - Gathering feedback - Not yet implemented

Accepted: Decision approved and implemented - Team consensus reached - Implementation started or completed - This is the steady state for most ADRs

Deprecated: Decision no longer relevant - Technology no longer used - Requirements changed - Kept for historical context

Superseded: Replaced by newer decision - Link to the ADR that replaces this one - Explains why the decision changed - Maintains the decision chain

Living Documents

Unlike strict immutability, we treat ADRs as "living documents":

## Notes

**2025-09-04**: Initial decision to adopt Redis for session storage.

**2025-09-15**: Implementation completed. Redis performing well with
average response time of 2ms. Session data now persists across
application restarts as expected.

**2025-10-01**: Identified issue with Redis memory usage during high
traffic. Added memory limit configuration and eviction policy. See
configuration in `settings/production.py`.

**2025-11-12**: Expanded Redis usage to include API rate limiting.
Performance remains good. Consider creating separate ADR for rate
limiting pattern.

Notes section captures: - Implementation learnings - Real-world performance results - Configuration changes - Related decisions - Unexpected challenges - Success metrics

Always include timestamps for updates to maintain history.

Example ADRs

Example 1: Technology Choice

# 20250904_143022 Adopt Redis for Session Storage

Date: 2025-09-04

Status: Accepted

Author: Bob Florian

## Context

Our Django application runs on AWS ECS Fargate with auto-scaling. Currently,
sessions are stored using Django's default database-backed sessions. This
creates several problems:

1. **Stateful processes**: Sessions tie users to specific containers, requiring
   sticky sessions at the load balancer
2. **Database load**: Every request hits the database for session data
3. **Scaling friction**: Adding containers requires load balancer reconfiguration
4. **Performance**: Database queries add 50-100ms to every request

We evaluated three alternatives:
- **Database-backed sessions** (current): Simple but slow, not horizontally scalable
- **Redis sessions**: Fast, horizontally scalable, requires infrastructure
- **Signed cookie sessions**: No infrastructure, but limited size and security concerns

Requirements:
- Support horizontal scaling without sticky sessions
- Sub-10ms session access time
- Session persistence across application restarts
- Secure session storage (not client-side)

## Decision

We will adopt Redis for session storage using Amazon ElastiCache.

**Implementation approach**:
- Use `django-redis` package for Django integration
- Configure ElastiCache Redis cluster with replication
- Store all session data in Redis, not database
- Use Redis for other caching needs (query cache, rate limiting)

**Configuration**:
```python
SESSION_ENGINE = "django.contrib.sessions.backends.cache"
SESSION_CACHE_ALIAS = "default"

CACHES = {
    "default": {
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "redis://elasticache-endpoint:6379/1",
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
        }
    }
}

Consequences

Positive Consequences

  • True stateless processes: Any container can handle any request
  • Better performance: Redis access <5ms vs. 50-100ms for database
  • Horizontal scalability: Add containers without configuration changes
  • Auto-scaling: ECS can scale based on load without session concerns
  • Unified caching: Same infrastructure for sessions and query cache
  • Session persistence: Survives application restarts

Negative Consequences

  • Additional infrastructure: ElastiCache adds cost (~$50/month for small cluster)
  • New failure mode: Redis outage affects all users (mitigated with replication)
  • Operational complexity: Another service to monitor and maintain
  • Session size limits: Redis has memory constraints (mitigated with TTL)

Neutral Consequences

  • Learning curve: Team needs to understand Redis operations
  • Monitoring: Need CloudWatch alarms for Redis metrics
  • Backup strategy: Session data is ephemeral, no backup needed

Notes

2025-09-04: Initial implementation completed. ElastiCache cluster configured with two nodes (primary + replica) in different AZs for high availability.

2025-09-15: Performance testing shows average session access time of 2ms, down from 75ms with database-backed sessions. Page load times improved by 15%.

2025-10-01: Added CloudWatch alarms for Redis memory usage, CPU, and eviction rate. Configured maxmemory-policy to "volatile-lru" to handle memory pressure.

2025-11-01: Expanded Redis usage to include API rate limiting (django-ratelimit). Sharing infrastructure has been cost-effective. No performance degradation observed.

### Example 2: Architecture Pattern

```markdown
# 20250904_182829 Implement Multi-Tenant Account Authorization

Date: 2025-09-04

Status: Accepted

Author: Bob Florian

## Context

Our SaaS application serves multiple client organizations (tenants). Each tenant
has isolated data, and users belong to a single tenant. We need to ensure:

1. **Data isolation**: Users can only access their tenant's data
2. **Authorization**: Role-based access control within each tenant
3. **Performance**: Authorization checks don't slow down requests
4. **Maintainability**: Pattern is easy to apply across all views

Current situation:
- Basic authentication (login) works
- No systematic tenant isolation
- Manual checks in views (inconsistent, error-prone)
- Some endpoints accidentally leak data across tenants

We evaluated these approaches:
- **Manual checks in each view**: Simple but error-prone, easy to forget
- **Database-level isolation**: Separate databases per tenant (complex, costly)
- **Middleware-based**: Automatic but less flexible for complex rules
- **Mixin-based**: Reusable, explicit, works with Django patterns

## Decision

We will implement multi-tenant authorization using a mixin pattern with
rest_access_policy for fine-grained permissions.

**Pattern components**:

1. **AccountValidationMixin**: Base mixin for tenant-scoped querysets
2. **AccessPolicy classes**: Define role-based permissions per resource
3. **Tenant context**: Stored in request object by middleware

**Implementation**:

```python
class AccountValidationMixin:
    """Mixin to ensure tenant isolation in views."""

    def get_account(self, request):
        """Extract tenant from authenticated user."""
        if not request.user.is_authenticated:
            raise PermissionDenied("Authentication required")
        return request.user.tenant

    def get_queryset(self):
        """Filter queryset by tenant."""
        account = self.get_account(self.request)
        return super().get_queryset().filter(tenant=account)

class DocumentAccessPolicy(AccessPolicy):
    """Define who can access documents."""
    statements = [
        {
            "action": ["list", "retrieve"],
            "principal": "authenticated",
            "effect": "allow",
            "condition": "is_tenant_member"
        },
        {
            "action": ["create", "update", "destroy"],
            "principal": "authenticated",
            "effect": "allow",
            "condition": "is_owner_or_admin"
        }
    ]

class DocumentViewSet(AccountValidationMixin, viewsets.ModelViewSet):
    """Document API with tenant isolation."""
    permission_classes = [IsAuthenticated, DocumentAccessPolicy]
    # Queryset automatically filtered by tenant via mixin

Consequences

Positive Consequences

  • Systematic isolation: Every view using the mixin is tenant-scoped
  • Explicit pattern: Clear, visible authorization in every viewset
  • Flexible permissions: AccessPolicy supports complex role-based rules
  • Audit trail: Permission checks are logged
  • DRY: Reusable mixin reduces code duplication
  • Testable: Easy to test tenant isolation in unit tests

Negative Consequences

  • Must remember to use mixin: Developers can forget (mitigated with linting)
  • Performance overhead: Extra query filter on every request (minimal)
  • Learning curve: Team needs to understand AccessPolicy syntax
  • Manual in some cases: Complex authorization still requires custom logic

Neutral Consequences

  • Consistent pattern: All viewsets follow the same structure
  • Documentation needed: Need to document pattern for new developers
  • Migration effort: Existing views need to be updated to use mixin

Notes

2025-09-04: Initial implementation with AccountValidationMixin and basic AccessPolicy classes for core resources (users, documents, projects).

2025-09-20: Added custom management command to audit all viewsets and verify they use AccountValidationMixin. Found and fixed 3 viewsets that were missing tenant isolation.

2025-10-15: Extended pattern to handle multi-database routing. Some tenants have separate databases. Mixin now supports using() based on tenant.

2025-12-01: Team is comfortable with pattern. New viewsets consistently use mixin. No tenant data leakage incidents since implementation.

Related ADRs: - 20250904_170657_adopt-multi-database-architecture.md - Database routing - 20250904_190206_adopt-django-rest-framework-api-design.md - API patterns

## ADR Management

### Finding ADRs

**By chronological order**:
```bash
# List all ADRs in order
ls documentation/decisions/*.md

# Recent decisions
ls -lt documentation/decisions/*.md | head -5

By topic:

# Search filenames
ls documentation/decisions/*redis*.md

# Search content
grep -r "Redis" documentation/decisions/

By status:

# Find all accepted decisions
grep -l "Status: Accepted" documentation/decisions/*.md

# Find superseded decisions
grep -l "Status: Superseded" documentation/decisions/*.md

Updating ADRs

Add implementation notes:

## Notes

**2025-11-01**: Implementation completed. Real-world performance exceeds
expectations. Average response time 2ms vs. estimated 5ms.

Supersede an ADR:

# 20250904_143022 Adopt Redis for Session Storage

Status: Superseded by 20251205_101530_migrate-to-dynamodb-sessions.md

Create the new ADR explaining why:

# 20251205_101530 Migrate to DynamoDB for Session Storage

## Context

We originally adopted Redis for session storage (see
20250904_143022_adopt-redis-caching.md). After 1 year of operation,
we've identified limitations:

1. Redis requires manual management of ElastiCache clusters
2. Cost has grown from $50/month to $200/month
3. We already use DynamoDB for other features
4. DynamoDB's auto-scaling better matches our traffic patterns

## Decision

Migrate session storage from Redis to DynamoDB...

Team Notifications

Set up notifications for new ADRs:

# .git/hooks/post-merge
#!/bin/bash
# Notify team of new ADRs after git pull

NEW_ADRS=$(git diff HEAD@{1} --name-only --diff-filter=A | grep "documentation/decisions/.*\.md")

if [ -n "$NEW_ADRS" ]; then
    echo "🏛️  New Architecture Decision Records:"
    echo "$NEW_ADRS" | while read adr; do
        TITLE=$(grep "^# " "$adr" | head -1 | sed 's/^# //')
        CONTEXT=$(grep -A 2 "## Context" "$adr" | tail -1)
        echo "  → $adr"
        echo "    \"$CONTEXT...\""
    done
fi

Team Process

Who Can Create ADRs?

Anyone who understands the decision and can document it: - Developers implementing the feature - Architects designing the system - Team leads making technology choices - Product managers defining requirements

Best practice: The person who researched the options and made the recommendation should write the ADR.

Review and Approval

Lightweight process:

  1. Create ADR with status "Proposed"
  2. Share in PR for visibility
  3. Discuss in code review or team meeting
  4. Update based on feedback
  5. Merge when team agrees
  6. Change status to "Accepted" when implemented

For major decisions: - Schedule dedicated discussion time - Include relevant stakeholders - Document dissenting opinions in Notes - Allow time for consideration (don't rush)

Governance Questions

Discuss these with your team before starting:

  1. Who can create ADRs? Anyone? Specific roles?
  2. What requires team approval? All ADRs? Only major ones?
  3. How do we handle disagreements? Consensus? Majority? Tech lead decides?
  4. What's our decision-making principle? Disagree and commit? Unanimous consent?

Best Practices

Writing Good ADRs

Be specific:

# ❌ Vague
We will use a database for data storage.

# ✅ Specific
We will use MySQL 8.0 for our primary relational data store, hosted on
AWS RDS with Multi-AZ deployment.

Explain tradeoffs:

## Consequences

### Positive
- Fast read performance (indexed queries <10ms)
- Familiar to team (everyone knows SQL)

### Negative
- Scaling writes requires sharding (complex)
- Schema migrations can be slow on large tables

### Neutral
- Need to learn MySQL-specific features (vs. PostgreSQL)

Include context:

## Context

We currently handle 100K requests/day with 1M database records. Usage
is growing 20% monthly. Current PostgreSQL setup is maxing out at 80%
CPU during peak hours (2-4 PM EST).

We need to support:
- 500K requests/day within 6 months
- <100ms p95 response times
- $500/month infrastructure budget

Be honest about uncertainty:

## Notes

**2025-09-04**: We're estimating Redis will handle our scale, but we
haven't tested beyond 10K concurrent sessions. Will monitor closely
and may need to revisit if we exceed 50K sessions.

Common Mistakes

Too abstract:

# ❌ Not actionable
We will follow best practices for API design.

# ✅ Concrete
We will design REST APIs using Django REST Framework with:
- snake_case field names
- Pagination on all list endpoints
- Filtering via django-filter

Missing consequences:

# ❌ Incomplete
## Consequences
This will make our API faster.

# ✅ Complete
## Consequences

### Positive
- Redis caching reduces API response time from 200ms to 50ms
- Lower database load (60% fewer queries)

### Negative
- ElastiCache adds $50/month cost
- Redis outage affects all users
- Need to monitor cache hit rate

### Neutral
- Team needs to learn Redis basics
- Cache invalidation strategy required

Too much detail:

# ❌ Too detailed (belongs in code)
We will use Redis with the following configuration:
- maxmemory: 2gb
- maxmemory-policy: allkeys-lru
- timeout: 300
- tcp-keepalive: 60
[50 more configuration parameters...]

# ✅ Right level (architecture-focused)
We will use Redis for session storage with memory eviction policy
set to LRU. Configuration details are in settings/production.py.

Integration with Development Workflow

During Planning

Create ADRs before implementing major features:

graph LR
    A[Feature Request] --> B[Research Options]
    B --> C[Create ADR]
    C --> D[Team Review]
    D --> E{Approved?}
    E -->|Yes| F[Implement]
    E -->|No| B
    F --> G[Update ADR Notes]

    style C fill:#e1f5ff
    style D fill:#fff9e1
    style F fill:#e8f5e9

During Development

Update ADRs as you learn:

## Notes

**2025-09-04**: Started implementation.

**2025-09-10**: Discovered Redis Cluster doesn't support all commands
we need. Switching to single-node with replication instead.

**2025-09-15**: Implementation complete. Performance better than expected.

During Code Review

Reference ADRs in PRs:

## Summary
Implements Redis session storage as decided in
documentation/decisions/20250904_143022_adopt-redis-caching.md

## Changes
- Added django-redis dependency
- Configured ElastiCache connection
- Updated session settings
- Added Redis health check

## Testing
- Session persistence across restarts: ✅
- Performance: 2ms average (better than 5ms target)

During Onboarding

New team members read ADRs to understand:

Suggested reading order:

  1. Framework decisions: Why Django? Why DRF?
  2. Infrastructure decisions: Why AWS? Why ECS?
  3. Architecture patterns: Multi-tenancy, database routing
  4. Recent decisions: What changed in the last 6 months?

Tools and Automation

Management Command

Create a Django management command for ADRs:

# management/commands/create_decision.py
from django.core.management.base import BaseCommand
from datetime import datetime
import os
import subprocess

class Command(BaseCommand):
    help = "Create a new Architecture Decision Record"

    def add_arguments(self, parser):
        parser.add_argument("title", type=str, help="Decision title (verb-phrase)")

    def handle(self, *args, **options):
        title = options["title"]
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        filename = f"{timestamp}_{title}.md"
        filepath = f"documentation/decisions/{filename}"

        # Get git author
        try:
            author = subprocess.check_output(
                ["git", "config", "user.name"],
                text=True
            ).strip()
        except subprocess.CalledProcessError:
            author = "Unknown"

        # Create from template
        with open("documentation/decisions/template.md", "r") as template:
            content = template.read()
            content = content.replace("YYYY-MM-DD", datetime.now().strftime("%Y-%m-%d"))
            content = content.replace("[Git username]", author)
            content = content.replace("[Verb Phrase Title]", title.replace("-", " ").title())

        with open(filepath, "w") as f:
            f.write(content)

        self.stdout.write(
            self.style.SUCCESS(f"Created decision record: {filepath}")
        )
        self.stdout.write("Edit the file to add context, decision, and consequences.")

Linting

Validate ADR format:

# scripts/validate_adrs.py
import re
import sys
from pathlib import Path

def validate_adr(filepath):
    """Validate ADR has required sections."""
    with open(filepath) as f:
        content = f.read()

    required_sections = ["## Context", "## Decision", "## Consequences"]
    missing = [s for s in required_sections if s not in content]

    if missing:
        print(f"❌ {filepath}: Missing sections: {missing}")
        return False

    # Check status is valid
    status_match = re.search(r"Status: (\w+)", content)
    if not status_match:
        print(f"❌ {filepath}: Missing Status field")
        return False

    valid_statuses = ["Proposed", "Accepted", "Deprecated", "Superseded"]
    status = status_match.group(1)
    if status not in valid_statuses:
        print(f"❌ {filepath}: Invalid status '{status}'")
        return False

    print(f"✅ {filepath}")
    return True

if __name__ == "__main__":
    adrs = Path("documentation/decisions").glob("*.md")
    adrs = [a for a in adrs if a.name not in ["README.md", "template.md"]]

    results = [validate_adr(adr) for adr in adrs]

    if not all(results):
        sys.exit(1)

Run in CI:

# .github/workflows/validate.yml
- name: Validate ADRs
  run: python scripts/validate_adrs.py

Next Steps

  1. Set up ADR directory: Create documentation/decisions/ with README and template
  2. Create management command: Automate ADR creation
  3. Document your first decision: Why you're adopting ADRs (meta!)
  4. Identify recent decisions: Retroactively document major choices
  5. Integrate into workflow: Make ADRs part of planning process
  6. Train the team: Share this guide, create example ADRs
  7. Iterate: Adjust process based on team feedback

Start Simple

Don't create ADRs for every decision from the past. Start with new decisions and retroactively document 2-3 major choices. The value comes from building the habit, not documenting ancient history.

Further Reading

Example: Adopting ADRs

Here's an example ADR about adopting ADRs (meta!):

# 20250904_155954 Adopt Architecture Decision Records

Date: 2025-09-04

Status: Accepted

Author: Bob Florian

## Context

The project has grown in complexity with multiple integrations, technology
choices (HTMX, Alpine.js, TailwindCSS), and database patterns (multi-tenancy,
multiple databases). As the team scales and new developers join, we need a way to:

- Preserve reasoning behind architectural choices
- Ensure team alignment on important decisions
- Help new team members understand system evolution
- Create searchable history of decisions
- Reduce repeated architectural discussions

Currently, decisions are communicated through:
- Code comments (limited context)
- Slack conversations (ephemeral)
- Meeting notes (scattered across different systems)

This creates knowledge silos and makes it difficult to understand the "why"
behind architectural choices.

## Decision

We will adopt Architecture Decision Records (ADRs) using Michael Nygard's
lightweight template.

**Implementation**:
- Directory: `/documentation/decisions/`
- Naming: `YYYYMMDD_HHMMSS_verb-phrase.md` format
- Template: Context, Decision, Consequences, Notes sections
- Tooling: Django management command `create_decision`
- Living documents: Updates in Notes section with timestamps

**Scope**: Document architectural decisions that affect system design,
technology choices, integration patterns, and cross-team coordination.

## Consequences

### Positive Consequences
- Knowledge preservation for future developers
- Team alignment on important decisions
- Easier onboarding for new team members
- Searchable decision history
- Reduced repetition of architectural discussions

### Negative Consequences
- Additional process to remember
- Maintenance overhead for keeping ADRs updated
- Learning curve for ADR format
- Risk of over-documentation

### Neutral Consequences
- Need to maintain decisions directory
- May need to adjust template based on experience
- Integration with development workflow

## Notes

**2025-09-04**: Initial implementation completed including directory structure,
README, template, management command, and this first ADR as an example.

**2025-10-01**: Team has created 15 ADRs. Process is lightweight and valuable.
New developers appreciate the context. Continuing to refine.