ADR-driven solo development: 38 decisions, one architect
Architecture Decision Records are usually framed as a team artifact — a way for multiple engineers to align on why a decision was made. They are arguably more valuable solo. Past-you and future-you don't share context as well as you'd think, and an ADR is the smallest thing that survives the gap. After 38 ADRs across nine months of building a single platform, I'd recommend this as one of the highest-leverage process disciplines I've adopted solo. Your mileage will depend on the lifetime of what you're building.
What an ADR is, in one paragraph
An Architecture Decision Record is a short Markdown document that captures one architectural decision, in roughly this structure: context (the situation that forced a choice), decision (what you chose), consequences (what this implies — good and bad), alternatives considered (other options you rejected and why). They live next to the code, usually in docs/adr/, numbered sequentially. The original format comes from Michael Nygard's 2011 post and has been refined by many teams since.
The discipline is small enough to fit in 20 minutes. The compound effect over a year is large.
The case for ADRs solo
Team ADRs exist to align humans who don't share a brain. Solo ADRs exist to align past-you and future-you, who share a brain but not the memory of why specific decisions were made. The intuition that "I'll remember why I did this" is reliably wrong on a six-month horizon. Specific examples from my own ADR set that I could not have reconstructed without writing them:
- Why module A imports module B via
forwardRefinstead of an event. Six months later, "because it works" is not enough — you need to know whether this was a deliberate atomicity choice, a temporary shortcut, or a mistake. ADR-033 records this as "atomic cross-module writes are the documented carve-out from the events-only rule," with the explicit migration plan for when the module is extracted. - Why the workflow engine uses a sentinel exception inside transactions. Without the ADR, the future me looks at the code, sees a thrown exception inside a "no throwing" codebase, and rewrites it. With the ADR, the future me reads the context — TypeORM only rolls back on throws, domain code must not throw, sentinel bridges the two — and leaves it alone. ADR-002 + ADR-033.
- Why we picked Result<T, string> instead of typed errors. The trade-off is real: stringly-typed errors lose information. The ADR records the consideration of typed errors and the explicit rejection ("simpler API beats type-safe error taxonomy for our scale"), so the future me doesn't relitigate this decision every quarter.
The pattern is: the cost of an ADR is paid once. The benefit is paid every time you'd otherwise wonder.
How I write them
My ADRs follow the standard format with two additions I find earn their keep.
Alternatives Considered, with rejection reasons. The standard ADR template includes this section but doesn't emphasize it. I treat it as load-bearing. For every meaningful decision, I list 2–3 alternatives and state — in one or two sentences — why each was rejected. This is where future-me gets the most value, because the question I most often re-ask is "did past-me consider X?" If X is in the rejected list with a reason, I can evaluate whether the reason still holds.
Tags and cross-links. Each ADR has tags (architecture / workflow-engine / concurrency / MS-readiness / etc.) and cross-links to other ADRs it builds on or contradicts. This turns the ADR set from a list of documents into a graph of decisions. When I add a new ADR that depends on three earlier ones, the new one links them, and the relationship is visible at a glance.
One thing I do not do: write ADRs for every code change. ADRs are for architectural decisions with at least one non-obvious alternative. "I added a getter for this property" is not an ADR. "I chose a CAS-based concurrency primitive over pg_cron or advisory locks for non-idempotent workflow steps" is. The bar is "would future-me wonder why this was done this way?"
When to write an ADR
Three triggers that I find correlate well with "this should be an ADR":
- I'm choosing between two or more options that each have real costs. If there's no genuine alternative, there's no decision — just a default. Don't ADR defaults.
- The choice affects code I haven't written yet. Architectural decisions propagate. A choice about "how do modules communicate" affects every cross-module interaction in the codebase. That's exactly the kind of decision that's costly to revisit and worth recording.
- I'm doing something that looks wrong without context. A sentinel exception in a no-throw codebase. A
forwardRefacross modules in an event-driven architecture. Ananyin the workflow engine base class. Each of these would get flagged in code review. Each is deliberate. Each gets an ADR explaining why.
The third trigger is the most important. ADRs are the antidote to future-me "fixing" past-me's deliberate trade-offs.
The status field
I use four ADR statuses: Proposed, Accepted, Superseded, Deprecated. Most live in Accepted. The interesting cases are the rest.
Superseded is what happens when a later decision replaces an earlier one. The old ADR stays — you do not delete it — but its status is updated and a link to the superseding ADR is added at the top. Future-me reading the old ADR sees "this was the decision in March 2026; it was replaced by ADR-031 in April." This is more useful than the alternative ("delete the old one and lose the history of why"), because the old reasoning often explains constraints that led to the new decision.
Deprecated is what happens when a decision becomes obsolete without being replaced. A primitive that's no longer used, an integration that's been removed. The ADR stays as a marker that this was once a thing.
I have not yet superseded an ADR with another in my set — most decisions have held — but I have deprecated two. Even those have been useful: when someone asks "why doesn't this module do X anymore?" the deprecated ADR has the answer.
The thing solo developers usually skip
Most solo developers don't write ADRs because the perceived benefit (sharing context with teammates) doesn't apply. This misframes the value. ADRs aren't primarily about sharing context with teammates. They're about creating context that can be shared at all. The act of writing a decision down forces clarity about what the decision actually is — what alternatives were available, what costs each carries, what considerations tipped the balance.
Half the time I sit down to write an ADR for a decision I think I've made, I realize halfway through that I haven't actually decided anything yet. I have a vague preference. Forcing myself to name the alternatives and articulate why each is worse than my preferred option is a way of converting vague preference into actual decision. Sometimes the alternatives turn out to be better than I thought, and I change my mind. Better to discover this at ADR-writing time than after writing the code.
This is the "rubber duck" effect, but for architecture rather than debugging. The duck makes you say the thing out loud. The ADR makes you write it down. The act of writing surfaces gaps the act of thinking doesn't.
The 38 in nine months number
I wrote 38 ADRs in roughly nine months — 36 full decisions plus a couple of refinement notes that update earlier ADRs as the design evolved. That averages to about one per week, which is sustainable. The distribution is uneven — early on I wrote four or five in the first two weeks while making foundational decisions, then settled to one or two a week as the codebase stabilized. The math is: 20 minutes per ADR, four ADRs a month, eighty minutes a month. For a system you'll live in for years, this is trivial.
The titles of mine, by topic cluster:
- Foundations: Clean Architecture Migration, Result Pattern, Value Objects, Event-Driven Decoupling, Type Guards over Assertions
- Application structure: Use Case Pattern, Query Services, Repository Interfaces, Result Combinators, Base Classes for Cross-Cutting
- Event infrastructure: Event Bus Dual Pattern, Event Routing Metadata, BullMQ Job Queue
- Workflow engine: Workflow-Driven Document Processing, Workflow Config Validation, Batch-Aware Use Cases, Claim State Primitive, Claim Recovery Contract
- Cross-cutting: Three-Tier Data Access, Remote Modules Pattern, Template Sync, RBAC, DDD Cross-Module Boundaries, Workflow Config Validation
- Cross-module atomicity: Cross-Module Atomic Writes (the sentinel pattern)
- Tenancy & observability: Multi-Tenancy, Tenancy Isolation & RLS, ALS Request Context Spine
- Integration: EDC Three-Lane Interaction, BD Data Acquisition
Pattern: foundations early, infrastructure middle, integrations late, refinement throughout. Each ADR builds on earlier ones, and the cross-links make the dependency graph navigable.
What I'd recommend
If you're a solo developer building something you intend to live with for more than a year, consider writing ADRs. Start with five and see if you keep going. The discipline that matters is:
- Write them while making the decision, not after
- Include alternatives and rejection reasons
- Keep them short — one page is plenty for most
- Cross-link liberally
- Don't delete superseded ones
If you're hiring a senior engineer, asking to see their ADRs is a useful filter — though hardly the only one. A candidate who has 30+ ADRs across a real codebase has demonstrated a discipline that is not widely shared. The ADRs themselves will tell you more about how they think than a single system-design interview usually does.
And if you're me, six months from now, reading this: the ADRs were worth the time. Keep writing them.