Mine one messy design-discussion transcript for every decision it contains, draft five ADRs with an AI assistant in a consistent house style, then turn the AI against your highest-stakes decision with an adversarial review prompt — and revise the ADR to survive it.
Anita's team at Orbit made five real architecture decisions in a 41-minute call about ingestion eventing. As usual, none were written down. Six months from now someone will ask "why SQS and not Kafka?" and the answer will be a shrug. The ADR factory pattern — AI drafts, human decides, adversary attacks — makes capturing decisions cheaper than losing them.
~75 minutes hands-on · Core tier (maps to module M3). Cost: ₹0 — design-artifact lab; any free-tier chat assistant or a local Ollama model.
Create orbit-adrs/ with the transcript below as transcript.md, plus an empty adr/ directory. Use MADR-style structure for every ADR: Title · Status · Context · Decision · Options considered · Consequences · Revisit when.
[Orbit #arch-sync — ingestion eventing — 26 Jun, 41 min, edited]
Anita: Recap: orbit-docs-copilot ingestion is S3-upload driven. The embed
worker fires straight off the S3 notification. Two incidents last month:
a 4,000-file bulk upload throttled us, and we dropped events during a
worker deploy. I want a buffer between upload and embed.
Karthik (dev lead): Kafka. We'll need streaming eventually — click
analytics, audit feeds. MSK gives us replay and multiple consumers.
Meera (SRE): Who operates it? We are four people. MSK is still brokers,
partitions, rebalancing pain. What does ingestion actually need?
Anita: Durability, retry, backpressure. One consumer today. Fan-out is
speculative — nothing on the roadmap needs a second consumer this year.
Karthik: EventBridge then? Native S3 integration, no brokers at all.
Meera: EventBridge is a router. We want a work queue — per-message retry
and a dead-letter path. That's SQS.
Anita: Agreed. SQS as the ingestion buffer. Kafka becomes a re-decision
if a second consumer or a replay requirement actually lands — I'll
write the exit trigger down so this doesn't harden into dogma.
Karthik: FIFO or standard? A tenant's docs should embed in order.
Meera: Should they? Chunks are independent rows. The only real hazard is
two revisions of the same doc embedding out of order.
Anita: Handle that at write time — the worker compares revision ids and
refuses to overwrite newer with older. Idempotent, revision-aware
consumer means standard queue is fine and we keep the throughput.
Meera: Poison messages. A corrupt PDF crashed the worker twice in May
and it just retried forever.
Anita: Dead-letter queue. Five receives, then DLQ. Alarm on DLQ depth
greater than zero; weekly redrive review. Meera owns the alarm.
Karthik: What's the message body? Today the worker parses the raw S3
event shape. Sales wants a SharePoint connector next year — that
coupling leaks everywhere.
Anita: Wrap it. Versioned envelope: schema_version, tenant_id, doc_id,
revision, source, object_uri. Worker validates the envelope and sends
unknown major versions to the DLQ. No S3-specific fields in the body.
Meera: One more — the upload API waits for embedding to finish before
returning 200. That's why bulk uploads time out.
Anita: That dies today. Upload returns 202 once the queue write commits.
Embedding is async; the doc registry tracks status.
Karthik: Sales will ask why an upload "finished" but search misses it.
Anita: Status field — uploaded, embedding, searchable — surfaced in the
UI. Okay: I owe the ARB five ADRs from this call. Drafts by Friday.
Read the transcript yourself and list every decision actually taken (not merely discussed) as one line each: decision, who pushed back, what alternative lost. Then ask your assistant to do the same extraction, and diff the two lists. There are five decisions; keep only ones the transcript supports — AI extraction routinely promotes discussion points ("Kafka someday") into decisions.
✅ Checkpoint: a reconciled list of exactly 5 decisions, each traceable to transcript lines.
For each decision, prompt the assistant with the transcript plus the MADR skeleton and this instruction: "Draft the ADR. Context and options must come only from the transcript. Where the transcript gives a rationale, quote it. Leave Status as 'proposed'. Do not invent benchmarks, limits, or prices." Save as adr/0001-…md through adr/0005-…md. Read each draft against the transcript before moving on — the classic failure is the AI backfilling plausible-but-unsaid rationale, which turns your decision record into fiction.
✅ Checkpoint: five ADRs on disk, every Context/Options claim traceable to the transcript, all statuses "proposed".
The backbone choice (SQS over Kafka/EventBridge) carries the most irreversibility and the most future traffic. Attack it with a fresh conversation — do not reuse the drafting chat, which is anchored to agree with itself:
You are a hostile principal engineer reviewing this ADR before the ARB.
Attack it: What breaks at 100x document volume? What does the "second
consumer" future actually cost under this choice vs the losing options?
Which stated rationale is weakest? What failure mode is unhandled? What
would make this decision look foolish in 18 months? Be specific; no
praise; do not rewrite the ADR — produce numbered objections only.
Triage the objections into three buckets in redteam-0001.md: accept (changes the ADR), acknowledge (goes into Consequences as a known risk), reject (wrong or out of scope — say why). Verify any objection that leans on a service capability or quota claim against vendor docs before accepting it.
✅ Checkpoint: redteam-0001.md with ≥6 objections triaged into accept/acknowledge/reject, each with a one-line reason.
Fold accepted objections into the decision text, acknowledged ones into Consequences, and sharpen "Revisit when" into concrete exit triggers (e.g., a second consumer with a committed delivery date, a replay requirement, sustained throughput beyond the queue's comfort zone — expressed as observable conditions, not vibes). Keep the original draft as adr/0001-…-v1.md so the before/after diff is visible. Set the revised ADR's status to "accepted".
✅ Checkpoint: revised ADR 0001 plus preserved v1; diff shows substantive changes driven by the red team.
Ask the assistant to cross-check all five ADRs for contradictions (does the envelope ADR agree with the DLQ ADR on how invalid messages die? does the async-ingestion ADR reference the status field consistently?) and fix what it finds — after confirming each reported contradiction is real.
✅ Checkpoint: a noted-and-fixed (or explicitly none-found) contradiction list appended to redteam-0001.md.
AI removes the cost of writing decisions down and the social cost of attacking them — but the decision, the triage of objections, and the sign-off stay human. An ADR the author red-teamed reads very differently in front of an ARB.
The orbit-adrs/ folder: 5 ADRs, the preserved v1, redteam-0001.md. Self-grading rubric (pass = 7/9):
Real design calls are full of client names and commercial context. Under a restrictive MSA: run this lab's synthetic transcript as training, then for real work either use the client-approved tenancy, or pre-redact transcripts (names → roles, systems → generic labels) before prompting, or run extraction and drafting on a local model via Ollama. The ADRs themselves are usually client deliverables — the raw transcript and chat history are what must not leak.
The five decisions: (1) SQS as the ingestion buffer — over Kafka/MSK (ops burden, speculative fan-out) and EventBridge (router, not a work queue); exit triggers: real second consumer or replay requirement. (2) Standard queue + idempotent, revision-aware consumer — over FIFO; ordering hazard handled at write time via revision comparison. (3) DLQ after five receives, alarm on depth > 0, weekly redrive review, Meera owns the alarm. (4) Versioned event envelope (schema_version, tenant_id, doc_id, revision, source, object_uri); unknown major versions to DLQ; no source-specific fields. (5) Asynchronous ingestion — upload returns 202 on queue-write commit; doc-registry status field (uploaded → embedding → searchable) surfaced in the UI.