Every design process produces decisions. Most design processes produce very little record of why those decisions were made, what alternatives were considered, and — most importantly — what the best argument against the chosen approach was and why it was rejected. The absence of this record is not a documentation problem. It is an intellectual honesty problem, and it has a specific downstream cost: when circumstances change, when the decision needs to be revisited, when a new team member asks why the system works the way it does, there is nothing to examine. The decision is present in the code. The reasoning is nowhere.

The Architecture Decision Record — ADR — is the antidote to this. In its full form, it is a short document that captures the context that made a decision necessary, the decision itself, the alternatives that were considered and rejected, the consequences the decision carries, and — in the version I consider essential — a direct rebuttal to the strongest objection that could be raised against it. That last field is the one that makes an ADR a design artefact rather than documentation. Anyone can write down what they decided. Writing down the best argument against your decision, and answering it honestly, requires something more: the intellectual confidence to have actually tested the decision before committing to it.

This article walks through three ADRs from real design work — two from an autonomous deal desk pipeline and one from an enterprise knowledge graph system — with full context, alternatives, consequences, and the objection each decision had to survive. The goal is not to justify those specific decisions. It is to show what an ADR looks like when it is doing its job.


The anatomy of an ADR that earns its place

The ADR format has been described by Michael Nygard and formalised in various templates over the years. The core structure — context, decision, consequences — is widely reproduced. What is less consistently applied is the constraint that distinguishes a useful ADR from a ceremonial one: the requirement that the rejected alternatives be named with their actual reasons for rejection, not dismissed; and that the primary objection to the accepted decision be stated directly and answered.

The table below maps each field to its purpose and the question it must answer to be considered complete. The Rebuttal field is marked optional in some templates. I treat it as required for any decision that involves a meaningful trade-off — which is to say, any decision worth recording at all.

ADR field anatomy — what each field is for and what makes it complete
Field
What it must answer
What makes it incomplete
Context Required
What situation made this decision necessary? What constraints existed? What was the design space?
Stating the technical problem without the business or operational constraint that shaped the options. Context that a reader must already understand to make sense of.
Decision Required
What did we choose? Stated precisely enough that a future reader can tell whether the current system reflects this decision or has diverged from it.
"We chose X." Full stop. A decision without its scope — what it applies to and what it does not — cannot be used to evaluate future deviations.
Alternatives rejected Required
What did we not choose, and why specifically? The reason for rejection must be substantive — not "too complex" but "too complex because of reason Y in context Z."
A list of names without reasons. "We considered Temporal.io but rejected it" is not an alternative evaluation. It is a reference to an evaluation that was not recorded.
Consequences Required
What does this decision commit us to? What does it make harder? What maintenance, migration, or cost obligations does it carry?
Only positive consequences. Every architectural decision creates constraints. An ADR that lists only benefits has not been honestly evaluated.
Rebuttal Required in practice
What is the strongest argument against this decision? State it precisely — as an objection a well-informed critic would actually make — and answer it directly.
A weak or strawman objection, easily dismissed. The rebuttal field is only valuable when the objection is strong enough that answering it required genuine reasoning. If the objection is trivial, the decision was probably not a real trade-off.

ADR in practice: three decisions, three rebuttals

What follows are three Architecture Decision Records drawn from real design work. They are presented in the format described above, with the Rebuttal field treated as a required element. Each is accompanied by a brief commentary on what made the decision difficult and what the rebuttal had to actually establish — not just assert.

Decision one: the orchestration framework choice

The first decision concerns the orchestration layer of an autonomous deal desk: a GCP-native agentic pipeline that processes inbound Salesforce opportunities and generates compliant quote documents without human intervention below a configurable value threshold. The orchestration layer must implement a ReAct loop with tool-calling, state management, retry handling, and first-class interrupt nodes for HITL. The candidate options were the Vertex AI Agent SDK and a custom Python implementation against the Gemini API directly.

The objection this decision had to survive was not a weak one. Framework portability is a real consideration in enterprise deployments, and the argument for avoiding GCP lock-in is made by competent architects with good reasons. The rebuttal had to establish not just that the Vertex AI SDK was better for this use case, but that accepting the lock-in was the right trade-off given the defined deployment boundary. That is a different, and more honest, argument than "Vertex AI is better."

ADR-001 Vertex AI Agent SDK over custom ReAct implementation Accepted
Context
The orchestration layer requires a ReAct loop with tool-calling, state management, and retry handling. Two primary options exist: implement the loop directly in Python against the Gemini API, or use the Vertex AI Agent SDK which provides these as managed primitives. The system is GCP-native by requirement; portability to other clouds is not a defined business requirement for this deployment.
Decision
Use the Vertex AI Agent SDK. It provides managed tool-call dispatch, turn-limit enforcement, structured memory, and built-in Vertex AI tracing — all required capabilities. Applies to all orchestration layers in this deployment. Scope does not extend to future deployments with different cloud constraints.
Rejected
LangGraph/LangChain: Framework-agnostic and more portable, but requires manual implementation of tool-call retry logic, quota enforcement, and IAM integration — all of which the Agent SDK provides natively. The portability benefit is not realised in a GCP-native deployment.

Custom Python ReAct: Maximum flexibility, but no managed observability, no built-in retry, no quota enforcement. Implementation surface increases by an estimated 40% without proportionate capability gain. Maintenance cost shifts entirely to the team.

Vertex AI Reasoning Engine: Higher abstraction layer, less control over the ReAct loop topology. HITL interrupt nodes require first-class graph primitives that Reasoning Engine does not expose at the required granularity.
Consequences
Coupling to the Vertex AI ecosystem. Migration to another framework requires re-implementing the tool manifest, state model, and interrupt node topology. The LangGraph ADK integration must be maintained as a versioned dependency. Graph topology changes require Architecture Board review — a governance overhead that custom implementations avoid but that also provides audit trail coverage that custom implementations lack.
Rebuttal to primary objection

Objection: LangGraph is framework-agnostic and avoids GCP lock-in, which should be the default posture for any enterprise architecture.

This objection is architecturally valid as a general principle. The rebuttal is not that lock-in is acceptable — it is that portability is not free, and the cost must be evaluated against the defined deployment boundary. This system is GCP-native by design: AlloyDB, Chronicle, Workload Identity, VPC Service Controls, Cloud KMS. The portability benefit of a framework-agnostic orchestrator is only realised if the other components are also portable. They are not. Accepting framework portability at the orchestration layer while maintaining GCP-specific infrastructure at every other layer is a false compromise: it adds the complexity of avoiding lock-in without delivering the benefit. If the deployment boundary changes — if multi-cloud becomes a real requirement — the correct response is to re-evaluate this ADR with the new constraint, not to pre-optimise for a requirement that does not yet exist.

Decision two: the vector store selection

The second decision concerns the storage layer for contract precedent embeddings in the same pipeline: approximately 500,000 document chunks, retrieved by approximate nearest-neighbour search with metadata filters applied at query time. The candidate options were AlloyDB with the pgvector extension and Vertex AI Vector Search.

This is a canonical "managed service versus integrated service" trade-off, and the objection to the chosen approach — that Vertex AI Vector Search is the more scalable, lower-operational-overhead option — is one that Google's own documentation would support. The rebuttal has to be specific about the boundary condition at which AlloyDB is the right answer, and what the documented migration path is when that boundary is crossed.

ADR-002 AlloyDB with pgvector over Vertex AI Vector Search for contract embeddings Accepted
Context
Contract precedent retrieval requires ANN vector search over approximately 500,000 document chunks. A metadata filter requirement — segment, value tier, date recency — must be applied at query time. The filter-plus-vector query is most efficiently expressed when both operations run on the same relation, eliminating a cross-system join. The corpus is expected to grow to 5 million chunks over a 24-month horizon.
Decision
AlloyDB PostgreSQL with the pgvector extension and HNSW indexing. Metadata filters expressed as SQL WHERE clauses on the same relation as the vector column. HA cluster with two read replicas and automatic failover under 30 seconds. The decision applies until corpus size exceeds 5 million chunks — the documented graduation trigger to Vertex AI Vector Search.
Rejected
Vertex AI Vector Search: Fully managed, scales to billions of vectors. Rejected at this corpus size because metadata filtering requires a cross-service join: vector results retrieved from Vector Search, then filtered against AlloyDB metadata. This join adds latency and architectural complexity that the single-relation AlloyDB model avoids. Vector Search becomes the documented graduation path at 5M+ chunks.

Pinecone: Best-in-class ANN recall and query performance. Rejected because it introduces a third cloud dependency in a GCP-native system, and its metadata filtering model, while capable, would require the same cross-service join problem as Vector Search.

BigQuery ML vector search: Suitable for batch retrieval; online ANN latency at sub-200ms is not achievable at the required consistency level. Not suitable for the 90-second end-to-end deal processing target.
Consequences
AlloyDB cluster requires HA configuration, backup policy, and HNSW index management. The HNSW index must be rebuilt on major corpus updates — an operation that requires a maintenance window. At 10M vectors, the HNSW in-memory index requires approximately 60 GiB RAM on the primary node, making AlloyDB cost-prohibitive at that scale. The graduation path to Vertex AI Vector Search is documented and must be executed before the 5M-chunk threshold is reached.
Rebuttal to primary objection

Objection: Vertex AI Vector Search is fully managed, scales to billions of vectors without operational overhead, and is the correct default choice for any GCP-native vector retrieval use case.

At billion-scale corpora, this objection is decisive and AlloyDB would be the wrong choice. The rebuttal is specific to the 500K–5M chunk range and the filter-plus-vector query pattern. At this scale, the operational overhead of AlloyDB — HA configuration, index management — is bounded and well-understood. The cross-service join that Vertex AI Vector Search would require adds latency that is directly measurable: in testing, the metadata post-filter join adds 40–80ms to query time, which represents a meaningful fraction of the 90-second end-to-end target for a multi-step pipeline. The decision is not "AlloyDB is better than Vector Search." It is "AlloyDB is better for this query pattern at this corpus scale, with a documented graduation path when the scale changes." The graduation path is specified in the Day 2 operations runbook. This ADR is a time-bounded decision, not a permanent one.

Figure 1 — ADR lifecycle within the TOGAF ADM: where decisions are made, recorded, and revisited
ADR lifecycle in TOGAF ADM phases Flow diagram showing how Architecture Decision Records are created during ADM Phases B through D, reviewed at Phase H governance gates, and triggered for revision by change requests or new constraints. ADRs feed the architecture requirements register and the risk register. PHASE A Architecture Vision PHASE B–D Design phases — ADRs created here PHASE E–F Migration planning PHASE G Implementation governance PHASE H Governance gate — ADRs reviewed here New constraint or change request triggers ADR revision Architecture requirements register (consequence tracking) Risk register (rejected alternatives as risks) Migration roadmap (graduation triggers) Governance evidence (Phase H review artefacts) ADRs are created in Phases B–D, reviewed at Phase H governance gates, and revised when constraints change. Rejected alternatives become the risk register. Graduation triggers become the migration roadmap. Both are live documents.
ADRs are not static documents filed after a decision is made. In a TOGAF-governed programme they are living artefacts: created during design phases, used as input to the risk register and requirements register, reviewed at Phase H governance gates, and revised when new constraints make the original decision worth reconsidering. The graduation triggers in ADR-002 — the 5M-chunk threshold that triggers migration to Vertex AI Vector Search — are themselves architectural commitments that appear in the Day 2 operations roadmap.

Decision three: the knowledge graph store

The third ADR comes from a different project: an enterprise knowledge graph system that models relationships between people, expertise, engagements, and methodologies for a professional services firm. The graph store choice was between Neo4j Aura Enterprise and Amazon Neptune. This decision is worth examining in detail not because of the technology choice, but because of what the context section reveals: the decision was not primarily about graph performance. It was about query language standardisation, operational network topology, and the team's existing skill set.

The objection this decision had to survive was the cross-cloud dependency argument — which is a real architectural concern, not a weak one. The rebuttal required establishing that GCP-native network peering with Neo4j eliminated the primary operational objection to a third-party graph database in a GCP-native system.

ADR-003 Neo4j Aura Enterprise for knowledge graph store over Amazon Neptune Accepted
Context
The enterprise knowledge graph requires a graph database supporting: Cypher query language for ontology-governed traversals; the APOC procedure library for ontology management operations including schema migration and relationship aggregation; GCP-native network peering to avoid public internet egress from the VPC; and a consistent query model across the engineering team. The system is deployed in GCP europe-west3 by regulatory requirement.
Decision
Neo4j Aura Enterprise 5.x with GCP Private Service Connect. One dedicated instance per enterprise tenant — no shared Neo4j instances. Tenant isolation enforced at the GCP project boundary with VPC Service Controls, supplemented by a per-tenant CMEK key ring in Cloud KMS for cryptographic isolation. The Cypher query model is the standard for graph operations across the platform.
Rejected
Amazon Neptune: Mature graph database with strong operational track record. Rejected on three grounds: (1) cross-cloud network complexity — routing graph traffic from GCP to AWS introduces latency, egress cost, and a VPN/interconnect dependency that adds operational surface without proportionate capability gain; (2) Gremlin and SPARQL are not team-standard query languages — adopting Neptune would require parallel skill development; (3) GCP-native integrations (Cloud Audit Logs, Workload Identity, VPC-SC) are not available for a cross-cloud deployment without custom bridging.

Spanner Graph: Would eliminate the third-party graph dependency entirely. Rejected because Spanner Graph was in preview status at the time of this ADR with insufficient APOC-equivalent procedure library for the ontology management operations required. Documented as the re-evaluation candidate when Spanner Graph reaches GA with APOC parity.
Consequences
Neo4j licensing is a per-GB cost driver that must be modelled against graph growth trajectory. One dedicated instance per tenant increases cost linearly with tenant count — acceptable for the B2B enterprise model, where tenant count is bounded. Graph topology changes (new node types, new relationship types) require Architecture Board review and a Cypher schema migration. The APOC procedure library version must be pinned and tested on Neo4j version upgrades.
Rebuttal to primary objection

Objection: A GCP-native system should not depend on a third-party database product from a different cloud vendor. Amazon Neptune at least offers a managed service on a major cloud with established enterprise SLAs; Neo4j Aura introduces a third vendor dependency with its own availability risk and pricing model.

This objection frames the choice as "GCP-native vendor" versus "third-party vendor," which is the wrong frame for this decision. The actual choice is between: (a) a graph database with native GCP Private Service Connect, CMEK integration, and a query model the team knows, deployed as a GCP-managed service partnership; and (b) a graph database on a different cloud, requiring cross-cloud network bridging, egress cost management, and a query model the team would need to learn. Both are third-party dependencies. The Neo4j option is more deeply integrated with GCP's operational model. The Neptune option has a worse operational profile for a GCP-native deployment. The "GCP-native purity" argument favours Spanner Graph, which is the documented re-evaluation candidate — not Neptune. This ADR does not choose Neo4j over a GCP-native option. It chooses the best available option given the current GA status of GCP's own graph product.


What an ADR reveals that a design document does not

Reading these three ADRs in sequence, a pattern becomes visible that is not visible in any other design artefact: the shape of the decision space at the time each choice was made. The rejected alternatives are not historical footnotes. They are the live risk register — the options that were available but not chosen, each of which might become the right answer if circumstances change.

ADR-002's rejection of Vertex AI Vector Search, for example, contains within it a specific trigger condition: 5 million chunks. When the corpus crosses that threshold, the ADR is not merely out of date — it contains, in the rejection rationale, the precise argument for why the original decision no longer applies. The migration path writes itself from the original ADR. This is the property that makes a well-written ADR more valuable than any amount of post-hoc documentation.

The rebuttal field reveals something different: the quality of the reasoning at the time of the decision. A rebuttal that engages a weak objection tells you the decision was not seriously challenged. A rebuttal that engages a strong objection and answers it with a specific, bounded argument tells you the decision was tested. The ADR-001 rebuttal on lock-in, for example, does not argue that lock-in is acceptable as a general principle. It argues that portability is only valuable if it applies to the full system, and that this system's other components preclude portability regardless of the orchestration framework choice. That is a specific argument. It can be falsified. If a future requirement changes the portability assumption, the rebuttal fails and the ADR must be revisited.

An ADR that does not engage the strongest objection is not an architectural record. It is a justification. The difference matters most precisely when circumstances change and you need to know whether the original decision was right for reasons that still apply, or right for reasons that no longer do.


The ADR as a hiring signal

There is a practical reason why ADRs matter to how architects are evaluated that goes beyond their value as design artefacts. In a system design interview, the questions that separate strong candidates from adequate ones are not "what would you build?" They are "what did you decide not to build, and why?" and "what's the strongest argument against your proposed design, and how do you answer it?"

These are ADR questions. They probe whether the candidate has actually tested their design decisions under adversarial conditions — whether they have sat with the best objection and produced a specific, bounded response — or whether they have simply arrived at a conclusion and looked for reasons to support it. The difference is audible in the answer. It is also visible in the design artefacts a candidate produces: an ADR with a strong rebuttal is evidence of the former. A design document with no rejected alternatives and no objections engaged is evidence of the latter.

The discipline of writing the rebuttal field — of finding the strongest objection and answering it honestly — is the discipline that makes a design decision trustworthy. Not because the answer is always right, but because the process of finding the strongest objection and testing the decision against it is the only way to know whether the decision is right for the right reasons, and whether those reasons will still hold when the system is operating under conditions its designers did not fully anticipate.

References & Further Reading

Nygard, M. (2011). "Documenting Architecture Decisions." Cognitect Blog. — The original article defining the ADR format: Context, Decision, Status, Consequences. The foundational reference for the structure described in this article. Available at thinkrelevance.com.

Keeling, M. (2017). Design It! From Programmer to Software Architect. Pragmatic Programmers. — Chapter 11 covers architectural decisions as first-class design artefacts. Keeling's framing of the decision as the unit of architectural thinking, not the diagram, is the grounding for this article's argument.

The Open Group, TOGAF 10 Standard — Architecture Decision Records are referenced throughout the ADM as evidence artefacts for Phase H Architecture Change Management reviews. The relationship between ADRs, the requirements register, and the risk register described in Figure 1 follows the TOGAF Phase H governance model.

Richards, M. and Ford, N. (2020). Fundamentals of Software Architecture. O'Reilly. — Chapter 19, "Making Architecture Decisions," provides the technical architect's perspective on documenting trade-offs. The emphasis on making decisions reversible where possible, and documenting the conditions under which reversal is appropriate, underlies the graduation trigger pattern in ADR-002.

Hohpe, G. (2020). The Software Architect Elevator. O'Reilly. — The distinction between decisions made at the penthouse level (strategy) and the engine room level (implementation), and why both must be documented, is the theoretical underpinning for why ADRs belong in a TOGAF Phase H governance gate and not just in a code repository.

Continue reading

These notes are published when there is something worth saying. To receive new Field Notes directly, write to hello@datadomine.com with the subject line: Field Notes.

All Field Notes Programmes Get in touch