Post
JA EN

The ADR Ledger Behind a DDD-First AI Prototype: A Hotel Booking Walkthrough

The ADR Ledger Behind a DDD-First AI Prototype: A Hotel Booking Walkthrough
  • Audience: Anyone who read the practical guide on using ADRs in DDD development and thought, “I’d like to see what actual ADRs look like.” If you’ve read the hotel-booking implementation walkthrough, this reads as its “behind the scenes.”
  • Prerequisites: Reading the practical guide first is recommended. It also helps to have read the implementation walkthrough of the same scenario, so you can see which code each ADR corresponds to.
  • Reading time: about 18 min

Overview

I previously published a walkthrough of a seven-phase workflow for having an AI build a prototype DDD-first, implemented for a fictional hotel chain, “Minatoya Hotels.” It traced the project from Event Storming through the handoff to the productionization engineer—but there was a parallel artifact it never depicted: the ledger of ADRs (Architecture Decision Records).

In every phase, countless design decisions were actually being made. Should “reservation” be split into Reservation and Booking? Should the domain be cut into four boundaries? How should the relationship with the accounting system work? How should state be expressed in types? As I argued in the practical guide, these are decisions that—separate from the “current constraints (the What)” written in AGENTS.md—should be preserved in ADRs as the “Why we did it this way, and what we gave up (the Why).”

This article shows the ten ADRs produced in the Minatoya Hotels project one by one, in Michael Nygard’s format (Status / Context / Decision / Consequences)1. The important thing is that this is not a display of polished, cleanly written deliverables. A boundary gets drawn wrong and redrawn (ADR-0002 is superseded by ADR-0010), a definition gets overlooked and added later (ADR-0008), an AI drafts an ADR and a human approves it (ADR-0006)—we follow how the practices discussed in the practical guide actually behave on the ledger. Read the ledger chronologically and you see, laid bare, the history of what this project thought, where it got things wrong, and how it fixed them. That is the value of ADRs.

Premise: where to keep ADRs, and how they divide labor with AGENTS.md

Role 4 (AI Environment Engineer) decided where to keep the ADRs at the start of the project.

1
2
3
4
5
6
7
8
9
project-root/
├── AGENTS.md            # current constraints (loaded by the AI every turn)
├── docs/
│   └── adr/             # the ledger of why (append-only)
│       ├── 0001-reservation-booking-split.md
│       ├── 0002-four-bounded-contexts.md
│       └── ...
├── src/contexts/        # code per Bounded Context
└── CLAUDE.md            # contains the one line "Check docs/adr/ before changing the design"

The division-of-labor rule is simple. AGENTS.md states things in the present tense and briefly: “A Reservation is a hold, expires in 30 minutes.” Why Reservation and Booking are split, and why 30 minutes, goes in the ADR. The AI reads AGENTS.md every turn, so we keep it from bloating. The ADRs are not read in everyday work, but the AI consults them when asked “why is it this way?” or when it tries to change the design. CLAUDE.md carries the single line “Check docs/adr/ before changing the design,” so the AI doesn’t unilaterally overturn past decisions2.

One note: the management of these ten ADRs—filing, drafting, formatting and numbering, supersede handling, consistency checks—is run by the AI in this project. What the humans (Role 1, Role 4) hold onto is the strategic decision itself and the “validation of the Why.” Where I write “Role 1 writes” below, the precise meaning is “Role 1 decides and finalizes the Why; the AI does the filing and formatting.”

Now let’s watch the ADRs come into being phase by phase.

Behind Phase 1: the domain-split decisions (Role 1)

The three days of Event Storming produced the two most important ADRs. The one responsible for the decision and finalizing the Why is Role 1 (the Domain Definer); the filing and formatting are done by the AI.

ADR-0001: Separate Reservation and Booking as distinct concepts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# ADR-0001: Separate Reservation and Booking into distinct Aggregates

## Status
accepted (2026-XX-XX)

## Context
On day 2 of Event Storming, we found that the "reservation" the booking staff
means and the "reservation" the front desk means are different things. The former
is a pre-authorization hold before card verification; the latter is a confirmed
slot that gets recorded in accounting. Implementing both as a single "reservation"
concept buries the before/after of card verification and the timing of accounting
recognition inside status flags, and the meaning of "confirmed" drifts by context
when the AI implements it.

## Decision
Call the hold a Reservation and the confirmed slot (after successful card
verification) a Booking, and implement them as separate Aggregate Roots. Also
define prohibitions in the glossary (ubiquitous language)—e.g., do not call a
Reservation's Confirmed state "reservation established."

## Alternatives considered
- A single Reservation entity carrying only a status field
  → Rejected. The accounting-recognition boundary doesn't surface in the type, and
    there's a high risk the AI implements "confirmed" ambiguously.
- Leaning on a generic term like "Order"
  → Rejected. It isn't hotel-domain vocabulary, and it pulls toward the generic
    terms in the training data.

## Consequences
+ The vocabulary stays consistent across types, tables, and UI.
+ The accounting-integration boundary (Booking = the unit of revenue recognition)
  becomes clear.
- More Aggregates, and we must make the Reservation → Booking transition use case
  explicit.
Follow-up: the transition is triggered by the card-verification-success event
(captured as INV-3 in types and tests).

This decision is the crux of preventing the accident the DDD-first design rationale warns about—”the AI’s chosen vocabulary and business guesses becoming an established fact.” As the parable3 of an AI agent that confused booking and reservation shows, leaving this ambiguous leads to real, business-level harm. That is exactly why we preserve it, with its rationale, as an ADR.

ADR-0002: Split the domain into four Bounded Contexts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# ADR-0002: Split into four Bounded Contexts

## Status
accepted (2026-XX-XX) — later partially superseded by ADR-0010

## Context
From the clusters of events and the differences in vocabulary, four areas emerged:
customer reservation, inventory management, front-desk operations, and housekeeping.
How do we translate this into structure?

## Decision
Split into four Bounded Contexts, separated into directories under src/contexts/.
Cross-context imports are permitted only through an ACL layer; anything else is
made to fail in the guardrail CI.

## Alternatives considered
- Pushing through with a single domain model → Rejected (the term collisions of
  "room" vs. "inventory" can't be resolved).
- Splitting into microservices from the start → Rejected. Overkill at the beta
  stage. A modular monolith is enough, and the cost of redrawing a boundary later
  if it turns out to be wrong is low.

## Consequences
+ The boundaries are reflected in file structure and CI, so even if the AI writes
  boundary-crossing code, it's detected.
- If the way a boundary was drawn turns out to be wrong, it affects already-generated
  code (this materializes in ADR-0010, below).

Note the Status line of this ADR. “later partially superseded by ADR-0010”—this boundary gets redrawn later. We’ll see how in Phase 7. What matters is that “why we split it into four to begin with, and in this particular way” is preserved here for when we redraw it.

Behind Phase 2: the decisions on boundary relationships and business rules (Role 1)

During the week spent organizing the five elements of domain definition (ubiquitous language, domain model, Bounded Context, invariants, use cases), the relationships with external systems and the business numbers were decided.

ADR-0003: Make the relationship with the accounting system (existing on-prem) Conformist + ACL

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# ADR-0003: Make the accounting-system boundary Conformist, and prevent vocabulary
            contamination with an ACL

## Status
accepted (2026-XX-XX)

## Context
Integration with the existing on-prem accounting system is a hard requirement.
The accounting side can't be changed, and its vocabulary differs (the accounting
"reservation" is revenue-recognition based).

## Decision
The relationship is Conformist (we follow the other party's vocabulary). However,
we place an ACL (Anti-Corruption Layer) between it and the customer-reservation
context to prevent the accounting side's vocabulary from invading our own domain.
During the beta period, what's beyond the ACL is a mock; real integration is
implemented in the productionization phase.

## Consequences
+ The domain model isn't contaminated by the accounting system's circumstances.
- The cost of implementing and maintaining the ACL. During beta, accounting
  integration runs on manual copy-paste (documented as a known constraint).

ADR-0004: Set Reservation’s expiry to 30 minutes (a business decision)

This is not a technical decision but a business one (a BDR-style ADR). Shown here in summary form.

Status accepted / Context How long to hold a hold slot. Too long and inventory dies; too short and the customer’s hold expires before payment / Decision 30 minutes. Captured as INV-2 in types and property tests / Alternatives 15 minutes (concern about increased drop-off) and 60 minutes (squeezes inventory). 30 minutes by agreement between management and the front desk / Consequences Balances inventory turnover against customer experience. Expiry processing requires monitoring (→ ADR-0006)

Where preserving “why 30 minutes” pays off is six months later, when someone says “actually, let’s make it 45 minutes.” If there’s a record that we considered 15 and 60 minutes at the time and settled on 30, the discussion can restart from the delta.

Behind Phase 3: the technology-selection decisions (Role 4)

In this phase—writing AGENTS.md and finalizing the tech stack—Role 4 (AI Environment Engineer) wrote the technology-selection ADRs.

ADR-0005: Use OpenNext on AWS for hosting (do not adopt Vercel)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# ADR-0005: Adopt OpenNext on AWS for hosting

## Status
accepted (2026-XX-XX)

## Context
We use Next.js 16 as the production technology. The easiest option is Vercel, but
this project has requirements for (a) integration with the on-prem accounting
system over a VPC, (b) in-house data management, and (c) cost predictability.

## Decision
Adopt OpenNext on AWS (Lambda + CloudFront + S3 + Aurora Serverless v2).

## Alternatives considered
- Vercel → Rejected. VPC-based on-prem integration is difficult, and we want to
  avoid vendor lock-in.
- Cloudflare (@cloudflare/next-on-pages) → On hold. The structure can be assembled
  equivalently, but for reasons of Aurora/VPC integration we choose AWS this time.
  We keep it as a candidate for a future migration target.

## Consequences
+ We can manage it in-house on AWS and integrate with the accounting system over a VPC.
- Operations are complex (CDK, Lambda execution constraints, connection pooling =
  RDS Proxy required).
  → These constraints are transcribed in the present tense into AGENTS.md's
    "OpenNext-specific rules."

Here the separation of What / Why from the practical guide becomes concrete. ADR-0005 preserves “why we dropped Vercel and chose AWS.” Meanwhile, the present-tense constraints that follow from it—”Lambda is read-only, so temp files go only in /tmp,” “decompose long-running processing onto EventBridge”—are transcribed into AGENTS.md and consulted by the AI every turn. The Why and the What of the same decision are placed in separate files, with separate lifespans.

Behind Phase 4: the type-expression decision (Role 4)

ADR-0007: Express domain state with a discriminated union

Status accepted / Context Reservation/Booking have multiple states. We want to prevent invalid state combinations and protect the quality of AI-generated code with types / Decision Make status a discriminated union, and fix the fields each state carries in the type (e.g., only Pending carries expiresAt) / Alternatives Lining up optional fields on a single interface → Rejected (invalid state combinations become expressible). enum + a separate table → overkill / Consequences Invalid states become compile errors, so the AI can’t write type violations. Adding a state requires updating every branch

This ADR is the design-decision backing for the foothold I described in the practical guide as “AGENTS.md and types physically protect the AI’s quality.” Preserve “why a discriminated union rather than optional fields,” and later when the AI proposes “wouldn’t optional be simpler?” the reason for rejection is right there in the ledger.

Behind Phases 5–6: ADRs get operated—detection and drafting

This is a phase where ADRs are used more than born. Two situations arose.

Situation 1: Role 2 detects an ADR violation and escalates. While building the prototype, the AI generated code that imported an internal type of the customer-reservation context directly from the inventory-management context. The guardrail CI fails. Role 2 (an IT staffer fluent in the business, not an engineer) has no power to judge the technical merits of the code. But they could open docs/adr/0002-four-bounded-contexts.md and escalate to Role 4: “ADR-0002 decided that cross-context references are allowed only through the ACL. Isn’t this a violation?” As stated in the practical guide, Role 2 does not decide. Role 2 detects, grounded in an ADR.

Situation 2: the AI drafts an ADR and Role 4 approves it. “How to execute the 30-minute expiry,” which had been put on hold in Phase 3, gets implemented in Phase 5. After Role 4 made the decision, the work of writing it up as an ADR was handed to the AI. They handed it the PR diff (the addition of an EventBridge rule and a Lambda) and instructed, “Write a first draft of an ADR for this change”4. To the draft the AI generated, Role 4 appended the reasons for rejection (why it isn’t synchronous processing) and finalized it.

ADR-0006: Make Reservation-expiry processing asynchronous via EventBridge → Lambda

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# ADR-0006: Make expiry processing an EventBridge-driven asynchronous Lambda

## Status
accepted (2026-XX-XX)
## First draft: AI-generated (from the diff of PR #42) / Why and Alternatives:
   finalized by Role 4

## Context
How to execute "transition a Reservation to Expired after 30 minutes," as set in
ADR-0004.

## Decision
Use an AWS EventBridge 5-minute-interval schedule to invoke a dedicated Lambda that
transitions Pending Reservations past their expiresAt to Expired.

## Alternatives considered
- Checking for expiry synchronously inside request processing → Rejected. Lambda's
  execution constraints, and the problem that expiry is delayed during periods of
  no traffic.
- A resident worker → Rejected. Inconsistent with the OpenNext / Lambda setup
  (ADR-0005).

## Consequences
+ Consistent with ADR-0005's Lambda constraints. + Expiry processing is independent
  of customer requests.
- Up to a 5-minute expiry delay. Add a DLQ and alerts at productionization time
  (follow-up).

This is the live demonstration of the practical guide’s “the AI does the first draft, the human finalizes the Why.” The AI could accurately write from the diff that “an EventBridge and a Lambda were added (the What).” But “why synchronous processing was rejected” is a judgment that doesn’t appear in the diff, and Role 4 appended it. Noting the provenance in the Status field—”First draft: AI-generated / Why: finalized by Role 4”—lets you gauge the trustworthiness later.

Behind Phase 7 / beta operation: oversight, additions, and redrawing (Role 1)

The implementation walkthrough of the same scenario described how “pitfalls” materialized during beta operation. On the ADR ledger, those pitfalls are recorded as three new ADRs.

ADR-0008: Introduce cancellation-fee rules (after-the-fact correction of pitfall 1)

During beta operation, the owner realized that “leaving free cancellation in place is a problem for revenue.” In Phase 2, the business rule for cancellation fees had been forgotten in the definition—the pitfall the practical guide calls “definition quality propagates into the result.” We correct it with an ADR.

Status accepted / Context Phase 2 didn’t define cancellation fees, and the prototype was implemented as “free cancellation allowed.” A revenue problem surfaced during beta operation / Decision Give Booking a cancellation-fee rate that scales with the number of days from the stay date / Consequences Go back to the domain definition and add the rule. Append INV-7 to the invariants in AGENTS.md. This oversight was caused by “insufficient cross-review before handing the definition to the AI”; as a recurrence-prevention measure, we make a definition-review step mandatory.

An ADR can record not only the “decision” but also the reflection on why it was overlooked in the first place. That becomes a lesson for the next project.

ADR-0009: Establish a group-reservation context (feedback loop from pitfall 2)

During beta operation, the business knowledge that “group reservations are a separate flow from regular reservations” surfaced. Nobody was aware of it at first. This new ADR is the result of that discovery flowing back into the definition side.

Status accepted / Context Beta operation revealed that group reservations (multiple rooms at once, invoice payment, separate cancellation policy) are a separate flow from regular reservations / Decision Establish a new group-reservation context and add src/contexts/group-reservation/ / Consequences Add a fifth entry to the Bounded Context map. Update the context list in AGENTS.md. Do not let it affect the existing four contexts (preserve their independence)

This is not a supersede but a pure addition. The discovery of new domain knowledge doesn’t negate past decisions; it’s accreted onto the ledger.

ADR-0010: Redraw the front-desk / housekeeping boundary from “organization” to “responsibility” (pitfall 3, supersedes ADR-0002)

This is the most important ADR. The boundary drawn in ADR-0002 turned out, during beta operation, to be wrong.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# ADR-0010: Redraw the front-desk / housekeeping boundary on a responsibility basis

## Status
accepted (2026-XX-XX) — supersedes the relevant part of ADR-0002

## Context
ADR-0002 made "front-desk operations" and "housekeeping" separate contexts. But
during beta operation we found that at small branches the same team handles both.
Drawing the boundary by "organization (who does it)" was the mistake; it should have
been drawn by "responsibility (what is guaranteed)."

## Decision
Stop carving housekeeping out as an independent context, and redraw the boundary on
a responsibility basis. Keep only the housekeeping-completion notification path as
an integration point with front-desk operations.

## Consequences
+ The boundary fits the real business (the dual roles at small branches).
- Move part of the housekeeping context's code under front-desk.
- Mark the relevant part of ADR-0002 as superseded and link to this ADR.

Here the supersede mechanism from the practical guide paid off. When redrawing the boundary, ADR-0002 still held “why we split it into four to begin with, on an organizational basis.” So the redraw was nothing but a discussion of the delta—”the organizational basis was wrong, the responsibility basis is right.” Without ADR-0002, we’d have had to start from the archaeology of “wait, why did we split out housekeeping in the first place?” The fact that the reasons for past decisions were preserved is what lowered the cost of the do-over.

Diagrammed, the supersede relationships look like this.

flowchart TB
    A002["ADR-0002<br>Four Bounded Contexts<br>(split on organization)"]
    A010["ADR-0010<br>Redraw boundary on<br>responsibility basis"]
    A009["ADR-0009<br>New group-reservation<br>context (addition)"]
    A002 -->|"supersedes (partial)"| A010
    A002 -.->|"expand 4 → 5"| A009

Handoff: Role 3 reads from the ADR ledger

After three months of beta operation, Role 3 (the Productionization Engineer) takes over. The walkthrough sorted each component into “discard / keep,” but the speed and accuracy of those judgments are underpinned by being able to read the ADR ledger.

  • Being able to decide to keep contexts/*/domain/types.ts as-is comes from ADR-0007, which makes clear that “the discriminated union is an intentional quality design.”
  • Being able to decide to rewrite the mocks in contexts/*/acl/*.ts to production versions comes from ADR-0003, which states explicitly “mock during beta, real integration in production.”
  • Not being thrown by the housekeeping context’s structure differing from the others comes from being able to read in ADR-0010 the history of “we redrew it on a responsibility basis.”

Without ADRs, Role 3 would have to reverse-engineer the design intent from the code. With ADRs, they can read the design intent directly. This is the practical reality of what the practical guide called “ADRs are the handoff protocol across the four roles.”

Retrospective: the ledger of ten ADRs

The full picture of the ledger produced over the project.

ADRTitleStatusDecision & oversight (AI drafts/formats)Phase
0001Separate Reservation and BookingacceptedRole 1P1
0002Split into four Bounded Contextssuperseded by 0010 (partial)Role 1P1
0003Accounting system as Conformist + ACLacceptedRole 1P2
0004Reservation expiry of 30 minutesacceptedRole 1 (+ management)P2
0005Adopt OpenNext on AWS (rejected Vercel)acceptedRole 4P3
0006Expiry processing async via EventBridgeacceptedAI draft → finalized by Role 4P5
0007Express state with a discriminated unionacceptedRole 4P4
0008Introduce cancellation-fee rulesacceptedRole 1P7 (beta)
0009Establish group-reservation contextacceptedRole 1P7 (beta)
0010Redraw boundary on responsibility basisaccepted (supersedes 0002)Role 1P7 (beta)

From this table alone, you can survey the project’s decision-making history. Who decided what, what was overturned, and where the AI was involved are visible at a glance. This is information unique to the ledger—something you could never read out of AGENTS.md (a snapshot of current constraints).

A word of caution: the pitfalls listed in the practical guide show their faces in the demonstration too. Don’t write too many ADRs (trivial implementation choices during prototyping don’t become ADRs—the ten above are all narrowed to things that “will be decision-relevant three months later”); don’t take AI-drafted Whys at face value (as Role 4 finalized the rejection reasons in ADR-0006); when superseding, update AGENTS.md in the same PR (the boundary change in ADR-0010 is fixed simultaneously with the context list in AGENTS.md). Left alone, a ledger drifts away from reality. The principle that a decision only comes alive when it is referenced2 holds only when the ledger is maintained.

Conclusion

Behind Minatoya Hotels’ seven phases, ten ADRs ran in parallel. The four principles argued in the practical guide moved, concretely, on the ledger like this.

  • Separation of What / Why: ADR-0005’s “why Vercel was rejected” lives in the ADR; the consequent “Lambda constraints” live in AGENTS.md. The reason and the constraint of the same decision sit with separate lifespans.
  • Supersede underwrites the do-over: ADR-0002 could be redrawn by ADR-0010 because “why that boundary was chosen to begin with” was preserved. It was a discussion of the delta, not archaeology.
  • The handoff protocol across the four roles: Role 1 handles domain splits (0001-0004, 0008-0010), Role 4 handles technology selection (0005, 0007), Role 2 detects ADR violations and escalates, Role 3 reads the ledger to keep or discard.
  • The AI does the first draft, the human finalizes the Why: for ADR-0006, the AI wrote a first draft from the diff, and Role 4 appended the rejection reasons and finalized it.

The ADR ledger is not the project’s “meeting minutes.” It is a record of the why, for future humans and AIs to reference. Open Minatoya’s ledger six months later and a person—or a handed-off AI—can instantly answer “why is this system the way it is?” A decision comes alive not when it is written, but when it is referenced2.

The practical guide and the rationale piece are worth reading alongside this: This article focused on a concrete demonstration of an ADR ledger. The principles of using them—the pros and cons, the division of labor with AGENTS.md, what does and doesn’t become an ADR, and the practice of delegating management to the AI—are collected in How to use ADRs in DDD development, and the rationale and evidence for why ADRs are being re-evaluated in the AI era are collected in Why ADRs are being re-evaluated in the AI era.

Please also see other articles related to this theme:

References

References corresponding to the citation numbers in the text, listed in numerical order.

Other references (not cited by number in the text)

  • Agent Decision Records (AgDR) - me2resh, GitHub (2025). An extended standard for recording the autonomous decisions of AI agents. A reference for the idea of preserving provenance (who wrote the first draft) in metadata, as in ADR-0006. [Reliability: medium]
  1. Documenting Architecture Decisions - Michael Nygard, Relevance / Cognitect (2011-11-15). The origin of the ADR. This article’s ADR bodies follow the Status / Context / Decision / Consequences format. Officially hubbed at adr.github.io. [Reliability: high] ↩︎

  2. A Prescription for SDD Fatigue: Recording Architectural Decisions with ADRs in the AI Era - Kosk, Zenn (2026). “A design decision comes alive not when it is written but when it is referenced,” the “supersede mechanism,” and the “CLAUDE.md rule to check ADRs before changing the design.” [Reliability: medium] (practitioner blog) ↩︎ ↩︎2 ↩︎3

  3. Domain Driven Agent Design - Russ Miles (2025). A parable of an AI agent that confused booking and reservation (the motivation for ADR-0001). [Reliability: medium] (practitioner blog) ↩︎

  4. From Stale Docs to Living Architecture: Automating ADRs with GitHub + LLM - Iraj Hedayati, Medium (2025-09-14). A workflow where an LLM generates a first draft of an ADR from a PR’s diff and a human reviews and refines it (the model for ADR-0006). [Reliability: medium] (practitioner blog) ↩︎

This post is licensed under CC BY 4.0 by the author.