{"title":"Platform Toolsmith","subtitle":"Build logs, patterns, and opinions about platform engineering and internal developer tools.","link":[{"@attributes":{"href":"https:\/\/platformtoolsmith.com\/feed.xml","rel":"self"}},{"@attributes":{"href":"https:\/\/platformtoolsmith.com\/"}}],"updated":"2026-11-02T00:00:00Z","id":"https:\/\/platformtoolsmith.com\/","author":{"name":"Alexandre Castro","email":"alex@platformtoolsmith.com"},"entry":[{"title":"Architecture Starts With a Conversation, Not a Document","link":{"@attributes":{"href":"https:\/\/platformtoolsmith.com\/blog\/architecture-document-types\/"}},"updated":"2026-11-02T00:00:00Z","id":"https:\/\/platformtoolsmith.com\/blog\/architecture-document-types\/","content":"<p>On our project tracker, a team couldn't move work into Active without filling in two required fields: who approved the architecture, and the date they approved it. On paper, nothing started without an architecture review. In practice, the tracker only checked that those two boxes weren't blank.<\/p>\n<p><img src=\"https:\/\/platformtoolsmith.com\/assets\/blog\/mole-language-before-law-img-2.5c4bebe1.900.webp\" alt=\"A mole at a narrow gate, waving a form through as soon as its two boxes are stamped, without reading it.\" width=\"1920\" height=\"1080\" loading=\"lazy\" decoding=\"async\" srcset=\"https:\/\/platformtoolsmith.com\/assets\/blog\/mole-language-before-law-img-2.5c4bebe1.400.webp 400w, https:\/\/platformtoolsmith.com\/assets\/blog\/mole-language-before-law-img-2.5c4bebe1.900.webp 900w, https:\/\/platformtoolsmith.com\/assets\/blog\/mole-language-before-law-img-2.5c4bebe1.1800.webp 1800w\" sizes=\"(min-width: 1024px) 773px, 100vw\" \/><\/p>\n<p>Eighty-three items carry that date. Twenty of them share one date. Some of those are mine: the migration that moved the items onto architecture's board wrote my name and its own date. Delivery teams filled in the rest. I can't tell you whether a real decision sits behind any of them, and neither can the field. That's the problem.<\/p>\n<p>If those were backfilled, it's a team being rational about a gate. Put a field in the way of progress and the field gets filled in. So what's the cheapest way to fill it? A plausible value nobody has to defend.<\/p>\n<blockquote class=\"alert-blockquote\">\n<div class=\"alert-header\">\n                [ <i class=\"pixelart-icons-font-square-alert\"><\/i> <span>WARNING<\/span> ]\n              <\/div><p>The gate checks that the field is filled in, not that a decision exists behind it.<\/p>\n<\/blockquote>\n<p>So don't build a gate. Build a language. An architecture function earns its authority by unblocking, and everything in this series comes back to that sentence. None of it works until the teams you're trying to help can understand you, and that shared understanding is a language. In this job, the language is documents.<\/p>\n<p><strong>Enterprise Architecture By Hand<\/strong> is a five-part guide to running an architecture function with no mandate, no budget and no title. It's for the staff engineer, principal or tech lead who will do it anyway. Each part ends with something you can do the same week; this one ends with seven document types and the full template for each.<\/p>\n<h2 id=\"the-seven-document-types\">The seven document types<\/h2>\n<p>Seven document types carry our framework, one question each. Keep the split clean and the rest follows.<\/p>\n<p><strong>Request for comments (RFC): should we do this?<\/strong> Short, ephemeral, and collaborative: it gathers advice on a significant change before a decision is made, then resolves into an ADR or closes.<\/p>\n<p><strong>Architecture decision record (ADR): what did we choose, and why?<\/strong> Write the decision once, when it lands, then never edit the decision. The one edit a record allows is the Superseded by line, naming the later record that replaced it. Change your mind and you write a new record.<\/p>\n<p><strong>Solution architecture document (SAD), the design doc: what is the target state?<\/strong> This one stays alive as the umbrella for a program spanning multiple statements of work, beside the code it describes, because the code is what makes it wrong first.<\/p>\n<p><strong>Standard (STD): what are the rules today?<\/strong> The living rulebook that codifies durable rules produced by accepted ADRs: the ADRs hold the why, and this document holds the what.<\/p>\n<p><strong>Runbook (RUN): how do I do it?<\/strong> A procedural, step-by-step operational guide with verification and rollback, implementing the rules that live in standards.<\/p>\n<p><strong>Statement of work (SOW): what are we delivering?<\/strong> A bounded delivery document that pairs deliverables with acceptance criteria and a minimum viable solution floor, from start to done.<\/p>\n<p><strong>Opportunity brief (BRIEF): why fund this?<\/strong> A one-page executive pitch in business terms (the problem, the cost of doing nothing, the options, the ask, and the success measures) whose only job is to win sponsorship and spawn a SAD.<\/p>\n<p>Each of the seven questions has its own lifecycle: ephemeral, frozen, or living.<\/p>\n<ul>\n<li><strong>Ephemeral:<\/strong> two proposals that do their job and stop. The RFC (&quot;should we do this?&quot;) gathers advice until it resolves into an ADR or closes and freezes, and the BRIEF (&quot;why fund this?&quot;) pitches the business case in one page until it is funded or parked and spawns a SAD or SOW.<\/li>\n<li><strong>Frozen:<\/strong> two records that do not get rewritten. The ADR (&quot;what did we choose, and why?&quot;) is immutable once accepted, never edited except to name the record that superseded it, and the SOW (&quot;what are we delivering?&quot;) bounds a delivery and is frozen at done.<\/li>\n<li><strong>Living:<\/strong> three documents that evolve with the system. The SAD (&quot;what is the target state?&quot;) updates as decisions land, the STD (&quot;what are the rules today?&quot;) is versioned and reviewed on cadence, and the RUN (&quot;how do I do it?&quot;) is tested in exercises or incidents and archived when it goes stale.<\/li>\n<\/ul>\n<p>If your company has better names for the seven, use those instead, because the words matter less than the questions.<\/p>\n<h2 id=\"how-the-seven-fit-together\">How the seven fit together<\/h2>\n<div class=\"mermaid-container\">\n<div class=\"mermaid-diagram\" tabindex=\"0\">\n<svg class=\"ea-flow\" viewBox=\"-2 -2 903 476\" role=\"img\" aria-labelledby=\"ea-flow-title ea-flow-desc\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\">\n<title id=\"ea-flow-title\">How the seven architecture document types connect<\/title>\n<desc id=\"ea-flow-desc\">A funded BRIEF becomes a SAD, which decomposes into SOWs. A big decision during a SOW raises an RFC. A decided RFC becomes an ADR, which rolls up to the SAD and is codified into a Standard, which is applied through a Runbook.<\/desc>\n<style>.ea-flow{display:block;width:100%;min-width:560px;height:auto;font-family:'Space Grotesk',system-ui,sans-serif}.ea-flow text{text-anchor:middle;fill:var(--mermaid-text, #000000)}.ea-flow .f-box{fill:var(--mermaid-node-bg, #FFFFFF);stroke:var(--mermaid-node-border, #000000);stroke-width:1.5}.ea-flow .f-phase{font-size:13px;font-weight:700;letter-spacing:.08em}.ea-flow .f-doc{font-size:19px;font-weight:700}.ea-flow .f-sub{font-size:13px;fill:var(--theme-text-muted, #6A6A6A)}.ea-flow .f-q{font-size:14.5px;font-style:italic}.ea-flow .f-life{font-size:11.5px;fill:var(--theme-text-muted, #6A6A6A)}.ea-flow .f-label{font-size:12.5px;fill:var(--theme-text-muted, #6A6A6A)}.ea-flow .f-line{fill:none;stroke:var(--mermaid-line, #FF5C00);stroke-width:2}.ea-flow .f-dash{stroke-dasharray:7 5}.ea-flow marker path{fill:var(--mermaid-line, #FF5C00)}<\/style>\n<defs><marker id=\"ea-flow-arrow\" viewBox=\"0 0 10 10\" refX=\"7\" refY=\"5\" markerWidth=\"7\" markerHeight=\"7\" orient=\"auto-start-reverse\"><path d=\"M0 0L10 5L0 10z\"><\/path><\/marker><\/defs>\n<rect class=\"f-box\" x=\"0\" y=\"0\" width=\"170\" height=\"172\"><\/rect>\n<rect x=\"0\" y=\"0\" width=\"170\" height=\"30\" style=\"fill:var(--diagram-origin, #A0694B)\"><\/rect>\n<text class=\"f-phase\" x=\"85.0\" y=\"20\" style=\"fill:var(--diagram-origin-label, #000000)\">PITCH<\/text>\n<text class=\"f-doc\" x=\"85.0\" y=\"56\">BRIEF<\/text>\n<text class=\"f-sub\" x=\"85.0\" y=\"75\">One-Pager<\/text>\n<text class=\"f-q\" x=\"85.0\" y=\"111\">Why fund this?<\/text>\n<text class=\"f-life\" x=\"85.0\" y=\"145\">Draft \u2192 Pitched \u2192<\/text>\n<text class=\"f-life\" x=\"85.0\" y=\"160\">Funded \/ Parked<\/text>\n<rect class=\"f-box\" x=\"243\" y=\"0\" width=\"170\" height=\"172\"><\/rect>\n<rect x=\"243\" y=\"0\" width=\"170\" height=\"30\" style=\"fill:var(--diagram-define, #00A0C8)\"><\/rect>\n<text class=\"f-phase\" x=\"328.0\" y=\"20\" style=\"fill:var(--diagram-define-label, #000000)\">DEFINE<\/text>\n<text class=\"f-doc\" x=\"328.0\" y=\"56\">SAD<\/text>\n<text class=\"f-sub\" x=\"328.0\" y=\"75\">Solution Architecture<\/text>\n<text class=\"f-q\" x=\"328.0\" y=\"102\">What is the<\/text>\n<text class=\"f-q\" x=\"328.0\" y=\"120\">target state?<\/text>\n<text class=\"f-life\" x=\"328.0\" y=\"145\">Draft \u2192 Approved \u2192<\/text>\n<text class=\"f-life\" x=\"328.0\" y=\"160\">Living \u2192 Retired<\/text>\n<rect class=\"f-box\" x=\"486\" y=\"0\" width=\"170\" height=\"172\"><\/rect>\n<rect x=\"486\" y=\"0\" width=\"170\" height=\"30\" style=\"fill:var(--diagram-act, #10C840)\"><\/rect>\n<text class=\"f-phase\" x=\"571.0\" y=\"20\" style=\"fill:var(--diagram-act-label, #000000)\">EXECUTE<\/text>\n<text class=\"f-doc\" x=\"571.0\" y=\"56\">SOW<\/text>\n<text class=\"f-sub\" x=\"571.0\" y=\"75\">Statement of Work<\/text>\n<text class=\"f-q\" x=\"571.0\" y=\"102\">What are we<\/text>\n<text class=\"f-q\" x=\"571.0\" y=\"120\">delivering?<\/text>\n<text class=\"f-life\" x=\"571.0\" y=\"153\">Draft \u2192 In Flight \u2192 Done<\/text>\n<rect class=\"f-box\" x=\"0\" y=\"300\" width=\"170\" height=\"172\"><\/rect>\n<rect x=\"0\" y=\"300\" width=\"170\" height=\"30\" style=\"fill:var(--diagram-hold, #FAC800)\"><\/rect>\n<text class=\"f-phase\" x=\"85.0\" y=\"320\" style=\"fill:var(--diagram-hold-label, #000000)\">EXPLORE<\/text>\n<text class=\"f-doc\" x=\"85.0\" y=\"356\">RFC<\/text>\n<text class=\"f-sub\" x=\"85.0\" y=\"375\">Proposal<\/text>\n<text class=\"f-q\" x=\"85.0\" y=\"411\">Should we do this?<\/text>\n<text class=\"f-life\" x=\"85.0\" y=\"445\">Draft \u2192 Open for Advice<\/text>\n<text class=\"f-life\" x=\"85.0\" y=\"460\">\u2192 Resolved \/ Closed<\/text>\n<rect class=\"f-box\" x=\"243\" y=\"300\" width=\"170\" height=\"172\"><\/rect>\n<rect x=\"243\" y=\"300\" width=\"170\" height=\"30\" style=\"fill:var(--diagram-decide, #D24040)\"><\/rect>\n<text class=\"f-phase\" x=\"328.0\" y=\"320\" style=\"fill:var(--diagram-decide-label, #FFFFFF)\">DECIDE<\/text>\n<text class=\"f-doc\" x=\"328.0\" y=\"356\">ADR<\/text>\n<text class=\"f-sub\" x=\"328.0\" y=\"375\">Decision Record<\/text>\n<text class=\"f-q\" x=\"328.0\" y=\"402\">What did we choose,<\/text>\n<text class=\"f-q\" x=\"328.0\" y=\"420\">and why?<\/text>\n<text class=\"f-life\" x=\"328.0\" y=\"445\">Proposed \u2192 Accepted<\/text>\n<text class=\"f-life\" x=\"328.0\" y=\"460\">\u2192 Superseded<\/text>\n<rect class=\"f-box\" x=\"486\" y=\"300\" width=\"170\" height=\"172\"><\/rect>\n<rect x=\"486\" y=\"300\" width=\"170\" height=\"30\" style=\"fill:var(--diagram-define, #00A0C8)\"><\/rect>\n<text class=\"f-phase\" x=\"571.0\" y=\"320\" style=\"fill:var(--diagram-define-label, #000000)\">DEFINE<\/text>\n<text class=\"f-doc\" x=\"571.0\" y=\"356\">STANDARD<\/text>\n<text class=\"f-sub\" x=\"571.0\" y=\"375\">Rules<\/text>\n<text class=\"f-q\" x=\"571.0\" y=\"402\">What are the<\/text>\n<text class=\"f-q\" x=\"571.0\" y=\"420\">rules today?<\/text>\n<text class=\"f-life\" x=\"571.0\" y=\"445\">Draft \u2192 Active<\/text>\n<text class=\"f-life\" x=\"571.0\" y=\"460\">\u2192 Superseded<\/text>\n<rect class=\"f-box\" x=\"729\" y=\"300\" width=\"170\" height=\"172\"><\/rect>\n<rect x=\"729\" y=\"300\" width=\"170\" height=\"30\" style=\"fill:var(--diagram-define, #00A0C8)\"><\/rect>\n<text class=\"f-phase\" x=\"814.0\" y=\"320\" style=\"fill:var(--diagram-define-label, #000000)\">DEFINE<\/text>\n<text class=\"f-doc\" x=\"814.0\" y=\"356\">RUN<\/text>\n<text class=\"f-sub\" x=\"814.0\" y=\"375\">Runbook<\/text>\n<text class=\"f-q\" x=\"814.0\" y=\"411\">How do I do it?<\/text>\n<text class=\"f-life\" x=\"814.0\" y=\"445\">Published \u2192 Maintained<\/text>\n<text class=\"f-life\" x=\"814.0\" y=\"460\">\u2192 Archived<\/text>\n<path class=\"f-line\" d=\"M170 86H240\" marker-end=\"url(#ea-flow-arrow)\"><\/path>\n<text class=\"f-label\" x=\"206.5\" y=\"78\" text-anchor=\"middle\">funded<\/text>\n<path class=\"f-line\" d=\"M413 86H483\" marker-end=\"url(#ea-flow-arrow)\"><\/path>\n<text class=\"f-label\" x=\"449.5\" y=\"63\" text-anchor=\"middle\">decomposes<\/text>\n<text class=\"f-label\" x=\"449.5\" y=\"78\" text-anchor=\"middle\">into<\/text>\n<path class=\"f-line f-dash\" d=\"M571.0 172V236H85.0V297\" marker-end=\"url(#ea-flow-arrow)\"><\/path>\n<text class=\"f-label\" x=\"450\" y=\"226\" text-anchor=\"middle\">big decision mid-flight? raise an RFC<\/text>\n<path class=\"f-line\" d=\"M328.0 300V175\" marker-end=\"url(#ea-flow-arrow)\"><\/path>\n<text class=\"f-label\" x=\"337.0\" y=\"276\" text-anchor=\"start\">rolls up to<\/text>\n<path class=\"f-line\" d=\"M170 386H240\" marker-end=\"url(#ea-flow-arrow)\"><\/path>\n<text class=\"f-label\" x=\"206.5\" y=\"378\" text-anchor=\"middle\">decided<\/text>\n<path class=\"f-line\" d=\"M413 386H483\" marker-end=\"url(#ea-flow-arrow)\"><\/path>\n<text class=\"f-label\" x=\"449.5\" y=\"378\" text-anchor=\"middle\">codified<\/text>\n<path class=\"f-line\" d=\"M656 386H726\" marker-end=\"url(#ea-flow-arrow)\"><\/path>\n<text class=\"f-label\" x=\"692.5\" y=\"378\" text-anchor=\"middle\">applied<\/text>\n<\/svg>\n<\/div>\n<\/div>\n<p>Three flows run through it. Money flows left to right: a brief gets funded, a SAD defines the target, SOWs deliver it. Decisions flow up: an RFC resolves into an ADR, and the ADR rolls up to the SAD. Rules flow on from there: accepted ADRs get codified into a standard, and runbooks apply it. The dashed line is the one people skip: when a big decision surfaces mid-delivery, it leaves the SOW and goes through an RFC.<\/p>\n<h2 id=\"the-seven-templates\">The seven templates<\/h2>\n<p>The full templates, ready to copy. They're long because a template isn't a summary. Each one opens with an HTML comment for AI assistants, invisible when rendered: delete it before you publish, or keep it if your team drafts with one.<\/p>\n<p><strong>Request for comments (RFC)<\/strong><\/p>\n<div class=\"md-block\"><pre class=\"language-markdown\"><code class=\"language-markdown\"><span class=\"token front-matter-block\"><span class=\"token punctuation\">---<\/span>\n<span class=\"token front-matter yaml language-yaml\"><span class=\"token key atrule\">id<\/span><span class=\"token punctuation\">:<\/span> RFC<span class=\"token punctuation\">-<\/span>NNNN\n<span class=\"token key atrule\">title<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token string\">\"[Short proposal title]\"<\/span>\n<span class=\"token key atrule\">status<\/span><span class=\"token punctuation\">:<\/span> draft                    <span class=\"token comment\"># draft | open-for-advice | resolved | closed<\/span>\n<span class=\"token key atrule\">author<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token string\">\"[Proposing engineer(s)]\"<\/span>\n<span class=\"token key atrule\">sponsor<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token string\">\"[Tech lead helping guide the proposal. Recruit one early]\"<\/span>\n<span class=\"token key atrule\">affected_teams<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token punctuation\">[<\/span><span class=\"token punctuation\">]<\/span>               <span class=\"token comment\"># every team this touches: your advice audience<\/span>\n<span class=\"token key atrule\">feedback_deadline<\/span><span class=\"token punctuation\">:<\/span> YYYY<span class=\"token punctuation\">-<\/span>MM<span class=\"token punctuation\">-<\/span>DD    <span class=\"token comment\"># default: two weeks from opening<\/span>\n<span class=\"token key atrule\">review_session<\/span><span class=\"token punctuation\">:<\/span> YYYY<span class=\"token punctuation\">-<\/span>MM<span class=\"token punctuation\">-<\/span>DD       <span class=\"token comment\"># architecture forum slot, if needed<\/span>\n<span class=\"token key atrule\">resulting_adr<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token string\">\"\"<\/span>                <span class=\"token comment\"># ADR-NNNN once resolved, or \"N\/A, closed\"<\/span>\n<span class=\"token key atrule\">related<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token punctuation\">[<\/span><span class=\"token punctuation\">]<\/span>                      <span class=\"token comment\"># SAD \/ SOW this proposal affects<\/span>\n<span class=\"token key atrule\">ai_assistance<\/span><span class=\"token punctuation\">:<\/span> none              <span class=\"token comment\"># or \"AI-assisted draft, reviewed by [Name] on YYYY-MM-DD\"<\/span><\/span>\n<span class=\"token punctuation\">---<\/span><\/span>\n\n<span class=\"token comment\">&lt;!--\nFor AI assistants. This is a Request for Comments: an ephemeral proposal answering\n\"should we do this?\". It gathers advice, then resolves into an ADR or closes, and is frozen.\nRules: keep must-haves separate from the preferred approach. Draft open questions, not\nconclusions. Add advice rows only for real, named people the human relays. Keep it short\nenough to review in one sitting.\nGround truth: fill a field only from what the human told you or gave you. If it's missing,\nkeep the [placeholder] and ask. A blank field is correct; an invented one is a defect.\nAsk: Which teams are affected? What spikes or evidence exist? What feedback deadline?\n--><\/span>\n\n<span class=\"token title important\"><span class=\"token punctuation\">#<\/span> RFC: [Short proposal title]<\/span>\n\nAn RFC gathers advice on a significant change before any decision is made. Anyone may open one. It resolves into an ADR or closes, and is then frozen.\n\n<span class=\"token blockquote punctuation\">><\/span> <span class=\"token bold\"><span class=\"token punctuation\">**<\/span><span class=\"token content\">This is not a decision.<\/span><span class=\"token punctuation\">**<\/span><\/span> This document gathers advice on what to do; the ADR records what was decided and why. The feedback deadline is real: an RFC without one is where ideas go to die.\n\n<span class=\"token title important\"><span class=\"token punctuation\">##<\/span> 01 \u00b7 Problem \/ opportunity<\/span>\n\nWhy this proposal exists and why now, in terms an affected team recognizes: a pain point, a deadline, a new constraint, an opportunity.\n\n[What is the situation, and what happens if nothing changes?]\n\n<span class=\"token title important\"><span class=\"token punctuation\">##<\/span> 02 \u00b7 Proposal<\/span>\n\nDetailed enough to give advice on, not an implementation spec.\n\n<span class=\"token title important\"><span class=\"token punctuation\">###<\/span> Must-haves<\/span>\n\nProperties any acceptable solution needs, kept apart from the approach so advisors can challenge one without losing the other.\n\n<span class=\"token list punctuation\">-<\/span> [Property any acceptable solution must satisfy]\n<span class=\"token list punctuation\">-<\/span> [...]\n\n<span class=\"token title important\"><span class=\"token punctuation\">###<\/span> Proposed approach<\/span>\n\n[The change being proposed. A Mermaid sketch is welcome; it freezes with the RFC at resolution.]\n\n<span class=\"token title important\"><span class=\"token punctuation\">##<\/span> 03 \u00b7 Alternatives considered (optional)<\/span>\n\nOne line per option is enough. The full trade-off analysis happens in the ADR once advice is in.\n\n<span class=\"token list punctuation\">-<\/span> [Alternative A: one line on why it is not preferred]\n<span class=\"token list punctuation\">-<\/span> [Do nothing: what the status quo costs]\n\n<span class=\"token title important\"><span class=\"token punctuation\">##<\/span> 04 \u00b7 Impact, risks and spikes<\/span>\n\n<span class=\"token table\"><span class=\"token table-header-row\"><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> Aspect <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> Detail <\/span><span class=\"token punctuation\">|<\/span>\n<\/span><span class=\"token table-line\"><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span>\n<\/span><span class=\"token table-data-rows\"><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> Affected parties <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [Teams \/ systems touched, and how] <\/span><span class=\"token punctuation\">|<\/span>\n<span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> Risks <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [What could go wrong; security or compliance implications] <\/span><span class=\"token punctuation\">|<\/span>\n<span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> Spikes \/ evidence <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [Experiments run or needed; link the findings] <\/span><span class=\"token punctuation\">|<\/span>\n<span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> Cost signal <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [Rough order of magnitude: licence, effort, run cost] <\/span><span class=\"token punctuation\">|<\/span>\n<\/span><\/span>\n<span class=\"token title important\"><span class=\"token punctuation\">##<\/span> 05 \u00b7 Advice and feedback<\/span>\n\nThe working surface of the RFC. Advisors add rows; the author answers every one. When the RFC resolves, this table seeds the ADR's advice section.\n\n<span class=\"token table\"><span class=\"token table-header-row\"><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> Advisor \/ role <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> Feedback <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> Response (adopted \/ adapted \/ declined: why) <\/span><span class=\"token punctuation\">|<\/span>\n<\/span><span class=\"token table-line\"><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span>\n<\/span><span class=\"token table-data-rows\"><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [Name, role] <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [Concern, suggestion, or red flag] <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [Disposition and reason] <\/span><span class=\"token punctuation\">|<\/span>\n<\/span><\/span>\n<span class=\"token blockquote punctuation\">><\/span> <span class=\"token bold\"><span class=\"token punctuation\">**<\/span><span class=\"token content\">Resolving debates.<\/span><span class=\"token punctuation\">**<\/span><\/span> Most advice resolves asynchronously in the table. If a debate is still open near the deadline, book a slot at the architecture forum rather than extending the deadline: extension is how proposals stall.\n\n<span class=\"token title important\"><span class=\"token punctuation\">##<\/span> 06 \u00b7 Resolution<\/span>\n\nFilled once, then frozen. Capture the reason, so the idea isn't re-litigated from scratch next year.\n\n<span class=\"token table\"><span class=\"token table-header-row\"><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> Field <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> Detail <\/span><span class=\"token punctuation\">|<\/span>\n<\/span><span class=\"token table-line\"><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span>\n<\/span><span class=\"token table-data-rows\"><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> Outcome <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [Resolved \u2192 ADR-NNNN \/ Closed] <\/span><span class=\"token punctuation\">|<\/span>\n<span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> Rationale <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [One paragraph: why this outcome] <\/span><span class=\"token punctuation\">|<\/span>\n<span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> Date <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [YYYY-MM-DD] <\/span><span class=\"token punctuation\">|<\/span>\n<\/span><\/span><\/code><\/pre>\n<\/div>\n<p><strong>Architecture decision record (ADR)<\/strong><\/p>\n<div class=\"md-block\"><pre class=\"language-markdown\"><code class=\"language-markdown\"><span class=\"token front-matter-block\"><span class=\"token punctuation\">---<\/span>\n<span class=\"token front-matter yaml language-yaml\"><span class=\"token key atrule\">id<\/span><span class=\"token punctuation\">:<\/span> ADR<span class=\"token punctuation\">-<\/span>NNNN                     <span class=\"token comment\"># claimed from the ADR folder; never guess the next number<\/span>\n<span class=\"token key atrule\">title<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token string\">\"[Short, present-tense decision title]\"<\/span>\n<span class=\"token key atrule\">status<\/span><span class=\"token punctuation\">:<\/span> proposed                 <span class=\"token comment\"># proposed | accepted | deprecated | superseded<\/span>\n<span class=\"token key atrule\">owning_team<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token string\">\"[Team \/ domain making the decision]\"<\/span>\n<span class=\"token key atrule\">author<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token string\">\"[Name, role]\"<\/span>\n<span class=\"token key atrule\">supersedes<\/span><span class=\"token punctuation\">:<\/span> none                 <span class=\"token comment\"># ADR-NNNN or none<\/span>\n<span class=\"token key atrule\">superseded_by<\/span><span class=\"token punctuation\">:<\/span> none\n<span class=\"token key atrule\">related_rfc<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token string\">\"\"<\/span>                  <span class=\"token comment\"># or \"none, direct to ADR\"<\/span>\n<span class=\"token key atrule\">related_sad<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token string\">\"\"<\/span>\n<span class=\"token key atrule\">related_sows<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token punctuation\">[<\/span><span class=\"token punctuation\">]<\/span>\n<span class=\"token key atrule\">date_proposed<\/span><span class=\"token punctuation\">:<\/span> YYYY<span class=\"token punctuation\">-<\/span>MM<span class=\"token punctuation\">-<\/span>DD\n<span class=\"token key atrule\">date_decided<\/span><span class=\"token punctuation\">:<\/span> YYYY<span class=\"token punctuation\">-<\/span>MM<span class=\"token punctuation\">-<\/span>DD\n<span class=\"token key atrule\">ai_assistance<\/span><span class=\"token punctuation\">:<\/span> none<\/span>\n<span class=\"token punctuation\">---<\/span><\/span>\n\n<span class=\"token comment\">&lt;!--\nFor AI assistants. This is an Architecture Decision Record: an immutable, point-in-time\nrecord answering \"what did we choose, and why?\". Once accepted it is never edited, only\nsuperseded by a new ADR.\nRules: write Context value-neutral (facts, forces, constraints; the tension, not the answer).\nGive negative consequences equal weight: an ADR that lists no costs is unfinished. Add advice\nrows only for consultations the human reports. Embed diagrams as Mermaid or a static export\nfrozen at decision time; no live diagram links.\nGround truth: fill a field only from what the human told you or gave you; otherwise keep the\n[placeholder] and ask.\nAsk: Who was consulted, and what did each say? Which alternatives were seriously evaluated,\nincluding doing nothing? What is the single strongest reason for the chosen option?\n--><\/span>\n\n<span class=\"token title important\"><span class=\"token punctuation\">#<\/span> ADR-NNNN: [Short, present-tense decision title]<\/span>\n\nOne ADR, one decision. Once accepted, don't rewrite it: supersede it with a new ADR.\n\n<span class=\"token title important\"><span class=\"token punctuation\">##<\/span> 01 \u00b7 Context<\/span>\n\nThe forces at play (technical, business, organizational, security, project-local) and the tension between them. A reader six months from now should understand the situation without already knowing the outcome. Focus on the why, not the what.\n\n<span class=\"token list punctuation\">-<\/span> [What is the situation? What constraints, deadlines, or existing systems are in play?]\n<span class=\"token list punctuation\">-<\/span> [What tensions exist: speed vs. cost, build vs. buy, flexibility vs. simplicity?]\n<span class=\"token list punctuation\">-<\/span> [Why does this decision need to be made now? What is the trigger?]\n\n<span class=\"token title important\"><span class=\"token punctuation\">##<\/span> 02 \u00b7 Decision drivers (optional)<\/span>\n\nThe criteria the decision is judged against. Listing them up front makes the comparison defensible.\n\n<span class=\"token list punctuation\">-<\/span> [e.g. operational cost ceiling of $X \/ month]\n<span class=\"token list punctuation\">-<\/span> [e.g. must integrate with the existing infrastructure-as-code stack]\n<span class=\"token list punctuation\">-<\/span> [e.g. auditability \/ data residency requirement]\n<span class=\"token list punctuation\">-<\/span> [e.g. team familiarity \/ time to delivery]\n\n<span class=\"token title important\"><span class=\"token punctuation\">##<\/span> 03 \u00b7 Considered alternatives<\/span>\n\nEvery option seriously evaluated, including doing nothing. The chosen option goes here too; the Decision section only commits to it.\n\n<span class=\"token list punctuation\">-<\/span> <span class=\"token bold\"><span class=\"token punctuation\">**<\/span><span class=\"token content\">A \u00b7 [Name]:<\/span><span class=\"token punctuation\">**<\/span><\/span> [One-line description]\n<span class=\"token list punctuation\">-<\/span> <span class=\"token bold\"><span class=\"token punctuation\">**<\/span><span class=\"token content\">B \u00b7 [Name]:<\/span><span class=\"token punctuation\">**<\/span><\/span> [One-line description]\n<span class=\"token list punctuation\">-<\/span> <span class=\"token bold\"><span class=\"token punctuation\">**<\/span><span class=\"token content\">C \u00b7 Do nothing:<\/span><span class=\"token punctuation\">**<\/span><\/span> [What happens if we make no change]\n\n<span class=\"token table\"><span class=\"token table-header-row\"><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> Alternative <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> Pros <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> Cons <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> Verdict <\/span><span class=\"token punctuation\">|<\/span>\n<\/span><span class=\"token table-line\"><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span>\n<\/span><span class=\"token table-data-rows\"><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [A \u00b7 Name] <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [Key strengths] <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [Key weaknesses \/ risks] <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [Chosen \/ Rejected] <\/span><span class=\"token punctuation\">|<\/span>\n<span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [B \u00b7 Name] <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [...] <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [...] <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [...] <\/span><span class=\"token punctuation\">|<\/span>\n<span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [C \u00b7 Status quo] <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [...] <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [...] <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [...] <\/span><span class=\"token punctuation\">|<\/span>\n<\/span><\/span>\n<span class=\"token title important\"><span class=\"token punctuation\">##<\/span> 04 \u00b7 Advice sought<\/span>\n\nEveryone affected and everyone with relevant expertise who was consulted, what they said, and how it was handled. Advice comes before acceptance: an unconsulted affected party is a process failure.\n\n<span class=\"token table\"><span class=\"token table-header-row\"><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> Advisor \/ role <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> Advice given <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> Disposition (adopted \/ adapted \/ declined: why) <\/span><span class=\"token punctuation\">|<\/span>\n<\/span><span class=\"token table-line\"><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span>\n<\/span><span class=\"token table-data-rows\"><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [Name, role \/ why consulted] <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [Summary of the advice] <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [...] <\/span><span class=\"token punctuation\">|<\/span>\n<\/span><\/span>\n<span class=\"token title important\"><span class=\"token punctuation\">##<\/span> 05 \u00b7 Decision<\/span>\n\nIn full sentences and active voice. Specific enough to act on: vague decisions aren't decisions.\n\n[We will adopt [Alternative X] because [primary justification].]\n\n[Scope: where this applies, and where it explicitly does not.]\n\n<span class=\"token blockquote punctuation\">><\/span> <span class=\"token bold\"><span class=\"token punctuation\">**<\/span><span class=\"token content\">Diagrams here are frozen.<\/span><span class=\"token punctuation\">**<\/span><\/span> Embed Mermaid source or a static export captured at decision time. If a live board is useful, link it separately and label it \"current state\".\n\n<span class=\"token title important\"><span class=\"token punctuation\">##<\/span> 06 \u00b7 Consequences<\/span>\n\nEvery consequence shapes the team and the system. Negative ones aren't a sign of a bad decision; hiding them is.\n\n<span class=\"token list punctuation\">-<\/span> <span class=\"token bold\"><span class=\"token punctuation\">**<\/span><span class=\"token content\">Positive:<\/span><span class=\"token punctuation\">**<\/span><\/span> [What gets better or becomes possible]\n<span class=\"token list punctuation\">-<\/span> <span class=\"token bold\"><span class=\"token punctuation\">**<\/span><span class=\"token content\">Negative:<\/span><span class=\"token punctuation\">**<\/span><\/span> [What gets harder; the cost or risk we accept]\n<span class=\"token list punctuation\">-<\/span> <span class=\"token bold\"><span class=\"token punctuation\">**<\/span><span class=\"token content\">Neutral:<\/span><span class=\"token punctuation\">**<\/span><\/span> [Side effects worth recording]\n\n<span class=\"token title important\"><span class=\"token punctuation\">###<\/span> Follow-up actions (optional)<\/span>\n\n<span class=\"token list punctuation\">-<\/span> [New ADRs, SOWs to spin up, deprecations to schedule, owners to notify]\n\n<span class=\"token blockquote punctuation\">><\/span> <span class=\"token bold\"><span class=\"token punctuation\">**<\/span><span class=\"token content\">Review before accepting.<\/span><span class=\"token punctuation\">**<\/span><\/span> An accepted ADR cannot be rewritten. Confirm every affected party was asked (\u00a704) before you change the status.\n\n<span class=\"token title important\"><span class=\"token punctuation\">##<\/span> 07 \u00b7 Compliance and validation (optional)<\/span>\n\n<span class=\"token list punctuation\">-<\/span> [How conformance is checked: CI policy, IaC module, lint rule, review gate]\n<span class=\"token list punctuation\">-<\/span> [What signal tells us the decision was right or wrong: metric, SLO, cost line, incident class]\n\n<span class=\"token title important\"><span class=\"token punctuation\">##<\/span> 08 \u00b7 References (optional)<\/span>\n\n<span class=\"token list punctuation\">-<\/span> [Prior ADRs, parent SAD, spikes, vendor docs, benchmarks, incident reports]<\/code><\/pre>\n<\/div>\n<p><strong>Solution architecture document (SAD)<\/strong><\/p>\n<div class=\"md-block\"><pre class=\"language-markdown\"><code class=\"language-markdown\"><span class=\"token front-matter-block\"><span class=\"token punctuation\">---<\/span>\n<span class=\"token front-matter yaml language-yaml\"><span class=\"token key atrule\">id<\/span><span class=\"token punctuation\">:<\/span> SAD<span class=\"token punctuation\">-<\/span>NNNN\n<span class=\"token key atrule\">title<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token string\">\"[Program name]\"<\/span>\n<span class=\"token key atrule\">status<\/span><span class=\"token punctuation\">:<\/span> draft                    <span class=\"token comment\"># draft | in-review | approved<\/span>\n<span class=\"token key atrule\">version<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token number\">0.1<\/span>\n<span class=\"token key atrule\">owning_team<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token string\">\"[Lead team \/ domain]\"<\/span>\n<span class=\"token key atrule\">author<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token string\">\"[Name, role]\"<\/span>\n<span class=\"token key atrule\">sponsor<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token string\">\"[Executive sponsor, title]\"<\/span>\n<span class=\"token key atrule\">originating_brief<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token string\">\"\"<\/span>            <span class=\"token comment\"># the BRIEF that won sponsorship, or \"N\/A\"<\/span>\n<span class=\"token key atrule\">related_adrs<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token punctuation\">[<\/span><span class=\"token punctuation\">]<\/span>\n<span class=\"token key atrule\">related_sows<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token punctuation\">[<\/span><span class=\"token punctuation\">]<\/span>\n<span class=\"token key atrule\">related_rfcs<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token punctuation\">[<\/span><span class=\"token punctuation\">]<\/span>\n<span class=\"token key atrule\">last_updated<\/span><span class=\"token punctuation\">:<\/span> YYYY<span class=\"token punctuation\">-<\/span>MM<span class=\"token punctuation\">-<\/span>DD\n<span class=\"token key atrule\">ai_assistance<\/span><span class=\"token punctuation\">:<\/span> none<\/span>\n<span class=\"token punctuation\">---<\/span><\/span>\n\n<span class=\"token comment\">&lt;!--\nFor AI assistants. This is a Solution Architecture Document: the living umbrella for a program\nspanning multiple SOWs, answering \"what is the target state?\". It evolves as decisions are made.\nRules: keep the Executive Summary readable by a non-technical sponsor. Map every capability row\nto a SOW; an unmapped capability is an open question. Live diagrams belong here; frozen views\nbelong in ADRs. Ask for concrete ADR, SOW and BRIEF IDs instead of leaving brackets.\nGround truth: fill a field only from what the human told you or gave you; otherwise keep the\n[placeholder] and ask.\nAsk: Which accepted ADRs already constrain this program? What is out of scope? Which\ncapabilities become which SOWs, and in what order?\n--><\/span>\n\n<span class=\"token title important\"><span class=\"token punctuation\">#<\/span> SAD: [Program name]<\/span>\n\nThe umbrella architecture for a program that spans multiple SOWs: target state, principles, capability decomposition, key decisions. SOWs deliver the components; ADRs record the decisions and link back here. Keep it alive: the diagram is authoritative for structure, the prose for rationale.\n\n<span class=\"token title important\"><span class=\"token punctuation\">##<\/span> 01 \u00b7 Executive summary<\/span>\n\n[2\u20134 sentences a non-technical sponsor can read: what changes, who benefits, when we get there.]\n\n<span class=\"token title important\"><span class=\"token punctuation\">##<\/span> 02 \u00b7 Business context and drivers<\/span>\n\nWhy this program exists, and what happens if we do nothing.\n\n<span class=\"token list punctuation\">-<\/span> [Driver, e.g. end-of-life vendor contract \/ compliance gap]\n<span class=\"token list punctuation\">-<\/span> [Driver, e.g. cost reduction target of $X]\n\n<span class=\"token bold\"><span class=\"token punctuation\">**<\/span><span class=\"token content\">Strategic alignment:<\/span><span class=\"token punctuation\">**<\/span><\/span> [How this ties to company goals, tech strategy, or regulatory commitments.]\n\n<span class=\"token title important\"><span class=\"token punctuation\">##<\/span> 03 \u00b7 Current state<\/span>\n\n<span class=\"token table\"><span class=\"token table-header-row\"><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> Component <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> Purpose <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> Owner <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> Disposition <\/span><span class=\"token punctuation\">|<\/span>\n<\/span><span class=\"token table-line\"><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span>\n<\/span><span class=\"token table-data-rows\"><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [Component A] <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [What it does today] <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [Team] <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [Replace \/ Retain \/ Retire] <\/span><span class=\"token punctuation\">|<\/span>\n<\/span><\/span>\n<span class=\"token bold\"><span class=\"token punctuation\">**<\/span><span class=\"token content\">Known pain points<\/span><span class=\"token punctuation\">**<\/span><\/span>\n\n<span class=\"token list punctuation\">-<\/span> [Pain point]\n\n<span class=\"token title important\"><span class=\"token punctuation\">##<\/span> 04 \u00b7 Target state<\/span>\n\n[Embed C4 context and container diagrams, or equivalent. Logical view first, deployment view second.]\n\n<span class=\"token table\"><span class=\"token table-header-row\"><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> Component <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> Responsibility <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> Technology <\/span><span class=\"token punctuation\">|<\/span>\n<\/span><span class=\"token table-line\"><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span>\n<\/span><span class=\"token table-data-rows\"><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [Component] <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [What it does, what it owns] <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [e.g. secrets manager] <\/span><span class=\"token punctuation\">|<\/span>\n<\/span><\/span>\n<span class=\"token title important\"><span class=\"token punctuation\">##<\/span> 05 \u00b7 Architecture principles<\/span>\n\nNon-negotiable rules every SOW respects. Keep it to five to eight.\n\n<span class=\"token table\"><span class=\"token table-header-row\"><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> Principle <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> Implication <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> Anti-pattern <\/span><span class=\"token punctuation\">|<\/span>\n<\/span><span class=\"token table-line\"><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span>\n<\/span><span class=\"token table-data-rows\"><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> Identity is the perimeter <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> Every secret access is authenticated via SSO\/OIDC and audited <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> Static API keys, shared service accounts <\/span><span class=\"token punctuation\">|<\/span>\n<span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [Principle] <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [...] <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [...] <\/span><span class=\"token punctuation\">|<\/span>\n<\/span><\/span>\n<span class=\"token title important\"><span class=\"token punctuation\">##<\/span> 06 \u00b7 Capability decomposition \u2192 SOW map<\/span>\n\nThe bridge between this document and delivery: each row is, or will become, one SOW.\n\n<span class=\"token table\"><span class=\"token table-header-row\"><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> Capability <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> Description <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> SOW <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> Phase <\/span><span class=\"token punctuation\">|<\/span>\n<\/span><span class=\"token table-line\"><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span>\n<\/span><span class=\"token table-data-rows\"><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [Capability] <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [What it provides] <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [SOW-A] <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [Phase 1] <\/span><span class=\"token punctuation\">|<\/span>\n<\/span><\/span>\n<span class=\"token title important\"><span class=\"token punctuation\">##<\/span> 07 \u00b7 Cross-cutting concerns<\/span>\n\nEvery SOW inherits these rather than redefining them.\n\n<span class=\"token list punctuation\">-<\/span> <span class=\"token bold\"><span class=\"token punctuation\">**<\/span><span class=\"token content\">Security and compliance:<\/span><span class=\"token punctuation\">**<\/span><\/span> [Threat model, applicable controls, data classification, encryption, audit trail]\n<span class=\"token list punctuation\">-<\/span> <span class=\"token bold\"><span class=\"token punctuation\">**<\/span><span class=\"token content\">Identity and access:<\/span><span class=\"token punctuation\">**<\/span><\/span> [Identity provider, MFA, break-glass procedure, role model]\n<span class=\"token list punctuation\">-<\/span> <span class=\"token bold\"><span class=\"token punctuation\">**<\/span><span class=\"token content\">Observability:<\/span><span class=\"token punctuation\">**<\/span><\/span> [Logging, metrics, SLO catalogue, alert routing. SOWs MUST emit these signals]\n<span class=\"token list punctuation\">-<\/span> <span class=\"token bold\"><span class=\"token punctuation\">**<\/span><span class=\"token content\">Reliability and DR:<\/span><span class=\"token punctuation\">**<\/span><\/span> [RPO \/ RTO, multi-region posture, failover model]\n<span class=\"token list punctuation\">-<\/span> <span class=\"token bold\"><span class=\"token punctuation\">**<\/span><span class=\"token content\">Cost model:<\/span><span class=\"token punctuation\">**<\/span><\/span> [Funding source, run rate, showback, cost guardrails]\n\n<span class=\"token title important\"><span class=\"token punctuation\">##<\/span> 08 \u00b7 Key architecture decisions<\/span>\n\nEach row links to a standalone ADR; summaries only here. Every linked ADR must be accepted, and the architecture reviewed, before implementation begins.\n\n<span class=\"token table\"><span class=\"token table-header-row\"><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> ADR <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> Decision <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> Chosen option <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> Status <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> Date <\/span><span class=\"token punctuation\">|<\/span>\n<\/span><span class=\"token table-line\"><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span>\n<\/span><span class=\"token table-data-rows\"><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [ADR-0001] <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [Decision title] <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [Chosen option] <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [Accepted] <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [YYYY-MM-DD] <\/span><span class=\"token punctuation\">|<\/span>\n<\/span><\/span>\n<span class=\"token title important\"><span class=\"token punctuation\">##<\/span> 09 \u00b7 Roadmap and phasing<\/span>\n\nEach phase has an exit criterion that gates the next.\n\n<span class=\"token table\"><span class=\"token table-header-row\"><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> Phase <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> Outcome <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> SOWs <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> Exit criteria <\/span><span class=\"token punctuation\">|<\/span>\n<\/span><span class=\"token table-line\"><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span>\n<\/span><span class=\"token table-data-rows\"><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> 1: Foundation <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [Outcome] <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [SOW-A, SOW-B] <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [Measurable exit] <\/span><span class=\"token punctuation\">|<\/span>\n<span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> 2: Expansion <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [...] <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [...] <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [...] <\/span><span class=\"token punctuation\">|<\/span>\n<span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> 3: Decommission <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [Legacy retired] <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [SOW-N] <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [Contracts terminated, traffic at zero] <\/span><span class=\"token punctuation\">|<\/span>\n<\/span><\/span>\n<span class=\"token title important\"><span class=\"token punctuation\">##<\/span> 10 \u00b7 Dependencies<\/span>\n\n<span class=\"token list punctuation\">-<\/span> <span class=\"token bold\"><span class=\"token punctuation\">**<\/span><span class=\"token content\">External:<\/span><span class=\"token punctuation\">**<\/span><\/span> [Vendor \/ SaaS: version, contract status, support level; regulatory dependencies]\n<span class=\"token list punctuation\">-<\/span> <span class=\"token bold\"><span class=\"token punctuation\">**<\/span><span class=\"token content\">Internal:<\/span><span class=\"token punctuation\">**<\/span><\/span> [Teams, systems, shared platform components]\n\n<span class=\"token title important\"><span class=\"token punctuation\">##<\/span> 11 \u00b7 Risks and mitigations<\/span>\n\nValidate every change against regulatory, compliance and security requirements before implementation.\n\n<span class=\"token table\"><span class=\"token table-header-row\"><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> Risk <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> Likelihood <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> Impact <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> Mitigation \/ owner <\/span><span class=\"token punctuation\">|<\/span>\n<\/span><span class=\"token table-line\"><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span>\n<\/span><span class=\"token table-data-rows\"><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [Risk] <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [L \/ M \/ H] <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [L \/ M \/ H] <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [Plan, owner] <\/span><span class=\"token punctuation\">|<\/span>\n<\/span><\/span>\n<span class=\"token title important\"><span class=\"token punctuation\">##<\/span> 12 \u00b7 Assumptions<\/span>\n\n<span class=\"token list punctuation\">-<\/span> [Assumption]\n\n<span class=\"token title important\"><span class=\"token punctuation\">##<\/span> 13 \u00b7 Out of scope<\/span>\n\nWhat people might expect here but is excluded. This is what stops scope creep at the SOW level.\n\n<span class=\"token list punctuation\">-<\/span> [Excluded item, and why]\n\n<span class=\"token title important\"><span class=\"token punctuation\">##<\/span> 14 \u00b7 Glossary<\/span>\n\n<span class=\"token table\"><span class=\"token table-header-row\"><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> Term <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> Definition <\/span><span class=\"token punctuation\">|<\/span>\n<\/span><span class=\"token table-line\"><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span>\n<\/span><span class=\"token table-data-rows\"><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [Term a new joiner would have to look up] <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [Plain-language definition] <\/span><span class=\"token punctuation\">|<\/span>\n<\/span><\/span>\n<span class=\"token title important\"><span class=\"token punctuation\">##<\/span> 15 \u00b7 References<\/span>\n\n<span class=\"token list punctuation\">-<\/span> [Vendor evaluation, threat model, cost model]<\/code><\/pre>\n<\/div>\n<p><strong>Standard (STD)<\/strong><\/p>\n<div class=\"md-block\"><pre class=\"language-markdown\"><code class=\"language-markdown\"><span class=\"token front-matter-block\"><span class=\"token punctuation\">---<\/span>\n<span class=\"token front-matter yaml language-yaml\"><span class=\"token key atrule\">id<\/span><span class=\"token punctuation\">:<\/span> STD<span class=\"token punctuation\">-<\/span>NNNN                     <span class=\"token comment\"># claimed from the standards folder; never guess the next number<\/span>\n<span class=\"token key atrule\">title<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token string\">\"[Short, imperative title, e.g. Server Hardening Baseline]\"<\/span>\n<span class=\"token key atrule\">status<\/span><span class=\"token punctuation\">:<\/span> draft                    <span class=\"token comment\"># draft | in-review | active | superseded | retired<\/span>\n<span class=\"token key atrule\">version<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token number\">1.0<\/span>\n<span class=\"token key atrule\">owner<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token string\">\"[Named person accountable for currency: a person, not a team]\"<\/span>\n<span class=\"token key atrule\">review_team<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token string\">\"[Security, affected platform teams, compliance where applicable]\"<\/span>\n<span class=\"token key atrule\">author<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token string\">\"[Name, role]\"<\/span>\n<span class=\"token key atrule\">source_adrs<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token punctuation\">[<\/span><span class=\"token punctuation\">]<\/span>                  <span class=\"token comment\"># REQUIRED: the accepted ADRs that justify this standard<\/span>\n<span class=\"token key atrule\">supersedes<\/span><span class=\"token punctuation\">:<\/span> none\n<span class=\"token key atrule\">superseded_by<\/span><span class=\"token punctuation\">:<\/span> none\n<span class=\"token key atrule\">compliance_mappings<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token punctuation\">[<\/span><span class=\"token punctuation\">]<\/span>          <span class=\"token comment\"># e.g. PCI DSS, NIST 800-53, CIS, your regional regulators<\/span>\n<span class=\"token key atrule\">review_cadence<\/span><span class=\"token punctuation\">:<\/span> annual           <span class=\"token comment\"># minimum, plus the out-of-cycle triggers in \u00a707<\/span>\n<span class=\"token key atrule\">next_review<\/span><span class=\"token punctuation\">:<\/span> YYYY<span class=\"token punctuation\">-<\/span>MM<span class=\"token punctuation\">-<\/span>DD\n<span class=\"token key atrule\">last_updated<\/span><span class=\"token punctuation\">:<\/span> YYYY<span class=\"token punctuation\">-<\/span>MM<span class=\"token punctuation\">-<\/span>DD\n<span class=\"token key atrule\">ai_assistance<\/span><span class=\"token punctuation\">:<\/span> none<\/span>\n<span class=\"token punctuation\">---<\/span><\/span>\n\n<span class=\"token comment\">&lt;!--\nFor AI assistants. This is a Standard: the living rulebook answering \"what are the rules\ntoday?\". Versioned, owned, reviewed on cadence, machine-verified where possible.\nRules: exactly one capitalized RFC 2119 keyword per control. Complete each control row:\nrequirement, mapping, verification, evidence. If a control can't be tied to a source ADR,\nflag the gap and suggest an RFC: write no unjustified rules. Keep procedures out; they\nbelong in a runbook.\nGround truth: fill a field only from what the human told you or gave you; otherwise keep the\n[placeholder] and ask.\nAsk: Which accepted ADRs justify these controls? How is each control verified by machine,\nand where do results land? Which compliance frameworks must the mapping cover?\n--><\/span>\n\n<span class=\"token title important\"><span class=\"token punctuation\">#<\/span> STD-NNNN: [Short, imperative title]<\/span>\n\nThe living rulebook. ADRs hold the why; this holds the what. One standard, one control domain.\n\n<span class=\"token blockquote punctuation\">><\/span> <span class=\"token bold\"><span class=\"token punctuation\">**<\/span><span class=\"token content\">The justification test.<\/span><span class=\"token punctuation\">**<\/span><\/span> Every standard cites the accepted ADRs that produced it. If no ADR can justify a rule, question whether the rule should exist. A standard with no source ADRs does not pass review.\n\n<span class=\"token title important\"><span class=\"token punctuation\">##<\/span> 01 \u00b7 Purpose and scope<\/span>\n\n[The risk or inconsistency this standard removes, and the position the organization takes.]\n\n<span class=\"token table\"><span class=\"token table-header-row\"><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> Boundary <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> Definition <\/span><span class=\"token punctuation\">|<\/span>\n<\/span><span class=\"token table-line\"><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span>\n<\/span><span class=\"token table-data-rows\"><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> In scope <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [Systems, environments, accounts, e.g. \"all production server instances\"] <\/span><span class=\"token punctuation\">|<\/span>\n<span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> Out of scope <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [Exclusions, and the standard that covers them] <\/span><span class=\"token punctuation\">|<\/span>\n<\/span><\/span>\n<span class=\"token title important\"><span class=\"token punctuation\">##<\/span> 02 \u00b7 Roles and responsibilities<\/span>\n\n<span class=\"token table\"><span class=\"token table-header-row\"><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> Role <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> Responsibility <\/span><span class=\"token punctuation\">|<\/span>\n<\/span><span class=\"token table-line\"><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span>\n<\/span><span class=\"token table-data-rows\"><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> Standard owner <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> Keeps it current, runs the review cycle, owns the exception register <\/span><span class=\"token punctuation\">|<\/span>\n<span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> Review team <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> Validates operational feasibility and compliance alignment at each review <\/span><span class=\"token punctuation\">|<\/span>\n<span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> Enforcing teams <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> Implement and operate the controls <\/span><span class=\"token punctuation\">|<\/span>\n<span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> All engineers <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> Comply with MUST controls; request an exception when they can't <\/span><span class=\"token punctuation\">|<\/span>\n<\/span><\/span>\n<span class=\"token title important\"><span class=\"token punctuation\">##<\/span> 03 \u00b7 Requirement language (RFC 2119)<\/span>\n\nEvery control uses exactly one of these, capitalized.\n\n<span class=\"token table\"><span class=\"token table-header-row\"><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> Keyword <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> Meaning <\/span><span class=\"token punctuation\">|<\/span>\n<\/span><span class=\"token table-line\"><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span>\n<\/span><span class=\"token table-data-rows\"><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> MUST \/ SHALL <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> Absolute requirement. Non-compliance needs an approved, time-boxed exception (\u00a705) <\/span><span class=\"token punctuation\">|<\/span>\n<span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> MUST NOT <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> Absolute prohibition. Same exception process <\/span><span class=\"token punctuation\">|<\/span>\n<span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> SHOULD <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> Strong recommendation. Deviation needs a documented justification <\/span><span class=\"token punctuation\">|<\/span>\n<span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> MAY <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> Genuinely optional, once every MUST and SHOULD is met <\/span><span class=\"token punctuation\">|<\/span>\n<\/span><\/span>\n<span class=\"token title important\"><span class=\"token punctuation\">##<\/span> 04 \u00b7 Controls<\/span>\n\nNumber controls STD-NNNN-CC and group them by function. This section says what must be true, not how: procedures belong in a linked runbook, so the rules stay stable while the procedures evolve.\n\n<span class=\"token title important\"><span class=\"token punctuation\">###<\/span> [Control group A, e.g. Authentication and access]<\/span>\n\n<span class=\"token table\"><span class=\"token table-header-row\"><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> ID <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> Requirement <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> Mapping <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> Verification <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> Evidence <\/span><span class=\"token punctuation\">|<\/span>\n<\/span><span class=\"token table-line\"><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span>\n<\/span><span class=\"token table-data-rows\"><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> STD-NNNN-01 <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [All administrative sessions MUST enforce MFA] <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [NIST IA-2; PCI DSS 8.4] <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [Query sign-in logs for admin roles] <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [Exported policy; sign-in log sample] <\/span><span class=\"token punctuation\">|<\/span>\n<span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> STD-NNNN-02 <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [Data at rest SHOULD be encrypted with AES-256] <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [NIST SC-28] <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [Config rule \/ volume encryption query] <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [Generated config report; key rotation logs] <\/span><span class=\"token punctuation\">|<\/span>\n<\/span><\/span>\n<span class=\"token title important\"><span class=\"token punctuation\">###<\/span> [Control group B, e.g. Logging and monitoring]<\/span>\n\n<span class=\"token table\"><span class=\"token table-header-row\"><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> ID <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> Requirement <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> Mapping <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> Verification <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> Evidence <\/span><span class=\"token punctuation\">|<\/span>\n<\/span><span class=\"token table-line\"><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span>\n<\/span><span class=\"token table-data-rows\"><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> STD-NNNN-11 <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [...] <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [...] <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [...] <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [...] <\/span><span class=\"token punctuation\">|<\/span>\n<\/span><\/span>\n<span class=\"token title important\"><span class=\"token punctuation\">##<\/span> 05 \u00b7 Exceptions<\/span>\n\nA controlled, time-boxed deviation with explicit risk acceptance. Indefinite waivers quietly accumulate risk debt.\n\n<span class=\"token list punctuation\">1.<\/span> Every exception MUST be time-boxed: 12 months maximum, shorter where the remediation plan allows.\n<span class=\"token list punctuation\">2.<\/span> Every exception MUST name a risk owner with the authority to accept the risk. Approving one is accepting a risk, not granting a bypass.\n<span class=\"token list punctuation\">3.<\/span> Every exception MUST define compensating controls and a remediation plan.\n<span class=\"token list punctuation\">4.<\/span> 90 days before expiry, the submitter is asked for a status update; renewal runs an abbreviated approval.\n<span class=\"token list punctuation\">5.<\/span> An exception that lapses without review is a governance failure, and is escalated as one.\n<span class=\"token list punctuation\">6.<\/span> Approval scales with blast radius: one team \u2192 standard owner; cross-team or compliance-relevant \u2192 architecture forum; enterprise or regulatory exposure \u2192 CISO or executive risk owner.\n\n<span class=\"token blockquote punctuation\">><\/span> <span class=\"token bold\"><span class=\"token punctuation\">**<\/span><span class=\"token content\">Compensating controls<\/span><span class=\"token punctuation\">**<\/span><\/span> must meet the intent and rigor of the original requirement, defend against the same risk, go beyond what other controls already require, and cover the extra risk of the deviation for its full duration. \"We accept the risk\" is not a compensating control.\n\n<span class=\"token table\"><span class=\"token table-header-row\"><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> ID <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> Scope <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> Risk owner <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> Compensating controls <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> Approved by <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> Expires <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> Status <\/span><span class=\"token punctuation\">|<\/span>\n<\/span><span class=\"token table-line\"><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span>\n<\/span><span class=\"token table-data-rows\"><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> EX-01 <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [Systems \/ controls excepted] <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [Name, role] <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [Mitigations in place] <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [Name] <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [YYYY-MM-DD] <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [Active \/ Expired \/ Closed] <\/span><span class=\"token punctuation\">|<\/span>\n<\/span><\/span>\n<span class=\"token title important\"><span class=\"token punctuation\">##<\/span> 06 \u00b7 Verification and drift detection<\/span>\n\nCheck adherence continuously, not only at review time. Prefer policy-as-code and scheduled scans over manual attestation.\n\n<span class=\"token table\"><span class=\"token table-header-row\"><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> Aspect <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> Definition <\/span><span class=\"token punctuation\">|<\/span>\n<\/span><span class=\"token table-line\"><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span>\n<\/span><span class=\"token table-data-rows\"><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> Method <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [e.g. config rules, compliance policies, scheduled benchmark scans] <\/span><span class=\"token punctuation\">|<\/span>\n<span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> Cadence <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [Continuous \/ daily \/ weekly, per control group] <\/span><span class=\"token punctuation\">|<\/span>\n<span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> Results land in <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [Dashboard \/ SIEM \/ compliance report] <\/span><span class=\"token punctuation\">|<\/span>\n<span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> Drift response <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [Auto-remediate \/ ticket \/ escalate, by criticality] <\/span><span class=\"token punctuation\">|<\/span>\n<\/span><\/span>\n<span class=\"token title important\"><span class=\"token punctuation\">##<\/span> 07 \u00b7 Review and maintenance<\/span>\n\nReviewed at least annually, plus out of cycle when triggered by:\n\n<span class=\"token list punctuation\">-<\/span> A major architectural change in the control domain\n<span class=\"token list punctuation\">-<\/span> A new or changed regulatory requirement\n<span class=\"token list punctuation\">-<\/span> Relevant threat intelligence\n<span class=\"token list punctuation\">-<\/span> A newly accepted ADR that supersedes or amends a source ADR\n\n<span class=\"token table\"><span class=\"token table-header-row\"><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> Version <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> Date <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> Author <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> Change <\/span><span class=\"token punctuation\">|<\/span>\n<\/span><span class=\"token table-line\"><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span>\n<\/span><span class=\"token table-data-rows\"><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> 1.0 <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [YYYY-MM-DD] <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [Name] <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [Initial release: codifies ADR-NNNN] <\/span><span class=\"token punctuation\">|<\/span>\n<\/span><\/span><\/code><\/pre>\n<\/div>\n<p><strong>Runbook (RUN)<\/strong><\/p>\n<div class=\"md-block\"><pre class=\"language-markdown\"><code class=\"language-markdown\"><span class=\"token front-matter-block\"><span class=\"token punctuation\">---<\/span>\n<span class=\"token front-matter yaml language-yaml\"><span class=\"token key atrule\">id<\/span><span class=\"token punctuation\">:<\/span> RUN<span class=\"token punctuation\">-<\/span>NNNN\n<span class=\"token key atrule\">title<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token string\">\"[Task-oriented title]\"<\/span>\n<span class=\"token key atrule\">type<\/span><span class=\"token punctuation\">:<\/span> incident<span class=\"token punctuation\">-<\/span>response          <span class=\"token comment\"># incident-response | how-to | scheduled-operation | onboarding<\/span>\n<span class=\"token key atrule\">status<\/span><span class=\"token punctuation\">:<\/span> published                <span class=\"token comment\"># published | archived<\/span>\n<span class=\"token key atrule\">owner<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token string\">\"[Named person accountable for accuracy]\"<\/span>\n<span class=\"token key atrule\">author<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token string\">\"[Name, role]\"<\/span>\n<span class=\"token key atrule\">implements_stds<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token punctuation\">[<\/span><span class=\"token punctuation\">]<\/span>              <span class=\"token comment\"># standards whose rules this procedure implements, or none<\/span>\n<span class=\"token key atrule\">systems_touched<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token punctuation\">[<\/span><span class=\"token punctuation\">]<\/span>\n<span class=\"token key atrule\">required_access<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token punctuation\">[<\/span><span class=\"token punctuation\">]<\/span>              <span class=\"token comment\"># verify these before you need them at 3 a.m.<\/span>\n<span class=\"token key atrule\">last_tested<\/span><span class=\"token punctuation\">:<\/span> YYYY<span class=\"token punctuation\">-<\/span>MM<span class=\"token punctuation\">-<\/span>DD          <span class=\"token comment\"># by whom; stale if blank or old<\/span>\n<span class=\"token key atrule\">last_updated<\/span><span class=\"token punctuation\">:<\/span> YYYY<span class=\"token punctuation\">-<\/span>MM<span class=\"token punctuation\">-<\/span>DD\n<span class=\"token key atrule\">ai_assistance<\/span><span class=\"token punctuation\">:<\/span> none<\/span>\n<span class=\"token punctuation\">---<\/span><\/span>\n\n<span class=\"token comment\">&lt;!--\nFor AI assistants. This is a Runbook: a step-by-step operational procedure answering\n\"how do I do it?\". Non-normative: the rules it implements live in standards.\nRules: give every state-changing step a Verify cell with the concrete expected output.\nWrite commands copy-paste ready, with placeholders clearly marked. For a step with no\nrollback path, write \"no rollback: escalate before executing\". State stop conditions\nexplicitly: halting beats momentum in an incident.\nGround truth: fill a field only from what the human told you or gave you; otherwise keep the\n[placeholder] and ask.\nAsk: What exactly triggers this procedure? What access does the executor need, and what is\nthe escalation path without it? When was it last tested, and by whom?\n--><\/span>\n\n<span class=\"token title important\"><span class=\"token punctuation\">#<\/span> RUN: [Task-oriented title]<\/span>\n\nHow to do one thing, step by step, with verification and rollback. Rules live in standards, reasoning in ADRs.\n\n<span class=\"token blockquote punctuation\">><\/span> <span class=\"token bold\"><span class=\"token punctuation\">**<\/span><span class=\"token content\">Test it like a fire extinguisher.<\/span><span class=\"token punctuation\">**<\/span><\/span> An outdated runbook is more dangerous than none. Update Last Tested every time it's exercised, in anger or in a game day. A runbook nobody has tested in a year belongs in the archive, not in your trust.\n\n<span class=\"token title important\"><span class=\"token punctuation\">##<\/span> 01 \u00b7 When to use this<\/span>\n\n<span class=\"token table\"><span class=\"token table-header-row\"><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> Aspect <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> Detail <\/span><span class=\"token punctuation\">|<\/span>\n<\/span><span class=\"token table-line\"><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span>\n<\/span><span class=\"token table-data-rows\"><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> Triggers <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [Alert name \/ error pattern \/ threshold \/ scheduled task] <\/span><span class=\"token punctuation\">|<\/span>\n<span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> Impact if untreated <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [User-visible impact and severity] <\/span><span class=\"token punctuation\">|<\/span>\n<span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> Who executes <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [Role: on-call SRE, DBA, platform engineer] <\/span><span class=\"token punctuation\">|<\/span>\n<\/span><\/span>\n<span class=\"token title important\"><span class=\"token punctuation\">##<\/span> 02 \u00b7 Prerequisites<\/span>\n\nEverything needed before step 1. Link consoles, dashboards and log queries directly: hunting for links mid-incident is how mistakes happen.\n\n<span class=\"token list punctuation\">-<\/span> [Permission or role required, and the escalation path if you lack it]\n<span class=\"token list punctuation\">-<\/span> [Tooling: CLI versions, network access path, bastion or session access]\n<span class=\"token list punctuation\">-<\/span> [Observability links: dashboard, log query, trace view, alert definition]\n<span class=\"token list punctuation\">-<\/span> [Variables: name, where the value comes from, an example]\n\n<span class=\"token title important\"><span class=\"token punctuation\">##<\/span> 03 \u00b7 Procedure<\/span>\n\nEvery step that changes state carries its own verification. Capture evidence as you go: UTC timestamp, executor, approver, before and after state.\n\n<span class=\"token table\"><span class=\"token table-header-row\"><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> <span class=\"token title important\"><span class=\"token punctuation\">#<\/span> <\/span><\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> Action <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> Verify before continuing <\/span><span class=\"token punctuation\">|<\/span>\n<\/span><span class=\"token table-line\"><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span>\n<\/span><span class=\"token table-data-rows\"><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> 1 <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [Exact command or console action, copy-paste ready] <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [Expected output or state] <\/span><span class=\"token punctuation\">|<\/span>\n<span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> 2 <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [...] <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [...] <\/span><span class=\"token punctuation\">|<\/span>\n<span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> 3 <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [...] <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [...] <\/span><span class=\"token punctuation\">|<\/span>\n<\/span><\/span>\n<span class=\"token blockquote punctuation\">><\/span> <span class=\"token bold\"><span class=\"token punctuation\">**<\/span><span class=\"token content\">Stop conditions.<\/span><span class=\"token punctuation\">**<\/span><\/span> [Signals that mean halt and roll back: a validation error, unexpected blast radius, error rate climbing after a step. Pushing past one turns an incident into an outage.]\n\n<span class=\"token title important\"><span class=\"token punctuation\">##<\/span> 04 \u00b7 Rollback<\/span>\n\nTreat the procedure as a transaction: undo in reverse order to last known-good.\n\n<span class=\"token table\"><span class=\"token table-header-row\"><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> Step <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> Compensating action <\/span><span class=\"token punctuation\">|<\/span>\n<\/span><span class=\"token table-line\"><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span>\n<\/span><span class=\"token table-data-rows\"><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> 3 <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [How to undo step 3] <\/span><span class=\"token punctuation\">|<\/span>\n<span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> 2 <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [How to undo step 2] <\/span><span class=\"token punctuation\">|<\/span>\n<span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> 1 <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [How to undo step 1, or \"no rollback: escalate before executing\"] <\/span><span class=\"token punctuation\">|<\/span>\n<\/span><\/span>\n<span class=\"token title important\"><span class=\"token punctuation\">##<\/span> 05 \u00b7 Escalation<\/span>\n\n<span class=\"token table\"><span class=\"token table-header-row\"><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> Escalate to <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> When <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> How <\/span><span class=\"token punctuation\">|<\/span>\n<\/span><span class=\"token table-line\"><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span>\n<\/span><span class=\"token table-data-rows\"><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [SME \/ owning team] <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [e.g. restore fails, or lag stays above 60 s after two checks] <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [Chat channel \/ on-call schedule] <\/span><span class=\"token punctuation\">|<\/span>\n<span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [Incident commander] <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [User-visible impact ongoing] <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [Paging path] <\/span><span class=\"token punctuation\">|<\/span>\n<\/span><\/span>\n<span class=\"token title important\"><span class=\"token punctuation\">##<\/span> 06 \u00b7 Maintenance<\/span>\n\nLog every exercise, including dry runs. Archive, don't delete, when this stops earning its keep.\n\n<span class=\"token table\"><span class=\"token table-header-row\"><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> Date <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> Exercised by <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> Context <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> Changes made <\/span><span class=\"token punctuation\">|<\/span>\n<\/span><span class=\"token table-line\"><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span>\n<\/span><span class=\"token table-data-rows\"><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [YYYY-MM-DD] <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [Name] <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [Incident ID \/ game day] <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [e.g. steps 2\u20133 updated: new console path] <\/span><span class=\"token punctuation\">|<\/span>\n<\/span><\/span><\/code><\/pre>\n<\/div>\n<p><strong>Statement of work (SOW)<\/strong><\/p>\n<div class=\"md-block\"><pre class=\"language-markdown\"><code class=\"language-markdown\"><span class=\"token front-matter-block\"><span class=\"token punctuation\">---<\/span>\n<span class=\"token front-matter yaml language-yaml\"><span class=\"token key atrule\">id<\/span><span class=\"token punctuation\">:<\/span> SOW<span class=\"token punctuation\">-<\/span>NNNN\n<span class=\"token key atrule\">title<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token string\">\"[Project name]\"<\/span>\n<span class=\"token key atrule\">status<\/span><span class=\"token punctuation\">:<\/span> draft                    <span class=\"token comment\"># draft | in-review | approved<\/span>\n<span class=\"token key atrule\">version<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token number\">0.1<\/span>\n<span class=\"token key atrule\">owning_team<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token string\">\"[Team name]\"<\/span>\n<span class=\"token key atrule\">author<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token string\">\"[Name, role]\"<\/span>\n<span class=\"token key atrule\">sponsor<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token string\">\"[Executive sponsor, title]\"<\/span>\n<span class=\"token key atrule\">parent_sad<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token string\">\"\"<\/span>                   <span class=\"token comment\"># the SAD this delivers against, or \"standalone\"<\/span>\n<span class=\"token key atrule\">related_adrs<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token punctuation\">[<\/span><span class=\"token punctuation\">]<\/span>                 <span class=\"token comment\"># decisions this SOW implements or is constrained by<\/span>\n<span class=\"token key atrule\">last_updated<\/span><span class=\"token punctuation\">:<\/span> YYYY<span class=\"token punctuation\">-<\/span>MM<span class=\"token punctuation\">-<\/span>DD\n<span class=\"token key atrule\">ai_assistance<\/span><span class=\"token punctuation\">:<\/span> none<\/span>\n<span class=\"token punctuation\">---<\/span><\/span>\n\n<span class=\"token comment\">&lt;!--\nFor AI assistants. This is a Statement of Work: a bounded delivery document answering\n\"what are we delivering?\". It has a start and an end, and is frozen at done.\nRules: pair every deliverable with a verifiable acceptance criterion. Treat the Minimum\nViable Solution as a floor to defend: push scope out of it, not in. When a significant\narchitectural decision surfaces, send it to an RFC and record only the resulting ADR link\nhere. Ask for the parent SAD and related ADR IDs early.\nGround truth: fill a field only from what the human told you or gave you; otherwise keep the\n[placeholder] and ask.\nAsk: What is explicitly out of scope? What are the floors for scale, uptime, testability and\nobservability? Which SAD capability does this SOW deliver?\n--><\/span>\n\n<span class=\"token title important\"><span class=\"token punctuation\">#<\/span> SOW: [Project name]<\/span>\n\nA bounded delivery document: what gets delivered, and it's frozen at done.\n\n<span class=\"token title important\"><span class=\"token punctuation\">##<\/span> 01 \u00b7 Description<\/span>\n\n[What the project is and why it exists.]\n\n<span class=\"token title important\"><span class=\"token punctuation\">##<\/span> 02 \u00b7 Documentation<\/span>\n\n<span class=\"token list punctuation\">-<\/span> [Doc link or note]\n\n<span class=\"token title important\"><span class=\"token punctuation\">##<\/span> 03 \u00b7 Objectives<\/span>\n\n<span class=\"token list punctuation\">-<\/span> [Objective]\n\n<span class=\"token title important\"><span class=\"token punctuation\">##<\/span> 04 \u00b7 Scope of work<\/span>\n\nDefine the boundaries early, and list what's explicitly out of scope.\n\n[What the project includes, and where relevant what it doesn't.]\n\n<span class=\"token table\"><span class=\"token table-header-row\"><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> Source <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> Target <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> Notes <\/span><span class=\"token punctuation\">|<\/span>\n<\/span><span class=\"token table-line\"><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span>\n<\/span><span class=\"token table-data-rows\"><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [System \/ DB \/ path] <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [System \/ DB \/ path] <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [Caveats; for migrations only] <\/span><span class=\"token punctuation\">|<\/span>\n<\/span><\/span>\n<span class=\"token title important\"><span class=\"token punctuation\">##<\/span> 05 \u00b7 Minimum viable solution<\/span>\n\nThe smallest version that meets the primary purpose, with explicit non-functional floors. If it's not in the MVS, it's deferred.\n\n<span class=\"token list punctuation\">-<\/span> <span class=\"token bold\"><span class=\"token punctuation\">**<\/span><span class=\"token content\">Scalability:<\/span><span class=\"token punctuation\">**<\/span><\/span> [Handle [current load] and scale to [anticipated load]]\n<span class=\"token list punctuation\">-<\/span> <span class=\"token bold\"><span class=\"token punctuation\">**<\/span><span class=\"token content\">Reliability:<\/span><span class=\"token punctuation\">**<\/span><\/span> [Achieve [uptime goal] with failover to [fallback]]\n<span class=\"token list punctuation\">-<\/span> <span class=\"token bold\"><span class=\"token punctuation\">**<\/span><span class=\"token content\">Testability:<\/span><span class=\"token punctuation\">**<\/span><\/span> [Tests cover [critical functions]]\n<span class=\"token list punctuation\">-<\/span> <span class=\"token bold\"><span class=\"token punctuation\">**<\/span><span class=\"token content\">Observability:<\/span><span class=\"token punctuation\">**<\/span><\/span> [Logging for [key operations], monitoring on [primary metrics]]\n\n<span class=\"token title important\"><span class=\"token punctuation\">##<\/span> 06 \u00b7 Deliverables<\/span>\n\n<span class=\"token table\"><span class=\"token table-header-row\"><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> Deliverable <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> Acceptance criterion <\/span><span class=\"token punctuation\">|<\/span>\n<\/span><span class=\"token table-line\"><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span>\n<\/span><span class=\"token table-data-rows\"><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [Deliverable] <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [How we'll know it's done] <\/span><span class=\"token punctuation\">|<\/span>\n<\/span><\/span>\n<span class=\"token title important\"><span class=\"token punctuation\">##<\/span> 07 \u00b7 Resources<\/span>\n\n<span class=\"token list punctuation\">-<\/span> [Team: people involved]\n\n<span class=\"token title important\"><span class=\"token punctuation\">##<\/span> 08 \u00b7 Implementation<\/span>\n\n[Before\/after flowcharts, architecture diagrams.]\n\n<span class=\"token blockquote punctuation\">><\/span> <span class=\"token bold\"><span class=\"token punctuation\">**<\/span><span class=\"token content\">Big decision mid-flight?<\/span><span class=\"token punctuation\">**<\/span><\/span> It doesn't get buried in this SOW. Raise an RFC, take it through the forum, and link the resulting ADR in the frontmatter. This document records delivery, not decisions.\n\n<span class=\"token title important\"><span class=\"token punctuation\">###<\/span> Critical path<\/span>\n\nWhat must work reliably for the system to operate: what, not how.\n\n<span class=\"token table\"><span class=\"token table-header-row\"><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> Step <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> Action <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> SLO <\/span><span class=\"token punctuation\">|<\/span>\n<\/span><span class=\"token table-line\"><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span>\n<\/span><span class=\"token table-data-rows\"><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> 1 <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [Action or milestone] <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [Respond within [X] ms for [P]% of requests] <\/span><span class=\"token punctuation\">|<\/span>\n<span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> 2 <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [Following action] <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [Complete within [Y] s for [Q]% of users] <\/span><span class=\"token punctuation\">|<\/span>\n<\/span><\/span>\n<span class=\"token title important\"><span class=\"token punctuation\">##<\/span> 09 \u00b7 Dependencies (optional)<\/span>\n\n<span class=\"token list punctuation\">-<\/span> <span class=\"token bold\"><span class=\"token punctuation\">**<\/span><span class=\"token content\">External:<\/span><span class=\"token punctuation\">**<\/span><\/span> [Team or resource needed to complete [task]]\n<span class=\"token list punctuation\">-<\/span> <span class=\"token bold\"><span class=\"token punctuation\">**<\/span><span class=\"token content\">Internal:<\/span><span class=\"token punctuation\">**<\/span><\/span> [Team or system dependency]\n\n<span class=\"token title important\"><span class=\"token punctuation\">##<\/span> 10 \u00b7 Assumptions (optional)<\/span>\n\n<span class=\"token list punctuation\">-<\/span> [Assumption]\n\n<span class=\"token title important\"><span class=\"token punctuation\">##<\/span> 11 \u00b7 Risks (optional)<\/span>\n\n<span class=\"token list punctuation\">-<\/span> [Risk]\n\n<span class=\"token title important\"><span class=\"token punctuation\">##<\/span> 12 \u00b7 Basis for time estimate (optional)<\/span>\n\n<span class=\"token table\"><span class=\"token table-header-row\"><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> Item <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> Rough estimate <\/span><span class=\"token punctuation\">|<\/span>\n<\/span><span class=\"token table-line\"><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span>\n<\/span><span class=\"token table-data-rows\"><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [Milestone 1] <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [Estimate] <\/span><span class=\"token punctuation\">|<\/span>\n<span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [Milestone 2] <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [Estimate] <\/span><span class=\"token punctuation\">|<\/span>\n<span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> Total <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [Estimate] <\/span><span class=\"token punctuation\">|<\/span>\n<\/span><\/span>\n<span class=\"token title important\"><span class=\"token punctuation\">##<\/span> 13 \u00b7 Purposeful design checklist<\/span>\n\n<span class=\"token list punctuation\">-<\/span> <span class=\"token bold\"><span class=\"token punctuation\">**<\/span><span class=\"token content\">Aligned with business need:<\/span><span class=\"token punctuation\">**<\/span><\/span> [Focuses on [core objective] without [unneeded features]]\n<span class=\"token list punctuation\">-<\/span> <span class=\"token bold\"><span class=\"token punctuation\">**<\/span><span class=\"token content\">No premature optimization:<\/span><span class=\"token punctuation\">**<\/span><\/span> [Optimizes [current bottleneck], not speculative future needs]\n<span class=\"token list punctuation\">-<\/span> <span class=\"token bold\"><span class=\"token punctuation\">**<\/span><span class=\"token content\">MVS is complete:<\/span><span class=\"token punctuation\">**<\/span><\/span> [Includes [necessary components] without [extra complexity]]\n\n<span class=\"token blockquote punctuation\">><\/span> <span class=\"token bold\"><span class=\"token punctuation\">**<\/span><span class=\"token content\">Scope is a contract.<\/span><span class=\"token punctuation\">**<\/span><\/span> Any change to scope, timeline or deliverables after approval goes through formal change management.\n\n<span class=\"token title important\"><span class=\"token punctuation\">##<\/span> 14 \u00b7 References<\/span>\n\n<span class=\"token list punctuation\">-<\/span> [Document or resource name and link]<\/code><\/pre>\n<\/div>\n<p><strong>Opportunity brief (BRIEF)<\/strong><\/p>\n<div class=\"md-block\"><pre class=\"language-markdown\"><code class=\"language-markdown\"><span class=\"token front-matter-block\"><span class=\"token punctuation\">---<\/span>\n<span class=\"token front-matter yaml language-yaml\"><span class=\"token key atrule\">id<\/span><span class=\"token punctuation\">:<\/span> BRIEF<span class=\"token punctuation\">-<\/span>NNNN\n<span class=\"token key atrule\">title<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token string\">\"[Outcome-phrased opportunity title]\"<\/span>\n<span class=\"token key atrule\">status<\/span><span class=\"token punctuation\">:<\/span> draft                    <span class=\"token comment\"># draft | pitched | funded | parked<\/span>\n<span class=\"token key atrule\">author<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token string\">\"[Name, role]\"<\/span>\n<span class=\"token key atrule\">sponsor_sought<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token string\">\"[Executive whose backing this needs]\"<\/span>\n<span class=\"token key atrule\">resulting<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token string\">\"\"<\/span>                    <span class=\"token comment\"># the SAD \/ SOW, filled when funded<\/span>\n<span class=\"token key atrule\">date<\/span><span class=\"token punctuation\">:<\/span> YYYY<span class=\"token punctuation\">-<\/span>MM<span class=\"token punctuation\">-<\/span>DD\n<span class=\"token key atrule\">ai_assistance<\/span><span class=\"token punctuation\">:<\/span> none<\/span>\n<span class=\"token punctuation\">---<\/span><\/span>\n\n<span class=\"token comment\">&lt;!--\nFor AI assistants. This is an Opportunity Brief: a one-page executive pitch answering\n\"why fund this?\". Its only job is to win sponsorship; the architecture comes later, in\nthe SAD it spawns.\nRules: business language throughout (cost, time, risk, competitive position); no jargon.\nEvery number the human hasn't supplied is a question, not an estimate. Express the\ninvestment as a range and state the exact decision needed. Over one page? Cut until it fits.\nGround truth: fill a field only from what the human told you or gave you; otherwise keep the\n[placeholder] and ask.\nAsk: What does inaction cost, quantified? What specific approval is being requested? Which\nnumbers, by which dates, measure success?\n--><\/span>\n\n<span class=\"token title important\"><span class=\"token punctuation\">#<\/span> BRIEF: [Outcome-phrased opportunity title]<\/span>\n\n<span class=\"token blockquote punctuation\">><\/span> <span class=\"token bold\"><span class=\"token punctuation\">**<\/span><span class=\"token content\">One page when filled.<\/span><span class=\"token punctuation\">**<\/span><\/span> Bottom line up front, outcomes not technology. Aim for a third text, a third visual, a third white space. If it spills past a page, it's a SAD trying to be born: cut until it fits.\n\n<span class=\"token title important\"><span class=\"token punctuation\">##<\/span> 01 \u00b7 The problem<\/span>\n\nWhat keeps leadership up at night, in cost, time, risk or competitive position.\n\n[Current situation: the market shift, bottleneck, regulatory pressure or contract event forcing the question.]\n\n[Cost of doing nothing: money, time, risk, quantified.]\n\n<span class=\"token title important\"><span class=\"token punctuation\">##<\/span> 02 \u00b7 Options<\/span>\n\nValidated options, not a single path. The recommendation goes first and says why it wins.\n\n<span class=\"token table\"><span class=\"token table-header-row\"><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> Option <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> What it is <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> Why \/ why not <\/span><span class=\"token punctuation\">|<\/span>\n<\/span><span class=\"token table-line\"><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span>\n<\/span><span class=\"token table-data-rows\"><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [Recommended] <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [One line] <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [Why it delivers the best value] <\/span><span class=\"token punctuation\">|<\/span>\n<span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [Alternative B] <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [One line] <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [Why bypassed: cost, risk, time] <\/span><span class=\"token punctuation\">|<\/span>\n<span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> Status quo <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> Do nothing <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [What that costs: ties to \u00a701] <\/span><span class=\"token punctuation\">|<\/span>\n<\/span><\/span>\n<span class=\"token title important\"><span class=\"token punctuation\">##<\/span> 03 \u00b7 The ask<\/span>\n\nUse a range, not a point estimate: ranges survive first contact with finance.\n\n<span class=\"token table\"><span class=\"token table-header-row\"><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> Element <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> Detail <\/span><span class=\"token punctuation\">|<\/span>\n<\/span><span class=\"token table-line\"><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span>\n<\/span><span class=\"token table-data-rows\"><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> Decision needed <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [The approval requested, and by when] <\/span><span class=\"token punctuation\">|<\/span>\n<span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> Investment <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [$X\u2013$Y: licence, effort, run cost] <\/span><span class=\"token punctuation\">|<\/span>\n<span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> Timeline <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [Phase 1 value by, full delivery by] <\/span><span class=\"token punctuation\">|<\/span>\n<span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> Owner if funded <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [Who runs it, and the SAD \/ SOW that gets spun up] <\/span><span class=\"token punctuation\">|<\/span>\n<\/span><\/span>\n<span class=\"token title important\"><span class=\"token punctuation\">##<\/span> 04 \u00b7 Success measures<\/span>\n\nNumbers with dates, so the follow-up conversation is about results, not recollections.\n\n<span class=\"token table\"><span class=\"token table-header-row\"><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> Measure <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> Target <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-header important\"> By when <\/span><span class=\"token punctuation\">|<\/span>\n<\/span><span class=\"token table-line\"><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span><span class=\"token punctuation\">---<\/span><span class=\"token punctuation\">|<\/span>\n<\/span><span class=\"token table-data-rows\"><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [ROI \/ payback \/ cost reduction] <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [$ or %] <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [Quarter] <\/span><span class=\"token punctuation\">|<\/span>\n<span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [Operational metric, e.g. cycle time] <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [From X to Y] <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [Quarter] <\/span><span class=\"token punctuation\">|<\/span>\n<span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [Adoption, e.g. teams onboarded] <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [N of M] <\/span><span class=\"token punctuation\">|<\/span><span class=\"token table-data\"> [Quarter] <\/span><span class=\"token punctuation\">|<\/span>\n<\/span><\/span><\/code><\/pre>\n<\/div>\n<h2 id=\"the-review-request\">The review request<\/h2>\n<p>A pull request tells you that work exists (a branch, a diff, some green checks). It tells you nothing about whether a human was asked for anything. You can open a PR, keep pushing to it, and leave it open all week because you want CI to run, and none of that is a request for review.<\/p>\n<p>An objection I hear a lot is that tracking review state somewhere else is duplicate work. Cool, but check the assumption first: an open PR and work that's ready for another human look like the same event and aren't the same fact. Move the item to Review and the state becomes the message. The author moves the card to ask, and nothing is blocked if they don't, so a state you set in order to ask has no reason to be faked. What that buys you: a timestamp, a count of how many times the work bounced back, and the name of whoever looked (which is more than a chat ping leaves behind).<\/p>\n<p>Your request is the signal, and the artifact is only the evidence attached to it. If you want advice, say advice. If you want a decision, say decision. Say it in the first line, because a reader who has to guess what you want will pick the cheaper option, which is nothing.<\/p>\n<h2 id=\"document-access\">Document access<\/h2>\n<p>One rule ended a lot of argument for me: no artifact counts unless a reader outside the team can open it. A decision parked in a private channel, in a personal drive folder, or behind a sharing dialog doesn't exist in any useful sense. Of the first 20 pre-reads that reached our intake, the one place a request comes in, 8 lived in somebody's personal drive folder and 1 existed nowhere I could reach at all. A personal link 403s for everyone else, so every reference to it in your decision log is a dead end. If the one person who can open it leaves, you never had the document.<\/p>\n<p><img src=\"https:\/\/platformtoolsmith.com\/assets\/blog\/mole-language-before-law-open.f66e4d69.900.webp\" alt=\"A mole moving a page from a locked drawer onto a wall that anyone can read.\" width=\"1920\" height=\"1080\" loading=\"lazy\" decoding=\"async\" srcset=\"https:\/\/platformtoolsmith.com\/assets\/blog\/mole-language-before-law-open.f66e4d69.400.webp 400w, https:\/\/platformtoolsmith.com\/assets\/blog\/mole-language-before-law-open.f66e4d69.900.webp 900w, https:\/\/platformtoolsmith.com\/assets\/blog\/mole-language-before-law-open.f66e4d69.1800.webp 1800w\" sizes=\"(min-width: 1024px) 773px, 100vw\" \/><\/p>\n<p>The flip side of openness is a readership decision: the moment a file that was shared with three named people lands in our company-readable library, I've granted everyone access to it. That's why copies come in through a check of their own (content, sharing, findable, not already there), and why money and live security detail come out of a document before it moves. Ask who can read it before you move it.<\/p>\n<h2 id=\"template-location\">Template location<\/h2>\n<p>Standards came later as a fourth type, once enough decisions pointed the same way, and I had prefixed it STD, which reads as an unrelated acronym to anyone outside a standards conversation. A display-only change would've left two names for one document type, which is exactly the drift the framework exists to stop, so we did the full rename while exactly one document used it, an afternoon's work at the time.<\/p>\n<p>Then I got the bill for where our templates lived. The chain ran one way:<\/p>\n<div class=\"mermaid-container\">\n<div class=\"mermaid-diagram\" tabindex=\"0\">\n<svg class=\"ea-loc\" viewBox=\"-2 -2 884 318\" role=\"img\" aria-labelledby=\"ea-loc-title ea-loc-desc\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\">\n<title id=\"ea-loc-title\">Where the templates live, and where the copy breaks<\/title>\n<desc id=\"ea-loc-desc\">A shared drive is the master. The templates move into the repo. A tool keeps its own copy with a hard-coded file name, so a local edit at the far end is wiped by the next sync.<\/desc>\n<style>.ea-loc{display:block;width:100%;min-width:560px;height:auto;font-family:'Space Grotesk',system-ui,sans-serif}.ea-loc text{text-anchor:middle;fill:var(--mermaid-text, #000000)}.ea-loc .b{fill:var(--mermaid-node-bg, #FFFFFF);stroke:var(--mermaid-node-border, #000000);stroke-width:1.5}.ea-loc .f-dash{stroke-dasharray:6 4}.ea-loc .band{font-size:13.8px;font-weight:700;letter-spacing:.08em}.ea-loc .t{font-size:20.4px;font-weight:700}.ea-loc .s{font-size:15.0px;fill:var(--theme-text-muted, #6A6A6A)}.ea-loc .f{font-size:13.8px;fill:var(--theme-text-muted, #6A6A6A)}.ea-loc .lab{font-size:14.4px;fill:var(--theme-text-muted, #6A6A6A)}.ea-loc .ln{fill:none;stroke:var(--mermaid-line, #FF5C00);stroke-width:2}.ea-loc .dash{stroke-dasharray:7 5}.ea-loc marker path{fill:var(--mermaid-line, #FF5C00)}<\/style>\n<defs><marker id=\"ea-loc-arrow\" viewBox=\"0 0 10 10\" refX=\"7\" refY=\"5\" markerWidth=\"7\" markerHeight=\"7\" orient=\"auto-start-reverse\"><path d=\"M0 0L10 5L0 10z\"><\/path><\/marker><\/defs>\n<rect class=\"b\" x=\"0\" y=\"0\" width=\"230\" height=\"110\"><\/rect>\n<rect x=\"0\" y=\"0\" width=\"230\" height=\"29\" style=\"fill:var(--diagram-origin, #A0694B)\"><\/rect>\n<text class=\"band\" x=\"115.0\" y=\"20.0\" style=\"fill:var(--diagram-origin-label, #000000)\">SOURCE<\/text>\n<text class=\"t\" x=\"115.0\" y=\"59.4\">Shared drive<\/text>\n<text class=\"s\" x=\"115.0\" y=\"87.7\">the master<\/text>\n<rect class=\"b\" x=\"325\" y=\"0\" width=\"230\" height=\"110\"><\/rect>\n<rect x=\"325\" y=\"0\" width=\"230\" height=\"29\" style=\"fill:var(--diagram-neutral, #F0F0DC)\"><\/rect>\n<text class=\"band\" x=\"440.0\" y=\"20.0\" style=\"fill:var(--diagram-neutral-label, #000000)\">REPO<\/text>\n<text class=\"t\" x=\"440.0\" y=\"49.7\">Repo templates<\/text>\n<text class=\"s\" x=\"440.0\" y=\"77.9\">in the repo engineers<\/text>\n<text class=\"s\" x=\"440.0\" y=\"97.4\">open<\/text>\n<rect class=\"b\" x=\"650\" y=\"0\" width=\"230\" height=\"110\"><\/rect>\n<rect x=\"650\" y=\"0\" width=\"230\" height=\"29\" style=\"fill:var(--diagram-neutral, #F0F0DC)\"><\/rect>\n<text class=\"band\" x=\"765.0\" y=\"20.0\" style=\"fill:var(--diagram-neutral-label, #000000)\">TOOL<\/text>\n<text class=\"t\" x=\"765.0\" y=\"59.4\">Tool copy<\/text>\n<text class=\"s\" x=\"765.0\" y=\"87.7\">hard-coded file name<\/text>\n<rect class=\"b\" x=\"650\" y=\"204\" width=\"230\" height=\"110\"><\/rect>\n<rect x=\"650\" y=\"204\" width=\"230\" height=\"29\" style=\"fill:var(--diagram-neutral, #F0F0DC)\"><\/rect>\n<text class=\"band\" x=\"765.0\" y=\"224.0\" style=\"fill:var(--diagram-neutral-label, #000000)\">LOCAL<\/text>\n<text class=\"t\" x=\"765.0\" y=\"263.4\">Local edit<\/text>\n<text class=\"s\" x=\"765.0\" y=\"291.7\">at the far end<\/text>\n<path class=\"ln\" d=\"M230 55.0H322\" marker-end=\"url(#ea-loc-arrow)\"><\/path>\n<text class=\"lab\" x=\"277.5\" y=\"43.0\" text-anchor=\"middle\">moved into<\/text>\n<path class=\"ln\" d=\"M555 55.0H647\" marker-end=\"url(#ea-loc-arrow)\"><\/path>\n<text class=\"lab\" x=\"602.5\" y=\"43.0\" text-anchor=\"middle\">copied<\/text>\n<path class=\"ln dash\" d=\"M765.0 204V113\" marker-end=\"url(#ea-loc-arrow)\"><\/path>\n<text class=\"lab\" x=\"777.0\" y=\"168\" text-anchor=\"start\">next sync wipes it<\/text>\n<\/svg>\n<\/div>\n<\/div>\n<p>Edit a copy at the far end and the next sync wipes it. A hard-coded file name for the old type stayed inside the tool, so the old name outlived the rename, and a document with the old name turned up on another site long after everyone agreed the rename was finished. A search of one site proves only that one site is clean.<\/p>\n<p>The version I'd start with today: one template, where the work happens, in the repo your engineers already have open. If a copy has to exist somewhere else, know which copy is upstream and edit that one, then check the whole estate before you call a rename done.<\/p>\n<h2 id=\"field-freezes\">Field freezes<\/h2>\n<p>Freezing fields is where architecture functions overreach (mine included). One blunt test: does anything downstream use the value, not just check that it's there? If a rollup, a report, a search or an audit depends on that field, freeze it and tell people why (a frozen field with a stated reason is easier to live with). If nothing uses it, leave it free.<\/p>\n<p>My judgement, and only a judgement: the freeze question is the best filter I've found for telling governance that helps a team from governance that performs for an audience.<\/p>\n<p>Ours passes that test in one place. Every item in an architecture area carries one link to its portfolio row, because the weekly rollup counts rows for people outside the architecture function, so that link is frozen and the field carries help text saying why. The approval date that opened this post failed the same test: the gate checked that a value was there, and nothing used it.<\/p>\n<p>A freeze protects a value that already exists. It doesn't stop that value being cleared, and it does nothing at all to a field nobody has filled in yet. So protect a field in every state (before it's valid, while it's required, after it's recorded), and test it live.<\/p>\n<h2 id=\"what-you-can-do-this-week\">What you can do this week<\/h2>\n<p>None of this needs permission. Three things you can do without asking anyone:<\/p>\n<ol>\n<li>Put the templates next to the work: start with the three we started with (decision record, design doc, request for comments), or take all seven.<\/li>\n<li>Take something you're about to do, write the request, and send it to two people it affects. Say in the first line whether you want advice or a decision.<\/li>\n<li>Pick a decision your team already made, write it as a decision record, and put it somewhere a reader outside your team can open.<\/li>\n<\/ol>\n<p>All seven templates are above, each ready to copy.<\/p>\n<p>That's a working architecture language on day one: the seven questions, the place to ask, and one record you can defend.<\/p>\n<p>Next up, the forum: where a request lands, and what has to leave the room for it to count.<\/p>\n"},{"title":"Stop Architecture Drift: Operationalizing ADRs with Automated Fitness Functions","link":{"@attributes":{"href":"https:\/\/platformtoolsmith.com\/blog\/operationalizing-adrs-fitness-functions\/"}},"updated":"2026-04-06T00:00:00Z","id":"https:\/\/platformtoolsmith.com\/blog\/operationalizing-adrs-fitness-functions\/","content":"<p>Six months after we standardized on OpenSearch, a pull request introduced Datadog into a service. The ADR existed. It had been discussed, approved, and stored in the repo. The PR was still green.<\/p>\n<p>That is architecture drift. Not because engineers are careless. Because memory does not scale across hundreds of people and dozens of repositories.<\/p>\n<p><img src=\"https:\/\/platformtoolsmith.com\/assets\/blog\/gopher-architecture-drift.48cbc562.900.webp\" alt=\"A mole points proudly at a neat blueprint while pipes and wires drift out of place behind it.\" width=\"1920\" height=\"1080\" loading=\"lazy\" decoding=\"async\" srcset=\"https:\/\/platformtoolsmith.com\/assets\/blog\/gopher-architecture-drift.48cbc562.400.webp 400w, https:\/\/platformtoolsmith.com\/assets\/blog\/gopher-architecture-drift.48cbc562.900.webp 900w, https:\/\/platformtoolsmith.com\/assets\/blog\/gopher-architecture-drift.48cbc562.1800.webp 1800w\" sizes=\"(min-width: 1024px) 773px, 100vw\" \/><\/p>\n<p>After we started checking ADRs in CI, we caught several violations like this in the first month and dozens more in the first quarter before they reached production.<\/p>\n<p>This is the difference between documenting architecture and operationalizing it. Documentation tells people what the decision was. Operationalization puts that decision in the path of delivery.<\/p>\n<p>This post is the system behind that: take an ADR, turn it into an automated check, and run it against every PR diff. I went this direction because I needed one approach that worked across Go, Python, Terraform, and Kubernetes without maintaining a separate rule engine for each.<\/p>\n<h2 id=\"the-problem-architecture-decisions-nobody-remembers\">The Problem: Architecture Decisions Nobody Remembers<\/h2>\n<p><img src=\"https:\/\/platformtoolsmith.com\/assets\/blog\/gopher-forgotten-adrs.0bfef015.900.webp\" alt=\"A mole walks past a dusty filing cabinet labeled ADR, thick with cobwebs and spilling papers.\" width=\"1920\" height=\"1080\" loading=\"lazy\" decoding=\"async\" srcset=\"https:\/\/platformtoolsmith.com\/assets\/blog\/gopher-forgotten-adrs.0bfef015.400.webp 400w, https:\/\/platformtoolsmith.com\/assets\/blog\/gopher-forgotten-adrs.0bfef015.900.webp 900w, https:\/\/platformtoolsmith.com\/assets\/blog\/gopher-forgotten-adrs.0bfef015.1800.webp 1800w\" sizes=\"(min-width: 1024px) 773px, 100vw\" \/><\/p>\n<p>Most architecture teams do good work. They write ADRs, join design reviews, publish standards, and explain trade-offs. But documents do not participate in delivery. A decision can be technically correct and still have zero enforcement once it leaves the meeting.<\/p>\n<p>At scale, that becomes a latency problem. You only discover drift after merge, during an incident, or when a migration turns into a quarter-long cleanup project. The issue is not that architects need more authority. The issue is that documentation does not stop drift. Running checks do.<\/p>\n<p>The fix is to move architecture into the same path as tests, policy, and build checks. Make the decision executable.<\/p>\n<h2 id=\"a-quick-primer-on-architecture-decision-records-adr\">A Quick Primer on Architecture Decision Records [ADR]<\/h2>\n<p>If you already use Architectural Decision Records, this part will feel familiar. But the template matters here because bad ADRs are hard to operationalize. If the decision is vague, the automation will be vague too.<\/p>\n<p>I still like <a href=\"https:\/\/cognitect.com\/blog\/2011\/11\/15\/documenting-architecture-decisions\">Michael Nygard's original format<\/a>, with a few practical tweaks, because it keeps the decision grounded in context instead of turning it into policy theater.<\/p>\n<p>Here is the template I use:<\/p>\n<div class=\"md-block\"><pre class=\"language-markdown\"><code class=\"language-markdown\"><span class=\"token title important\"><span class=\"token punctuation\">#<\/span> Architecture Decision Record<\/span>\n\nDate: YYYY-MM-DD\n\nStatus: proposed | accepted | deprecated | superseded (add reference)\n\n<span class=\"token title important\"><span class=\"token punctuation\">##<\/span> Context<\/span>\n\nThis section describes the forces at play, including technological, political,\nsocial, and project local. These forces are probably in tension and should be\ncalled out as such. The language in this section is value-neutral. It is simply\ndescribing facts.\n\n<span class=\"token title important\"><span class=\"token punctuation\">##<\/span> Considered Alternatives<\/span>\n\nThis section describes the alternatives evaluated together with the pros, cons,\nand reasoning why to move or not to move with it.\n\n<span class=\"token list punctuation\">*<\/span> Alternative 1: [description, pros, cons, reasoning]\n<span class=\"token list punctuation\">*<\/span> Alternative 2: [description, pros, cons, reasoning]\n\n<span class=\"token title important\"><span class=\"token punctuation\">##<\/span> Decision<\/span>\n\nThis section describes our response to these forces. It is stated in full\nsentences, with an active voice. \"We will ...\"\n\n<span class=\"token title important\"><span class=\"token punctuation\">##<\/span> Consequences<\/span>\n\nThis section describes the resulting context, after applying the decision.\nAll consequences should be listed here, not just the \"positive\" ones. A\nparticular decision may have positive, negative, and neutral consequences,\nbut all of them affect the team and project in the future.<\/code><\/pre>\n<\/div>\n<p>The two sections that matter most for operationalization are <strong>Considered Alternatives<\/strong> and <strong>Consequences<\/strong>. The first tells future readers what you rejected. The second tells you what kind of drift to watch for. If those sections are vague, your fitness function will be vague too.<\/p>\n<p>This is the part most teams skip. The ADR is not the enforcement mechanism; it is the source material for one.<\/p>\n<ul>\n<li><strong>Context<\/strong> tells you where the rule applies.<\/li>\n<li><strong>Decision<\/strong> tells you what must or must not happen.<\/li>\n<li><strong>Consequences<\/strong> tell you what kinds of drift, exceptions, and migration warnings the check should account for.<\/li>\n<\/ul>\n<p>If I cannot turn those sections into a concrete detection rule, the ADR is probably not specific enough yet.<\/p>\n<blockquote class=\"alert-blockquote\">\n<div class=\"alert-header\">\n                [ <i class=\"pixelart-icons-font-zap\"><\/i> <span>TIP<\/span> ]\n              <\/div><p>Store your ADRs in version control, not a wiki. Put them in a <code>\/docs\/adr<\/code> folder in your main repo or a dedicated architecture repo. This gives you history, blame, and\u2014critically\u2014the ability to reference them from CI pipelines.<\/p>\n<\/blockquote>\n<h2 id=\"from-documentation-to-enforcement-operationalizing-adrs\">From Documentation to Enforcement: Operationalizing ADRs<\/h2>\n<p>So you've got a folder full of ADRs. Now what?<\/p>\n<p>This is where fitness functions come in. A fitness function is any automated check that verifies an architectural constraint \u2014 the term comes from <em>Building Evolutionary Architecture<\/em>, but the concept is just &quot;automated architecture tests.&quot; Some examples:<\/p>\n<ul>\n<li><strong>Dependency fitness function:<\/strong> Does <code>go.mod<\/code> contain any database drivers other than our standard (PostgreSQL)?<\/li>\n<li><strong>Layer fitness function:<\/strong> Does the <code>domain<\/code> package import from <code>infrastructure<\/code>? (It shouldn't in hexagonal architecture.)<\/li>\n<li><strong>Security fitness function:<\/strong> Are all external API calls using our approved HTTP client with circuit breakers?<\/li>\n<\/ul>\n<p>The key insight is that <strong>every ADR that isn't explicitly deprecated or superseded should map to at least one fitness function.<\/strong> That is the operationalizing step. In practice, if you only check ADRs marked &quot;accepted,&quot; you'll miss most of them \u2014 plenty of ADRs sit at &quot;proposed&quot; or have no status at all. The filter should be inclusive: check everything that hasn't been explicitly retired.<\/p>\n<div class=\"mermaid-container\">\n<div class=\"mermaid-diagram\" tabindex=\"0\">\n<svg class=\"dg-adr1\" viewBox=\"-2 -2 924 332\" role=\"img\" aria-labelledby=\"dg-adr1-title dg-adr1-desc\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\">\n<title id=\"dg-adr1-title\">Turning an ADR into enforcement<\/title>\n<desc id=\"dg-adr1-desc\">After an ADR is written, ask if it can be automated. If yes, create a fitness function and add it to the CI pipeline, so violations are caught in PRs. If no, flag it for manual review triggers and add it to the architecture review checklist.<\/desc>\n<style>.dg-adr1{display:block;width:100%;min-width:560px;height:auto;font-family:'Space Grotesk',system-ui,sans-serif}.dg-adr1 text{text-anchor:middle;fill:var(--mermaid-text, #000000)}.dg-adr1 .b{fill:var(--mermaid-node-bg, #FFFFFF);stroke:var(--mermaid-node-border, #000000);stroke-width:1.5}.dg-adr1 .grp{fill:none;stroke:var(--mermaid-node-border, #000000);stroke-width:1.5;stroke-dasharray:3 5}.dg-adr1 .band{font-size:13.8px;font-weight:700;letter-spacing:.08em}.dg-adr1 .t{font-size:17.5px;font-weight:700}.dg-adr1 .gt{font-size:14.4px;font-weight:700;text-anchor:start;fill:var(--theme-text-muted, #6A6A6A);paint-order:stroke;stroke:var(--mermaid-node-bg, #FFFFFF);stroke-width:6px;stroke-linejoin:round}.dg-adr1 .lab{font-size:14.4px;fill:var(--theme-text-muted, #6A6A6A);paint-order:stroke;stroke:var(--mermaid-node-bg, #FFFFFF);stroke-width:6px;stroke-linejoin:round}.dg-adr1 .ln{fill:none;stroke:var(--mermaid-line, #FF5C00);stroke-width:2}.dg-adr1 .life{fill:none;stroke:var(--mermaid-node-border, #000000);stroke-width:1.5;stroke-dasharray:3 5}.dg-adr1 .dash{stroke-dasharray:7 5}.dg-adr1 marker path{fill:var(--mermaid-line, #FF5C00)}<\/style>\n<defs><marker id=\"dg-adr1-arrow\" viewBox=\"0 0 10 10\" refX=\"7\" refY=\"5\" markerWidth=\"7\" markerHeight=\"7\" orient=\"auto-start-reverse\"><path d=\"M0 0L10 5L0 10z\"><\/path><\/marker><\/defs>\n<path class=\"ln\" d=\"M200 67L238 67\" marker-end=\"url(#dg-adr1-arrow)\"><\/path>\n<path class=\"ln\" d=\"M440 67L478 67\" marker-end=\"url(#dg-adr1-arrow)\"><\/path>\n<path class=\"ln\" d=\"M680 67L718 67\" marker-end=\"url(#dg-adr1-arrow)\"><\/path>\n<path class=\"ln\" d=\"M820 134L820 192\" marker-end=\"url(#dg-adr1-arrow)\"><\/path>\n<path class=\"ln\" d=\"M340 134L340 192\" marker-end=\"url(#dg-adr1-arrow)\"><\/path>\n<path class=\"ln\" d=\"M440 261L478 261\" marker-end=\"url(#dg-adr1-arrow)\"><\/path>\n<rect class=\"b\" x=\"0\" y=\"0\" width=\"200\" height=\"134\"><\/rect>\n<rect x=\"0\" y=\"0\" width=\"200\" height=\"29\" style=\"fill:var(--diagram-origin, #A0694B)\"><\/rect>\n<text class=\"band\" x=\"100.0\" y=\"20\" style=\"fill:var(--diagram-origin-label, #000000)\">START<\/text>\n<text class=\"t\" x=\"100.0\" y=\"87.5\">ADR Written<\/text>\n<rect class=\"b\" x=\"240\" y=\"0\" width=\"200\" height=\"134\"><\/rect>\n<rect x=\"240\" y=\"0\" width=\"200\" height=\"29\" style=\"fill:var(--diagram-decide, #D24040)\"><\/rect>\n<text class=\"band\" x=\"340.0\" y=\"20\" style=\"fill:var(--diagram-decide-label, #FFFFFF)\">DECIDE<\/text>\n<text class=\"t\" x=\"340.0\" y=\"75.5\">Can it be<\/text>\n<text class=\"t\" x=\"340.0\" y=\"99.5\">automated?<\/text>\n<rect class=\"b\" x=\"480\" y=\"0\" width=\"200\" height=\"134\"><\/rect>\n<rect x=\"480\" y=\"0\" width=\"200\" height=\"29\" style=\"fill:var(--diagram-act, #10C840)\"><\/rect>\n<text class=\"band\" x=\"580.0\" y=\"20\" style=\"fill:var(--diagram-act-label, #000000)\">BUILD<\/text>\n<text class=\"t\" x=\"580.0\" y=\"75.5\">Create Fitness<\/text>\n<text class=\"t\" x=\"580.0\" y=\"99.5\">Function<\/text>\n<rect class=\"b\" x=\"720\" y=\"0\" width=\"200\" height=\"134\"><\/rect>\n<rect x=\"720\" y=\"0\" width=\"200\" height=\"29\" style=\"fill:var(--diagram-act, #10C840)\"><\/rect>\n<text class=\"band\" x=\"820.0\" y=\"20\" style=\"fill:var(--diagram-act-label, #000000)\">SHIP<\/text>\n<text class=\"t\" x=\"820.0\" y=\"87.5\">Add to CI Pipeline<\/text>\n<rect class=\"b\" x=\"240\" y=\"194\" width=\"200\" height=\"134\"><\/rect>\n<rect x=\"240\" y=\"194\" width=\"200\" height=\"29\" style=\"fill:var(--diagram-hold, #FAC800)\"><\/rect>\n<text class=\"band\" x=\"340.0\" y=\"214\" style=\"fill:var(--diagram-hold-label, #000000)\">PARK IT<\/text>\n<text class=\"t\" x=\"340.0\" y=\"269.5\">Flag for Manual<\/text>\n<text class=\"t\" x=\"340.0\" y=\"293.5\">Review Triggers<\/text>\n<rect class=\"b\" x=\"480\" y=\"194\" width=\"200\" height=\"134\"><\/rect>\n<rect x=\"480\" y=\"194\" width=\"200\" height=\"29\" style=\"fill:var(--diagram-define, #00A0C8)\"><\/rect>\n<text class=\"band\" x=\"580.0\" y=\"214\" style=\"fill:var(--diagram-define-label, #000000)\">CHECK<\/text>\n<text class=\"t\" x=\"580.0\" y=\"269.5\">Add to Architecture<\/text>\n<text class=\"t\" x=\"580.0\" y=\"293.5\">Review Checklist<\/text>\n<rect class=\"b\" x=\"720\" y=\"194\" width=\"200\" height=\"134\"><\/rect>\n<rect x=\"720\" y=\"194\" width=\"200\" height=\"29\" style=\"fill:var(--diagram-neutral, #F0F0DC)\"><\/rect>\n<text class=\"band\" x=\"820.0\" y=\"214\" style=\"fill:var(--diagram-neutral-label, #000000)\">CAUGHT<\/text>\n<text class=\"t\" x=\"820.0\" y=\"269.5\">Violations Caught<\/text>\n<text class=\"t\" x=\"820.0\" y=\"293.5\">in PRs<\/text>\n<text class=\"lab\" x=\"460\" y=\"58\" style=\"text-anchor:middle\">Yes<\/text>\n<text class=\"lab\" x=\"340\" y=\"168\" style=\"text-anchor:middle\">No<\/text>\n<\/svg>\n<\/div>\n<\/div>\n<p>Traditional fitness functions are written in code. In Go, you might use a tool like <code>go-arch-lint<\/code> or write custom tests that inspect the AST. In Java, ArchUnit is the gold standard. In TypeScript, you might use dependency-cruiser.<\/p>\n<p>But here's the problem: <strong>most organizations are not monolingual.<\/strong><\/p>\n<p>Let's say you've decided to standardize on OpenSearch for log aggregation. That decision affects:<\/p>\n<ul>\n<li>Your Go services (which might import a Datadog or Splunk client)<\/li>\n<li>Your Python scripts (which might use different logging libraries)<\/li>\n<li>Your Terraform modules (which might provision the wrong resources)<\/li>\n<li>Your Kubernetes manifests (which might include sidecars for the wrong vendor)<\/li>\n<\/ul>\n<p>Writing a fitness function in each language sounds manageable until you count the surfaces: Go dependencies, Python imports, Terraform resources, Helm values, raw YAML, and environment variables. Half the drift is in configuration, not application code.<\/p>\n<p>That is why I started treating fitness functions as prompts instead of parsers. I already had an AI reviewer reading PR diffs through <a href=\"https:\/\/platformtoolsmith.com\/blog\/building-archbot-ai-code-reviewer\/\">Archbot<\/a>. Adding architecture checks meant adding more instructions, not shipping another language-specific rule engine.<\/p>\n<p>The idea is simple. Instead of writing code that parses each language's dependency file, you describe what a violation looks like and let an LLM analyze the diff.<\/p>\n<h2 id=\"operationalizing-the-adr-two-approaches\">Operationalizing the ADR: Two Approaches<\/h2>\n<p>The flow is straightforward: start with an ADR, express the violation patterns so an LLM can check them, and run that check against the PR diff. If the model finds a violation, return a structured comment.<\/p>\n<p>There are two ways to do this, and I've tried both.<\/p>\n<h3 id=\"approach-1-separate-fitness-function-prompts\">Approach 1: Separate Fitness Function Prompts<\/h3>\n<p>The more structured option: for each ADR, you write a corresponding &quot;fitness function prompt&quot; \u2014 a standalone markdown file that describes what constitutes a violation and how to identify it. When a PR comes in, the AI analyzes the diff against all active prompts and flags any violations.<\/p>\n<p>This works well when you want fine-grained control over detection rules, or when the people writing fitness functions are different from the people writing ADRs.<\/p>\n<p>Let me show you a complete example.<\/p>\n<h4 id=\"the-adr\">The ADR<\/h4>\n<div class=\"md-block\"><pre class=\"language-markdown\"><code class=\"language-markdown\"><span class=\"token title important\"><span class=\"token punctuation\">#<\/span> ADR-042: Standardize on OpenSearch for Log Aggregation<\/span>\n\nDate: 2025-09-15\n\nStatus: accepted\n\n<span class=\"token title important\"><span class=\"token punctuation\">##<\/span> Context<\/span>\n\nWe currently have three different log aggregation solutions in production:\n<span class=\"token list punctuation\">-<\/span> Datadog (legacy, from acquisition)\n<span class=\"token list punctuation\">-<\/span> Splunk (used by compliance team)\n<span class=\"token list punctuation\">-<\/span> OpenSearch (adopted by new services)\n\nThis fragmentation causes operational overhead (three dashboards, three query\nlanguages, three billing relationships) and makes cross-service debugging\ndifficult. Engineers waste time context-switching between tools.\n\n<span class=\"token title important\"><span class=\"token punctuation\">##<\/span> Considered Alternatives<\/span>\n\n<span class=\"token list punctuation\">*<\/span> <span class=\"token bold\"><span class=\"token punctuation\">**<\/span><span class=\"token content\">Keep multi-vendor approach:<\/span><span class=\"token punctuation\">**<\/span><\/span> Pros: no migration effort, teams keep familiar\n  tools. Cons: ongoing operational overhead, cross-service debugging remains\n  painful, costs continue to grow.\n\n<span class=\"token list punctuation\">*<\/span> <span class=\"token bold\"><span class=\"token punctuation\">**<\/span><span class=\"token content\">Standardize on Datadog:<\/span><span class=\"token punctuation\">**<\/span><\/span> Pros: strong APM integration, existing team\n  familiarity. Cons: highest cost option, vendor lock-in concerns, no\n  self-hosted option.\n\n<span class=\"token list punctuation\">*<\/span> <span class=\"token bold\"><span class=\"token punctuation\">**<\/span><span class=\"token content\">Standardize on OpenSearch:<\/span><span class=\"token punctuation\">**<\/span><\/span> Pros: open-source, self-hosted option available,\n  good cost\/feature balance, already adopted by new services. Cons: migration\n  effort required, less mature APM story.\n\n<span class=\"token title important\"><span class=\"token punctuation\">##<\/span> Decision<\/span>\n\nWe will standardize on OpenSearch for log aggregation. Specifically:\n<span class=\"token list punctuation\">-<\/span> Application logs ship to OpenSearch via Fluent Bit sidecars\n<span class=\"token list punctuation\">-<\/span> No direct integration with Datadog, Splunk, or other log vendors in\n  application code\n<span class=\"token list punctuation\">-<\/span> Existing Datadog\/Splunk integrations should be migrated by Q2 2026\n\n<span class=\"token title important\"><span class=\"token punctuation\">##<\/span> Consequences<\/span>\n\n<span class=\"token list punctuation\">-<\/span> Single pane of glass for log queries (positive)\n<span class=\"token list punctuation\">-<\/span> Reduced vendor costs, ~40% savings projected (positive)\n<span class=\"token list punctuation\">-<\/span> Consistent logging patterns across all services (positive)\n<span class=\"token list punctuation\">-<\/span> Migration effort required for legacy services (negative)\n<span class=\"token list punctuation\">-<\/span> Team retraining on OpenSearch query syntax (negative)\n<span class=\"token list punctuation\">-<\/span> Some Datadog-specific features like APM correlation need alternatives (negative)\n<span class=\"token list punctuation\">-<\/span> Compliance team will need updated runbooks for OpenSearch (neutral)<\/code><\/pre>\n<\/div>\n<h4 id=\"the-fitness-function-prompt\">The Fitness Function Prompt<\/h4>\n<p>Now here's the corresponding fitness function, written as a prompt:<\/p>\n<div class=\"md-block\"><pre class=\"language-markdown\"><code class=\"language-markdown\"><span class=\"token title important\"><span class=\"token punctuation\">##<\/span> Fitness Function: ADR-042 OpenSearch Standardization<\/span>\n\n<span class=\"token bold\"><span class=\"token punctuation\">**<\/span><span class=\"token content\">ADR Reference:<\/span><span class=\"token punctuation\">**<\/span><\/span> ADR-042\n<span class=\"token bold\"><span class=\"token punctuation\">**<\/span><span class=\"token content\">Enforcement:<\/span><span class=\"token punctuation\">**<\/span><\/span> Block new integrations; warn on modifications to legacy\n\n<span class=\"token title important\"><span class=\"token punctuation\">###<\/span> What to Check<\/span>\n<span class=\"token list punctuation\">-<\/span> Direct Datadog\/Splunk SDK imports or dependencies (Go, Python, Node, Terraform)\n<span class=\"token list punctuation\">-<\/span> Vendor-specific logging configuration (DD_<span class=\"token italic\"><span class=\"token punctuation\">*<\/span><span class=\"token content\">, SPLUNK_<\/span><span class=\"token punctuation\">*<\/span><\/span> env vars)\n<span class=\"token list punctuation\">-<\/span> Sidecar configurations for non-approved vendors\n\n<span class=\"token title important\"><span class=\"token punctuation\">###<\/span> How to Respond<\/span>\n<span class=\"token list punctuation\">-<\/span> NEW integrations: Flag as BLOCKING with ADR reference and migration guide link\n<span class=\"token list punctuation\">-<\/span> MODIFICATIONS to existing: Flag as WARNING with migration deadline\n\n<span class=\"token title important\"><span class=\"token punctuation\">###<\/span> What to Ignore<\/span>\n<span class=\"token list punctuation\">-<\/span> Documentation, test fixtures, removal of vendor code<\/code><\/pre>\n<\/div>\n<p>You get the idea. Each ADR gets a prompt that describes what to look for, how to respond, and what to ignore. You can tune the prompt for each ADR independently.<\/p>\n<p>But after running this in production, I found a simpler way.<\/p>\n<h3 id=\"approach-2-let-the-agent-read-adrs-directly\">Approach 2: Let the Agent Read ADRs Directly<\/h3>\n<p>Here's what I discovered after building the system: <strong>you don't need separate fitness function files at all.<\/strong> The ADR itself \u2014 specifically the Decision and Consequences sections \u2014 already contains everything the agent needs to check compliance.<\/p>\n<p>Think about what a well-written ADR tells you:<\/p>\n<ul>\n<li><strong>Decision<\/strong> says &quot;We will use X&quot; and &quot;No direct integration with Y&quot; \u2014 that's your violation definition right there.<\/li>\n<li><strong>Consequences<\/strong> describes what drift looks like \u2014 &quot;migration effort required for legacy services&quot; tells the agent what existing code might look like versus what new code should look like.<\/li>\n<\/ul>\n<p>When you write a separate fitness function file, you're translating the ADR into a second document. That creates a sync problem: the ADR gets updated, but the fitness function prompt doesn't. Or the ADR gets nuanced (&quot;except for the compliance team's audit logs&quot;), but the prompt still flags those cases.<\/p>\n<p>The simpler approach: give the agent tools to read ADRs directly, and let it figure out what's relevant.<\/p>\n<p>The agent gets two tools instead of a library of prompt files:<\/p>\n<ol>\n<li><strong><code>list_adrs<\/code><\/strong> \u2014 returns a summary table for triage:<\/li>\n<\/ol>\n<div class=\"my-8 border-2 border-black dark:border-border-color bg-background-light dark:bg-background-dark font-mono text-lg relative shadow-[4px_4px_0_rgba(0,0,0,1)] dark:shadow-none\"><div class=\"overflow-x-auto p-4 pt-6\" tabindex=\"0\"><table>\n<thead>\n<tr>\n<th>ID<\/th>\n<th>Title<\/th>\n<th>Status<\/th>\n<th>Summary<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>ADR-042<\/td>\n<td>OpenSearch for Log Aggregation<\/td>\n<td>accepted<\/td>\n<td>Mandates OpenSearch, forbids Datadog\/Splunk integrations...<\/td>\n<\/tr>\n<tr>\n<td>ADR-043<\/td>\n<td>API Versioning Standard<\/td>\n<td>proposed<\/td>\n<td>Requires URL path versioning for all public APIs...<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div><\/div><ol>\n<li><strong><code>read_adr<\/code><\/strong> \u2014 fetches the full content of a specific ADR by file path.<\/li>\n<\/ol>\n<p>The system prompt instructs the agent to call <code>list_adrs<\/code>, identify which ADRs are relevant to the PR's changes based on summaries, then call <code>read_adr<\/code> for each relevant one. It checks the diff against each ADR's Decision and Consequences sections and reports violations with specific quotes.<\/p>\n<p>The payoff is even bigger than the separate-files approach: adding a new architectural check means writing an ADR. That's it. The people writing ADRs don't need to write a second file, learn a prompt format, or coordinate with the platform team. The agent picks it up automatically.<\/p>\n<h4 id=\"the-kind-of-diff-it-catches\">The Kind of Diff It Catches<\/h4>\n<p>Here is the sort of change this catches without writing a Node-specific rule:<\/p>\n<pre class=\"language-diff\"><code class=\"language-diff\">diff --git a\/package.json b\/package.json\n<span class=\"token coord\">@@ -18,6 +18,7 @@<\/span>\n<span class=\"token unchanged\"><span class=\"token prefix unchanged\"> <\/span>  \"dependencies\": {\n<\/span><span class=\"token inserted-sign inserted\"><span class=\"token prefix inserted\">+<\/span>    \"dd-trace\": \"^5.4.0\",\n<\/span><span class=\"token unchanged\"><span class=\"token prefix unchanged\"> <\/span>    \"pino\": \"^9.7.0\"\n<span class=\"token prefix unchanged\"> <\/span>  }\n<span class=\"token prefix unchanged\"> <\/span>}\n<\/span><\/code><\/pre>\n<p>That is enough context for the model to flag a new vendor-specific logging dependency and tie it back to ADR-042.<\/p>\n<blockquote>\n<p><strong>BLOCKING:<\/strong> This PR introduces a new Datadog integration. Per ADR-042, we've standardized on OpenSearch for log aggregation. Please use Fluent Bit sidecars instead.<\/p>\n<\/blockquote>\n<h4 id=\"how-it-works-in-practice\">How it works in practice<\/h4>\n<p>One production choice matters here: decide whether model failures should fail open or fail closed. For something like ADR-042, fail open makes sense \u2014 a missed Datadog import is annoying but not dangerous. For a security ADR banning unencrypted secrets in config, you probably want fail-closed. Match the failure mode to the cost of drift for the decision you're enforcing.<\/p>\n<div class=\"mermaid-container\">\n<div class=\"mermaid-diagram\" tabindex=\"0\">\n<svg class=\"dg-adr2\" viewBox=\"-2 -2 934 1014\" role=\"img\" aria-labelledby=\"dg-adr2-title dg-adr2-desc\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\">\n<title id=\"dg-adr2-title\">How the architecture bot uses ADR summaries<\/title>\n<desc id=\"dg-adr2-desc\">ADRs feed an AI-generated summary table of about 5KB with titles and enforcement rules. In CI a pull request reaches the architecture bot, which triages against the summaries to find relevant ADRs. It reads only those in full and checks the diff against their decision and consequences. A violation gives a PR comment with the ADR reference. No violation passes the check.<\/desc>\n<style>.dg-adr2{display:block;width:100%;min-width:560px;height:auto;font-family:'Space Grotesk',system-ui,sans-serif}.dg-adr2 text{text-anchor:middle;fill:var(--mermaid-text, #000000)}.dg-adr2 .b{fill:var(--mermaid-node-bg, #FFFFFF);stroke:var(--mermaid-node-border, #000000);stroke-width:1.5}.dg-adr2 .grp{fill:none;stroke:var(--mermaid-node-border, #000000);stroke-width:1.5;stroke-dasharray:3 5}.dg-adr2 .band{font-size:13.8px;font-weight:700;letter-spacing:.08em}.dg-adr2 .t{font-size:17.5px;font-weight:700}.dg-adr2 .gt{font-size:14.4px;font-weight:700;text-anchor:start;fill:var(--theme-text-muted, #6A6A6A);paint-order:stroke;stroke:var(--mermaid-node-bg, #FFFFFF);stroke-width:6px;stroke-linejoin:round}.dg-adr2 .lab{font-size:14.4px;fill:var(--theme-text-muted, #6A6A6A);paint-order:stroke;stroke:var(--mermaid-node-bg, #FFFFFF);stroke-width:6px;stroke-linejoin:round}.dg-adr2 .ln{fill:none;stroke:var(--mermaid-line, #FF5C00);stroke-width:2}.dg-adr2 .life{fill:none;stroke:var(--mermaid-node-border, #000000);stroke-width:1.5;stroke-dasharray:3 5}.dg-adr2 .dash{stroke-dasharray:7 5}.dg-adr2 marker path{fill:var(--mermaid-line, #FF5C00)}<\/style>\n<defs><marker id=\"dg-adr2-arrow\" viewBox=\"0 0 10 10\" refX=\"7\" refY=\"5\" markerWidth=\"7\" markerHeight=\"7\" orient=\"auto-start-reverse\"><path d=\"M0 0L10 5L0 10z\"><\/path><\/marker><\/defs>\n<rect class=\"grp\" x=\"0\" y=\"0\" width=\"690\" height=\"162\"><\/rect>\n<text class=\"gt\" x=\"12\" y=\"22\">ADR Repository<\/text>\n<rect class=\"grp\" x=\"230\" y=\"180\" width=\"240\" height=\"182\"><\/rect>\n<text class=\"gt\" x=\"242\" y=\"202\">AI-Generated Summaries<\/text>\n<rect class=\"grp\" x=\"0\" y=\"380\" width=\"930\" height=\"630\"><\/rect>\n<text class=\"gt\" x=\"12\" y=\"402\">CI Pipeline<\/text>\n<path class=\"ln\" d=\"M350 146L350 212\" marker-end=\"url(#dg-adr2-arrow)\"><\/path>\n<path class=\"ln\" d=\"M120 146L120 281L248 281\" marker-end=\"url(#dg-adr2-arrow)\"><\/path>\n<path class=\"ln\" d=\"M520 146L520 254L452 254\" marker-end=\"url(#dg-adr2-arrow)\"><\/path>\n<path class=\"ln\" d=\"M220 474L248 474\" marker-end=\"url(#dg-adr2-arrow)\"><\/path>\n<path class=\"ln\" d=\"M350 420L350 350\" marker-end=\"url(#dg-adr2-arrow)\"><\/path>\n<path class=\"ln\" d=\"M450 314L580 314L580 418\" marker-end=\"url(#dg-adr2-arrow)\"><\/path>\n<path class=\"ln\" d=\"M580 528L580 568\" marker-end=\"url(#dg-adr2-arrow)\"><\/path>\n<path class=\"ln\" d=\"M680 474L810 474L810 568\" marker-end=\"url(#dg-adr2-arrow)\"><\/path>\n<path class=\"ln\" d=\"M580 678L580 718\" marker-end=\"url(#dg-adr2-arrow)\"><\/path>\n<path class=\"ln\" d=\"M810 678L810 779L682 779\" marker-end=\"url(#dg-adr2-arrow)\"><\/path>\n<path class=\"ln\" d=\"M480 779L452 779\" marker-end=\"url(#dg-adr2-arrow)\"><\/path>\n<path class=\"ln\" d=\"M350 838L350 868\" marker-end=\"url(#dg-adr2-arrow)\"><\/path>\n<path class=\"ln\" d=\"M250 779L222 779\" marker-end=\"url(#dg-adr2-arrow)\"><\/path>\n<rect class=\"b\" x=\"20\" y=\"38\" width=\"200\" height=\"108\"><\/rect>\n<rect x=\"20\" y=\"38\" width=\"200\" height=\"29\" style=\"fill:var(--diagram-origin, #A0694B)\"><\/rect>\n<text class=\"band\" x=\"120.0\" y=\"58\" style=\"fill:var(--diagram-origin-label, #000000)\">SOURCE<\/text>\n<text class=\"t\" x=\"120.0\" y=\"100.5\">ADR-042:<\/text>\n<text class=\"t\" x=\"120.0\" y=\"124.5\">OpenSearch<\/text>\n<rect class=\"b\" x=\"250\" y=\"38\" width=\"200\" height=\"108\"><\/rect>\n<rect x=\"250\" y=\"38\" width=\"200\" height=\"29\" style=\"fill:var(--diagram-origin, #A0694B)\"><\/rect>\n<text class=\"band\" x=\"350.0\" y=\"58\" style=\"fill:var(--diagram-origin-label, #000000)\">SOURCE<\/text>\n<text class=\"t\" x=\"350.0\" y=\"100.5\">ADR-043:<\/text>\n<text class=\"t\" x=\"350.0\" y=\"124.5\">API Versioning<\/text>\n<rect class=\"b\" x=\"480\" y=\"38\" width=\"200\" height=\"108\"><\/rect>\n<rect x=\"480\" y=\"38\" width=\"200\" height=\"29\" style=\"fill:var(--diagram-origin, #A0694B)\"><\/rect>\n<text class=\"band\" x=\"580.0\" y=\"58\" style=\"fill:var(--diagram-origin-label, #000000)\">SOURCE<\/text>\n<text class=\"t\" x=\"580.0\" y=\"100.5\">ADR-044:<\/text>\n<text class=\"t\" x=\"580.0\" y=\"124.5\">No Direct DB<\/text>\n<rect class=\"b\" x=\"250\" y=\"214\" width=\"200\" height=\"134\"><\/rect>\n<rect x=\"250\" y=\"214\" width=\"200\" height=\"29\" style=\"fill:var(--diagram-define, #00A0C8)\"><\/rect>\n<text class=\"band\" x=\"350.0\" y=\"234\" style=\"fill:var(--diagram-define-label, #000000)\">SUMMARY<\/text>\n<text class=\"t\" x=\"350.0\" y=\"277.5\">Summary Table<\/text>\n<text class=\"t\" x=\"350.0\" y=\"301.5\">~5KB of titles +<\/text>\n<text class=\"t\" x=\"350.0\" y=\"325.5\">enforcement rules<\/text>\n<rect class=\"b\" x=\"20\" y=\"420\" width=\"200\" height=\"108\"><\/rect>\n<rect x=\"20\" y=\"420\" width=\"200\" height=\"29\" style=\"fill:var(--diagram-origin, #A0694B)\"><\/rect>\n<text class=\"band\" x=\"120.0\" y=\"440\" style=\"fill:var(--diagram-origin-label, #000000)\">START<\/text>\n<text class=\"t\" x=\"120.0\" y=\"494.5\">Pull Request<\/text>\n<rect class=\"b\" x=\"250\" y=\"420\" width=\"200\" height=\"108\"><\/rect>\n<rect x=\"250\" y=\"420\" width=\"200\" height=\"29\" style=\"fill:var(--diagram-act, #10C840)\"><\/rect>\n<text class=\"band\" x=\"350.0\" y=\"440\" style=\"fill:var(--diagram-act-label, #000000)\">EXECUTE<\/text>\n<text class=\"t\" x=\"350.0\" y=\"494.5\">Architecture Bot<\/text>\n<rect class=\"b\" x=\"480\" y=\"420\" width=\"200\" height=\"108\"><\/rect>\n<rect x=\"480\" y=\"420\" width=\"200\" height=\"29\" style=\"fill:var(--diagram-decide, #D24040)\"><\/rect>\n<text class=\"band\" x=\"580.0\" y=\"440\" style=\"fill:var(--diagram-decide-label, #FFFFFF)\">DECIDE<\/text>\n<text class=\"t\" x=\"580.0\" y=\"482.5\">Which ADRs<\/text>\n<text class=\"t\" x=\"580.0\" y=\"506.5\">are relevant?<\/text>\n<rect class=\"b\" x=\"480\" y=\"570\" width=\"200\" height=\"108\"><\/rect>\n<rect x=\"480\" y=\"570\" width=\"200\" height=\"29\" style=\"fill:var(--diagram-act, #10C840)\"><\/rect>\n<text class=\"band\" x=\"580.0\" y=\"590\" style=\"fill:var(--diagram-act-label, #000000)\">READ<\/text>\n<text class=\"t\" x=\"580.0\" y=\"644.5\">Read Full ADR-042<\/text>\n<rect class=\"b\" x=\"710\" y=\"570\" width=\"200\" height=\"108\"><\/rect>\n<rect x=\"710\" y=\"570\" width=\"200\" height=\"29\" style=\"fill:var(--diagram-act, #10C840)\"><\/rect>\n<text class=\"band\" x=\"810.0\" y=\"590\" style=\"fill:var(--diagram-act-label, #000000)\">READ<\/text>\n<text class=\"t\" x=\"810.0\" y=\"644.5\">Read Full ADR-044<\/text>\n<rect class=\"b\" x=\"480\" y=\"720\" width=\"200\" height=\"118\"><\/rect>\n<rect x=\"480\" y=\"720\" width=\"200\" height=\"29\" style=\"fill:var(--diagram-define, #00A0C8)\"><\/rect>\n<text class=\"band\" x=\"580.0\" y=\"740\" style=\"fill:var(--diagram-define-label, #000000)\">CHECK<\/text>\n<text class=\"t\" x=\"580.0\" y=\"775.5\">Check Diff Against<\/text>\n<text class=\"t\" x=\"580.0\" y=\"799.5\">Decision +<\/text>\n<text class=\"t\" x=\"580.0\" y=\"823.5\">Consequences<\/text>\n<rect class=\"b\" x=\"250\" y=\"720\" width=\"200\" height=\"118\"><\/rect>\n<rect x=\"250\" y=\"720\" width=\"200\" height=\"29\" style=\"fill:var(--diagram-decide, #D24040)\"><\/rect>\n<text class=\"band\" x=\"350.0\" y=\"740\" style=\"fill:var(--diagram-decide-label, #FFFFFF)\">DECIDE<\/text>\n<text class=\"t\" x=\"350.0\" y=\"799.5\">Violations?<\/text>\n<rect class=\"b\" x=\"20\" y=\"720\" width=\"200\" height=\"118\"><\/rect>\n<rect x=\"20\" y=\"720\" width=\"200\" height=\"29\" style=\"fill:var(--diagram-neutral, #F0F0DC)\"><\/rect>\n<text class=\"band\" x=\"120.0\" y=\"740\" style=\"fill:var(--diagram-neutral-label, #000000)\">PASS<\/text>\n<text class=\"t\" x=\"120.0\" y=\"799.5\">Check Passes<\/text>\n<rect class=\"b\" x=\"250\" y=\"870\" width=\"200\" height=\"108\"><\/rect>\n<rect x=\"250\" y=\"870\" width=\"200\" height=\"29\" style=\"fill:var(--diagram-neutral, #F0F0DC)\"><\/rect>\n<text class=\"band\" x=\"350.0\" y=\"890\" style=\"fill:var(--diagram-neutral-label, #000000)\">COMMENT<\/text>\n<text class=\"t\" x=\"350.0\" y=\"932.5\">PR Comment with<\/text>\n<text class=\"t\" x=\"350.0\" y=\"956.5\">ADR Reference<\/text>\n<text class=\"lab\" x=\"350\" y=\"404\" style=\"text-anchor:middle\">Step 1: Triage<\/text>\n<text class=\"lab\" x=\"580\" y=\"548\" style=\"text-anchor:middle\">ADR-042<\/text>\n<text class=\"lab\" x=\"810\" y=\"465.0\" style=\"text-anchor:middle\">ADR-044<\/text>\n<text class=\"lab\" x=\"350\" y=\"858\" style=\"text-anchor:middle\">Yes<\/text>\n<text class=\"lab\" x=\"235\" y=\"770.0\" style=\"text-anchor:middle\">No<\/text>\n<\/svg>\n<\/div>\n<\/div>\n<h2 id=\"production-lessons-what-i-learned-after-shipping-this\">Production Lessons: What I Learned After Shipping This<\/h2>\n<p>The conceptual model above is clean. Production was messier. Here are the three lessons that would have saved me the most time if someone had told me upfront.<\/p>\n<h3 id=\"why-llms-excuse-architecture-violations\">Why LLMs Excuse Architecture Violations<\/h3>\n<p><img src=\"https:\/\/platformtoolsmith.com\/assets\/blog\/gopher-probably-fine.ff7ce86a.900.webp\" alt=\"A mole in a lab coat gives a calm thumbs-up beside a server rack with a red warning light.\" width=\"1920\" height=\"1080\" loading=\"lazy\" decoding=\"async\" srcset=\"https:\/\/platformtoolsmith.com\/assets\/blog\/gopher-probably-fine.ff7ce86a.400.webp 400w, https:\/\/platformtoolsmith.com\/assets\/blog\/gopher-probably-fine.ff7ce86a.900.webp 900w, https:\/\/platformtoolsmith.com\/assets\/blog\/gopher-probably-fine.ff7ce86a.1800.webp 1800w\" sizes=\"(min-width: 1024px) 773px, 100vw\" \/><\/p>\n<p>This one surprised me. The agent saw a clear ADR mandating a specific SDK for Go services. The codebase used a different SDK. The agent's reasoning? &quot;This codebase predates the ADR and might be an exception.&quot;<\/p>\n<p>It dismissed the violation without flagging it.<\/p>\n<p>LLMs are trained to be helpful, and &quot;helpful&quot; often means finding reasons why something is probably fine. That is exactly the wrong behavior for compliance checking.<\/p>\n<p>The fix: explicit anti-rationalization rules in the system prompt.<\/p>\n<pre><code>Report what you see. Do NOT rationalize away violations.\n\nIf an ADR mandates technology X and the code uses technology Y, that is a\nviolation \u2014 even if you believe the codebase predates the ADR, might have an\nexception, or has a reasonable justification.\n\nYour job is to flag deviations from the ADR text, not to judge whether\nexceptions apply. Humans will decide if an exception is warranted.\n<\/code><\/pre>\n<p>This is a universal lesson for anyone using LLMs for compliance, policy enforcement, or audit. If the model can find a reason to say &quot;looks fine,&quot; it will. You have to explicitly tell it not to.<\/p>\n<h3 id=\"summary-first-triage-is-required-at-scale\">Summary-First Triage Is Required at Scale<\/h3>\n<p>I mentioned this above, but it deserves emphasis because the failure mode is invisible. With a handful of ADRs, you can dump everything into context and it works great. At 20+ ADRs, the API starts timing out or the model loses focus in the middle of a massive context window.<\/p>\n<p>The two-step workflow \u2014 summaries for triage, full reads for relevant ADRs \u2014 is not an optimization. It's a requirement. Plan for it from the start if you have more than 10 ADRs.<\/p>\n<h3 id=\"prompt-iteration-is-the-real-work\">Prompt Iteration Is the Real Work<\/h3>\n<p>Here's a timeline from my production rollout:<\/p>\n<ul>\n<li><strong>Iteration 1:<\/strong> Instructions sounded optional (&quot;you may want to check ADRs&quot;). The agent skipped them entirely.<\/li>\n<li><strong>Iteration 2:<\/strong> Made instructions mandatory. Agent read all ADR content at once. API timeout.<\/li>\n<li><strong>Iteration 3:<\/strong> Added summary-based triage. Agent read summaries but never called the tool to read full content.<\/li>\n<li><strong>Iteration 4:<\/strong> Fixed tool instructions. Agent read full content but rationalized away violations.<\/li>\n<li><strong>Iteration 5:<\/strong> Added anti-rationalization rules. Agent started flagging violations but was too aggressive \u2014 flagging test fixtures and migration documentation.<\/li>\n<li><strong>Iteration 6:<\/strong> Added &quot;What to Ignore&quot; guidance. Getting closer.<\/li>\n<li><strong>Iteration 7+:<\/strong> Tuned severity levels and response formatting.<\/li>\n<\/ul>\n<p>Seven iterations. Each one taught me something about how LLMs interpret instructions.<\/p>\n<p>The biggest takeaway: <strong>if an instruction sounds optional, the agent will skip it.<\/strong> Don't say &quot;you may want to check.&quot; Say &quot;You MUST call <code>read_adr<\/code> for EACH potentially relevant ADR.&quot; Mandatory language is not rude \u2014 it's how you get consistent behavior from an LLM.<\/p>\n<p>Plan for at least five iterations on your prompt. Test against real PRs, not synthetic examples. The edge cases in real diffs will teach you things you can't anticipate.<\/p>\n<h2 id=\"tradeoffs-ai-vs-static-analysis\">Tradeoffs: AI vs Static Analysis<\/h2>\n<p>Let me be direct about the trade-offs.<\/p>\n<p><strong>Traditional code-based fitness functions<\/strong> give you:<\/p>\n<ul>\n<li>Deterministic results (same input = same output)<\/li>\n<li>Fast execution (no API calls)<\/li>\n<li>No token costs<\/li>\n<\/ul>\n<p><strong>AI-based fitness functions<\/strong> give you:<\/p>\n<ul>\n<li>Cross-language support with one implementation<\/li>\n<li>Natural language rules anyone can write<\/li>\n<li>Fuzzy matching (catches variations you didn't anticipate)<\/li>\n<li>Contextual understanding (knows the difference between adding and removing a dependency)<\/li>\n<\/ul>\n<p>For most organizations, especially those with polyglot stacks, the AI approach wins on maintainability. The cost? You're trading determinism for flexibility. Sometimes the LLM will miss something. Sometimes it'll flag a false positive.<\/p>\n<p>But here's the thing: manual code review has the same problems. Reviewers miss things. Reviewers have false positives (&quot;I don't like this pattern&quot; when it's actually fine). At least with AI fitness functions, the rules are written down and consistently applied.<\/p>\n<p>That said, AI fitness functions are not a replacement for linters, type systems, or targeted static analysis. I still reach for traditional code-based checks when:<\/p>\n<ul>\n<li>The rule must be deterministic and always blocking<\/li>\n<li>The violation is semantic rather than structural<\/li>\n<li>A false positive would be expensive or noisy<\/li>\n<li>The check needs to run with near-zero latency and no external dependency<\/li>\n<\/ul>\n<p>The sweet spot for AI is cross-language policy enforcement and structural drift. The sweet spot for static analysis is precise, language-local invariants. In practice, the best system uses both.<\/p>\n<h2 id=\"how-to-roll-this-out-without-overengineering-it\">How to Roll This Out Without Overengineering It<\/h2>\n<p>If you want to try this approach:<\/p>\n<ol>\n<li><strong>Audit your ADRs.<\/strong> Which ones aren't deprecated or superseded? List them.<\/li>\n<\/ol>\n<blockquote class=\"alert-blockquote\">\n<div class=\"alert-header\">\n                [ <i class=\"pixelart-icons-font-zap\"><\/i> <span>TIP<\/span> ]\n              <\/div><p>Don't filter by &quot;accepted&quot; only \u2014 in most repos, half the ADRs that matter are still marked &quot;proposed&quot; or have no status at all. Be inclusive.<\/p>\n<\/blockquote>\n<ol>\n<li>\n<p><strong>Classify by verifiability.<\/strong> For each ADR, ask: &quot;Can I describe what a violation looks like?&quot; If yes, it's a candidate for automation.<\/p>\n<\/li>\n<li>\n<p><strong>Start with one high-value check.<\/strong> Pick an ADR that gets violated frequently. If you're using the agent-reads-ADRs approach, make sure the Decision section is specific enough. If you're writing separate prompts, write the first one. Hook it into your review pipeline.<\/p>\n<\/li>\n<li>\n<p><strong>Plan for iteration.<\/strong> Your first prompt will be wrong. The agent will skip checks, timeout on too much context, or rationalize away violations. Budget for at least five iterations against real PRs. This is the actual work.<\/p>\n<\/li>\n<li>\n<p><strong>Add anti-rationalization rules early.<\/strong> Don't wait until you see the agent excusing violations. Add explicit &quot;report, don't judge&quot; instructions from the start.<\/p>\n<\/li>\n<li>\n<p><strong>Build the library.<\/strong> Once you have a system checking 5-10 ADRs, the flywheel kicks in. New ADRs automatically get checked. Engineers start writing better ADRs because they know the Decision section will actually be enforced.<\/p>\n<\/li>\n<\/ol>\n<p>Start with the ADR that already creates expensive cleanup when it gets ignored. Do not start with your most abstract principle. Start with the one that repeatedly costs your team time.<\/p>\n<p>The Datadog PR from the opening was not a one-off. It was what happens when important decisions live in documentation and delivery lives somewhere else.<\/p>\n<p>Once we put the ADR in the review path, the feedback loop collapsed from months to minutes. In the first month, it caught several violations we would have otherwise discovered later. By the end of the first quarter, it had caught dozens more across repositories and file types that no single linter could have covered.<\/p>\n<p>Operationalizing architecture is not magic. It is just architecture that finally made it into code.<\/p>\n<h2 id=\"whats-next\">What's Next<\/h2>\n<p>This is part 1 of a three-part series on operationalizing architecture:<\/p>\n<ul>\n<li><strong>Part 1:<\/strong> Operationalizing ADRs with Automated Fitness Functions (you are here)<\/li>\n<li><strong>Part 2:<\/strong> Spotting the Big Changes: Automating Impactful PR Detection for Architects <em>(coming soon)<\/em> \u2014 Not all PRs are equal. Learn how to automatically flag the ones that need architect review.<\/li>\n<li><strong>Part 3:<\/strong> Navigating Tech Choices: How to Turn Your Tech Radar into an Active Architecture Guide <em>(coming soon)<\/em> \u2014 Your Tech Radar shouldn't be a PDF. Make it a dynamic fitness function.<\/li>\n<\/ul>\n<p>I'm spending more time building architecture systems that turn standards into working guardrails. If that's the kind of platform problem you're working on too, you can grab time here.<\/p>\n<a href=\"https:\/\/cal.com\/alexandrecastrotech\/mentoring\" class=\"text-primary-text-light dark:text-primary-text hover:opacity-80 transition-opacity flex items-center gap-2 font-bold uppercase tracking-wide min-h-11\">\n    <i class=\"pixelart-icons-font-calendar\"><\/i> Book a mentoring session\n<\/a>\n"},{"title":"Agentic AI Code Review: From Confidently Wrong to Evidence-Based","link":{"@attributes":{"href":"https:\/\/platformtoolsmith.com\/blog\/agentic-ai-code-review\/"}},"updated":"2026-03-09T00:00:00Z","id":"https:\/\/platformtoolsmith.com\/blog\/agentic-ai-code-review\/","content":"<p>Archbot flagged a &quot;blocker&quot; on a PR. It cited the diff, built a plausible chain of reasoning, and suggested a fix.<\/p>\n<p>It was completely wrong. Not &quot;LLMs are sometimes wrong&quot; wrong \u2014 more like <em>convincing enough that a senior engineer spent 20 minutes disproving it<\/em>.<\/p>\n<p>The missing detail wasn't subtle. It was a guard clause sitting in a helper two files away.<br \/>\nArchbot just didn't have that file.<\/p>\n<p>That failure mode wasn't a prompt problem.<br \/>\nIt was a <strong>context problem<\/strong>.<\/p>\n<p>So I stopped trying to predict what context the model would need up-front, and switched to an agentic loop: give the model tools to fetch evidence as it goes, and require it to end with a structured &quot;submit review&quot; action.<\/p>\n<p>This post is the architectural why and how (and the reliability plumbing that made it work). There are good hosted AI review tools now. This post is about the pattern underneath them.<\/p>\n<blockquote class=\"alert-blockquote\">\n<div class=\"alert-header\">\n                [ <i class=\"pixelart-icons-font-info-box\"><\/i> <span>NOTE<\/span> ]\n              <\/div><p>Names, repos, and examples are intentionally generalized. This is about the design patterns, not a particular company.<\/p>\n<\/blockquote>\n<p>If you want the backstory on what Archbot is and why I built it, start with the original post: <a href=\"https:\/\/platformtoolsmith.com\/blog\/building-archbot-ai-code-reviewer\/\">Building Archbot: AI Code Review for GitHub Enterprise<\/a>.<\/p>\n<h2 id=\"the-fixed-pipeline-and-where-it-breaks\">The fixed pipeline (and where it breaks)<\/h2>\n<p>My original design was what a lot of &quot;LLM workflow&quot; systems converge to:<\/p>\n<ol>\n<li>Give the model the PR diff.<\/li>\n<li>Give it some representation of the repo.<\/li>\n<li>Ask it to pick the files that matter.<\/li>\n<li>Feed only those files back in for the real review.<\/li>\n<li>Optionally run a second pass to critique the first pass.<\/li>\n<\/ol>\n<p>That critique pass is helpful for catching obvious nonsense and reducing overconfident tone.<br \/>\nBut it can't solve the core issue: if both passes are reasoning over the same clipped context, they're both still guessing.<\/p>\n<p>It looks like this:<\/p>\n<div class=\"mermaid-container\">\n<div class=\"mermaid-diagram\" tabindex=\"0\">\n<svg class=\"dg-arb1\" viewBox=\"-2 -2 784 294\" role=\"img\" aria-labelledby=\"dg-arb1-title dg-arb1-desc\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\">\n<title id=\"dg-arb1-title\">How the reviewer moves from a pull request to a submitted review<\/title>\n<desc id=\"dg-arb1-desc\">A pull request opens. Phase 1 selects files and the agent builds focused context. Phase 2a reviews the code, phase 2b critiques that review, and then the review is submitted.<\/desc>\n<style>.dg-arb1{display:block;width:100%;min-width:560px;height:auto;font-family:'Space Grotesk',system-ui,sans-serif}.dg-arb1 text{text-anchor:middle;fill:var(--mermaid-text, #000000)}.dg-arb1 .b{fill:var(--mermaid-node-bg, #FFFFFF);stroke:var(--mermaid-node-border, #000000);stroke-width:1.5}.dg-arb1 .grp{fill:none;stroke:var(--mermaid-node-border, #000000);stroke-width:1.5;stroke-dasharray:3 5}.dg-arb1 .band{font-size:13.8px;font-weight:700;letter-spacing:.08em}.dg-arb1 .t{font-size:17.5px;font-weight:700}.dg-arb1 .gt{font-size:14.4px;font-weight:700;text-anchor:start;fill:var(--theme-text-muted, #6A6A6A);paint-order:stroke;stroke:var(--mermaid-node-bg, #FFFFFF);stroke-width:6px;stroke-linejoin:round}.dg-arb1 .lab{font-size:14.4px;fill:var(--theme-text-muted, #6A6A6A);paint-order:stroke;stroke:var(--mermaid-node-bg, #FFFFFF);stroke-width:6px;stroke-linejoin:round}.dg-arb1 .ln{fill:none;stroke:var(--mermaid-line, #FF5C00);stroke-width:2}.dg-arb1 .life{fill:none;stroke:var(--mermaid-node-border, #000000);stroke-width:1.5;stroke-dasharray:3 5}.dg-arb1 .dash{stroke-dasharray:7 5}.dg-arb1 marker path{fill:var(--mermaid-line, #FF5C00)}<\/style>\n<defs><marker id=\"dg-arb1-arrow\" viewBox=\"0 0 10 10\" refX=\"7\" refY=\"5\" markerWidth=\"7\" markerHeight=\"7\" orient=\"auto-start-reverse\"><path d=\"M0 0L10 5L0 10z\"><\/path><\/marker><\/defs>\n<path class=\"ln\" d=\"M240 60L268 60\" marker-end=\"url(#dg-arb1-arrow)\"><\/path>\n<path class=\"ln\" d=\"M510 60L538 60\" marker-end=\"url(#dg-arb1-arrow)\"><\/path>\n<path class=\"ln\" d=\"M660 120L660 168\" marker-end=\"url(#dg-arb1-arrow)\"><\/path>\n<path class=\"ln\" d=\"M540 230L512 230\" marker-end=\"url(#dg-arb1-arrow)\"><\/path>\n<path class=\"ln\" d=\"M270 230L242 230\" marker-end=\"url(#dg-arb1-arrow)\"><\/path>\n<rect class=\"b\" x=\"0\" y=\"0\" width=\"240\" height=\"120\"><\/rect>\n<rect x=\"0\" y=\"0\" width=\"240\" height=\"29\" style=\"fill:var(--diagram-origin, #A0694B)\"><\/rect>\n<text class=\"band\" x=\"120.0\" y=\"20\" style=\"fill:var(--diagram-origin-label, #000000)\">START<\/text>\n<text class=\"t\" x=\"120.0\" y=\"68.5\">PR opened<\/text>\n<text class=\"t\" x=\"120.0\" y=\"92.5\">or updated<\/text>\n<rect class=\"b\" x=\"270\" y=\"0\" width=\"240\" height=\"120\"><\/rect>\n<rect x=\"270\" y=\"0\" width=\"240\" height=\"29\" style=\"fill:var(--diagram-define, #00A0C8)\"><\/rect>\n<text class=\"band\" x=\"390.0\" y=\"20\" style=\"fill:var(--diagram-define-label, #000000)\">SELECT<\/text>\n<text class=\"t\" x=\"390.0\" y=\"68.5\">Phase 1:<\/text>\n<text class=\"t\" x=\"390.0\" y=\"92.5\">select_files<\/text>\n<rect class=\"b\" x=\"540\" y=\"0\" width=\"240\" height=\"120\"><\/rect>\n<rect x=\"540\" y=\"0\" width=\"240\" height=\"29\" style=\"fill:var(--diagram-act, #10C840)\"><\/rect>\n<text class=\"band\" x=\"660.0\" y=\"20\" style=\"fill:var(--diagram-act-label, #000000)\">PACK<\/text>\n<text class=\"t\" x=\"660.0\" y=\"68.5\">Build focused context<\/text>\n<text class=\"t\" x=\"660.0\" y=\"92.5\">(diff + selected files)<\/text>\n<rect class=\"b\" x=\"540\" y=\"170\" width=\"240\" height=\"120\"><\/rect>\n<rect x=\"540\" y=\"170\" width=\"240\" height=\"29\" style=\"fill:var(--diagram-act, #10C840)\"><\/rect>\n<text class=\"band\" x=\"660.0\" y=\"190\" style=\"fill:var(--diagram-act-label, #000000)\">REVIEW<\/text>\n<text class=\"t\" x=\"660.0\" y=\"238.5\">Phase 2a:<\/text>\n<text class=\"t\" x=\"660.0\" y=\"262.5\">perform_code_review<\/text>\n<rect class=\"b\" x=\"270\" y=\"170\" width=\"240\" height=\"120\"><\/rect>\n<rect x=\"270\" y=\"170\" width=\"240\" height=\"29\" style=\"fill:var(--diagram-define, #00A0C8)\"><\/rect>\n<text class=\"band\" x=\"390.0\" y=\"190\" style=\"fill:var(--diagram-define-label, #000000)\">CRITIQUE<\/text>\n<text class=\"t\" x=\"390.0\" y=\"238.5\">Phase 2b:<\/text>\n<text class=\"t\" x=\"390.0\" y=\"262.5\">critique_review<\/text>\n<rect class=\"b\" x=\"0\" y=\"170\" width=\"240\" height=\"120\"><\/rect>\n<rect x=\"0\" y=\"170\" width=\"240\" height=\"29\" style=\"fill:var(--diagram-decide, #D24040)\"><\/rect>\n<text class=\"band\" x=\"120.0\" y=\"190\" style=\"fill:var(--diagram-decide-label, #FFFFFF)\">SUBMIT<\/text>\n<text class=\"t\" x=\"120.0\" y=\"250.5\">Submit review<\/text>\n<\/svg>\n<\/div>\n<\/div>\n<p>On paper, it's elegant:<\/p>\n<ul>\n<li>The model is great at prioritizing.<\/li>\n<li>Keeping context small should reduce hallucinations.<\/li>\n<li>A critique pass should catch the worst bad takes.<\/li>\n<\/ul>\n<p>In practice, it fails in a very specific way.<\/p>\n<h2 id=\"the-core-failure-you-cant-pre-select-the-missing-piece\">The core failure: you can't pre-select the missing piece<\/h2>\n<p>Code review isn't &quot;read these files&quot;.<br \/>\nIt's &quot;follow the chain of evidence until you understand the behavior&quot;.<\/p>\n<p>And chains are not predictable.<\/p>\n<p>You start in <code>handler.go<\/code>, notice it calls <code>validate()<\/code>, jump to <code>validate.go<\/code>, see it wraps a client, jump to <code>client.go<\/code>, and only then realize the bug is a timeout default in <code>config\/defaults.go<\/code>.<\/p>\n<p>The fixed pipeline made that exploration impossible.<\/p>\n<p>Once Phase 1 picked the &quot;important&quot; files, Phase 2 was stuck. If the model realized it needed one more file to confirm a claim, it had no way to fetch it.<\/p>\n<p>That led to two bad outcomes:<\/p>\n<ul>\n<li><strong>False positives with confidence.<\/strong> The model would infer behavior from a partial call chain and present it as fact.<\/li>\n<li><strong>Missed risk.<\/strong> The model would never see the one file that made a change dangerous, because that file didn't look important from the diff.<\/li>\n<\/ul>\n<p>I could tune prompts.<br \/>\nI could add more heuristics to file selection.<br \/>\nI could increase the file budget.<\/p>\n<p>But that's all the same bet: that I can guess the right context ahead of time.<\/p>\n<p>That bet is wrong more often than it feels.<\/p>\n<h2 id=\"the-shift-stop-guessing-context-let-the-model-fetch-it\">The shift: stop guessing context, let the model fetch it<\/h2>\n<p>The agentic version flips the contract:<\/p>\n<ul>\n<li>The system does <em>not<\/em> attempt to build a perfect context.<\/li>\n<li>It gives the model a toolset for finding evidence.<\/li>\n<li>It loops until the model either submits a review (terminal tool) or hits a budget\/timeout.<\/li>\n<\/ul>\n<p>Instead of a fixed pipeline, it's an exploration loop:<\/p>\n<div class=\"mermaid-container\">\n<div class=\"mermaid-diagram\" tabindex=\"0\">\n<svg class=\"dg-arb2\" viewBox=\"-2 -2 864 700\" role=\"img\" aria-labelledby=\"dg-arb2-title dg-arb2-desc\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\">\n<title id=\"dg-arb2-title\">The tool loop between the model and its tools<\/title>\n<desc id=\"dg-arb2-desc\">The loop sends the system prompt to the model. The model calls tools for PR data, the diff, code search, file content and the base version, one at a time. It then submits a structured review to the loop, and the loop ends.<\/desc>\n<style>.dg-arb2{display:block;width:100%;min-width:560px;height:auto;font-family:'Space Grotesk',system-ui,sans-serif}.dg-arb2 text{text-anchor:middle;fill:var(--mermaid-text, #000000)}.dg-arb2 .b{fill:var(--mermaid-node-bg, #FFFFFF);stroke:var(--mermaid-node-border, #000000);stroke-width:1.5}.dg-arb2 .grp{fill:none;stroke:var(--mermaid-node-border, #000000);stroke-width:1.5;stroke-dasharray:3 5}.dg-arb2 .band{font-size:13.8px;font-weight:700;letter-spacing:.08em}.dg-arb2 .t{font-size:17.5px;font-weight:700}.dg-arb2 .gt{font-size:14.4px;font-weight:700;text-anchor:start;fill:var(--theme-text-muted, #6A6A6A);paint-order:stroke;stroke:var(--mermaid-node-bg, #FFFFFF);stroke-width:6px;stroke-linejoin:round}.dg-arb2 .lab{font-size:14.4px;fill:var(--theme-text-muted, #6A6A6A);paint-order:stroke;stroke:var(--mermaid-node-bg, #FFFFFF);stroke-width:6px;stroke-linejoin:round}.dg-arb2 .ln{fill:none;stroke:var(--mermaid-line, #FF5C00);stroke-width:2}.dg-arb2 .life{fill:none;stroke:var(--mermaid-node-border, #000000);stroke-width:1.5;stroke-dasharray:3 5}.dg-arb2 .dash{stroke-dasharray:7 5}.dg-arb2 marker path{fill:var(--mermaid-line, #FF5C00)}<\/style>\n<defs><marker id=\"dg-arb2-arrow\" viewBox=\"0 0 10 10\" refX=\"7\" refY=\"5\" markerWidth=\"7\" markerHeight=\"7\" orient=\"auto-start-reverse\"><path d=\"M0 0L10 5L0 10z\"><\/path><\/marker><\/defs>\n<path class=\"life\" d=\"M770 84L770 696\"><\/path>\n<path class=\"life\" d=\"M430 84L430 696\"><\/path>\n<path class=\"life\" d=\"M90 84L90 696\"><\/path>\n<path class=\"ln\" d=\"M90 118L428 118\" marker-end=\"url(#dg-arb2-arrow)\"><\/path>\n<path class=\"ln\" d=\"M430 164L768 164\" marker-end=\"url(#dg-arb2-arrow)\"><\/path>\n<path class=\"ln dash\" d=\"M770 210L432 210\" marker-end=\"url(#dg-arb2-arrow)\"><\/path>\n<path class=\"ln\" d=\"M430 256L768 256\" marker-end=\"url(#dg-arb2-arrow)\"><\/path>\n<path class=\"ln dash\" d=\"M770 302L432 302\" marker-end=\"url(#dg-arb2-arrow)\"><\/path>\n<path class=\"ln\" d=\"M430 348L768 348\" marker-end=\"url(#dg-arb2-arrow)\"><\/path>\n<path class=\"ln dash\" d=\"M770 394L432 394\" marker-end=\"url(#dg-arb2-arrow)\"><\/path>\n<path class=\"ln\" d=\"M430 440L768 440\" marker-end=\"url(#dg-arb2-arrow)\"><\/path>\n<path class=\"ln dash\" d=\"M770 486L432 486\" marker-end=\"url(#dg-arb2-arrow)\"><\/path>\n<path class=\"ln\" d=\"M430 532L768 532\" marker-end=\"url(#dg-arb2-arrow)\"><\/path>\n<path class=\"ln dash\" d=\"M770 578L432 578\" marker-end=\"url(#dg-arb2-arrow)\"><\/path>\n<path class=\"ln\" d=\"M430 624L92 624\" marker-end=\"url(#dg-arb2-arrow)\"><\/path>\n<path class=\"ln dash\" d=\"M90 670L428 670\" marker-end=\"url(#dg-arb2-arrow)\"><\/path>\n<rect class=\"b\" x=\"0.0\" y=\"0\" width=\"180\" height=\"84\"><\/rect>\n<rect x=\"0.0\" y=\"0\" width=\"180\" height=\"29\" style=\"fill:var(--diagram-origin, #A0694B)\"><\/rect>\n<text class=\"band\" x=\"90.0\" y=\"20\" style=\"fill:var(--diagram-origin-label, #000000)\">START<\/text>\n<text class=\"t\" x=\"90.0\" y=\"62.5\">Loop<\/text>\n<rect class=\"b\" x=\"340.0\" y=\"0\" width=\"180\" height=\"84\"><\/rect>\n<rect x=\"340.0\" y=\"0\" width=\"180\" height=\"29\" style=\"fill:var(--diagram-act, #10C840)\"><\/rect>\n<text class=\"band\" x=\"430.0\" y=\"20\" style=\"fill:var(--diagram-act-label, #000000)\">ASK<\/text>\n<text class=\"t\" x=\"430.0\" y=\"62.5\">Model<\/text>\n<rect class=\"b\" x=\"680.0\" y=\"0\" width=\"180\" height=\"84\"><\/rect>\n<rect x=\"680.0\" y=\"0\" width=\"180\" height=\"29\" style=\"fill:var(--diagram-neutral, #F0F0DC)\"><\/rect>\n<text class=\"band\" x=\"770.0\" y=\"20\" style=\"fill:var(--diagram-neutral-label, #000000)\">TOOL<\/text>\n<text class=\"t\" x=\"770.0\" y=\"62.5\">Tools<\/text>\n<text class=\"lab\" x=\"260.0\" y=\"109\" style=\"text-anchor:middle\">System prompt + initial task<\/text>\n<text class=\"lab\" x=\"600.0\" y=\"155\" style=\"text-anchor:middle\">get_pr_info<\/text>\n<text class=\"lab\" x=\"600.0\" y=\"201\" style=\"text-anchor:middle\">metadata<\/text>\n<text class=\"lab\" x=\"600.0\" y=\"247\" style=\"text-anchor:middle\">get_pr_diff<\/text>\n<text class=\"lab\" x=\"600.0\" y=\"293\" style=\"text-anchor:middle\">diff<\/text>\n<text class=\"lab\" x=\"600.0\" y=\"339\" style=\"text-anchor:middle\">search_code (&quot;newFunction&quot; usage)<\/text>\n<text class=\"lab\" x=\"600.0\" y=\"385\" style=\"text-anchor:middle\">matches + file:line<\/text>\n<text class=\"lab\" x=\"600.0\" y=\"431\" style=\"text-anchor:middle\">get_file_content (the call site)<\/text>\n<text class=\"lab\" x=\"600.0\" y=\"477\" style=\"text-anchor:middle\">file text<\/text>\n<text class=\"lab\" x=\"600.0\" y=\"523\" style=\"text-anchor:middle\">get_file_at_base (before version)<\/text>\n<text class=\"lab\" x=\"600.0\" y=\"569\" style=\"text-anchor:middle\">baseline text<\/text>\n<text class=\"lab\" x=\"260.0\" y=\"615\" style=\"text-anchor:middle\">submit_code_review (structured)<\/text>\n<text class=\"lab\" x=\"260.0\" y=\"661\" style=\"text-anchor:middle\">loop ends<\/text>\n<\/svg>\n<\/div>\n<\/div>\n<p>You can summarize the new rule as:<\/p>\n<p>&quot;If you can't cite it, go fetch it.&quot;<\/p>\n<p>Under the hood, this ended up being three pieces:<\/p>\n<ol>\n<li>A loop that alternates between &quot;model turn&quot; and &quot;tool turn&quot;, with budgets and context hygiene.<\/li>\n<li>A tool interface that can mark certain tools as terminal.<\/li>\n<li>Different toolsets per mode (full review vs chat vs command), reusing the same loop.<\/li>\n<\/ol>\n<p>The biggest reliability improvement wasn't &quot;more tools&quot;.<br \/>\nIt was making the model end by calling a <strong>terminal tool<\/strong>.<\/p>\n<p>In a fixed prompt pipeline, the model ends by printing Markdown.<br \/>\nThat makes it tempting to optimize for <em>sounding right<\/em>.<\/p>\n<p>In an agentic loop, the model ends by <em>submitting an object<\/em>.<br \/>\nThat changes the incentives:<\/p>\n<ul>\n<li>It's harder to hand-wave; you have to populate fields.<\/li>\n<li>You can enforce structure (severity, inline comments, evidence links).<\/li>\n<li>The loop can treat &quot;no submission&quot; as failure.<\/li>\n<\/ul>\n<p>So what does the terminal tool actually look like? And how do you wire all this together without it becoming a mess? Let's build it.<\/p>\n<h2 id=\"what-changed-in-review-quality\">What changed in review quality<\/h2>\n<p>The qualitative shift after going agentic wasn't &quot;more comments&quot;.<br \/>\nIt was <strong>more <em>explainable<\/em> comments<\/strong>.<\/p>\n<p>The best code review feedback has three properties:<\/p>\n<ul>\n<li>It points at a specific behavior.<\/li>\n<li>It cites the relevant code.<\/li>\n<li>It proposes a fix (or at least a direction) with trade-offs.<\/li>\n<\/ul>\n<p>Tools make that possible.<\/p>\n<p>Instead of:<\/p>\n<p>&quot;This might break retries.&quot;<\/p>\n<p>You get:<\/p>\n<p>&quot;In <code>foo\/bar.go:123<\/code>, the new call bypasses <code>withRetry(...)<\/code>. All other call sites use <code>withRetry(...)<\/code> (see matches in <code>search_code<\/code>). If that's intentional, we should document why; otherwise, wrap it.&quot;<\/p>\n<p>Here's what that looks like in practice:<\/p>\n<p><img src=\"https:\/\/platformtoolsmith.com\/assets\/blog\/archbot-blocker-example.f7cd7962.832.webp\" alt=\"Archbot code review showing a \ud83d\udd34 Blocker finding about SQL driver registration with specific file reference, detailed explanation of the panic risk, and a suggested fix\" width=\"832\" height=\"1204\" loading=\"lazy\" decoding=\"async\" srcset=\"https:\/\/platformtoolsmith.com\/assets\/blog\/archbot-blocker-example.f7cd7962.400.webp 400w, https:\/\/platformtoolsmith.com\/assets\/blog\/archbot-blocker-example.f7cd7962.832.webp 832w\" sizes=\"(min-width: 1024px) 773px, 100vw\" \/><\/p>\n<p>When the model has the ability (and expectation) to fetch evidence, it stops guessing.<\/p>\n<p>The way I measure that shift: does it cite exact locations, or hedge? Does it go fetch more evidence when uncertain, or just keep talking? How often do I have to spend time <em>disproving<\/em> a comment?<\/p>\n<p>In other words: does it behave like a cautious reviewer, or a persuasive one?<\/p>\n<h2 id=\"build-it-yourself-the-pieces-you-need\">Build it yourself: the pieces you need<\/h2>\n<p>Here's how the pieces fit together \u2014 the tool interface, a few representative tools, the terminal tool, and how they wire into the loop.<\/p>\n<p>The examples below are simplified Go to show the minimum shape. They're not production code, but they're structurally complete \u2014 you could extend these into a working system.<\/p>\n<p>I didn't rebuild Archbot from scratch to go agentic. I took the existing review pipeline and wrapped it in a small loop: model turn \u2192 tool turn \u2192 repeat, plus a single terminal action (<code>submit_code_review<\/code>) to end the run. A hand-rolled loop is the fastest way to prove the product behavior (does it fetch evidence? does it stop guessing?) before you commit to a framework.<\/p>\n<blockquote class=\"alert-blockquote\">\n<div class=\"alert-header\">\n                [ <i class=\"pixelart-icons-font-zap\"><\/i> <span>TIP<\/span> ]\n              <\/div><p>Starting greenfield? <a href=\"https:\/\/google.github.io\/adk-docs\/\">Google's ADK<\/a> is a solid default \u2014 it provides a similar tool interface with built-in orchestration, tracing, and callback hooks. The patterns below (tool contracts, terminal actions, structured output, context hygiene) apply either way.<\/p>\n<\/blockquote>\n<h3 id=\"step-1-define-the-tool-contract\">Step 1: Define the tool contract<\/h3>\n<p>Every tool implements the same contract. The key method is <code>IsTerminal()<\/code> \u2014 it's what lets the loop know when to stop.<\/p>\n<pre class=\"language-go\"><code class=\"language-go\"><span class=\"token keyword\">type<\/span> Tool <span class=\"token keyword\">interface<\/span> <span class=\"token punctuation\">{<\/span>\n    <span class=\"token function\">Name<\/span><span class=\"token punctuation\">(<\/span><span class=\"token punctuation\">)<\/span> <span class=\"token builtin\">string<\/span>\n    <span class=\"token function\">Description<\/span><span class=\"token punctuation\">(<\/span><span class=\"token punctuation\">)<\/span> <span class=\"token builtin\">string<\/span>\n    <span class=\"token function\">InputSchema<\/span><span class=\"token punctuation\">(<\/span><span class=\"token punctuation\">)<\/span> <span class=\"token keyword\">map<\/span><span class=\"token punctuation\">[<\/span><span class=\"token builtin\">string<\/span><span class=\"token punctuation\">]<\/span><span class=\"token keyword\">interface<\/span><span class=\"token punctuation\">{<\/span><span class=\"token punctuation\">}<\/span>\n    <span class=\"token function\">Execute<\/span><span class=\"token punctuation\">(<\/span>ctx context<span class=\"token punctuation\">.<\/span>Context<span class=\"token punctuation\">,<\/span> input <span class=\"token keyword\">map<\/span><span class=\"token punctuation\">[<\/span><span class=\"token builtin\">string<\/span><span class=\"token punctuation\">]<\/span><span class=\"token keyword\">interface<\/span><span class=\"token punctuation\">{<\/span><span class=\"token punctuation\">}<\/span><span class=\"token punctuation\">)<\/span> <span class=\"token punctuation\">(<\/span><span class=\"token builtin\">string<\/span><span class=\"token punctuation\">,<\/span> <span class=\"token builtin\">error<\/span><span class=\"token punctuation\">)<\/span>\n    <span class=\"token function\">IsTerminal<\/span><span class=\"token punctuation\">(<\/span><span class=\"token punctuation\">)<\/span> <span class=\"token builtin\">bool<\/span>\n<span class=\"token punctuation\">}<\/span><\/code><\/pre>\n<h3 id=\"step-2-build-your-context-fetching-tools\">Step 2: Build your context-fetching tools<\/h3>\n<p>Most tools follow the same pattern: parse input, call an API, return a string. Keep them boring and deterministic.<\/p>\n<p>One thing I didn't appreciate early: these tools are part of your product surface area. Treat them like APIs \u2014 stable contracts, deterministic outputs, and tests that lock in behavior. If <code>search_code<\/code> returns garbage, the model will reason over garbage. The shortest path from &quot;I suspect X&quot; to &quot;here's the evidence&quot; should be one tool call.<\/p>\n<p>Here's <code>get_file_content<\/code>:<\/p>\n<pre class=\"language-go\"><code class=\"language-go\"><span class=\"token keyword\">type<\/span> GetFileContent <span class=\"token keyword\">struct<\/span> <span class=\"token punctuation\">{<\/span>\n    ghClient <span class=\"token operator\">*<\/span>github<span class=\"token punctuation\">.<\/span>Client\n    owner    <span class=\"token builtin\">string<\/span>\n    repo     <span class=\"token builtin\">string<\/span>\n    headSHA  <span class=\"token builtin\">string<\/span>\n<span class=\"token punctuation\">}<\/span>\n\n<span class=\"token keyword\">func<\/span> <span class=\"token punctuation\">(<\/span>t <span class=\"token operator\">*<\/span>GetFileContent<span class=\"token punctuation\">)<\/span> <span class=\"token function\">Name<\/span><span class=\"token punctuation\">(<\/span><span class=\"token punctuation\">)<\/span> <span class=\"token builtin\">string<\/span>        <span class=\"token punctuation\">{<\/span> <span class=\"token keyword\">return<\/span> <span class=\"token string\">\"get_file_content\"<\/span> <span class=\"token punctuation\">}<\/span>\n<span class=\"token keyword\">func<\/span> <span class=\"token punctuation\">(<\/span>t <span class=\"token operator\">*<\/span>GetFileContent<span class=\"token punctuation\">)<\/span> <span class=\"token function\">IsTerminal<\/span><span class=\"token punctuation\">(<\/span><span class=\"token punctuation\">)<\/span> <span class=\"token builtin\">bool<\/span>     <span class=\"token punctuation\">{<\/span> <span class=\"token keyword\">return<\/span> <span class=\"token boolean\">false<\/span> <span class=\"token punctuation\">}<\/span>\n<span class=\"token keyword\">func<\/span> <span class=\"token punctuation\">(<\/span>t <span class=\"token operator\">*<\/span>GetFileContent<span class=\"token punctuation\">)<\/span> <span class=\"token function\">Description<\/span><span class=\"token punctuation\">(<\/span><span class=\"token punctuation\">)<\/span> <span class=\"token builtin\">string<\/span>  <span class=\"token punctuation\">{<\/span>\n    <span class=\"token keyword\">return<\/span> <span class=\"token string\">\"Fetch the full content of a file at the PR's head commit.\"<\/span>\n<span class=\"token punctuation\">}<\/span>\n\n<span class=\"token keyword\">func<\/span> <span class=\"token punctuation\">(<\/span>t <span class=\"token operator\">*<\/span>GetFileContent<span class=\"token punctuation\">)<\/span> <span class=\"token function\">InputSchema<\/span><span class=\"token punctuation\">(<\/span><span class=\"token punctuation\">)<\/span> <span class=\"token keyword\">map<\/span><span class=\"token punctuation\">[<\/span><span class=\"token builtin\">string<\/span><span class=\"token punctuation\">]<\/span><span class=\"token keyword\">interface<\/span><span class=\"token punctuation\">{<\/span><span class=\"token punctuation\">}<\/span> <span class=\"token punctuation\">{<\/span>\n    <span class=\"token keyword\">return<\/span> <span class=\"token keyword\">map<\/span><span class=\"token punctuation\">[<\/span><span class=\"token builtin\">string<\/span><span class=\"token punctuation\">]<\/span><span class=\"token keyword\">interface<\/span><span class=\"token punctuation\">{<\/span><span class=\"token punctuation\">}<\/span><span class=\"token punctuation\">{<\/span>\n        <span class=\"token string\">\"type\"<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token string\">\"object\"<\/span><span class=\"token punctuation\">,<\/span>\n        <span class=\"token string\">\"properties\"<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token keyword\">map<\/span><span class=\"token punctuation\">[<\/span><span class=\"token builtin\">string<\/span><span class=\"token punctuation\">]<\/span><span class=\"token keyword\">interface<\/span><span class=\"token punctuation\">{<\/span><span class=\"token punctuation\">}<\/span><span class=\"token punctuation\">{<\/span>\n            <span class=\"token string\">\"path\"<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token keyword\">map<\/span><span class=\"token punctuation\">[<\/span><span class=\"token builtin\">string<\/span><span class=\"token punctuation\">]<\/span><span class=\"token keyword\">interface<\/span><span class=\"token punctuation\">{<\/span><span class=\"token punctuation\">}<\/span><span class=\"token punctuation\">{<\/span>\n                <span class=\"token string\">\"type\"<\/span><span class=\"token punctuation\">:<\/span>        <span class=\"token string\">\"string\"<\/span><span class=\"token punctuation\">,<\/span>\n                <span class=\"token string\">\"description\"<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token string\">\"File path relative to the repo root\"<\/span><span class=\"token punctuation\">,<\/span>\n            <span class=\"token punctuation\">}<\/span><span class=\"token punctuation\">,<\/span>\n        <span class=\"token punctuation\">}<\/span><span class=\"token punctuation\">,<\/span>\n        <span class=\"token string\">\"required\"<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token punctuation\">[<\/span><span class=\"token punctuation\">]<\/span><span class=\"token builtin\">string<\/span><span class=\"token punctuation\">{<\/span><span class=\"token string\">\"path\"<\/span><span class=\"token punctuation\">}<\/span><span class=\"token punctuation\">,<\/span>\n    <span class=\"token punctuation\">}<\/span>\n<span class=\"token punctuation\">}<\/span>\n\n<span class=\"token keyword\">func<\/span> <span class=\"token punctuation\">(<\/span>t <span class=\"token operator\">*<\/span>GetFileContent<span class=\"token punctuation\">)<\/span> <span class=\"token function\">Execute<\/span><span class=\"token punctuation\">(<\/span>\n    ctx context<span class=\"token punctuation\">.<\/span>Context<span class=\"token punctuation\">,<\/span> input <span class=\"token keyword\">map<\/span><span class=\"token punctuation\">[<\/span><span class=\"token builtin\">string<\/span><span class=\"token punctuation\">]<\/span><span class=\"token keyword\">interface<\/span><span class=\"token punctuation\">{<\/span><span class=\"token punctuation\">}<\/span><span class=\"token punctuation\">,<\/span>\n<span class=\"token punctuation\">)<\/span> <span class=\"token punctuation\">(<\/span><span class=\"token builtin\">string<\/span><span class=\"token punctuation\">,<\/span> <span class=\"token builtin\">error<\/span><span class=\"token punctuation\">)<\/span> <span class=\"token punctuation\">{<\/span>\n    path<span class=\"token punctuation\">,<\/span> <span class=\"token boolean\">_<\/span> <span class=\"token operator\">:=<\/span> input<span class=\"token punctuation\">[<\/span><span class=\"token string\">\"path\"<\/span><span class=\"token punctuation\">]<\/span><span class=\"token punctuation\">.<\/span><span class=\"token punctuation\">(<\/span><span class=\"token builtin\">string<\/span><span class=\"token punctuation\">)<\/span>\n    <span class=\"token keyword\">if<\/span> path <span class=\"token operator\">==<\/span> <span class=\"token string\">\"\"<\/span> <span class=\"token punctuation\">{<\/span>\n        <span class=\"token keyword\">return<\/span> <span class=\"token string\">\"\"<\/span><span class=\"token punctuation\">,<\/span> fmt<span class=\"token punctuation\">.<\/span><span class=\"token function\">Errorf<\/span><span class=\"token punctuation\">(<\/span><span class=\"token string\">\"path is required\"<\/span><span class=\"token punctuation\">)<\/span>\n    <span class=\"token punctuation\">}<\/span>\n\n    content<span class=\"token punctuation\">,<\/span> err <span class=\"token operator\">:=<\/span> t<span class=\"token punctuation\">.<\/span>ghClient<span class=\"token punctuation\">.<\/span><span class=\"token function\">GetFileContent<\/span><span class=\"token punctuation\">(<\/span>ctx<span class=\"token punctuation\">,<\/span> t<span class=\"token punctuation\">.<\/span>owner<span class=\"token punctuation\">,<\/span> t<span class=\"token punctuation\">.<\/span>repo<span class=\"token punctuation\">,<\/span> path<span class=\"token punctuation\">,<\/span> t<span class=\"token punctuation\">.<\/span>headSHA<span class=\"token punctuation\">)<\/span>\n    <span class=\"token keyword\">if<\/span> err <span class=\"token operator\">!=<\/span> <span class=\"token boolean\">nil<\/span> <span class=\"token punctuation\">{<\/span>\n        <span class=\"token keyword\">return<\/span> <span class=\"token string\">\"\"<\/span><span class=\"token punctuation\">,<\/span> fmt<span class=\"token punctuation\">.<\/span><span class=\"token function\">Errorf<\/span><span class=\"token punctuation\">(<\/span><span class=\"token string\">\"fetching %s: %w\"<\/span><span class=\"token punctuation\">,<\/span> path<span class=\"token punctuation\">,<\/span> err<span class=\"token punctuation\">)<\/span>\n    <span class=\"token punctuation\">}<\/span>\n\n    <span class=\"token keyword\">return<\/span> <span class=\"token function\">addLineNumbers<\/span><span class=\"token punctuation\">(<\/span>content<span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">,<\/span> <span class=\"token boolean\">nil<\/span> <span class=\"token comment\">\/\/ prefix each line with its number for precise citations<\/span>\n<span class=\"token punctuation\">}<\/span><\/code><\/pre>\n<p>And here's <code>search_code<\/code> \u2014 the tool the model reaches for when it sees a function call in a diff and wants to know &quot;where else is this used?&quot;<\/p>\n<pre class=\"language-go\"><code class=\"language-go\"><span class=\"token keyword\">type<\/span> SearchCode <span class=\"token keyword\">struct<\/span> <span class=\"token punctuation\">{<\/span>\n    repoFiles <span class=\"token keyword\">map<\/span><span class=\"token punctuation\">[<\/span><span class=\"token builtin\">string<\/span><span class=\"token punctuation\">]<\/span><span class=\"token builtin\">string<\/span> <span class=\"token comment\">\/\/ path -> content (from repomix or local clone)<\/span>\n    diffText  <span class=\"token builtin\">string<\/span>\n<span class=\"token punctuation\">}<\/span>\n\n<span class=\"token keyword\">func<\/span> <span class=\"token punctuation\">(<\/span>t <span class=\"token operator\">*<\/span>SearchCode<span class=\"token punctuation\">)<\/span> <span class=\"token function\">Name<\/span><span class=\"token punctuation\">(<\/span><span class=\"token punctuation\">)<\/span> <span class=\"token builtin\">string<\/span>    <span class=\"token punctuation\">{<\/span> <span class=\"token keyword\">return<\/span> <span class=\"token string\">\"search_code\"<\/span> <span class=\"token punctuation\">}<\/span>\n<span class=\"token keyword\">func<\/span> <span class=\"token punctuation\">(<\/span>t <span class=\"token operator\">*<\/span>SearchCode<span class=\"token punctuation\">)<\/span> <span class=\"token function\">IsTerminal<\/span><span class=\"token punctuation\">(<\/span><span class=\"token punctuation\">)<\/span> <span class=\"token builtin\">bool<\/span> <span class=\"token punctuation\">{<\/span> <span class=\"token keyword\">return<\/span> <span class=\"token boolean\">false<\/span> <span class=\"token punctuation\">}<\/span>\n\n<span class=\"token keyword\">func<\/span> <span class=\"token punctuation\">(<\/span>t <span class=\"token operator\">*<\/span>SearchCode<span class=\"token punctuation\">)<\/span> <span class=\"token function\">Execute<\/span><span class=\"token punctuation\">(<\/span>\n    ctx context<span class=\"token punctuation\">.<\/span>Context<span class=\"token punctuation\">,<\/span> input <span class=\"token keyword\">map<\/span><span class=\"token punctuation\">[<\/span><span class=\"token builtin\">string<\/span><span class=\"token punctuation\">]<\/span><span class=\"token keyword\">interface<\/span><span class=\"token punctuation\">{<\/span><span class=\"token punctuation\">}<\/span><span class=\"token punctuation\">,<\/span>\n<span class=\"token punctuation\">)<\/span> <span class=\"token punctuation\">(<\/span><span class=\"token builtin\">string<\/span><span class=\"token punctuation\">,<\/span> <span class=\"token builtin\">error<\/span><span class=\"token punctuation\">)<\/span> <span class=\"token punctuation\">{<\/span>\n    query<span class=\"token punctuation\">,<\/span> <span class=\"token boolean\">_<\/span> <span class=\"token operator\">:=<\/span> input<span class=\"token punctuation\">[<\/span><span class=\"token string\">\"query\"<\/span><span class=\"token punctuation\">]<\/span><span class=\"token punctuation\">.<\/span><span class=\"token punctuation\">(<\/span><span class=\"token builtin\">string<\/span><span class=\"token punctuation\">)<\/span>\n    <span class=\"token keyword\">if<\/span> query <span class=\"token operator\">==<\/span> <span class=\"token string\">\"\"<\/span> <span class=\"token punctuation\">{<\/span>\n        <span class=\"token keyword\">return<\/span> <span class=\"token string\">\"\"<\/span><span class=\"token punctuation\">,<\/span> fmt<span class=\"token punctuation\">.<\/span><span class=\"token function\">Errorf<\/span><span class=\"token punctuation\">(<\/span><span class=\"token string\">\"query is required\"<\/span><span class=\"token punctuation\">)<\/span>\n    <span class=\"token punctuation\">}<\/span>\n    queryLower <span class=\"token operator\">:=<\/span> strings<span class=\"token punctuation\">.<\/span><span class=\"token function\">ToLower<\/span><span class=\"token punctuation\">(<\/span>query<span class=\"token punctuation\">)<\/span>\n\n    <span class=\"token keyword\">var<\/span> matches <span class=\"token punctuation\">[<\/span><span class=\"token punctuation\">]<\/span><span class=\"token builtin\">string<\/span>\n\n    <span class=\"token comment\">\/\/ search repo files (production version uses ripgrep or tree-sitter for precision)<\/span>\n    <span class=\"token keyword\">for<\/span> path<span class=\"token punctuation\">,<\/span> content <span class=\"token operator\">:=<\/span> <span class=\"token keyword\">range<\/span> t<span class=\"token punctuation\">.<\/span>repoFiles <span class=\"token punctuation\">{<\/span>\n        lines <span class=\"token operator\">:=<\/span> strings<span class=\"token punctuation\">.<\/span><span class=\"token function\">Split<\/span><span class=\"token punctuation\">(<\/span>content<span class=\"token punctuation\">,<\/span> <span class=\"token string\">\"\\n\"<\/span><span class=\"token punctuation\">)<\/span>\n        <span class=\"token keyword\">for<\/span> i<span class=\"token punctuation\">,<\/span> line <span class=\"token operator\">:=<\/span> <span class=\"token keyword\">range<\/span> lines <span class=\"token punctuation\">{<\/span>\n            <span class=\"token keyword\">if<\/span> strings<span class=\"token punctuation\">.<\/span><span class=\"token function\">Contains<\/span><span class=\"token punctuation\">(<\/span>strings<span class=\"token punctuation\">.<\/span><span class=\"token function\">ToLower<\/span><span class=\"token punctuation\">(<\/span>line<span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">,<\/span> queryLower<span class=\"token punctuation\">)<\/span> <span class=\"token punctuation\">{<\/span>\n                matches <span class=\"token operator\">=<\/span> <span class=\"token function\">append<\/span><span class=\"token punctuation\">(<\/span>matches<span class=\"token punctuation\">,<\/span> fmt<span class=\"token punctuation\">.<\/span><span class=\"token function\">Sprintf<\/span><span class=\"token punctuation\">(<\/span><span class=\"token string\">\"%s:%d: %s\"<\/span><span class=\"token punctuation\">,<\/span> path<span class=\"token punctuation\">,<\/span> i<span class=\"token operator\">+<\/span><span class=\"token number\">1<\/span><span class=\"token punctuation\">,<\/span> strings<span class=\"token punctuation\">.<\/span><span class=\"token function\">TrimSpace<\/span><span class=\"token punctuation\">(<\/span>line<span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">)<\/span>\n            <span class=\"token punctuation\">}<\/span>\n        <span class=\"token punctuation\">}<\/span>\n    <span class=\"token punctuation\">}<\/span>\n\n    <span class=\"token comment\">\/\/ search the PR diff too<\/span>\n    <span class=\"token keyword\">for<\/span> i<span class=\"token punctuation\">,<\/span> line <span class=\"token operator\">:=<\/span> <span class=\"token keyword\">range<\/span> strings<span class=\"token punctuation\">.<\/span><span class=\"token function\">Split<\/span><span class=\"token punctuation\">(<\/span>t<span class=\"token punctuation\">.<\/span>diffText<span class=\"token punctuation\">,<\/span> <span class=\"token string\">\"\\n\"<\/span><span class=\"token punctuation\">)<\/span> <span class=\"token punctuation\">{<\/span>\n        <span class=\"token keyword\">if<\/span> strings<span class=\"token punctuation\">.<\/span><span class=\"token function\">Contains<\/span><span class=\"token punctuation\">(<\/span>strings<span class=\"token punctuation\">.<\/span><span class=\"token function\">ToLower<\/span><span class=\"token punctuation\">(<\/span>line<span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">,<\/span> queryLower<span class=\"token punctuation\">)<\/span> <span class=\"token punctuation\">{<\/span>\n            matches <span class=\"token operator\">=<\/span> <span class=\"token function\">append<\/span><span class=\"token punctuation\">(<\/span>matches<span class=\"token punctuation\">,<\/span> fmt<span class=\"token punctuation\">.<\/span><span class=\"token function\">Sprintf<\/span><span class=\"token punctuation\">(<\/span><span class=\"token string\">\"(diff):%d: %s\"<\/span><span class=\"token punctuation\">,<\/span> i<span class=\"token operator\">+<\/span><span class=\"token number\">1<\/span><span class=\"token punctuation\">,<\/span> strings<span class=\"token punctuation\">.<\/span><span class=\"token function\">TrimSpace<\/span><span class=\"token punctuation\">(<\/span>line<span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">)<\/span>\n        <span class=\"token punctuation\">}<\/span>\n    <span class=\"token punctuation\">}<\/span>\n\n    <span class=\"token keyword\">if<\/span> <span class=\"token function\">len<\/span><span class=\"token punctuation\">(<\/span>matches<span class=\"token punctuation\">)<\/span> <span class=\"token operator\">==<\/span> <span class=\"token number\">0<\/span> <span class=\"token punctuation\">{<\/span>\n        <span class=\"token keyword\">return<\/span> fmt<span class=\"token punctuation\">.<\/span><span class=\"token function\">Sprintf<\/span><span class=\"token punctuation\">(<\/span><span class=\"token string\">\"No matches found for %q\"<\/span><span class=\"token punctuation\">,<\/span> query<span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">,<\/span> <span class=\"token boolean\">nil<\/span>\n    <span class=\"token punctuation\">}<\/span>\n    <span class=\"token keyword\">return<\/span> strings<span class=\"token punctuation\">.<\/span><span class=\"token function\">Join<\/span><span class=\"token punctuation\">(<\/span>matches<span class=\"token punctuation\">,<\/span> <span class=\"token string\">\"\\n\"<\/span><span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">,<\/span> <span class=\"token boolean\">nil<\/span>\n<span class=\"token punctuation\">}<\/span><\/code><\/pre>\n<h3 id=\"step-3-define-the-terminal-tool\">Step 3: Define the terminal tool<\/h3>\n<p>This is what makes the loop an <em>agent<\/em> instead of a chatbot. The model doesn't print freeform text \u2014 it calls <code>submit_code_review<\/code> with structured data, and the loop ends.<\/p>\n<p>One subtle but important implementation detail: treat the terminal tool as <strong>non-executable<\/strong>. When the model emits <code>submit_code_review<\/code>, the loop captures the structured payload and ends immediately. Your application code (outside the loop) turns that payload into an actual GitHub review.<\/p>\n<p>That also gives you a schema you can validate. Here's what the model submits:<\/p>\n<pre class=\"language-json\"><code class=\"language-json\"><span class=\"token punctuation\">{<\/span>\n  <span class=\"token property\">\"summary\"<\/span><span class=\"token operator\">:<\/span> <span class=\"token string\">\"What changed + why it matters\"<\/span><span class=\"token punctuation\">,<\/span>\n  <span class=\"token property\">\"inline_comments\"<\/span><span class=\"token operator\">:<\/span> <span class=\"token punctuation\">[<\/span>\n    <span class=\"token punctuation\">{<\/span>\n      <span class=\"token property\">\"path\"<\/span><span class=\"token operator\">:<\/span> <span class=\"token string\">\"foo\/bar.go\"<\/span><span class=\"token punctuation\">,<\/span>\n      <span class=\"token property\">\"line\"<\/span><span class=\"token operator\">:<\/span> <span class=\"token number\">123<\/span><span class=\"token punctuation\">,<\/span>\n      <span class=\"token property\">\"severity\"<\/span><span class=\"token operator\">:<\/span> <span class=\"token string\">\"blocker\"<\/span><span class=\"token punctuation\">,<\/span>\n      <span class=\"token property\">\"comment\"<\/span><span class=\"token operator\">:<\/span> <span class=\"token string\">\"Specific issue, evidence, and a suggested fix\"<\/span>\n    <span class=\"token punctuation\">}<\/span>\n  <span class=\"token punctuation\">]<\/span><span class=\"token punctuation\">,<\/span>\n  <span class=\"token property\">\"high_level_feedback\"<\/span><span class=\"token operator\">:<\/span> <span class=\"token punctuation\">[<\/span>\n    <span class=\"token string\">\"Design-level note that isn't tied to a single line\"<\/span>\n  <span class=\"token punctuation\">]<\/span>\n<span class=\"token punctuation\">}<\/span><\/code><\/pre>\n<p>If the model puts &quot;blocker&quot; in the severity field but can't provide a path + line, that's the model telling you it doesn't have evidence.<\/p>\n<p>Here's the implementation:<\/p>\n<pre class=\"language-go\"><code class=\"language-go\"><span class=\"token keyword\">type<\/span> SubmitCodeReview <span class=\"token keyword\">struct<\/span><span class=\"token punctuation\">{<\/span><span class=\"token punctuation\">}<\/span>\n\n<span class=\"token keyword\">func<\/span> <span class=\"token punctuation\">(<\/span>t <span class=\"token operator\">*<\/span>SubmitCodeReview<span class=\"token punctuation\">)<\/span> <span class=\"token function\">Name<\/span><span class=\"token punctuation\">(<\/span><span class=\"token punctuation\">)<\/span> <span class=\"token builtin\">string<\/span>    <span class=\"token punctuation\">{<\/span> <span class=\"token keyword\">return<\/span> <span class=\"token string\">\"submit_code_review\"<\/span> <span class=\"token punctuation\">}<\/span>\n<span class=\"token keyword\">func<\/span> <span class=\"token punctuation\">(<\/span>t <span class=\"token operator\">*<\/span>SubmitCodeReview<span class=\"token punctuation\">)<\/span> <span class=\"token function\">IsTerminal<\/span><span class=\"token punctuation\">(<\/span><span class=\"token punctuation\">)<\/span> <span class=\"token builtin\">bool<\/span> <span class=\"token punctuation\">{<\/span> <span class=\"token keyword\">return<\/span> <span class=\"token boolean\">true<\/span> <span class=\"token punctuation\">}<\/span>\n<span class=\"token keyword\">func<\/span> <span class=\"token punctuation\">(<\/span>t <span class=\"token operator\">*<\/span>SubmitCodeReview<span class=\"token punctuation\">)<\/span> <span class=\"token function\">Description<\/span><span class=\"token punctuation\">(<\/span><span class=\"token punctuation\">)<\/span> <span class=\"token builtin\">string<\/span> <span class=\"token punctuation\">{<\/span>\n    <span class=\"token keyword\">return<\/span> <span class=\"token string\">`Submit your final code review. Every blocker and should-fix MUST\ninclude a file path and line number. If you cannot cite evidence, downgrade\nto nice-to-have or omit the finding.`<\/span>\n<span class=\"token punctuation\">}<\/span>\n\n<span class=\"token keyword\">func<\/span> <span class=\"token punctuation\">(<\/span>t <span class=\"token operator\">*<\/span>SubmitCodeReview<span class=\"token punctuation\">)<\/span> <span class=\"token function\">InputSchema<\/span><span class=\"token punctuation\">(<\/span><span class=\"token punctuation\">)<\/span> <span class=\"token keyword\">map<\/span><span class=\"token punctuation\">[<\/span><span class=\"token builtin\">string<\/span><span class=\"token punctuation\">]<\/span><span class=\"token keyword\">interface<\/span><span class=\"token punctuation\">{<\/span><span class=\"token punctuation\">}<\/span> <span class=\"token punctuation\">{<\/span>\n    <span class=\"token keyword\">return<\/span> <span class=\"token keyword\">map<\/span><span class=\"token punctuation\">[<\/span><span class=\"token builtin\">string<\/span><span class=\"token punctuation\">]<\/span><span class=\"token keyword\">interface<\/span><span class=\"token punctuation\">{<\/span><span class=\"token punctuation\">}<\/span><span class=\"token punctuation\">{<\/span>\n        <span class=\"token string\">\"type\"<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token string\">\"object\"<\/span><span class=\"token punctuation\">,<\/span>\n        <span class=\"token string\">\"properties\"<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token keyword\">map<\/span><span class=\"token punctuation\">[<\/span><span class=\"token builtin\">string<\/span><span class=\"token punctuation\">]<\/span><span class=\"token keyword\">interface<\/span><span class=\"token punctuation\">{<\/span><span class=\"token punctuation\">}<\/span><span class=\"token punctuation\">{<\/span>\n            <span class=\"token string\">\"summary\"<\/span><span class=\"token punctuation\">:<\/span>            <span class=\"token keyword\">map<\/span><span class=\"token punctuation\">[<\/span><span class=\"token builtin\">string<\/span><span class=\"token punctuation\">]<\/span><span class=\"token keyword\">interface<\/span><span class=\"token punctuation\">{<\/span><span class=\"token punctuation\">}<\/span><span class=\"token punctuation\">{<\/span><span class=\"token string\">\"type\"<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token string\">\"string\"<\/span><span class=\"token punctuation\">}<\/span><span class=\"token punctuation\">,<\/span>\n            <span class=\"token string\">\"inline_comments\"<\/span><span class=\"token punctuation\">:<\/span>    <span class=\"token keyword\">map<\/span><span class=\"token punctuation\">[<\/span><span class=\"token builtin\">string<\/span><span class=\"token punctuation\">]<\/span><span class=\"token keyword\">interface<\/span><span class=\"token punctuation\">{<\/span><span class=\"token punctuation\">}<\/span><span class=\"token punctuation\">{<\/span>\n                <span class=\"token string\">\"type\"<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token string\">\"array\"<\/span><span class=\"token punctuation\">,<\/span>\n                <span class=\"token string\">\"items\"<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token keyword\">map<\/span><span class=\"token punctuation\">[<\/span><span class=\"token builtin\">string<\/span><span class=\"token punctuation\">]<\/span><span class=\"token keyword\">interface<\/span><span class=\"token punctuation\">{<\/span><span class=\"token punctuation\">}<\/span><span class=\"token punctuation\">{<\/span>\n                    <span class=\"token string\">\"type\"<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token string\">\"object\"<\/span><span class=\"token punctuation\">,<\/span>\n                    <span class=\"token string\">\"properties\"<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token keyword\">map<\/span><span class=\"token punctuation\">[<\/span><span class=\"token builtin\">string<\/span><span class=\"token punctuation\">]<\/span><span class=\"token keyword\">interface<\/span><span class=\"token punctuation\">{<\/span><span class=\"token punctuation\">}<\/span><span class=\"token punctuation\">{<\/span>\n                        <span class=\"token string\">\"path\"<\/span><span class=\"token punctuation\">:<\/span>     <span class=\"token keyword\">map<\/span><span class=\"token punctuation\">[<\/span><span class=\"token builtin\">string<\/span><span class=\"token punctuation\">]<\/span><span class=\"token keyword\">interface<\/span><span class=\"token punctuation\">{<\/span><span class=\"token punctuation\">}<\/span><span class=\"token punctuation\">{<\/span><span class=\"token string\">\"type\"<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token string\">\"string\"<\/span><span class=\"token punctuation\">}<\/span><span class=\"token punctuation\">,<\/span>\n                        <span class=\"token string\">\"line\"<\/span><span class=\"token punctuation\">:<\/span>     <span class=\"token keyword\">map<\/span><span class=\"token punctuation\">[<\/span><span class=\"token builtin\">string<\/span><span class=\"token punctuation\">]<\/span><span class=\"token keyword\">interface<\/span><span class=\"token punctuation\">{<\/span><span class=\"token punctuation\">}<\/span><span class=\"token punctuation\">{<\/span><span class=\"token string\">\"type\"<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token string\">\"integer\"<\/span><span class=\"token punctuation\">}<\/span><span class=\"token punctuation\">,<\/span>\n                        <span class=\"token string\">\"severity\"<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token keyword\">map<\/span><span class=\"token punctuation\">[<\/span><span class=\"token builtin\">string<\/span><span class=\"token punctuation\">]<\/span><span class=\"token keyword\">interface<\/span><span class=\"token punctuation\">{<\/span><span class=\"token punctuation\">}<\/span><span class=\"token punctuation\">{<\/span><span class=\"token string\">\"type\"<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token string\">\"string\"<\/span><span class=\"token punctuation\">,<\/span> <span class=\"token string\">\"enum\"<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token punctuation\">[<\/span><span class=\"token punctuation\">]<\/span><span class=\"token builtin\">string<\/span><span class=\"token punctuation\">{<\/span><span class=\"token string\">\"blocker\"<\/span><span class=\"token punctuation\">,<\/span> <span class=\"token string\">\"should-fix\"<\/span><span class=\"token punctuation\">,<\/span> <span class=\"token string\">\"nice-to-have\"<\/span><span class=\"token punctuation\">}<\/span><span class=\"token punctuation\">}<\/span><span class=\"token punctuation\">,<\/span>\n                        <span class=\"token string\">\"comment\"<\/span><span class=\"token punctuation\">:<\/span>  <span class=\"token keyword\">map<\/span><span class=\"token punctuation\">[<\/span><span class=\"token builtin\">string<\/span><span class=\"token punctuation\">]<\/span><span class=\"token keyword\">interface<\/span><span class=\"token punctuation\">{<\/span><span class=\"token punctuation\">}<\/span><span class=\"token punctuation\">{<\/span><span class=\"token string\">\"type\"<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token string\">\"string\"<\/span><span class=\"token punctuation\">}<\/span><span class=\"token punctuation\">,<\/span>\n                    <span class=\"token punctuation\">}<\/span><span class=\"token punctuation\">,<\/span>\n                    <span class=\"token string\">\"required\"<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token punctuation\">[<\/span><span class=\"token punctuation\">]<\/span><span class=\"token builtin\">string<\/span><span class=\"token punctuation\">{<\/span><span class=\"token string\">\"path\"<\/span><span class=\"token punctuation\">,<\/span> <span class=\"token string\">\"line\"<\/span><span class=\"token punctuation\">,<\/span> <span class=\"token string\">\"severity\"<\/span><span class=\"token punctuation\">,<\/span> <span class=\"token string\">\"comment\"<\/span><span class=\"token punctuation\">}<\/span><span class=\"token punctuation\">,<\/span>\n                <span class=\"token punctuation\">}<\/span><span class=\"token punctuation\">,<\/span>\n            <span class=\"token punctuation\">}<\/span><span class=\"token punctuation\">,<\/span>\n            <span class=\"token string\">\"high_level_feedback\"<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token keyword\">map<\/span><span class=\"token punctuation\">[<\/span><span class=\"token builtin\">string<\/span><span class=\"token punctuation\">]<\/span><span class=\"token keyword\">interface<\/span><span class=\"token punctuation\">{<\/span><span class=\"token punctuation\">}<\/span><span class=\"token punctuation\">{<\/span>\n                <span class=\"token string\">\"type\"<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token string\">\"array\"<\/span><span class=\"token punctuation\">,<\/span>\n                <span class=\"token string\">\"items\"<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token keyword\">map<\/span><span class=\"token punctuation\">[<\/span><span class=\"token builtin\">string<\/span><span class=\"token punctuation\">]<\/span><span class=\"token keyword\">interface<\/span><span class=\"token punctuation\">{<\/span><span class=\"token punctuation\">}<\/span><span class=\"token punctuation\">{<\/span><span class=\"token string\">\"type\"<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token string\">\"string\"<\/span><span class=\"token punctuation\">}<\/span><span class=\"token punctuation\">,<\/span>\n            <span class=\"token punctuation\">}<\/span><span class=\"token punctuation\">,<\/span>\n        <span class=\"token punctuation\">}<\/span><span class=\"token punctuation\">,<\/span>\n        <span class=\"token string\">\"required\"<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token punctuation\">[<\/span><span class=\"token punctuation\">]<\/span><span class=\"token builtin\">string<\/span><span class=\"token punctuation\">{<\/span><span class=\"token string\">\"summary\"<\/span><span class=\"token punctuation\">,<\/span> <span class=\"token string\">\"inline_comments\"<\/span><span class=\"token punctuation\">}<\/span><span class=\"token punctuation\">,<\/span>\n    <span class=\"token punctuation\">}<\/span>\n<span class=\"token punctuation\">}<\/span>\n\n<span class=\"token keyword\">func<\/span> <span class=\"token punctuation\">(<\/span>t <span class=\"token operator\">*<\/span>SubmitCodeReview<span class=\"token punctuation\">)<\/span> <span class=\"token function\">Execute<\/span><span class=\"token punctuation\">(<\/span>\n    ctx context<span class=\"token punctuation\">.<\/span>Context<span class=\"token punctuation\">,<\/span> input <span class=\"token keyword\">map<\/span><span class=\"token punctuation\">[<\/span><span class=\"token builtin\">string<\/span><span class=\"token punctuation\">]<\/span><span class=\"token keyword\">interface<\/span><span class=\"token punctuation\">{<\/span><span class=\"token punctuation\">}<\/span><span class=\"token punctuation\">,<\/span>\n<span class=\"token punctuation\">)<\/span> <span class=\"token punctuation\">(<\/span><span class=\"token builtin\">string<\/span><span class=\"token punctuation\">,<\/span> <span class=\"token builtin\">error<\/span><span class=\"token punctuation\">)<\/span> <span class=\"token punctuation\">{<\/span>\n    <span class=\"token comment\">\/\/ In practice, the loop intercepts this before Execute runs.<\/span>\n    out<span class=\"token punctuation\">,<\/span> <span class=\"token boolean\">_<\/span> <span class=\"token operator\">:=<\/span> json<span class=\"token punctuation\">.<\/span><span class=\"token function\">MarshalIndent<\/span><span class=\"token punctuation\">(<\/span>input<span class=\"token punctuation\">,<\/span> <span class=\"token string\">\"\"<\/span><span class=\"token punctuation\">,<\/span> <span class=\"token string\">\"  \"<\/span><span class=\"token punctuation\">)<\/span>\n    <span class=\"token keyword\">return<\/span> <span class=\"token function\">string<\/span><span class=\"token punctuation\">(<\/span>out<span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">,<\/span> <span class=\"token boolean\">nil<\/span>\n<span class=\"token punctuation\">}<\/span><\/code><\/pre>\n<h3 id=\"step-4-write-the-loop\">Step 4: Write the loop<\/h3>\n<p>This is the core of the agent. Conceptually, it's tiny:<\/p>\n<pre class=\"language-go\"><code class=\"language-go\"><span class=\"token comment\">\/\/ Simplified: the real code has retries, token accounting, and timeouts.<\/span>\n<span class=\"token keyword\">for<\/span> iteration <span class=\"token operator\">:=<\/span> <span class=\"token number\">1<\/span><span class=\"token punctuation\">;<\/span> iteration <span class=\"token operator\">&lt;=<\/span> maxIterations<span class=\"token punctuation\">;<\/span> iteration<span class=\"token operator\">++<\/span> <span class=\"token punctuation\">{<\/span>\n    resp<span class=\"token punctuation\">,<\/span> err <span class=\"token operator\">:=<\/span> model<span class=\"token punctuation\">.<\/span><span class=\"token function\">Converse<\/span><span class=\"token punctuation\">(<\/span>ctx<span class=\"token punctuation\">,<\/span> messages<span class=\"token punctuation\">,<\/span> tools<span class=\"token punctuation\">)<\/span>\n    <span class=\"token keyword\">if<\/span> err <span class=\"token operator\">!=<\/span> <span class=\"token boolean\">nil<\/span> <span class=\"token punctuation\">{<\/span>\n        <span class=\"token keyword\">return<\/span> <span class=\"token boolean\">nil<\/span><span class=\"token punctuation\">,<\/span> err\n    <span class=\"token punctuation\">}<\/span>\n\n    <span class=\"token keyword\">if<\/span> resp<span class=\"token punctuation\">.<\/span><span class=\"token function\">HasToolCall<\/span><span class=\"token punctuation\">(<\/span><span class=\"token string\">\"submit_code_review\"<\/span><span class=\"token punctuation\">)<\/span> <span class=\"token punctuation\">{<\/span>\n        <span class=\"token keyword\">return<\/span> resp<span class=\"token punctuation\">.<\/span><span class=\"token function\">ToolInput<\/span><span class=\"token punctuation\">(<\/span><span class=\"token string\">\"submit_code_review\"<\/span><span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">,<\/span> <span class=\"token boolean\">nil<\/span>\n    <span class=\"token punctuation\">}<\/span>\n\n    toolMessages<span class=\"token punctuation\">,<\/span> err <span class=\"token operator\">:=<\/span> <span class=\"token function\">executeTools<\/span><span class=\"token punctuation\">(<\/span>ctx<span class=\"token punctuation\">,<\/span> resp<span class=\"token punctuation\">.<\/span>ToolCalls<span class=\"token punctuation\">)<\/span>\n    <span class=\"token keyword\">if<\/span> err <span class=\"token operator\">!=<\/span> <span class=\"token boolean\">nil<\/span> <span class=\"token punctuation\">{<\/span>\n        <span class=\"token keyword\">return<\/span> <span class=\"token boolean\">nil<\/span><span class=\"token punctuation\">,<\/span> err\n    <span class=\"token punctuation\">}<\/span>\n\n    messages <span class=\"token operator\">=<\/span> <span class=\"token function\">append<\/span><span class=\"token punctuation\">(<\/span>messages<span class=\"token punctuation\">,<\/span> toolMessages<span class=\"token operator\">...<\/span><span class=\"token punctuation\">)<\/span>\n    messages <span class=\"token operator\">=<\/span> <span class=\"token function\">shrinkStaleToolResults<\/span><span class=\"token punctuation\">(<\/span>messages<span class=\"token punctuation\">,<\/span> maxToolResultChars<span class=\"token punctuation\">)<\/span>\n<span class=\"token punctuation\">}<\/span>\n<span class=\"token keyword\">return<\/span> <span class=\"token boolean\">nil<\/span><span class=\"token punctuation\">,<\/span> fmt<span class=\"token punctuation\">.<\/span><span class=\"token function\">Errorf<\/span><span class=\"token punctuation\">(<\/span><span class=\"token string\">\"agent loop ended without submit_code_review\"<\/span><span class=\"token punctuation\">)<\/span><\/code><\/pre>\n<p>That <code>shrinkStaleToolResults<\/code> line is doing more work than it looks. The loop manages context aggressively:<\/p>\n<ul>\n<li>It keeps the diff and the most recent evidence.<\/li>\n<li>It truncates older tool results (the giant file dumps you needed 3 turns ago).<\/li>\n<li>If the model hits a context overflow anyway, it retries the same iteration after shrinking tool results harder.<\/li>\n<\/ul>\n<p>Why bother with all this shrinking? &quot;Why not just stuff the entire repo into the prompt and avoid tool calls?&quot; Because <strong>long context has its own failure modes<\/strong>.<\/p>\n<p>Even if your model <em>can<\/em> accept huge inputs, performance doesn't scale linearly with tokens. Two patterns show up in research and in practice:<\/p>\n<ol>\n<li><strong>&quot;Lost in the middle.&quot;<\/strong> Models tend to over-weight the beginning and end of long contexts, and under-use relevant info buried in the middle.<\/li>\n<li><strong>Distraction.<\/strong> Irrelevant context measurably reduces accuracy; the model starts pattern-matching on junk.<\/li>\n<\/ol>\n<p>If you want to go deep on the evidence:<\/p>\n<ul>\n<li><a href=\"https:\/\/aclanthology.org\/2024.tacl-1.9.pdf\">&quot;Lost in the Middle: How Language Models Use Long Contexts&quot;<\/a> (Liu et al., 2024)<\/li>\n<li><a href=\"https:\/\/arxiv.org\/pdf\/2302.00093\">&quot;Large Language Models Can Be Easily Distracted by Irrelevant Context&quot;<\/a> (Shi et al., 2023)<\/li>\n<li><a href=\"https:\/\/research.trychroma.com\/context-rot\">&quot;Context Rot: How Increasing Input Tokens Impacts LLM Performance&quot;<\/a> (Chroma Research, 2025)<\/li>\n<\/ul>\n<p>The practical takeaway isn't &quot;never use long context&quot;. It's this:<\/p>\n<p><strong>Treat tokens like budget, not storage.<\/strong><\/p>\n<p>This is boring plumbing. It is also the difference between a system that works on small PRs and a system that survives the messy ones.<\/p>\n<h3 id=\"step-5-wire-it-together\">Step 5: Wire it together<\/h3>\n<p>This is the entry point. You assemble your tools, set your budgets, run the loop, and handle the structured output.<\/p>\n<pre class=\"language-go\"><code class=\"language-go\"><span class=\"token keyword\">func<\/span> <span class=\"token function\">RunReview<\/span><span class=\"token punctuation\">(<\/span>ctx context<span class=\"token punctuation\">.<\/span>Context<span class=\"token punctuation\">,<\/span> model Model<span class=\"token punctuation\">,<\/span> pr PRContext<span class=\"token punctuation\">)<\/span> <span class=\"token punctuation\">(<\/span><span class=\"token operator\">*<\/span>ReviewResult<span class=\"token punctuation\">,<\/span> <span class=\"token builtin\">error<\/span><span class=\"token punctuation\">)<\/span> <span class=\"token punctuation\">{<\/span>\n    tools <span class=\"token operator\">:=<\/span> <span class=\"token punctuation\">[<\/span><span class=\"token punctuation\">]<\/span>Tool<span class=\"token punctuation\">{<\/span>\n        <span class=\"token operator\">&amp;<\/span>GetPRInfo<span class=\"token punctuation\">{<\/span>pr<span class=\"token punctuation\">:<\/span> pr<span class=\"token punctuation\">}<\/span><span class=\"token punctuation\">,<\/span>\n        <span class=\"token operator\">&amp;<\/span>GetPRDiff<span class=\"token punctuation\">{<\/span>pr<span class=\"token punctuation\">:<\/span> pr<span class=\"token punctuation\">}<\/span><span class=\"token punctuation\">,<\/span>\n        <span class=\"token operator\">&amp;<\/span>GetFileContent<span class=\"token punctuation\">{<\/span>ghClient<span class=\"token punctuation\">:<\/span> pr<span class=\"token punctuation\">.<\/span>Client<span class=\"token punctuation\">,<\/span> owner<span class=\"token punctuation\">:<\/span> pr<span class=\"token punctuation\">.<\/span>Owner<span class=\"token punctuation\">,<\/span> repo<span class=\"token punctuation\">:<\/span> pr<span class=\"token punctuation\">.<\/span>Repo<span class=\"token punctuation\">,<\/span> headSHA<span class=\"token punctuation\">:<\/span> pr<span class=\"token punctuation\">.<\/span>HeadSHA<span class=\"token punctuation\">}<\/span><span class=\"token punctuation\">,<\/span>\n        <span class=\"token operator\">&amp;<\/span>SearchCode<span class=\"token punctuation\">{<\/span>repoFiles<span class=\"token punctuation\">:<\/span> pr<span class=\"token punctuation\">.<\/span>RepoFiles<span class=\"token punctuation\">,<\/span> diffText<span class=\"token punctuation\">:<\/span> pr<span class=\"token punctuation\">.<\/span>Diff<span class=\"token punctuation\">}<\/span><span class=\"token punctuation\">,<\/span>\n        <span class=\"token operator\">&amp;<\/span>SubmitCodeReview<span class=\"token punctuation\">{<\/span><span class=\"token punctuation\">}<\/span><span class=\"token punctuation\">,<\/span>\n    <span class=\"token punctuation\">}<\/span>\n\n    config <span class=\"token operator\">:=<\/span> LoopConfig<span class=\"token punctuation\">{<\/span>\n        SystemPrompt<span class=\"token punctuation\">:<\/span> reviewSystemPrompt<span class=\"token punctuation\">,<\/span>\n        InitialMessage<span class=\"token punctuation\">:<\/span> fmt<span class=\"token punctuation\">.<\/span><span class=\"token function\">Sprintf<\/span><span class=\"token punctuation\">(<\/span>\n            <span class=\"token string\">\"Review PR #%d: %s\\nAuthor: %s\\nBase: %s \u2190 Head: %s\"<\/span><span class=\"token punctuation\">,<\/span>\n            pr<span class=\"token punctuation\">.<\/span>Number<span class=\"token punctuation\">,<\/span> pr<span class=\"token punctuation\">.<\/span>Title<span class=\"token punctuation\">,<\/span> pr<span class=\"token punctuation\">.<\/span>Author<span class=\"token punctuation\">,<\/span> pr<span class=\"token punctuation\">.<\/span>Base<span class=\"token punctuation\">,<\/span> pr<span class=\"token punctuation\">.<\/span>Head<span class=\"token punctuation\">,<\/span>\n        <span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">,<\/span>\n        Tools<span class=\"token punctuation\">:<\/span>              tools<span class=\"token punctuation\">,<\/span>\n        MaxIterations<span class=\"token punctuation\">:<\/span>      <span class=\"token number\">15<\/span><span class=\"token punctuation\">,<\/span>\n        MaxToolResultChars<span class=\"token punctuation\">:<\/span> <span class=\"token number\">120_000<\/span><span class=\"token punctuation\">,<\/span>\n    <span class=\"token punctuation\">}<\/span>\n\n    result<span class=\"token punctuation\">,<\/span> err <span class=\"token operator\">:=<\/span> <span class=\"token function\">RunLoop<\/span><span class=\"token punctuation\">(<\/span>ctx<span class=\"token punctuation\">,<\/span> model<span class=\"token punctuation\">,<\/span> config<span class=\"token punctuation\">)<\/span>\n    <span class=\"token keyword\">if<\/span> err <span class=\"token operator\">!=<\/span> <span class=\"token boolean\">nil<\/span> <span class=\"token punctuation\">{<\/span>\n        <span class=\"token keyword\">return<\/span> <span class=\"token boolean\">nil<\/span><span class=\"token punctuation\">,<\/span> err\n    <span class=\"token punctuation\">}<\/span>\n\n    <span class=\"token keyword\">if<\/span> result<span class=\"token punctuation\">.<\/span>TerminalToolName <span class=\"token operator\">!=<\/span> <span class=\"token string\">\"submit_code_review\"<\/span> <span class=\"token punctuation\">{<\/span>\n        <span class=\"token keyword\">return<\/span> <span class=\"token boolean\">nil<\/span><span class=\"token punctuation\">,<\/span> fmt<span class=\"token punctuation\">.<\/span><span class=\"token function\">Errorf<\/span><span class=\"token punctuation\">(<\/span><span class=\"token string\">\"loop ended without submitting a review\"<\/span><span class=\"token punctuation\">)<\/span>\n    <span class=\"token punctuation\">}<\/span>\n\n    <span class=\"token keyword\">return<\/span> <span class=\"token function\">parseReviewResult<\/span><span class=\"token punctuation\">(<\/span>result<span class=\"token punctuation\">.<\/span>TerminalToolInput<span class=\"token punctuation\">)<\/span>\n<span class=\"token punctuation\">}<\/span><\/code><\/pre>\n<p>The tools are simple. The loop is simple. The power comes from letting the model decide which tools to call, and in what order.<\/p>\n<p>That terminal tool pattern also gives you a clean way to run the same loop in different modes:<\/p>\n<ul>\n<li>Full PR review: includes <code>submit_code_review<\/code>.<\/li>\n<li>Interactive chat: no terminal tool; the loop returns assistant text.<\/li>\n<li>Command mode: terminal tool is &quot;submit result&quot; for that command.<\/li>\n<\/ul>\n<p>One loop. Different endings.<\/p>\n<h3 id=\"step-6-add-guardrails\">Step 6: Add guardrails<\/h3>\n<p>Agentic doesn't mean &quot;let it run forever&quot;. It means you give the model freedom <em>inside a box<\/em>:<\/p>\n<ul>\n<li><strong>Iteration caps.<\/strong> Hard limit on round-trips.<\/li>\n<li><strong>Timeouts.<\/strong> Total wall-clock budget.<\/li>\n<li><strong>Tool result limits.<\/strong> Max characters per tool output, and stricter limits for stale outputs.<\/li>\n<li><strong>Self-critique before submission.<\/strong> The terminal tool instructions include a checklist: cite evidence, downgrade uncertain claims, avoid bikeshedding, don't invent runtime failures.<\/li>\n<\/ul>\n<p>If you're building your own version, don't copy my numbers. Copy the pattern: budgets are part of the interface.<\/p>\n<h3 id=\"step-7-evaluate-on-real-prs\">Step 7: Evaluate on real PRs<\/h3>\n<p>Don't trust vibes. Pick 5-10 PRs where you already know what the real risks were \u2014 a missed nil check, a broken migration, a security issue someone caught in manual review. Run your loop against those PRs and check:<\/p>\n<ul>\n<li>Did it find the real issue?<\/li>\n<li>Did it cite the right file and line?<\/li>\n<li>Did it hallucinate problems that don't exist?<\/li>\n<li>When it was uncertain, did it fetch more evidence or just assert?<\/li>\n<\/ul>\n<p>That gives you a ground-truth baseline. From there, iterate on your tools (not your prompts).<\/p>\n<h2 id=\"the-trade-offs-because-there-are-always-trade-offs\">The trade-offs (because there are always trade-offs)<\/h2>\n<p>Agentic review is not a free lunch.<\/p>\n<ul>\n<li><strong>Latency:<\/strong> tool calls add round-trips. You have to make tools fast and cacheable.<\/li>\n<li><strong>Cost:<\/strong> more turns can mean more tokens. Budgeting is mandatory.<\/li>\n<li><strong>Tool design:<\/strong> bad tools produce bad behavior. (If <code>search_code<\/code> returns garbage, the model will reason over garbage.)<\/li>\n<li><strong>Security:<\/strong> tools are an exfiltration surface. Your tool layer needs authorization and redaction.<\/li>\n<\/ul>\n<p>But for code review, the win is that the model starts behaving like a cautious reviewer: it looks things up.<\/p>\n<p>Don't build a bigger prompt. Build a loop where the model can fetch evidence, update its hypothesis, and only finish when it submits a structured, checkable result. When you stop forcing the model to guess context, you stop debugging prompt vibes and start debugging actual interfaces.<\/p>\n<p>Want to build your own agentic review system? Let's talk through the architecture.<\/p>\n<a href=\"https:\/\/cal.com\/alexandrecastrotech\/mentoring\" class=\"text-primary-text-light dark:text-primary-text hover:opacity-80 transition-opacity flex items-center gap-2 font-bold uppercase tracking-wide min-h-11\">\n    <i class=\"pixelart-icons-font-calendar\"><\/i> Book a mentoring session\n<\/a>\n"},{"title":"Challenging Assumptions in Technology: From Being Right to Getting It Right","link":{"@attributes":{"href":"https:\/\/platformtoolsmith.com\/blog\/challenging-assumptions\/"}},"updated":"2026-02-11T00:00:00Z","id":"https:\/\/platformtoolsmith.com\/blog\/challenging-assumptions\/","content":"<p>Here's a story I tell my mentees often\u2014and it's one where I almost made an expensive mistake.<\/p>\n<p>A few years ago, my team was tasked with rebuilding the authentication system for one of our core products. We walked into that room with a massive assumption already locked and loaded: <em>&quot;Authentication isn't our core business. We should buy, not build.&quot;<\/em><\/p>\n<p>It made perfect sense on paper. We assumed buying a vendor solution like Auth0 would be faster, safer, and cheaper. We assumed building it ourselves was risky\u2014authentication is hard, right? If you mess it up, you leak credentials, you destroy trust, you crater the business. It felt like the &quot;smart senior engineer&quot; decision to make.<\/p>\n<p>But when we looked at the price tag, we hesitated. It was <em>expensive<\/em>. We were about to commit to substantial annual licensing fees, multi-year contracts, and integration work that everyone assumed was &quot;just configuration.&quot;<\/p>\n<p>So, we did something uncomfortable: we challenged our own &quot;smart&quot; assumption.<\/p>\n<p>We looked deeper. We found that OAuth 2.0 is actually very standardized and prescriptive. We realized that integrating an external vendor with our complex web of internal legacy identity providers was going to be a nightmare regardless of who wrote the auth server. The hard part wasn't the OAuth protocol\u2014it was mapping our Byzantine internal user hierarchies and permission structures.<\/p>\n<p>So we decided to be naive. We decided to build it.<\/p>\n<p>The result? It took us one quarter. It cost us a fraction of the vendor price\u2014saving substantial licensing fees annually. It's been running in production for years, scaling to multiple brands.<\/p>\n<p>Don't get me wrong\u2014buying isn't bad. We don't build Git from scratch; we don't build VMware from scratch. But in this specific case, the &quot;obvious&quot; choice was the wrong one. We nearly let a safe assumption cost us six figures and lock us into a decision we'd regret for years.<\/p>\n<p>This experience taught me a lesson that defines how I lead today: <strong>Engineering isn't about <em>being<\/em> right. It's about <em>getting<\/em> it right.<\/strong> And the only way to get it right is to relentlessly challenge your assumptions.<\/p>\n<h2 id=\"assumptions-are-hidden-technical-debt\">Assumptions Are Hidden Technical Debt<\/h2>\n<p>We talk a lot about technical debt in code\u2014variables named <code>temp2<\/code>, lack of tests, hardcoded values. But there is a much more dangerous kind of debt: <strong>Decision Inertia.<\/strong><\/p>\n<p><img src=\"https:\/\/platformtoolsmith.com\/assets\/blog\/gopher-challenge-assumptions.601c9297.900.webp\" alt=\"A mole rows a tiny boat around an iceberg whose bulk hides below the waterline.\" width=\"1920\" height=\"1080\" loading=\"lazy\" decoding=\"async\" srcset=\"https:\/\/platformtoolsmith.com\/assets\/blog\/gopher-challenge-assumptions.601c9297.400.webp 400w, https:\/\/platformtoolsmith.com\/assets\/blog\/gopher-challenge-assumptions.601c9297.900.webp 900w, https:\/\/platformtoolsmith.com\/assets\/blog\/gopher-challenge-assumptions.601c9297.1800.webp 1800w\" sizes=\"(min-width: 1024px) 773px, 100vw\" \/><\/p>\n<p>Every time you say, &quot;Well, we just use Git because that's what we use,&quot; or &quot;We can't rewrite this legacy app, it's too big,&quot; or &quot;We can't move off this bare-metal MySQL cluster, it's too stateful,&quot; you are taking out a loan. You are deferring the work of thinking. You are snowballing unverified beliefs into a mountain of constraints that eventually makes it impossible to move.<\/p>\n<p>Why do we do this?<\/p>\n<ol>\n<li>\n<p><strong>Survivorship Bias:<\/strong> We see that legacy system running and assume it's just &quot;old code&quot;\u2014but forget it solved real business problems that still exist today. That spaghetti monolith? It handles edge cases you don't even know about yet.<\/p>\n<\/li>\n<li>\n<p><strong>Vendor Demo Trap:<\/strong> We've all seen the slick demo where the vendor solution handles every use case with one click. But we forget that the integration is always harder than the demo, and our snowflake infrastructure never fits their assumptions.<\/p>\n<\/li>\n<li>\n<p><strong>Migration PTSD:<\/strong> We've been burned by past migrations that went sideways\u2014databases that didn't replicate correctly, services that couldn't handle production load. So we assume this one will fail too, even when the circumstances are completely different.<\/p>\n<\/li>\n<\/ol>\n<p>If you want to grow from a senior engineer to a principal or staff level, you have to stop accepting &quot;that's just how it is&quot; as an answer.<\/p>\n<blockquote class=\"alert-blockquote\">\n<div class=\"alert-header\">\n                [ <i class=\"pixelart-icons-font-warning-diamond\"><\/i> <span>IMPORTANT<\/span> ]\n              <\/div><p>Assumptions are the hidden technical debt of architecture.<\/p>\n<\/blockquote>\n<h2 id=\"the-act-framework\">The ACT Framework<\/h2>\n<p>Over the years, I've leaned on a simple mental model to help teams break out of this inertia. I call it the <strong>ACT Framework<\/strong>.<\/p>\n<blockquote class=\"alert-blockquote\">\n<div class=\"alert-header\">\n                [ <i class=\"pixelart-icons-font-zap\"><\/i> <span>TIP<\/span> ]\n              <\/div><p>Full disclosure: This isn't some new magic. It's the <a href=\"https:\/\/en.wikipedia.org\/wiki\/Scientific_method\"><strong>scientific method<\/strong><\/a>, simplified and repackaged for the chaos of daily engineering. I use it because it's memorable enough to apply in the heat of the moment, without needing a textbook to recall the steps.<\/p>\n<\/blockquote>\n<h3 id=\"1-awareness\">1. Awareness<\/h3>\n<p>You can't fix what you don't see. Most of the time, we aren't even aware we're making an assumption.<\/p>\n<ul>\n<li><strong>Recognize the trap:<\/strong> You assume the train knows the way to the destination until you end up in the wrong city.<\/li>\n<li><strong>The Daily Question:<\/strong> Ask yourself, &quot;What is one thing I am taking for granted today?&quot;<\/li>\n<li><strong>Challenge &quot;Facts&quot;:<\/strong> Maybe it's that your database needs to run on bare metal. Maybe it's that a service <em>needs<\/em> to be written in Go. Be aware of the &quot;facts&quot; that are actually just habits.<\/li>\n<\/ul>\n<h3 id=\"2-challenge\">2. Challenge<\/h3>\n<p>Once you spot an assumption, poke it. Even if you think it's right, challenge it anyway.<\/p>\n<ul>\n<li>&quot;What if we <em>didn't<\/em> use Kubernetes for this?&quot;<\/li>\n<li>&quot;What if we <em>did<\/em> rewrite this 10-year-old monolith?&quot;<\/li>\n<li>&quot;What if the 'industry standard' doesn't apply to our specific constraints?&quot;<\/li>\n<\/ul>\n<p>This fosters psychological safety. When you normalize challenging ideas, you create an environment where people aren't afraid to be wrong. You shift the focus from &quot;Who has the best idea?&quot; to &quot;What is the best idea?&quot;<\/p>\n<h3 id=\"3-test-evidence-is-king\">3. Test (Evidence is King)<\/h3>\n<p>This is the most critical step. Challenging without testing is just intellectual debate\u2014it's just noise.<\/p>\n<ul>\n<li><strong>Start small:<\/strong> We didn't build the whole thing first; we spun up a quick POC and load-tested it.<\/li>\n<li><strong>Gather data:<\/strong> We ran benchmarks and proved that modern virtualized instances could match or exceed the bare metal performance we thought we needed.<\/li>\n<li><strong>Decide with evidence:<\/strong> Opinions are cheap. Senior engineers have opinions; Staff engineers have data.<\/li>\n<\/ul>\n<div class=\"mermaid-container\">\n<div class=\"mermaid-diagram\" tabindex=\"0\">\n<svg class=\"dg-ca\" viewBox=\"-2 -2 894 348\" role=\"img\" aria-labelledby=\"dg-ca-title dg-ca-desc\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\">\n<title id=\"dg-ca-title\">The loop for challenging an assumption<\/title>\n<desc id=\"dg-ca-desc\">Start with awareness of what you take for granted. Challenge it by asking what if the opposite were true. Test it with a timeboxed spike of two to five days. If the evidence says the assumption holds, keep it and revisit next quarter, and return to awareness if the context changes. If the assumption breaks, you unlock velocity.<\/desc>\n<style>.dg-ca{display:block;width:100%;min-width:560px;height:auto;font-family:'Space Grotesk',system-ui,sans-serif}.dg-ca text{text-anchor:middle;fill:var(--mermaid-text, #000000)}.dg-ca .b{fill:var(--mermaid-node-bg, #FFFFFF);stroke:var(--mermaid-node-border, #000000);stroke-width:1.5}.dg-ca .grp{fill:none;stroke:var(--mermaid-node-border, #000000);stroke-width:1.5;stroke-dasharray:3 5}.dg-ca .band{font-size:13.8px;font-weight:700;letter-spacing:.08em}.dg-ca .t{font-size:17.5px;font-weight:700}.dg-ca .gt{font-size:14.4px;font-weight:700;text-anchor:start;fill:var(--theme-text-muted, #6A6A6A);paint-order:stroke;stroke:var(--mermaid-node-bg, #FFFFFF);stroke-width:6px;stroke-linejoin:round}.dg-ca .lab{font-size:14.4px;fill:var(--theme-text-muted, #6A6A6A);paint-order:stroke;stroke:var(--mermaid-node-bg, #FFFFFF);stroke-width:6px;stroke-linejoin:round}.dg-ca .ln{fill:none;stroke:var(--mermaid-line, #FF5C00);stroke-width:2}.dg-ca .life{fill:none;stroke:var(--mermaid-node-border, #000000);stroke-width:1.5;stroke-dasharray:3 5}.dg-ca .dash{stroke-dasharray:7 5}.dg-ca marker path{fill:var(--mermaid-line, #FF5C00)}<\/style>\n<defs><marker id=\"dg-ca-arrow\" viewBox=\"0 0 10 10\" refX=\"7\" refY=\"5\" markerWidth=\"7\" markerHeight=\"7\" orient=\"auto-start-reverse\"><path d=\"M0 0L10 5L0 10z\"><\/path><\/marker><\/defs>\n<path class=\"ln\" d=\"M200 67L228 67\" marker-end=\"url(#dg-ca-arrow)\"><\/path>\n<path class=\"ln\" d=\"M430 67L458 67\" marker-end=\"url(#dg-ca-arrow)\"><\/path>\n<path class=\"ln\" d=\"M660 67L688 67\" marker-end=\"url(#dg-ca-arrow)\"><\/path>\n<path class=\"ln\" d=\"M790 134L790 208\" marker-end=\"url(#dg-ca-arrow)\"><\/path>\n<path class=\"ln\" d=\"M730 134L730 172L330 172L330 208\" marker-end=\"url(#dg-ca-arrow)\"><\/path>\n<path class=\"ln dash\" d=\"M230 277L100 277L100 136\" marker-end=\"url(#dg-ca-arrow)\"><\/path>\n<rect class=\"b\" x=\"0\" y=\"0\" width=\"200\" height=\"134\"><\/rect>\n<rect x=\"0\" y=\"0\" width=\"200\" height=\"29\" style=\"fill:var(--diagram-origin, #A0694B)\"><\/rect>\n<text class=\"band\" x=\"100.0\" y=\"20\" style=\"fill:var(--diagram-origin-label, #000000)\">START<\/text>\n<text class=\"t\" x=\"100.0\" y=\"63.5\">Awareness<\/text>\n<text class=\"t\" x=\"100.0\" y=\"87.5\">What am I taking<\/text>\n<text class=\"t\" x=\"100.0\" y=\"111.5\">for granted?<\/text>\n<rect class=\"b\" x=\"230\" y=\"0\" width=\"200\" height=\"134\"><\/rect>\n<rect x=\"230\" y=\"0\" width=\"200\" height=\"29\" style=\"fill:var(--diagram-define, #00A0C8)\"><\/rect>\n<text class=\"band\" x=\"330.0\" y=\"20\" style=\"fill:var(--diagram-define-label, #000000)\">CHALLENGE<\/text>\n<text class=\"t\" x=\"330.0\" y=\"63.5\">Challenge<\/text>\n<text class=\"t\" x=\"330.0\" y=\"87.5\">What if the opposite<\/text>\n<text class=\"t\" x=\"330.0\" y=\"111.5\">were true?<\/text>\n<rect class=\"b\" x=\"460\" y=\"0\" width=\"200\" height=\"134\"><\/rect>\n<rect x=\"460\" y=\"0\" width=\"200\" height=\"29\" style=\"fill:var(--diagram-define, #00A0C8)\"><\/rect>\n<text class=\"band\" x=\"560.0\" y=\"20\" style=\"fill:var(--diagram-define-label, #000000)\">TEST<\/text>\n<text class=\"t\" x=\"560.0\" y=\"63.5\">Test<\/text>\n<text class=\"t\" x=\"560.0\" y=\"87.5\">Timeboxed spike<\/text>\n<text class=\"t\" x=\"560.0\" y=\"111.5\">(2-5 days)<\/text>\n<rect class=\"b\" x=\"690\" y=\"0\" width=\"200\" height=\"134\"><\/rect>\n<rect x=\"690\" y=\"0\" width=\"200\" height=\"29\" style=\"fill:var(--diagram-decide, #D24040)\"><\/rect>\n<text class=\"band\" x=\"790.0\" y=\"20\" style=\"fill:var(--diagram-decide-label, #FFFFFF)\">DECIDE<\/text>\n<text class=\"t\" x=\"790.0\" y=\"87.5\">Evidence says...<\/text>\n<rect class=\"b\" x=\"230\" y=\"210\" width=\"200\" height=\"134\"><\/rect>\n<rect x=\"230\" y=\"210\" width=\"200\" height=\"29\" style=\"fill:var(--diagram-hold, #FAC800)\"><\/rect>\n<text class=\"band\" x=\"330.0\" y=\"230\" style=\"fill:var(--diagram-hold-label, #000000)\">KEEP<\/text>\n<text class=\"t\" x=\"330.0\" y=\"273.5\">Keep it<\/text>\n<text class=\"t\" x=\"330.0\" y=\"297.5\">(revisit next<\/text>\n<text class=\"t\" x=\"330.0\" y=\"321.5\">quarter)<\/text>\n<rect class=\"b\" x=\"690\" y=\"210\" width=\"200\" height=\"134\"><\/rect>\n<rect x=\"690\" y=\"210\" width=\"200\" height=\"29\" style=\"fill:var(--diagram-neutral, #F0F0DC)\"><\/rect>\n<text class=\"band\" x=\"790.0\" y=\"230\" style=\"fill:var(--diagram-neutral-label, #000000)\">UNLOCK<\/text>\n<text class=\"t\" x=\"790.0\" y=\"297.5\">Unlock velocity<\/text>\n<text class=\"lab\" x=\"790\" y=\"174\" style=\"text-anchor:middle\">Assumption broken<\/text>\n<text class=\"lab\" x=\"560\" y=\"176\" style=\"text-anchor:middle\">Assumption holds<\/text>\n<text class=\"lab\" x=\"100\" y=\"267.0\" style=\"text-anchor:middle\">Context changed?<\/text>\n<\/svg>\n<\/div>\n<\/div>\n<h3 id=\"scaling-to-the-team-the-assumption-audit\">Scaling to the Team: The Assumption Audit<\/h3>\n<p>The ACT framework is great for checking your own biases, but challenging assumptions is ultimately a team sport. If you're the only one doing it, you'll quickly become &quot;that person&quot; who blocks every PR with philosophical questions. You need to bring the team along.<\/p>\n<p>I like to use a technique called <strong>Inversion<\/strong> during design reviews.<\/p>\n<p>Instead of asking &quot;Is this the right choice?&quot;, I ask the room:<\/p>\n<blockquote>\n<p><em>&quot;What conditions would have to be true for the <strong>opposite<\/strong> of this decision to be the right choice?&quot;<\/em><\/p>\n<\/blockquote>\n<p>This forces the team to articulate the hidden constraints they are assuming exist.<\/p>\n<p>For example, if the team assumes &quot;We must use Microservices,&quot; the inversion asks: <em>&quot;What would have to be true for a Monolith to be the better choice here?&quot;<\/em><\/p>\n<p>Usually, the answer is something like, <em>&quot;Well, we'd need to not care about independent deployments.&quot;<\/em> And then you can check: <em>&quot;Do we actually care about independent deployments for this specific internal tool?&quot;<\/em><\/p>\n<p>Here's the irony: we platform teams spend all day promoting microservices to product engineers\u2014&quot;decouple your domains!&quot; &quot;independent deployability!&quot;\u2014but then we turn around and build monolithic internal tools because &quot;it's just for us&quot; and &quot;we don't need to scale that.&quot; When you invert your own assumptions, you often find you're giving advice you don't follow.<\/p>\n<p>Suddenly, the assumption is visible. Now you can debate the constraint, not the solution.<\/p>\n<h2 id=\"the-power-of-naive-questions\">The Power of Naive Questions<\/h2>\n<p>There is a trap that experienced engineers fall into. We get cynical. We've seen projects fail, we've seen technologies hype cycle and die, and we start to assume that &quot;it's complicated&quot; means &quot;it's impossible.&quot;<\/p>\n<p>The most valuable engineers I know retain a degree of &quot;naivety&quot;\u2014the willingness to ask &quot;How hard can it be?&quot; even when the &quot;smart&quot; answer is &quot;don't even try.&quot;<\/p>\n<p>I saw this play out recently when someone asked if we could make our database infrastructure fully self-service using Ansible and Terraform. The room went quiet. Databases are the third rail\u2014stateful, critical, full of subtle operational constraints.<\/p>\n<p>I could have said no. I could have cited all the reasons it wouldn't work\u2014stateful workloads are different, replication is hard, compliance adds complexity, we'd need a whole platform team just for this.<\/p>\n<p>But instead, I said: &quot;I don't know. How hard can it be? Let's try and find out.&quot;<\/p>\n<p>We gave ourselves one quarter to prove it could work. We defined clear success criteria: spin up a production-ready, compliant Postgres instance in under an hour. The first attempt took three weeks and broke in spectacular ways. But we learned. We iterated. We automated the pain points one by one.<\/p>\n<p>Nine months later, you can spin up a production-ready, compliant Postgres instance in minutes with a single PR. We unlocked velocity that we assumed was impossible because we were willing to try.<\/p>\n<p>The lesson? Sometimes &quot;it's complicated&quot; is true. But sometimes it's just a story we tell ourselves to avoid the work of finding out.<\/p>\n<h2 id=\"how-to-sell-the-risk\">How to Sell the Risk<\/h2>\n<p>Now, I can hear you saying: <em>&quot;This sounds great, Alex, but my PM wants this feature shipped yesterday. I don't have time to play scientist.&quot;<\/em><\/p>\n<p>I get it. The pressure is real.<\/p>\n<p>When you challenge an assumption\u2014especially a &quot;safe&quot; one like buying a vendor or using a standard library\u2014you are introducing risk. You are trading certainty (even if it's expensive or slow) for uncertainty.<\/p>\n<p>Management hates uncertainty. So, don't sell them uncertainty. Sell them a <strong>Timeboxed Spike<\/strong>.<\/p>\n<blockquote class=\"alert-blockquote\">\n<div class=\"alert-header\">\n                [ <i class=\"pixelart-icons-font-info-box\"><\/i> <span>NOTE<\/span> ]\n              <\/div><p>Here is the script I use:<\/p>\n<p><em>&quot;I have a hunch this assumption might be wrong. Give me 2 days to verify it. If I'm right, we save weeks of complexity and significant budget. If I'm wrong, we lost 2 days. Is that a trade you're willing to make?&quot;<\/em><\/p>\n<\/blockquote>\n<p>Why does this work?<\/p>\n<ol>\n<li><strong>Capped Downside:<\/strong> You explicitly state the maximum loss (2 days).<\/li>\n<li><strong>Asymmetric Upside:<\/strong> The potential gain (weeks of time, budget) massively outweighs the cost.<\/li>\n<li><strong>Business Language:<\/strong> You aren't asking to &quot;refactor&quot; or &quot;explore&quot;; you are proposing a calculated bet with positive expected value.<\/li>\n<\/ol>\n<p>Most reasonable leaders will take that bet every single time.<\/p>\n<h2 id=\"the-goal-the-best-answer-wins\">The Goal: The Best Answer Wins<\/h2>\n<p>If you take one thing from this post, let it be this: <strong>Check your ego at the door.<\/strong><\/p>\n<p>Challenging assumptions isn't about being the smartest person in the room who points out logical fallacies. It's about vulnerability. It's about saying, &quot;I might be wrong about this, and I want to find out.&quot;<\/p>\n<p>It's uncomfortable. It requires you to talk to other teams, to push back on managers, to question legacy decisions made by people who are still at the company.<\/p>\n<p>But that is the job.<\/p>\n<p>The best engineers I know are the ones who are happiest when their own assumptions are proven wrong\u2014because that means they just learned something, and the team is about to build something better.<\/p>\n<p>So, here is my challenge to you: <strong>What is one assumption you are holding onto right now?<\/strong><\/p>\n<p>Document it. Challenge it. Test it.<\/p>\n<p>You might just save your company a significant amount of budget, or better yet, you might build something you didn't know was possible.<\/p>\n<hr \/>\n<p>Enjoyed this post? Challenging assumptions is easier when you have someone to pressure-test your thinking. I help Senior and Staff engineers navigate these kinds of complex decisions.<\/p>\n<a href=\"https:\/\/cal.com\/alexandrecastrotech\/mentoring\" class=\"text-primary-text-light dark:text-primary-text hover:opacity-80 transition-opacity flex items-center gap-2 font-bold uppercase tracking-wide min-h-11\">\n    <i class=\"pixelart-icons-font-calendar\"><\/i> Book a mentoring session\n<\/a>\n"},{"title":"You are a Senior Engineer, Mastering Communication & Influence (Part 3)","link":{"@attributes":{"href":"https:\/\/platformtoolsmith.com\/blog\/senior-engineer-part-3\/"}},"updated":"2026-01-12T00:00:00Z","id":"https:\/\/platformtoolsmith.com\/blog\/senior-engineer-part-3\/","content":"<!-- TODO: Remove this note in ~6 months (July 2026) -->\n<blockquote class=\"alert-blockquote\">\n<div class=\"alert-header\">\n                [ <i class=\"pixelart-icons-font-zap\"><\/i> <span>TIP<\/span> ]\n              <\/div><p><strong>A Note from the Future (2026)<\/strong><br \/>\nWhile writing this third part, I realized the original posts in this series needed a refresh to match the depth we're hitting here. So, I've gone back and significantly updated <a href=\"https:\/\/platformtoolsmith.com\/blog\/senior-engineer-part-1\/\">Part 1 (Career Growth)<\/a> and <a href=\"https:\/\/platformtoolsmith.com\/blog\/senior-engineer-part-2\/\">Part 2 (Architecture)<\/a> with new frameworks, diagrams, and actionable &quot;workout plans.&quot; If you haven't read them in a while (or ever), now is a great time to check them out!<\/p>\n<\/blockquote>\n<p>I once watched a brilliant engineer lose a promotion to someone with half his technical depth. The difference? The other guy could explain his work in a way that made executives actually care.<\/p>\n<p>This is a hard truth, but it's one we need to face. As we continue our journey through your professional development, it's time to zero in on the &quot;soft skills&quot;\u2014communication, influence, and leadership\u2014that often determine your career trajectory and impact more than your code does.<\/p>\n<p>As a Principal Engineer, I have the great opportunity to mentor many seniors, and I can tell you right now: the difference between a senior who stays at that level and one who advances to staff, principal, or tech lead positions almost always comes down to these soft skills. So, grab your favorite brew, and let's dive into the essentials that will set you apart.<\/p>\n<h2 id=\"the-art-of-communication\">The Art of Communication<\/h2>\n<p>Communication isn't just about conveying information; it's about making connections, inspiring your team, and presenting your ideas effectively. Here's the truth: you could design the most elegant system in the world, but if you can't explain why it matters, it won't get built. Whether you're explaining complex technical details to stakeholders or mentoring a newcomer, the way you communicate can make all the difference.<\/p>\n<h3 id=\"adaptability-tailoring-your-message\">Adaptability: Tailoring Your Message<\/h3>\n<p>In the fast-paced world of tech, your audience can range from highly technical peers to business-oriented stakeholders. The key to effective communication lies in your ability to adapt, and this is something I had to learn the hard way early in my career.<\/p>\n<p>Imagine you're explaining a new architectural decision. To your engineering team, you'll dive deep into the technical specifics\u2014maybe you're discussing the trade-offs between using a message queue versus direct API calls, the implications for eventual consistency, or the specifics of your chosen design pattern. You might talk about how this aligns with SOLID principles or how it improves your system's observability.<\/p>\n<p>However, when presenting that same decision to executives, your focus should shift entirely to the business impact. They don't care about your elegant use of the Strategy pattern. What they care about is: how does this decision enhance scalability? Will it reduce our cloud costs? Does it help us ship features faster? Will it improve our uptime and reduce those 3 AM incidents that wake up the on-call team?<\/p>\n<p>Here's a practical example: I once had to justify migrating our monolith to microservices. To the engineering team, I talked about service boundaries, deployment independence, and technology diversity. To the executive team, I framed it as: &quot;This will allow us to deploy new features 10x faster without risking the entire platform, and it will reduce our incident recovery time from hours to minutes.&quot; Same decision, completely different framing.<\/p>\n<p>The best part? You don't need to dumb anything down. You're just translating between contexts. Think of it like being bilingual\u2014you're fluent in both technical and business language, and you know when to use each.<\/p>\n<h3 id=\"patience-and-repetition-reinforcing-concepts\">Patience and Repetition: Reinforcing Concepts<\/h3>\n<p>Here's something nobody tells you about being a senior: you're going to explain the same thing over and over again. And that's okay! In fact, it's part of the job.<\/p>\n<p>Explaining the same concept multiple times, from different angles, until it clicks isn't a sign that you're bad at explaining or that your team isn't listening. It's simply how humans learn complex topics. Embrace this repetition\u2014it's an opportunity to refine your explanations and ensure everyone's on the same page.<\/p>\n<p>Think of it as an iterative process, much like software development itself. Each explanation is a new iteration, improving understanding and clarity. Your first explanation might be too technical. Your second might be too abstract. By the third or fourth time, you'll find that sweet spot where it finally clicks for everyone.<\/p>\n<blockquote class=\"alert-blockquote\">\n<div class=\"alert-header\">\n                [ <i class=\"pixelart-icons-font-clock\"><\/i> <span>SKILLS<\/span> ]\n              <\/div><p>I remember when I was introducing my team to event-driven architecture. The first time I explained it, I lost them in the technical details of event sourcing and CQRS. The second time, I used a restaurant ordering system as an analogy\u2014the waiter takes your order (event), the kitchen prepares it (event handler), and the waiter brings it out (another event). That's when I saw the lightbulbs go off. Sometimes you just need to find the right metaphor or example.<\/p>\n<\/blockquote>\n<p>This patience not only helps in solidifying concepts but also in building a collaborative and inclusive environment where people feel safe asking questions and admitting when they don't understand something.<\/p>\n<h3 id=\"embracing-questions-and-uncertainty\">Embracing Questions and Uncertainty<\/h3>\n<p>Questions are inevitable, and how you handle them sets you apart. Here's something I've learned: the moment you pretend to know everything is the moment you lose credibility. Embrace curiosity, admit when you don't know something, and commit to finding the answer. This openness builds trust and fosters a learning culture.<\/p>\n<p>For instance, in a meeting, if a junior developer asks a question that stumps you, instead of deflecting or making something up, acknowledge it honestly. Say something like, &quot;That's a great question, and honestly, I don't have the answer right now. Let me look into it and get back to you by end of day.&quot; This not only shows humility but also models the behavior you want to see in your team.<\/p>\n<p>I've had moments where a junior engineer asked about a specific edge case in our distributed system that I hadn't considered. Instead of brushing it off, I said, &quot;You know what? That's a scenario I haven't thought through. Let's whiteboard this together and figure out what would happen.&quot; We ended up discovering a real bug that could have caused data inconsistency. That junior engineer's question saved us from a production incident, and they felt empowered to keep asking tough questions.<\/p>\n<p>The best engineers I know are the ones who aren't afraid to say &quot;I don't know, but let's find out together.&quot;<\/p>\n<h3 id=\"feedback-and-suggestions-two-way-communication\">Feedback and Suggestions: Two-Way Communication<\/h3>\n<p>Constructive feedback is a two-way street, and getting good at both giving and receiving it will transform your team dynamics. For example, when reviewing a colleague's code, focus on providing specific, actionable feedback rather than vague criticisms. Instead of &quot;This code is messy,&quot; try &quot;I noticed this function is doing three different things. What if we extracted the validation logic into a separate function? It would make this easier to test and understand.&quot;<\/p>\n<p>On the flip side, when receiving feedback, approach it with an open mind. This is hard\u2014nobody likes hearing their work could be better. But here's the thing: even seemingly minor suggestions can spark innovation. I've had colleagues point out patterns in my code reviews that led to us establishing new team standards. I've had junior developers ask &quot;why&quot; questions that made me reconsider assumptions I'd been making for years.<\/p>\n<p>The best feedback loop I've seen worked like this: during code reviews, we'd frame suggestions as questions. &quot;Have you considered...?&quot; or &quot;What was the reasoning behind...?&quot; instead of &quot;You should...&quot; This small shift made feedback feel collaborative rather than critical.<\/p>\n<h2 id=\"diagrams-the-blueprint-of-understanding\">Diagrams: The Blueprint of Understanding<\/h2>\n<p><img src=\"https:\/\/platformtoolsmith.com\/assets\/blog\/gopher-whiteboard.04375cb8.900.webp\" alt=\"A mole connects a diagram of simple boxes and arrows on a board.\" width=\"1920\" height=\"1080\" loading=\"lazy\" decoding=\"async\" srcset=\"https:\/\/platformtoolsmith.com\/assets\/blog\/gopher-whiteboard.04375cb8.400.webp 400w, https:\/\/platformtoolsmith.com\/assets\/blog\/gopher-whiteboard.04375cb8.900.webp 900w, https:\/\/platformtoolsmith.com\/assets\/blog\/gopher-whiteboard.04375cb8.1800.webp 1800w\" sizes=\"(min-width: 1024px) 773px, 100vw\" \/><\/p>\n<p>As a Senior Engineer, explaining complex systems clearly is a big part of your role, and this is where diagrams become your best friend. I can't tell you how many times a 10-minute whiteboard session with a diagram has solved a problem that hours of Slack messages couldn't.<\/p>\n<h3 id=\"mastering-diagramming-techniques\">Mastering Diagramming Techniques<\/h3>\n<p>There are three main diagramming approaches you should have in your toolkit, and each serves a different purpose:<\/p>\n<p><strong>UML (Unified Modeling Language)<\/strong>: This is your go-to for detailing class structures and interactions. Use UML when you need to show how objects relate to each other, their methods, and their relationships. It's particularly useful when discussing design patterns with your team or planning out a new module's structure. For example, when I'm introducing the Factory pattern to my team, I'll use a UML class diagram to show the relationship between the factory, the interface, and the concrete implementations. It makes the abstract concept concrete.<\/p>\n<p><strong>C4 (Context, Containers, Components, Code)<\/strong>: This is ideal for contextualizing software architecture within its environment, and honestly, it's become my favorite for most architecture discussions. The beauty of C4 is that it works at multiple levels of zoom. At the Context level, you're showing how your system fits into the broader ecosystem\u2014what external systems does it talk to? Who are the users? Then you zoom into Containers to show the high-level technical building blocks (web app, database, message queue), then Components to show how the container is structured, and finally Code if you need that level of detail.<\/p>\n<p>I recently used C4 diagrams to explain our platform architecture to new team members. The Context diagram showed how we integrate with payment providers and our mobile apps. The Container diagram revealed our microservices, databases, and message queues. By the time we got to the Component diagram, they understood exactly where they'd be working and how their code would fit into the bigger picture.<\/p>\n<p><strong>ArchiMate<\/strong>: This is your enterprise architecture tool. It's more formal and structured than C4, making it perfect when you're dealing with complex business domains and need to show not just technical architecture but also business processes, organizational structure, and how they all interconnect. I'll be honest, I don't reach for ArchiMate often, but when I'm working with enterprise architects or documenting large-scale organizational changes, it's invaluable.<\/p>\n<h3 id=\"diagram-guidelines-clarity-and-consistency\">Diagram Guidelines: Clarity and Consistency<\/h3>\n<p>Here's the thing about diagrams: a bad diagram is worse than no diagram at all. A confusing diagram will derail your meeting and leave everyone more confused than when you started.<\/p>\n<p>Creating effective diagrams is both an art and a science. Here are the rules I follow:<\/p>\n<p><strong>Every element must earn its place.<\/strong> If a box, arrow, or label doesn't add clarity, remove it. I've seen diagrams with 50 boxes trying to show everything at once. Instead, create multiple diagrams at different levels of detail.<\/p>\n<p><strong>Clear titles and labels are non-negotiable.<\/strong> Your diagram should be understandable without you explaining it. I test this by showing diagrams to someone unfamiliar with the project and asking them to explain it back to me. If they can't, I simplify.<\/p>\n<p><strong>Consistency in visual language is crucial.<\/strong> Pick a color scheme and stick to it. For example, in my C4 diagrams, I always use blue for internal services, gray for external systems, and orange for databases. This consistency means that when someone sees a blue box, they immediately know it's something we own and control.<\/p>\n<p><strong>Use arrows deliberately.<\/strong> Show direction of data flow, not just &quot;these things are related.&quot; A bidirectional arrow should mean something different than a unidirectional one.<\/p>\n<p>I learned this the hard way after creating a beautiful, detailed diagram that nobody understood. Now I follow the principle: start simple, add complexity only when necessary. Your first diagram should be something you could sketch on a napkin.<\/p>\n<p>Here's what this looks like in practice. Let's say you're documenting a payment processing flow. Here's what happens when you ignore these guidelines:<\/p>\n<p><strong><i class=\"pixelart-icons-font-close\"><\/i> Before: Cluttered and Confusing<\/strong><\/p>\n<div class=\"mermaid-container\">\n<div class=\"mermaid-diagram\" tabindex=\"0\">\n<svg class=\"dg-se3a\" viewBox=\"-2 -2 974 688\" role=\"img\" aria-labelledby=\"dg-se3a-title dg-se3a-desc\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\">\n<title id=\"dg-se3a-title\">Everything a payment touches in the system<\/title>\n<desc id=\"dg-se3a-desc\">The web API calls the logging, payment and monitoring services. The payment service uses the config service, the user database, the payment events queue, an external payment provider and the analytics service. The queue feeds the email service. Logging writes to log storage, monitoring to the metrics database, and config reads the config database.<\/desc>\n<style>.dg-se3a{display:block;width:100%;min-width:680px;height:auto;font-family:'Space Grotesk',system-ui,sans-serif}.dg-se3a text{text-anchor:middle;fill:var(--mermaid-text, #000000)}.dg-se3a .b{fill:var(--mermaid-node-bg, #FFFFFF);stroke:var(--mermaid-node-border, #000000);stroke-width:1.5}.dg-se3a .grp{fill:none;stroke:var(--mermaid-node-border, #000000);stroke-width:1.5;stroke-dasharray:3 5}.dg-se3a .band{font-size:13.8px;font-weight:700;letter-spacing:.08em}.dg-se3a .t{font-size:17.5px;font-weight:700}.dg-se3a .gt{font-size:14.4px;font-weight:700;text-anchor:start;fill:var(--theme-text-muted, #6A6A6A);paint-order:stroke;stroke:var(--mermaid-node-bg, #FFFFFF);stroke-width:6px;stroke-linejoin:round}.dg-se3a .lab{font-size:14.4px;fill:var(--theme-text-muted, #6A6A6A);paint-order:stroke;stroke:var(--mermaid-node-bg, #FFFFFF);stroke-width:6px;stroke-linejoin:round}.dg-se3a .ln{fill:none;stroke:var(--mermaid-line, #FF5C00);stroke-width:2}.dg-se3a .life{fill:none;stroke:var(--mermaid-node-border, #000000);stroke-width:1.5;stroke-dasharray:3 5}.dg-se3a .dash{stroke-dasharray:7 5}.dg-se3a marker path{fill:var(--mermaid-line, #FF5C00)}<\/style>\n<defs><marker id=\"dg-se3a-arrow\" viewBox=\"0 0 10 10\" refX=\"7\" refY=\"5\" markerWidth=\"7\" markerHeight=\"7\" orient=\"auto-start-reverse\"><path d=\"M0 0L10 5L0 10z\"><\/path><\/marker><\/defs>\n<path class=\"ln\" d=\"M190 342L225 342L225 42L258 42\" marker-end=\"url(#dg-se3a-arrow)\"><\/path>\n<path class=\"ln\" d=\"M190 342L258 342\" marker-end=\"url(#dg-se3a-arrow)\"><\/path>\n<path class=\"ln\" d=\"M190 342L225 342L225 642L258 642\" marker-end=\"url(#dg-se3a-arrow)\"><\/path>\n<path class=\"ln\" d=\"M450 342L485 342L485 142L518 142\" marker-end=\"url(#dg-se3a-arrow)\"><\/path>\n<path class=\"ln\" d=\"M450 342L485 342L485 242L518 242\" marker-end=\"url(#dg-se3a-arrow)\"><\/path>\n<path class=\"ln\" d=\"M450 342L518 342\" marker-end=\"url(#dg-se3a-arrow)\"><\/path>\n<path class=\"ln\" d=\"M450 342L485 342L485 542L518 542\" marker-end=\"url(#dg-se3a-arrow)\"><\/path>\n<path class=\"ln\" d=\"M450 342L485 342L485 442L518 442\" marker-end=\"url(#dg-se3a-arrow)\" marker-start=\"url(#dg-se3a-arrow)\"><\/path>\n<path class=\"ln\" d=\"M450 42L518 42\" marker-end=\"url(#dg-se3a-arrow)\"><\/path>\n<path class=\"ln\" d=\"M450 642L518 642\" marker-end=\"url(#dg-se3a-arrow)\"><\/path>\n<path class=\"ln\" d=\"M710 142L778 142\" marker-end=\"url(#dg-se3a-arrow)\"><\/path>\n<path class=\"ln\" d=\"M710 342L778 342\" marker-end=\"url(#dg-se3a-arrow)\"><\/path>\n<rect class=\"b\" x=\"0\" y=\"300\" width=\"190\" height=\"84\"><\/rect>\n<rect x=\"0\" y=\"300\" width=\"190\" height=\"29\" style=\"fill:var(--diagram-origin, #A0694B)\"><\/rect>\n<text class=\"band\" x=\"95.0\" y=\"320\" style=\"fill:var(--diagram-origin-label, #000000)\">START<\/text>\n<text class=\"t\" x=\"95.0\" y=\"362.5\">Web API<\/text>\n<rect class=\"b\" x=\"260\" y=\"0\" width=\"190\" height=\"84\"><\/rect>\n<rect x=\"260\" y=\"0\" width=\"190\" height=\"29\" style=\"fill:var(--diagram-act, #10C840)\"><\/rect>\n<text class=\"band\" x=\"355.0\" y=\"20\" style=\"fill:var(--diagram-act-label, #000000)\">OWN<\/text>\n<text class=\"t\" x=\"355.0\" y=\"62.5\">Logging Service<\/text>\n<rect class=\"b\" x=\"260\" y=\"300\" width=\"190\" height=\"84\"><\/rect>\n<rect x=\"260\" y=\"300\" width=\"190\" height=\"29\" style=\"fill:var(--diagram-act, #10C840)\"><\/rect>\n<text class=\"band\" x=\"355.0\" y=\"320\" style=\"fill:var(--diagram-act-label, #000000)\">OWN<\/text>\n<text class=\"t\" x=\"355.0\" y=\"362.5\">Payment Service<\/text>\n<rect class=\"b\" x=\"260\" y=\"600\" width=\"190\" height=\"84\"><\/rect>\n<rect x=\"260\" y=\"600\" width=\"190\" height=\"29\" style=\"fill:var(--diagram-act, #10C840)\"><\/rect>\n<text class=\"band\" x=\"355.0\" y=\"620\" style=\"fill:var(--diagram-act-label, #000000)\">OWN<\/text>\n<text class=\"t\" x=\"355.0\" y=\"662.5\">Monitoring Service<\/text>\n<rect class=\"b\" x=\"520\" y=\"0\" width=\"190\" height=\"84\"><\/rect>\n<rect x=\"520\" y=\"0\" width=\"190\" height=\"29\" style=\"fill:var(--diagram-neutral, #F0F0DC)\"><\/rect>\n<text class=\"band\" x=\"615.0\" y=\"20\" style=\"fill:var(--diagram-neutral-label, #000000)\">STORE<\/text>\n<text class=\"t\" x=\"615.0\" y=\"62.5\">Log Storage<\/text>\n<rect class=\"b\" x=\"520\" y=\"100\" width=\"190\" height=\"84\"><\/rect>\n<rect x=\"520\" y=\"100\" width=\"190\" height=\"29\" style=\"fill:var(--diagram-act, #10C840)\"><\/rect>\n<text class=\"band\" x=\"615.0\" y=\"120\" style=\"fill:var(--diagram-act-label, #000000)\">OWN<\/text>\n<text class=\"t\" x=\"615.0\" y=\"162.5\">Config Service<\/text>\n<rect class=\"b\" x=\"520\" y=\"200\" width=\"190\" height=\"84\"><\/rect>\n<rect x=\"520\" y=\"200\" width=\"190\" height=\"29\" style=\"fill:var(--diagram-neutral, #F0F0DC)\"><\/rect>\n<text class=\"band\" x=\"615.0\" y=\"220\" style=\"fill:var(--diagram-neutral-label, #000000)\">STORE<\/text>\n<text class=\"t\" x=\"615.0\" y=\"262.5\">User Database<\/text>\n<rect class=\"b\" x=\"520\" y=\"300\" width=\"190\" height=\"84\"><\/rect>\n<rect x=\"520\" y=\"300\" width=\"190\" height=\"29\" style=\"fill:var(--diagram-neutral, #F0F0DC)\"><\/rect>\n<text class=\"band\" x=\"615.0\" y=\"320\" style=\"fill:var(--diagram-neutral-label, #000000)\">STORE<\/text>\n<text class=\"t\" x=\"615.0\" y=\"350.5\">Payment Events<\/text>\n<text class=\"t\" x=\"615.0\" y=\"374.5\">Queue<\/text>\n<rect class=\"b\" x=\"520\" y=\"400\" width=\"190\" height=\"84\"><\/rect>\n<rect x=\"520\" y=\"400\" width=\"190\" height=\"29\" style=\"fill:var(--diagram-neutral, #F0F0DC)\"><\/rect>\n<text class=\"band\" x=\"615.0\" y=\"420\" style=\"fill:var(--diagram-neutral-label, #000000)\">TOOL<\/text>\n<text class=\"t\" x=\"615.0\" y=\"450.5\">External Payment<\/text>\n<text class=\"t\" x=\"615.0\" y=\"474.5\">Provider<\/text>\n<rect class=\"b\" x=\"520\" y=\"500\" width=\"190\" height=\"84\"><\/rect>\n<rect x=\"520\" y=\"500\" width=\"190\" height=\"29\" style=\"fill:var(--diagram-act, #10C840)\"><\/rect>\n<text class=\"band\" x=\"615.0\" y=\"520\" style=\"fill:var(--diagram-act-label, #000000)\">OWN<\/text>\n<text class=\"t\" x=\"615.0\" y=\"562.5\">Analytics Service<\/text>\n<rect class=\"b\" x=\"520\" y=\"600\" width=\"190\" height=\"84\"><\/rect>\n<rect x=\"520\" y=\"600\" width=\"190\" height=\"29\" style=\"fill:var(--diagram-neutral, #F0F0DC)\"><\/rect>\n<text class=\"band\" x=\"615.0\" y=\"620\" style=\"fill:var(--diagram-neutral-label, #000000)\">STORE<\/text>\n<text class=\"t\" x=\"615.0\" y=\"662.5\">Metrics Database<\/text>\n<rect class=\"b\" x=\"780\" y=\"100\" width=\"190\" height=\"84\"><\/rect>\n<rect x=\"780\" y=\"100\" width=\"190\" height=\"29\" style=\"fill:var(--diagram-neutral, #F0F0DC)\"><\/rect>\n<text class=\"band\" x=\"875.0\" y=\"120\" style=\"fill:var(--diagram-neutral-label, #000000)\">STORE<\/text>\n<text class=\"t\" x=\"875.0\" y=\"162.5\">Config Database<\/text>\n<rect class=\"b\" x=\"780\" y=\"300\" width=\"190\" height=\"84\"><\/rect>\n<rect x=\"780\" y=\"300\" width=\"190\" height=\"29\" style=\"fill:var(--diagram-act, #10C840)\"><\/rect>\n<text class=\"band\" x=\"875.0\" y=\"320\" style=\"fill:var(--diagram-act-label, #000000)\">OWN<\/text>\n<text class=\"t\" x=\"875.0\" y=\"362.5\">Email Service<\/text>\n<\/svg>\n<\/div>\n<\/div>\n<p>This diagram is technically accurate\u2014all these components exist. But does it help you understand the payment flow? Probably not. You're drowning in infrastructure noise. Which path does a payment actually take? What's essential vs. what's just support infrastructure?<\/p>\n<p>Now, here's the same flow following the principles I just covered:<\/p>\n<p><strong><i class=\"pixelart-icons-font-check\"><\/i> After: Clear and Purposeful<\/strong><\/p>\n<div class=\"mermaid-container\">\n<div class=\"mermaid-diagram\" tabindex=\"0\">\n<svg class=\"dg-se3b\" viewBox=\"-2 -2 964 364\" role=\"img\" aria-labelledby=\"dg-se3b-title dg-se3b-desc\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\">\n<title id=\"dg-se3b-title\">One payment request from start to event<\/title>\n<desc id=\"dg-se3b-desc\">The web API sends a payment request to the payment service. The service validates the user in the user database, processes the payment with the external provider, gets the result back, and emits an event to the payment events queue.<\/desc>\n<style>.dg-se3b{display:block;width:100%;min-width:560px;height:auto;font-family:'Space Grotesk',system-ui,sans-serif}.dg-se3b text{text-anchor:middle;fill:var(--mermaid-text, #000000)}.dg-se3b .b{fill:var(--mermaid-node-bg, #FFFFFF);stroke:var(--mermaid-node-border, #000000);stroke-width:1.5}.dg-se3b .grp{fill:none;stroke:var(--mermaid-node-border, #000000);stroke-width:1.5;stroke-dasharray:3 5}.dg-se3b .band{font-size:13.8px;font-weight:700;letter-spacing:.08em}.dg-se3b .t{font-size:17.5px;font-weight:700}.dg-se3b .gt{font-size:14.4px;font-weight:700;text-anchor:start;fill:var(--theme-text-muted, #6A6A6A);paint-order:stroke;stroke:var(--mermaid-node-bg, #FFFFFF);stroke-width:6px;stroke-linejoin:round}.dg-se3b .lab{font-size:14.4px;fill:var(--theme-text-muted, #6A6A6A);paint-order:stroke;stroke:var(--mermaid-node-bg, #FFFFFF);stroke-width:6px;stroke-linejoin:round}.dg-se3b .ln{fill:none;stroke:var(--mermaid-line, #FF5C00);stroke-width:2}.dg-se3b .life{fill:none;stroke:var(--mermaid-node-border, #000000);stroke-width:1.5;stroke-dasharray:3 5}.dg-se3b .dash{stroke-dasharray:7 5}.dg-se3b marker path{fill:var(--mermaid-line, #FF5C00)}<\/style>\n<defs><marker id=\"dg-se3b-arrow\" viewBox=\"0 0 10 10\" refX=\"7\" refY=\"5\" markerWidth=\"7\" markerHeight=\"7\" orient=\"auto-start-reverse\"><path d=\"M0 0L10 5L0 10z\"><\/path><\/marker><\/defs>\n<path class=\"ln\" d=\"M200 180L378 180\" marker-end=\"url(#dg-se3b-arrow)\"><\/path>\n<path class=\"ln\" d=\"M480 130L480 50L758 50\" marker-end=\"url(#dg-se3b-arrow)\"><\/path>\n<path class=\"ln\" d=\"M580 158L758 158\" marker-end=\"url(#dg-se3b-arrow)\"><\/path>\n<path class=\"ln\" d=\"M760 202L582 202\" marker-end=\"url(#dg-se3b-arrow)\"><\/path>\n<path class=\"ln\" d=\"M480 230L480 310L758 310\" marker-end=\"url(#dg-se3b-arrow)\"><\/path>\n<rect class=\"b\" x=\"0\" y=\"130\" width=\"200\" height=\"100\"><\/rect>\n<rect x=\"0\" y=\"130\" width=\"200\" height=\"29\" style=\"fill:var(--diagram-origin, #A0694B)\"><\/rect>\n<text class=\"band\" x=\"100.0\" y=\"150\" style=\"fill:var(--diagram-origin-label, #000000)\">START<\/text>\n<text class=\"t\" x=\"100.0\" y=\"200.5\">Web API<\/text>\n<rect class=\"b\" x=\"380\" y=\"130\" width=\"200\" height=\"100\"><\/rect>\n<rect x=\"380\" y=\"130\" width=\"200\" height=\"29\" style=\"fill:var(--diagram-act, #10C840)\"><\/rect>\n<text class=\"band\" x=\"480.0\" y=\"150\" style=\"fill:var(--diagram-act-label, #000000)\">OWN<\/text>\n<text class=\"t\" x=\"480.0\" y=\"200.5\">Payment Service<\/text>\n<rect class=\"b\" x=\"760\" y=\"0\" width=\"200\" height=\"100\"><\/rect>\n<rect x=\"760\" y=\"0\" width=\"200\" height=\"29\" style=\"fill:var(--diagram-neutral, #F0F0DC)\"><\/rect>\n<text class=\"band\" x=\"860.0\" y=\"20\" style=\"fill:var(--diagram-neutral-label, #000000)\">STORE<\/text>\n<text class=\"t\" x=\"860.0\" y=\"70.5\">User Database<\/text>\n<rect class=\"b\" x=\"760\" y=\"130\" width=\"200\" height=\"100\"><\/rect>\n<rect x=\"760\" y=\"130\" width=\"200\" height=\"29\" style=\"fill:var(--diagram-neutral, #F0F0DC)\"><\/rect>\n<text class=\"band\" x=\"860.0\" y=\"150\" style=\"fill:var(--diagram-neutral-label, #000000)\">TOOL<\/text>\n<text class=\"t\" x=\"860.0\" y=\"188.5\">External Payment<\/text>\n<text class=\"t\" x=\"860.0\" y=\"212.5\">Provider<\/text>\n<rect class=\"b\" x=\"760\" y=\"260\" width=\"200\" height=\"100\"><\/rect>\n<rect x=\"760\" y=\"260\" width=\"200\" height=\"29\" style=\"fill:var(--diagram-neutral, #F0F0DC)\"><\/rect>\n<text class=\"band\" x=\"860.0\" y=\"280\" style=\"fill:var(--diagram-neutral-label, #000000)\">STORE<\/text>\n<text class=\"t\" x=\"860.0\" y=\"318.5\">Payment Events<\/text>\n<text class=\"t\" x=\"860.0\" y=\"342.5\">Queue<\/text>\n<text class=\"lab\" x=\"290\" y=\"171\" style=\"text-anchor:middle\">1. Payment Request<\/text>\n<text class=\"lab\" x=\"620\" y=\"41\" style=\"text-anchor:middle\">2. Validate User<\/text>\n<text class=\"lab\" x=\"670\" y=\"149\" style=\"text-anchor:middle\">3. Process Payment<\/text>\n<text class=\"lab\" x=\"670\" y=\"223\" style=\"text-anchor:middle\">4. Payment Result<\/text>\n<text class=\"lab\" x=\"620\" y=\"301\" style=\"text-anchor:middle\">5. Emit Event<\/text>\n<\/svg>\n<\/div>\n<\/div>\n<p>See the difference? Every element earns its place. The numbered arrows show you exactly how data flows through the system. The colors are consistent: blue for our internal services, orange for data stores, green for external systems. You could sketch this on a napkin in 30 seconds, and a new team member could understand the payment flow immediately.<\/p>\n<p>The logging service, monitoring service, and config service? They're important, but they don't help explain the payment flow, so they don't belong here. If you need to document how monitoring works, create a separate diagram for that.<\/p>\n<h2 id=\"the-art-of-presentation\">The Art of Presentation<\/h2>\n<p>Presenting effectively is crucial, whether in a formal setting or an informal team meeting. I've seen great ideas die because they were poorly presented, and mediocre ideas gain traction because they were presented well. Your technical skills got you to senior, but your presentation skills will determine how far you go beyond that.<\/p>\n<p>Here's something I wish someone had taught me earlier: know your audience and format before you build your presentation. A live presentation where you're persuading stakeholders needs minimal slides and compelling visuals\u2014you are the presentation, and your slides just support you. An RFC or architecture decision record that people will read asynchronously needs comprehensive detail and full context, because you won't be there to explain it.<\/p>\n<p>The worst mistake? Creating a dense document and trying to present it live, or creating a sparse slide deck and sending it to people expecting them to understand it without you. Match the format to the medium, and you'll save yourself hours of rework.<\/p>\n<h2 id=\"leadership-influence-over-authority\">Leadership: Influence Over Authority<\/h2>\n<p>Here's something that surprised me when I became a senior: leadership in tech isn't about wielding authority; it's about inspiring and guiding your team. You don't have direct reports (unless you transition to management), but you're expected to lead. This is what we call &quot;influence without authority,&quot; and it's one of the most important skills you'll develop.<\/p>\n<h3 id=\"leading-by-example\">Leading by Example<\/h3>\n<p>This sounds clich\u00e9, but it's true: demonstrate the behaviors and practices you want to see in your team. Leading by example builds trust and respect faster than any amount of talking.<\/p>\n<p>For instance, if you advocate for clean code, ensure your contributions to the codebase reflect those principles. I learned this when I was pushing my team to write better tests. I realized that some of my recent PRs had skimped on test coverage because I was rushing. How could I expect them to prioritize testing if I wasn't? So I made a point of writing comprehensive tests, adding thoughtful comments explaining my testing strategy, and during code reviews, I'd reference my own PRs as examples. The shift in team behavior was noticeable within weeks.<\/p>\n<p>If continuous learning is a value, share resources and lead knowledge-sharing sessions. I started a bi-weekly &quot;Tech Talk Tuesday&quot; where anyone could present something they learned\u2014a new tool, a design pattern, a debugging technique. I made sure to present first to set the tone and show it was okay to present on simple topics or to say &quot;I don't fully understand this yet, but here's what I've learned so far.&quot;<\/p>\n<p>Another true test of leadership is how you handle failure. When a production incident happens, it's easy to point fingers. But as a Senior Engineer, I learned to lead <strong>blameless post-mortems<\/strong>. Instead of asking &quot;Who broke this?&quot;, I asked &quot;How did our system allow this to happen?&quot; and &quot;What information were we missing?&quot; By shifting the focus from blame to process improvement, you don't just fix the bug\u2014you build psychological safety for the whole team. That's leadership.<\/p>\n<p>Here's the key: your team is watching you. If you say documentation is important but never update the docs, they won't either. If you say work-life balance matters but you're sending Slack messages at midnight, that's the culture you're creating. Lead by example, not by decree.<\/p>\n<h3 id=\"pushing-boundaries\">Pushing Boundaries<\/h3>\n<p>Growth comes from stepping out of your comfort zone, and as a senior, part of your job is to push both yourself and your team to grow. Whether it's learning a new technology, leading a challenging project, or advocating for a controversial architectural decision, continuous personal and professional development is key.<\/p>\n<p>Don't shy away from opportunities that stretch your abilities\u2014embrace them as a chance to grow. When my manager asked if I wanted to lead the migration from our monolith to microservices, my first thought was &quot;I've never done this at this scale before.&quot; My second thought was &quot;This is exactly why I should say yes.&quot; That project pushed me harder than anything in my career, and I learned more in those six months than in the previous two years.<\/p>\n<p>But here's the thing: pushing boundaries doesn't mean being reckless. It means calculated risks. It means doing your homework, building proof of concepts, getting feedback from trusted peers, and then going for it. Some of the best innovations I've seen came from engineers who said, &quot;I know this isn't how we've always done it, but what if we tried...&quot;<\/p>\n<p>Encourage your teammates to do the same. When a mid-level engineer proposes something ambitious, instead of listing all the reasons it might fail, ask them &quot;What would you need to make this work?&quot; and help them get there.<\/p>\n<h3 id=\"speaking-business\">Speaking Business<\/h3>\n<p><img src=\"https:\/\/platformtoolsmith.com\/assets\/blog\/gopher-roi.196f96f6.900.webp\" alt=\"A mole in an orange hardhat and a business tie points at a bar chart climbing steeply upward.\" width=\"1920\" height=\"1080\" loading=\"lazy\" decoding=\"async\" srcset=\"https:\/\/platformtoolsmith.com\/assets\/blog\/gopher-roi.196f96f6.400.webp 400w, https:\/\/platformtoolsmith.com\/assets\/blog\/gopher-roi.196f96f6.900.webp 900w, https:\/\/platformtoolsmith.com\/assets\/blog\/gopher-roi.196f96f6.1800.webp 1800w\" sizes=\"(min-width: 1024px) 773px, 100vw\" \/><\/p>\n<p>Understanding and speaking the language of business is crucial, and this is where many technical folks struggle. You need to master negotiation, understand risk, and use business terminology to align technical solutions with business objectives.<\/p>\n<p>Here's what I mean: when proposing a technical initiative, frame it in terms of business benefits\u2014how it will drive revenue, reduce costs, or improve customer satisfaction. Don't just say &quot;We need to refactor this service.&quot; Instead, say &quot;Refactoring this service will reduce our cloud costs by 30% and cut our time to ship new features from two weeks to two days, which means we can respond to customer requests faster than our competitors.&quot;<\/p>\n<p>Learn to speak in terms of ROI (Return on Investment), TCO (Total Cost of Ownership), and OKRs (Objectives and Key Results). When you're discussing technical debt, frame it as risk: &quot;This legacy system represents significant business risk. If it fails, we lose $50K per hour of downtime. Modernizing it is an investment in business continuity.&quot;<\/p>\n<p>I literally treat it like an equation: <code>(Current Pain $$ + Risk $$) &gt; (Migration Cost $$) = Green Light<\/code>. If I can't quantify the 'Current Pain' in dollars or time, I know I'm not ready to pitch it to leadership.<\/p>\n<p>I'll be honest: this felt uncomfortable at first. I became an engineer because I loved code, not because I wanted to think about quarterly revenue targets. But here's the reality: the work you do has to serve business goals. The better you are at connecting your technical work to business outcomes, the more autonomy you'll have, the more resources you'll get, and the more impact you'll make.<\/p>\n<p>Some practical ways to develop this skill: sit in on product planning meetings, read your company's quarterly reports, talk to your product managers about what customers are asking for, and most importantly, ask &quot;why&quot; about business decisions until you understand the reasoning.<\/p>\n<h2 id=\"keep-growing-keep-leading\">Keep Growing, Keep Leading<\/h2>\n<p>Your role as a Senior Engineer is complex and rewarding. The technical skills got you here, but these soft skills\u2014communication, presentation, and leadership\u2014will determine where you go next. By mastering these skills, you'll elevate not only your career but also those around you.<\/p>\n<p>Remember, every staff engineer, every principal engineer, every tech lead you admire got there not just because they were brilliant coders, but because they could communicate their brilliance, lead their teams, and create impact beyond their individual contributions.<\/p>\n<p>Stay curious, stay communicative, and keep leading the way. You've got this <i class=\"pixelart-icons-font-heart\"><\/i><\/p>\n<p>Communication is the leverage that multiplies your technical impact. Want to level up your leadership? Let's tackle your challenges together.<\/p>\n<a href=\"https:\/\/cal.com\/alexandrecastrotech\/mentoring\" class=\"text-primary-text-light dark:text-primary-text hover:opacity-80 transition-opacity flex items-center gap-2 font-bold uppercase tracking-wide min-h-11\">\n    <i class=\"pixelart-icons-font-calendar\"><\/i> Book a mentoring session\n<\/a>\n<h2 id=\"series-you-are-a-senior-engineer\">Series: You are a Senior Engineer<\/h2>\n<ul>\n<li><a href=\"https:\/\/platformtoolsmith.com\/blog\/senior-engineer-part-1\/\">You are a Senior Engineer, now what? (Part 1)<\/a><\/li>\n<li><a href=\"https:\/\/platformtoolsmith.com\/blog\/senior-engineer-part-2\/\">You are a Senior Engineer, Mastering Software Architecture and Design (Part 2)<\/a><\/li>\n<li><strong>You are a Senior Engineer, Mastering Communication &amp; Influence (Part 3)<\/strong><\/li>\n<\/ul>\n"},{"title":"Building a Custom AI Code Reviewer for GitHub Enterprise with Bedrock and Go","link":{"@attributes":{"href":"https:\/\/platformtoolsmith.com\/blog\/building-archbot-ai-code-reviewer\/"}},"updated":"2025-12-09T00:00:00Z","id":"https:\/\/platformtoolsmith.com\/blog\/building-archbot-ai-code-reviewer\/","content":"<p>Two weeks after deploying our custom AI code reviewer, it caught a bug that would have crashed our payment processing. A senior engineer's PR looked clean\u2014it compiled, passed linting, followed Go idioms. But the AI spotted a subtle pattern: we'd accidentally turned a soft dependency into a hard one. If our config service blinked, our main service would die.<\/p>\n<p>The engineer's response? &quot;Oh.&quot;<\/p>\n<p>That's when the team stopped treating <strong>Archbot<\/strong> like a toy.<\/p>\n<p>Here's the problem we were solving: we run <a href=\"https:\/\/github.com\/enterprise\">GitHub Enterprise<\/a> behind a VPN. Modern AI code review tools like <a href=\"https:\/\/cursor.sh\/\">Cursor<\/a>, <a href=\"https:\/\/github.com\/features\/copilot\">Github Copilot<\/a>, and <a href=\"https:\/\/coderabbit.ai\/\">CodeRabbit<\/a> expect cloud-hosted repos. We couldn't pipe proprietary code into them. But we still wanted AI code review on every PR.<\/p>\n<p>So I built it myself.<\/p>\n<p>This is the story of <strong>Archbot<\/strong>, the custom AI code reviewer I built to run securely inside enterprise networks. It started as a weekend hack (&quot;how hard could it be?&quot;). Spoiler alert: it didn't start well.<\/p>\n<p><strong>What I learned (and want to teach you):<\/strong><\/p>\n<ul>\n<li>Why naive git diff + LLM approaches fail<\/li>\n<li>How to manage context windows for large repos<\/li>\n<li>Using Bedrock Tool schemas for structured LLM output<\/li>\n<li>Two-phase AI architecture for intelligent filtering<\/li>\n<li>Production patterns for running AI tools in CI\/CD<\/li>\n<\/ul>\n<h2 id=\"why-git-diff-alone-fails-for-ai-code-review\">Why Git Diff Alone Fails for AI Code Review<\/h2>\n<p>My first iteration was painfully simple. I wrote a <a href=\"https:\/\/go.dev\/\">Go programming language<\/a> program that fetched the <code>git diff<\/code> of a PR and sent it to the LLM with a prompt like: &quot;Find bugs in this code.&quot;<\/p>\n<p>I felt like a genius for about five minutes. Then the results came in.<\/p>\n<p>The AI would confidently claim:<\/p>\n<blockquote class=\"alert-blockquote\">\n<div class=\"alert-header\">\n                [ <i class=\"pixelart-icons-font-bug\"><\/i> <span>OUTPUT<\/span> ]\n              <\/div><p><em>Variable <code>userRepository<\/code> is undefined.<\/em><\/p>\n<\/blockquote>\n<p>I\u2019d look at the code. It was imported right there at the top of the file. But because the <code>git diff<\/code> only shows <em>changed<\/em> lines (and a few lines of context), the AI couldn't see the imports.<\/p>\n<p>It was the equivalent of trying to review a book by reading only the sentences that were edited in the second draft. You lose the plot immediately.<\/p>\n<p><strong>Lesson learned:<\/strong> Context is king. Without the file definitions, imports, and surrounding scope, the AI is just guessing.<\/p>\n<p>So I went back to the drawing board.<\/p>\n<h2 id=\"git-hub-api-rate-limits-and-the-sequential-fetch-problem\">GitHub API Rate Limits and the Sequential Fetch Problem<\/h2>\n<p>My next attempt was obvious: don't just send the diff\u2014fetch the full files from GitHub's API.<\/p>\n<p>I updated the bot to parse the diff, identify which files were changed, and then use the GitHub API to fetch the <em>full content<\/em> of those files.<\/p>\n<p>This worked... until someone opened a PR that touched 40 files.<\/p>\n<p>My bot fired off 40 sequential API calls to GitHub.<br \/>\nGitHub fired back a <code>429 Too Many Requests<\/code>.<br \/>\nThe bot crashed.<br \/>\nThe developer waited 5 minutes for a review that never came.<\/p>\n<p>And even when it <em>did<\/em> work, it was slow. We were burning network time just shuttling strings around. I realized I was trying to rebuild <code>git clone<\/code> over HTTP, poorly.<\/p>\n<h2 id=\"using-repomix-to-pack-repository-context-for-ai\">Using Repomix to Pack Repository Context for AI<\/h2>\n<p>I needed a way to pack the repository context efficiently. That's when I found <strong><a href=\"https:\/\/github.com\/yamadashy\/repomix\">Repomix<\/a><\/strong> (formerly <code>repopack<\/code>).<\/p>\n<p>Repomix solves a subtle problem: AI models don't understand file systems. You can't just tar a repo and send it\u2014you need structure. Repomix walks your tree and outputs either XML or Markdown with clear file boundaries, respecting <code>.gitignore<\/code>, and including metadata like file sizes and token counts. It's like <code>pandoc<\/code> for codebases.<\/p>\n<p>Perfect, right?<\/p>\n<p>There was just one catch: <strong>Repomix is a Node.js tool.<\/strong><br \/>\n<strong>Archbot is written in Go.<\/strong><\/p>\n<p>Why didn't I just write the bot in Python\/TypeScript? Two reasons: <strong>Concurrency<\/strong> and <strong>Deployment<\/strong>. We needed to handle bursts of webhook events without spinning up heavy worker processes, and shipping a single static binary to our distroless containers keeps our security team happy.<\/p>\n<p>I didn't want to rewrite my entire bot in TypeScript. I didn't want to manage two separate deployment pipelines. And I definitely didn't want to write a Go port of Repomix (that's a two-month side quest).<\/p>\n<p>So I did what platform engineers do best: I made the tools work together.<\/p>\n<h3 id=\"the-multi-stage-build-solution\">The Multi-Stage Build Solution<\/h3>\n<p>The solution was a standard Docker multi-stage build: compile the Go binary in one stage, run it in a Node runtime in the second.<\/p>\n<pre class=\"language-dockerfile\"><code class=\"language-dockerfile\"><span class=\"token comment\"># Stage 1: Build the Go binary<\/span>\n<span class=\"token instruction\"><span class=\"token keyword\">FROM<\/span> golang:latest <span class=\"token keyword\">AS<\/span> builder<\/span>\n<span class=\"token instruction\"><span class=\"token keyword\">WORKDIR<\/span> \/app<\/span>\n<span class=\"token instruction\"><span class=\"token keyword\">COPY<\/span> . .<\/span>\n<span class=\"token instruction\"><span class=\"token keyword\">RUN<\/span> go mod download<\/span>\n<span class=\"token instruction\"><span class=\"token keyword\">RUN<\/span> CGO_ENABLED=0 GOOS=linux go build -a -o \/app\/archbot .\/cmd\/.<\/span>\n\n<span class=\"token comment\"># Stage 2: Runtime with Node for Repomix<\/span>\n<span class=\"token instruction\"><span class=\"token keyword\">FROM<\/span> node:lts-bullseye<\/span>\n<span class=\"token instruction\"><span class=\"token keyword\">RUN<\/span> apt-get update &amp;&amp; <span class=\"token operator\">\\<\/span>\n    apt-get install -y ca-certificates tzdata git &amp;&amp; <span class=\"token operator\">\\<\/span>\n    rm -rf \/var\/lib\/apt\/lists\/*<\/span>\n<span class=\"token instruction\"><span class=\"token keyword\">RUN<\/span> npm install -g repomix<\/span>\n\n<span class=\"token comment\"># Copy the pre-built Go binary from stage 1<\/span>\n<span class=\"token instruction\"><span class=\"token keyword\">COPY<\/span> <span class=\"token options\"><span class=\"token property\">--from<\/span><span class=\"token punctuation\">=<\/span><span class=\"token string\">builder<\/span><\/span> \/app\/archbot \/app\/<\/span>\n\n<span class=\"token instruction\"><span class=\"token keyword\">EXPOSE<\/span> 9000<\/span>\n<span class=\"token instruction\"><span class=\"token keyword\">CMD<\/span> [<span class=\"token string\">\"\/app\/archbot\"<\/span>]<\/span><\/code><\/pre>\n<p>We simply copy the compiled Go binary into a Node.js base image. The Go binary runs as the main process and uses <code>exec.Command<\/code> to shell out to the globally-installed <code>repomix<\/code> command when needed.<\/p>\n<p>It's not the purest architectural pattern, but it shipped the feature in 2 hours instead of 2 weeks. Sometimes, platform engineering is about making the tools work for you, not being a purist.<\/p>\n<h3 id=\"the-performance-win\">The Performance Win<\/h3>\n<p>Here's what this actually improved: <strong>we stopped hammering the GitHub API<\/strong>.<\/p>\n<p>When a PR comes in, Archbot clones the repo once (network call), runs <code>repomix<\/code> locally (disk I\/O), and gets a nice XML string of the codebase in ~2 seconds. Compare that to the &quot;Attempt 2&quot; approach where we made 40+ sequential API calls per PR.<\/p>\n<p>Git cloning is fast. S3 caching (we cache the repomix output) handles way more load than the on-prem GitHub API could. The bottleneck shifted from network to disk, which is exactly what you want.<\/p>\n<h2 id=\"context-window-management-for-large-repositories\">Context Window Management for Large Repositories<\/h2>\n<p>We deployed the Repomix version. It worked great for small microservices.<\/p>\n<p>Then we tried it on The Monolith\u2122.<\/p>\n<p>You know the one. Millions of lines of code. Hundreds of directories. When Repomix packed that repo, the resulting text file was massive\u2014way larger than the context window of even the generous 200k token models.<\/p>\n<p>And even if it <em>did<\/em> fit, sending 150k tokens for every simple bug fix is a great way to burn through your budget.<\/p>\n<p>I realized I couldn't just &quot;feed the repo&quot; to the AI. I had to replicate how <em>I<\/em> review code.<\/p>\n<p>When I review a PR, I don't read the entire codebase.<\/p>\n<ol>\n<li>I look at the diff.<\/li>\n<li>I check the file tree to see where the changes live.<\/li>\n<li>I open <em>only<\/em> the files that are relevant to the change to verify imports and usage.<\/li>\n<\/ol>\n<p>We needed a <strong>Two-Phase Architecture<\/strong>.<\/p>\n<p>Instead of blindly packing the entire repo, we split the review into two phases:<\/p>\n<div class=\"mermaid-container\">\n<div class=\"mermaid-diagram\" tabindex=\"0\">\n<svg class=\"dg-arch\" viewBox=\"-2 -2 964 984\" role=\"img\" aria-labelledby=\"dg-arch-title dg-arch-desc\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\">\n<title id=\"dg-arch-title\">How Archbot reviews a pull request in two phases<\/title>\n<desc id=\"dg-arch-desc\">A developer opens or updates a PR and the GitHub webhook triggers Archbot. Phase 1 sends the diff and directory structure to a Bedrock model, which selects files. Repomix packs those files into focused context. Phase 2 sends that context and the diff to a Bedrock model, which writes review comments. The GitHub API posts them to the pull request.<\/desc>\n<style>.dg-arch{display:block;width:100%;min-width:560px;height:auto;font-family:'Space Grotesk',system-ui,sans-serif}.dg-arch text{text-anchor:middle;fill:var(--mermaid-text, #000000)}.dg-arch .b{fill:var(--mermaid-node-bg, #FFFFFF);stroke:var(--mermaid-node-border, #000000);stroke-width:1.5}.dg-arch .grp{fill:none;stroke:var(--mermaid-node-border, #000000);stroke-width:1.5;stroke-dasharray:3 5}.dg-arch .band{font-size:13.8px;font-weight:700;letter-spacing:.08em}.dg-arch .t{font-size:17.5px;font-weight:700}.dg-arch .gt{font-size:14.4px;font-weight:700;text-anchor:start;fill:var(--theme-text-muted, #6A6A6A);paint-order:stroke;stroke:var(--mermaid-node-bg, #FFFFFF);stroke-width:6px;stroke-linejoin:round}.dg-arch .lab{font-size:14.4px;fill:var(--theme-text-muted, #6A6A6A);paint-order:stroke;stroke:var(--mermaid-node-bg, #FFFFFF);stroke-width:6px;stroke-linejoin:round}.dg-arch .ln{fill:none;stroke:var(--mermaid-line, #FF5C00);stroke-width:2}.dg-arch .life{fill:none;stroke:var(--mermaid-node-border, #000000);stroke-width:1.5;stroke-dasharray:3 5}.dg-arch .dash{stroke-dasharray:7 5}.dg-arch marker path{fill:var(--mermaid-line, #FF5C00)}<\/style>\n<defs><marker id=\"dg-arch-arrow\" viewBox=\"0 0 10 10\" refX=\"7\" refY=\"5\" markerWidth=\"7\" markerHeight=\"7\" orient=\"auto-start-reverse\"><path d=\"M0 0L10 5L0 10z\"><\/path><\/marker><\/defs>\n<rect class=\"grp\" x=\"0\" y=\"136\" width=\"960\" height=\"304\"><\/rect>\n<text class=\"gt\" x=\"12\" y=\"158\">Phase 1: Discovery<\/text>\n<rect class=\"grp\" x=\"0\" y=\"466\" width=\"590\" height=\"164\"><\/rect>\n<text class=\"gt\" x=\"12\" y=\"488\">Context Packing (Local)<\/text>\n<rect class=\"grp\" x=\"350\" y=\"656\" width=\"240\" height=\"324\"><\/rect>\n<text class=\"gt\" x=\"362\" y=\"678\">Phase 2: Deep Review<\/text>\n<path class=\"ln\" d=\"M200 50L368 50\" marker-end=\"url(#dg-arch-arrow)\"><\/path>\n<path class=\"ln\" d=\"M570 50L738 50\" marker-end=\"url(#dg-arch-arrow)\"><\/path>\n<path class=\"ln\" d=\"M840 100L840 178\" marker-end=\"url(#dg-arch-arrow)\"><\/path>\n<path class=\"ln\" d=\"M790 100L790 128L470 128L470 178\" marker-end=\"url(#dg-arch-arrow)\"><\/path>\n<path class=\"ln\" d=\"M470 280L470 318\" marker-end=\"url(#dg-arch-arrow)\"><\/path>\n<path class=\"ln\" d=\"M840 280L840 370L572 370\" marker-end=\"url(#dg-arch-arrow)\"><\/path>\n<path class=\"ln\" d=\"M370 370L212 370\" marker-end=\"url(#dg-arch-arrow)\"><\/path>\n<path class=\"ln\" d=\"M110 420L110 508\" marker-end=\"url(#dg-arch-arrow)\"><\/path>\n<path class=\"ln\" d=\"M210 560L368 560\" marker-end=\"url(#dg-arch-arrow)\"><\/path>\n<path class=\"ln\" d=\"M470 610L470 698\" marker-end=\"url(#dg-arch-arrow)\"><\/path>\n<path class=\"ln dash\" d=\"M570 230L655 230L655 774L572 774\" marker-end=\"url(#dg-arch-arrow)\"><\/path>\n<path class=\"ln\" d=\"M470 800L470 858\" marker-end=\"url(#dg-arch-arrow)\"><\/path>\n<path class=\"ln\" d=\"M570 910L738 910\" marker-end=\"url(#dg-arch-arrow)\"><\/path>\n<path class=\"ln\" d=\"M840 860L840 802\" marker-end=\"url(#dg-arch-arrow)\"><\/path>\n<rect class=\"b\" x=\"0\" y=\"0\" width=\"200\" height=\"100\"><\/rect>\n<rect x=\"0\" y=\"0\" width=\"200\" height=\"29\" style=\"fill:var(--diagram-origin, #A0694B)\"><\/rect>\n<text class=\"band\" x=\"100.0\" y=\"20\" style=\"fill:var(--diagram-origin-label, #000000)\">START<\/text>\n<text class=\"t\" x=\"100.0\" y=\"70.5\">Developer<\/text>\n<rect class=\"b\" x=\"370\" y=\"0\" width=\"200\" height=\"100\"><\/rect>\n<rect x=\"370\" y=\"0\" width=\"200\" height=\"29\" style=\"fill:var(--diagram-neutral, #F0F0DC)\"><\/rect>\n<text class=\"band\" x=\"470.0\" y=\"20\" style=\"fill:var(--diagram-neutral-label, #000000)\">TOOL<\/text>\n<text class=\"t\" x=\"470.0\" y=\"70.5\">GitHub Webhook<\/text>\n<rect class=\"b\" x=\"740\" y=\"0\" width=\"200\" height=\"100\"><\/rect>\n<rect x=\"740\" y=\"0\" width=\"200\" height=\"29\" style=\"fill:var(--diagram-act, #10C840)\"><\/rect>\n<text class=\"band\" x=\"840.0\" y=\"20\" style=\"fill:var(--diagram-act-label, #000000)\">EXECUTE<\/text>\n<text class=\"t\" x=\"840.0\" y=\"70.5\">Archbot Handler<\/text>\n<rect class=\"b\" x=\"370\" y=\"180\" width=\"200\" height=\"100\"><\/rect>\n<rect x=\"370\" y=\"180\" width=\"200\" height=\"29\" style=\"fill:var(--diagram-neutral, #F0F0DC)\"><\/rect>\n<text class=\"band\" x=\"470.0\" y=\"200\" style=\"fill:var(--diagram-neutral-label, #000000)\">INPUT<\/text>\n<text class=\"t\" x=\"470.0\" y=\"250.5\">Git Diff<\/text>\n<rect class=\"b\" x=\"740\" y=\"180\" width=\"200\" height=\"100\"><\/rect>\n<rect x=\"740\" y=\"180\" width=\"200\" height=\"29\" style=\"fill:var(--diagram-neutral, #F0F0DC)\"><\/rect>\n<text class=\"band\" x=\"840.0\" y=\"200\" style=\"fill:var(--diagram-neutral-label, #000000)\">INPUT<\/text>\n<text class=\"t\" x=\"840.0\" y=\"238.5\">Directory<\/text>\n<text class=\"t\" x=\"840.0\" y=\"262.5\">Structure<\/text>\n<rect class=\"b\" x=\"370\" y=\"320\" width=\"200\" height=\"100\"><\/rect>\n<rect x=\"370\" y=\"320\" width=\"200\" height=\"29\" style=\"fill:var(--diagram-define, #00A0C8)\"><\/rect>\n<text class=\"band\" x=\"470.0\" y=\"340\" style=\"fill:var(--diagram-define-label, #000000)\">SELECT<\/text>\n<text class=\"t\" x=\"470.0\" y=\"390.5\">Bedrock LLM<\/text>\n<rect class=\"b\" x=\"10\" y=\"320\" width=\"200\" height=\"100\"><\/rect>\n<rect x=\"10\" y=\"320\" width=\"200\" height=\"29\" style=\"fill:var(--diagram-neutral, #F0F0DC)\"><\/rect>\n<text class=\"band\" x=\"110.0\" y=\"340\" style=\"fill:var(--diagram-neutral-label, #000000)\">OUTPUT<\/text>\n<text class=\"t\" x=\"110.0\" y=\"378.5\">Selected Files<\/text>\n<text class=\"t\" x=\"110.0\" y=\"402.5\">JSON<\/text>\n<rect class=\"b\" x=\"10\" y=\"510\" width=\"200\" height=\"100\"><\/rect>\n<rect x=\"10\" y=\"510\" width=\"200\" height=\"29\" style=\"fill:var(--diagram-act, #10C840)\"><\/rect>\n<text class=\"band\" x=\"110.0\" y=\"530\" style=\"fill:var(--diagram-act-label, #000000)\">PACK<\/text>\n<text class=\"t\" x=\"110.0\" y=\"580.5\">Repomix<\/text>\n<rect class=\"b\" x=\"370\" y=\"510\" width=\"200\" height=\"100\"><\/rect>\n<rect x=\"370\" y=\"510\" width=\"200\" height=\"29\" style=\"fill:var(--diagram-neutral, #F0F0DC)\"><\/rect>\n<text class=\"band\" x=\"470.0\" y=\"530\" style=\"fill:var(--diagram-neutral-label, #000000)\">OUTPUT<\/text>\n<text class=\"t\" x=\"470.0\" y=\"580.5\">Focused Context<\/text>\n<rect class=\"b\" x=\"370\" y=\"700\" width=\"200\" height=\"100\"><\/rect>\n<rect x=\"370\" y=\"700\" width=\"200\" height=\"29\" style=\"fill:var(--diagram-act, #10C840)\"><\/rect>\n<text class=\"band\" x=\"470.0\" y=\"720\" style=\"fill:var(--diagram-act-label, #000000)\">REVIEW<\/text>\n<text class=\"t\" x=\"470.0\" y=\"770.5\">Bedrock LLM<\/text>\n<rect class=\"b\" x=\"370\" y=\"860\" width=\"200\" height=\"100\"><\/rect>\n<rect x=\"370\" y=\"860\" width=\"200\" height=\"29\" style=\"fill:var(--diagram-decide, #D24040)\"><\/rect>\n<text class=\"band\" x=\"470.0\" y=\"880\" style=\"fill:var(--diagram-decide-label, #FFFFFF)\">WRITE<\/text>\n<text class=\"t\" x=\"470.0\" y=\"918.5\">Review Comments<\/text>\n<text class=\"t\" x=\"470.0\" y=\"942.5\">JSON<\/text>\n<rect class=\"b\" x=\"740\" y=\"860\" width=\"200\" height=\"100\"><\/rect>\n<rect x=\"740\" y=\"860\" width=\"200\" height=\"29\" style=\"fill:var(--diagram-neutral, #F0F0DC)\"><\/rect>\n<text class=\"band\" x=\"840.0\" y=\"880\" style=\"fill:var(--diagram-neutral-label, #000000)\">TOOL<\/text>\n<text class=\"t\" x=\"840.0\" y=\"930.5\">GitHub API<\/text>\n<rect class=\"b\" x=\"740\" y=\"700\" width=\"200\" height=\"100\"><\/rect>\n<rect x=\"740\" y=\"700\" width=\"200\" height=\"29\" style=\"fill:var(--diagram-neutral, #F0F0DC)\"><\/rect>\n<text class=\"band\" x=\"840.0\" y=\"720\" style=\"fill:var(--diagram-neutral-label, #000000)\">OUTPUT<\/text>\n<text class=\"t\" x=\"840.0\" y=\"770.5\">Pull Request<\/text>\n<text class=\"lab\" x=\"285\" y=\"42\" style=\"text-anchor:middle\">Opens\/Updates PR<\/text>\n<text class=\"lab\" x=\"655\" y=\"42\" style=\"text-anchor:middle\">Triggers<\/text>\n<text class=\"lab\" x=\"290\" y=\"362\" style=\"text-anchor:middle\">Tool: select_files<\/text>\n<text class=\"lab\" x=\"470\" y=\"840\" style=\"text-anchor:middle\">Tool: perform_code_review<\/text>\n<text class=\"lab\" x=\"840\" y=\"840\" style=\"text-anchor:middle\">Posts Comments<\/text>\n<\/svg>\n<\/div>\n<\/div>\n<h3 id=\"phase-1-intelligent-file-selection\">Phase 1: Intelligent File Selection<\/h3>\n<p>Instead of sending the code immediately, we first send the AI the <strong>Directory Structure<\/strong> and the <strong>Diff<\/strong>.<\/p>\n<p>We ask it a simple question: <em>&quot;Given these changes, which files do you need to read to verify correctness?&quot;<\/em><\/p>\n<p>The system prompt for this phase is carefully engineered to guide the AI's selection:<\/p>\n<pre class=\"language-prompt\"><code class=\"language-prompt\">You are a senior code reviewer. You must use the select_files tool to identify\nwhich files are needed to verify the correctness of these changes. Consider:\nimports, interface definitions, test coverage, and related business logic.\n\nBe selective\u2014choose only files directly relevant to understanding the change.\nIf a PR modifies a handler, you likely need the service it calls and any\ninterfaces it implements. You don't need unrelated files in the same directory.<\/code><\/pre>\n<p>The AI is surprisingly good at this. It will say:<\/p>\n<blockquote class=\"alert-blockquote\">\n<div class=\"alert-header\">\n                [ <i class=\"pixelart-icons-font-lightbulb\"><\/i> <span>OUTPUT<\/span> ]\n              <\/div><p><em>&quot;I see you modified <code>user_handler.go<\/code>. I need to see <code>user_service.go<\/code> to check the interface and <code>user_handler_test.go<\/code> to verify coverage.&quot;<\/em><\/p>\n<\/blockquote>\n<h4 id=\"forcing-structured-responses-with-bedrock-tools\">Forcing Structured Responses with Bedrock Tools<\/h4>\n<p>Cool, but how do we actually tell Bedrock what format to return?<\/p>\n<p>This is where <a href=\"https:\/\/docs.aws.amazon.com\/bedrock\/latest\/userguide\/tool-use.html\">AWS Bedrock Tools<\/a> become critical. Instead of hoping the LLM returns parseable text, you define a JSON schema that <strong>forces<\/strong> the model to return structured data in exactly the format you specify.<\/p>\n<p>The model guarantees it returns valid JSON matching that schema. No regex parsing. No &quot;I hope the AI formatted this correctly.&quot; Just typed data you can unmarshal directly into a Go struct.<\/p>\n<p>We use this pattern for both phases: Phase 1 uses a <code>select_files<\/code> tool schema (returns an array of file paths), and Phase 2 uses a <code>perform_code_review<\/code> tool schema (returns structured review comments with severity levels and inline suggestions). The AI can't return freeform markdown even if it wants to. The schema is the contract.<\/p>\n<p>We'll see the actual code for these schemas in the implementation section below.<\/p>\n<h3 id=\"phase-2-the-focused-review\">Phase 2: The Focused Review<\/h3>\n<p>Now that we have the list of 5-10 relevant files, we use Repomix to pack them into a focused context. But here's the thing\u2014even those selected files come with baggage.<\/p>\n<p>Generated code. Vendored dependencies. Lock files. Binary artifacts. Test fixtures.<\/p>\n<p>This is where <strong>Repomix's exclusion list feature<\/strong> becomes critical. Instead of naively packing everything, Repomix lets you define exclusion patterns that filter out files that don't make sense for AI review. Think <code>.gitignore<\/code>, but specifically tuned for context packing.<\/p>\n<p>We configure Repomix to exclude:<\/p>\n<ul>\n<li>Generated proto files and mocks<\/li>\n<li>Third-party vendored code<\/li>\n<li>Large JSON fixtures and test data<\/li>\n<li>Binary assets and compiled artifacts<\/li>\n<li>Package lock files (sorry <code>package-lock.json<\/code>, you're 50k lines of noise)<\/li>\n<\/ul>\n<p>The result? I take the selected files and pack them cleanly\u2014stripping out the noise that would confuse the AI or burn tokens unnecessarily.<\/p>\n<p>This reduces the context size from ~100k tokens (full repo) to ~5k tokens (filtered, focused context). It's faster, cheaper, and because the noise is removed, the AI hallucinates less.<\/p>\n<p>Let's talk money. The naive approach of sending 150k input tokens plus ~5k output tokens to <a href=\"https:\/\/www.anthropic.com\/news\/claude-haiku-4-5\">Claude Haiku 4.5<\/a> via <a href=\"https:\/\/aws.amazon.com\/bedrock\/\">Bedrock<\/a> would cost roughly $0.18-0.20 per review (based on direct API pricing of $1\/$5 per million input\/output tokens\u2014Bedrock pricing may vary). With the two-phase approach and Repomix's exclusion filtering, I'm averaging 8k input tokens and ~2.5k output tokens per review\u2014about $0.02 per review. At 50 PRs\/day, that's the difference between $300\/month (naive) vs $30\/month (optimized). The two-phase architecture pays for itself immediately.<\/p>\n<h2 id=\"implementation-bedrock-tools-and-structured-llm-output\">Implementation: Bedrock Tools and Structured LLM Output<\/h2>\n<p>Okay, enough story time. Let's look at the code.<\/p>\n<p>The secret sauce isn't just the prompting\u2014it's forcing the LLM to return <strong>structured data<\/strong>. Instead of parsing freeform markdown (nightmare fuel), we use AWS Bedrock's &quot;Tool&quot; feature\u2014essentially a JSON schema that constrains the model's output. You define the shape you want, and the model guarantees it returns valid JSON matching that schema. No regex. No &quot;I hope the AI formatted this correctly.&quot; Just typed data.<\/p>\n<p>One quick note: we're using Bedrock's <strong>Converse API<\/strong>, not the older <code>InvokeModel<\/code> approach that required raw JSON payload construction. The Converse API handles message formatting, tool schemas, and multi-turn conversations for you. If you're still using <code>InvokeModel<\/code>, upgrade\u2014it'll save you hours of JSON wrangling.<\/p>\n<h3 id=\"step-1-the-contract-bedrock-tools\">Step 1: The Contract (Bedrock Tools)<\/h3>\n<p>First, we define the schema for our &quot;tools&quot;. In AWS Bedrock (or OpenAI), this tells the model exactly what JSON format we expect back.<\/p>\n<p>Here is the <code>select_files<\/code> tool. Note how we explicitly ask for an array of file paths and a reasoning field.<\/p>\n<pre class=\"language-go\"><code class=\"language-go\"><span class=\"token comment\">\/\/ Imports:<\/span>\n<span class=\"token comment\">\/\/ \"github.com\/aws\/aws-sdk-go-v2\/service\/bedrockruntime\/types\"<\/span>\n<span class=\"token comment\">\/\/ \"github.com\/aws\/aws-sdk-go-v2\/service\/bedrockruntime\/document\"<\/span>\n\n<span class=\"token comment\">\/\/ BuildSelectFilesTool creates a tool for selecting relevant files from a PR<\/span>\n<span class=\"token keyword\">func<\/span> <span class=\"token function\">BuildSelectFilesTool<\/span><span class=\"token punctuation\">(<\/span><span class=\"token punctuation\">)<\/span> <span class=\"token operator\">*<\/span>bedrockTypes<span class=\"token punctuation\">.<\/span>ToolConfiguration <span class=\"token punctuation\">{<\/span>\n    schemaMap <span class=\"token operator\">:=<\/span> <span class=\"token keyword\">map<\/span><span class=\"token punctuation\">[<\/span><span class=\"token builtin\">string<\/span><span class=\"token punctuation\">]<\/span><span class=\"token keyword\">interface<\/span><span class=\"token punctuation\">{<\/span><span class=\"token punctuation\">}<\/span><span class=\"token punctuation\">{<\/span>\n        <span class=\"token string\">\"type\"<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token string\">\"object\"<\/span><span class=\"token punctuation\">,<\/span>\n        <span class=\"token string\">\"properties\"<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token keyword\">map<\/span><span class=\"token punctuation\">[<\/span><span class=\"token builtin\">string<\/span><span class=\"token punctuation\">]<\/span><span class=\"token keyword\">interface<\/span><span class=\"token punctuation\">{<\/span><span class=\"token punctuation\">}<\/span><span class=\"token punctuation\">{<\/span>\n            <span class=\"token string\">\"files\"<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token keyword\">map<\/span><span class=\"token punctuation\">[<\/span><span class=\"token builtin\">string<\/span><span class=\"token punctuation\">]<\/span><span class=\"token keyword\">interface<\/span><span class=\"token punctuation\">{<\/span><span class=\"token punctuation\">}<\/span><span class=\"token punctuation\">{<\/span>\n                <span class=\"token string\">\"type\"<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token string\">\"array\"<\/span><span class=\"token punctuation\">,<\/span>\n                <span class=\"token string\">\"items\"<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token keyword\">map<\/span><span class=\"token punctuation\">[<\/span><span class=\"token builtin\">string<\/span><span class=\"token punctuation\">]<\/span><span class=\"token keyword\">interface<\/span><span class=\"token punctuation\">{<\/span><span class=\"token punctuation\">}<\/span><span class=\"token punctuation\">{<\/span>\n                    <span class=\"token string\">\"type\"<\/span><span class=\"token punctuation\">:<\/span>        <span class=\"token string\">\"string\"<\/span><span class=\"token punctuation\">,<\/span>\n                    <span class=\"token string\">\"description\"<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token string\">\"File path relative to repo root (e.g., 'src\/main.go')\"<\/span><span class=\"token punctuation\">,<\/span>\n                <span class=\"token punctuation\">}<\/span><span class=\"token punctuation\">,<\/span>\n                <span class=\"token string\">\"description\"<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token string\">\"List of relevant file paths to analyze.\"<\/span><span class=\"token punctuation\">,<\/span>\n            <span class=\"token punctuation\">}<\/span><span class=\"token punctuation\">,<\/span>\n            <span class=\"token string\">\"reasoning\"<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token keyword\">map<\/span><span class=\"token punctuation\">[<\/span><span class=\"token builtin\">string<\/span><span class=\"token punctuation\">]<\/span><span class=\"token keyword\">interface<\/span><span class=\"token punctuation\">{<\/span><span class=\"token punctuation\">}<\/span><span class=\"token punctuation\">{<\/span>\n                <span class=\"token string\">\"type\"<\/span><span class=\"token punctuation\">:<\/span>        <span class=\"token string\">\"string\"<\/span><span class=\"token punctuation\">,<\/span>\n                <span class=\"token string\">\"description\"<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token string\">\"Explanation of why these files were selected\"<\/span><span class=\"token punctuation\">,<\/span>\n            <span class=\"token punctuation\">}<\/span><span class=\"token punctuation\">,<\/span>\n        <span class=\"token punctuation\">}<\/span><span class=\"token punctuation\">,<\/span>\n        <span class=\"token string\">\"required\"<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token punctuation\">[<\/span><span class=\"token punctuation\">]<\/span><span class=\"token builtin\">string<\/span><span class=\"token punctuation\">{<\/span><span class=\"token string\">\"files\"<\/span><span class=\"token punctuation\">,<\/span> <span class=\"token string\">\"reasoning\"<\/span><span class=\"token punctuation\">}<\/span><span class=\"token punctuation\">,<\/span>\n    <span class=\"token punctuation\">}<\/span>\n\n    <span class=\"token comment\">\/\/ Wrap the schema in Bedrock's ToolConfiguration<\/span>\n    toolSpec <span class=\"token operator\">:=<\/span> <span class=\"token operator\">&amp;<\/span>bedrockTypes<span class=\"token punctuation\">.<\/span>ToolMemberToolSpec<span class=\"token punctuation\">{<\/span>\n        Value<span class=\"token punctuation\">:<\/span> bedrockTypes<span class=\"token punctuation\">.<\/span>ToolSpecification<span class=\"token punctuation\">{<\/span>\n            Name<span class=\"token punctuation\">:<\/span>        aws<span class=\"token punctuation\">.<\/span><span class=\"token function\">String<\/span><span class=\"token punctuation\">(<\/span><span class=\"token string\">\"select_files\"<\/span><span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">,<\/span>\n            Description<span class=\"token punctuation\">:<\/span> aws<span class=\"token punctuation\">.<\/span><span class=\"token function\">String<\/span><span class=\"token punctuation\">(<\/span><span class=\"token string\">\"Select the most relevant files for analysis based on the PR diff\"<\/span><span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">,<\/span>\n            InputSchema<span class=\"token punctuation\">:<\/span> <span class=\"token operator\">&amp;<\/span>bedrockTypes<span class=\"token punctuation\">.<\/span>ToolInputSchemaMemberJson<span class=\"token punctuation\">{<\/span>\n                Value<span class=\"token punctuation\">:<\/span> document<span class=\"token punctuation\">.<\/span><span class=\"token function\">NewLazyDocument<\/span><span class=\"token punctuation\">(<\/span>schemaMap<span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">,<\/span>\n            <span class=\"token punctuation\">}<\/span><span class=\"token punctuation\">,<\/span>\n        <span class=\"token punctuation\">}<\/span><span class=\"token punctuation\">,<\/span>\n    <span class=\"token punctuation\">}<\/span>\n\n    <span class=\"token keyword\">return<\/span> <span class=\"token operator\">&amp;<\/span>bedrockTypes<span class=\"token punctuation\">.<\/span>ToolConfiguration<span class=\"token punctuation\">{<\/span>\n        Tools<span class=\"token punctuation\">:<\/span> <span class=\"token punctuation\">[<\/span><span class=\"token punctuation\">]<\/span>bedrockTypes<span class=\"token punctuation\">.<\/span>Tool<span class=\"token punctuation\">{<\/span>toolSpec<span class=\"token punctuation\">}<\/span><span class=\"token punctuation\">,<\/span>\n        ToolChoice<span class=\"token punctuation\">:<\/span> <span class=\"token operator\">&amp;<\/span>bedrockTypes<span class=\"token punctuation\">.<\/span>ToolChoiceMemberTool<span class=\"token punctuation\">{<\/span>\n            Value<span class=\"token punctuation\">:<\/span> bedrockTypes<span class=\"token punctuation\">.<\/span>SpecificToolChoice<span class=\"token punctuation\">{<\/span>\n                Name<span class=\"token punctuation\">:<\/span> aws<span class=\"token punctuation\">.<\/span><span class=\"token function\">String<\/span><span class=\"token punctuation\">(<\/span><span class=\"token string\">\"select_files\"<\/span><span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">,<\/span>\n            <span class=\"token punctuation\">}<\/span><span class=\"token punctuation\">,<\/span>\n        <span class=\"token punctuation\">}<\/span><span class=\"token punctuation\">,<\/span>\n    <span class=\"token punctuation\">}<\/span>\n<span class=\"token punctuation\">}<\/span><\/code><\/pre>\n<p>This schema tells Bedrock: &quot;You must return a JSON object with a <code>files<\/code> array of strings and a <code>reasoning<\/code> string. No other format is acceptable.&quot;<\/p>\n<p>When you pass this tool configuration to the Converse API, Bedrock guarantees it returns valid JSON matching that schema. The same pattern applies to Phase 2\u2014we define a <code>perform_code_review<\/code> tool with fields for severity levels, inline comments, and code suggestions.<\/p>\n<h3 id=\"step-2-phase-1-file-selection\">Step 2: Phase 1 - File Selection<\/h3>\n<p>When a PR comes in, we grab the diff and the file tree (Repomix gives us a nice &quot;Directory Structure&quot; summary we can reuse). We send that to the model.<\/p>\n<p>Here's the core logic from <code>selectFilesForReview<\/code>. This is production code with the defensive patterns you need when running this on every PR across 30 repositories.<\/p>\n<pre class=\"language-go\"><code class=\"language-go\"><span class=\"token keyword\">func<\/span> <span class=\"token punctuation\">(<\/span>r <span class=\"token operator\">*<\/span>CodeReviewAnalyzer<span class=\"token punctuation\">)<\/span> <span class=\"token function\">selectFilesForReview<\/span><span class=\"token punctuation\">(<\/span>ctx context<span class=\"token punctuation\">.<\/span>Context<span class=\"token punctuation\">,<\/span> prDiff <span class=\"token builtin\">string<\/span><span class=\"token punctuation\">)<\/span> <span class=\"token punctuation\">(<\/span><span class=\"token punctuation\">[<\/span><span class=\"token punctuation\">]<\/span><span class=\"token builtin\">string<\/span><span class=\"token punctuation\">,<\/span> <span class=\"token builtin\">error<\/span><span class=\"token punctuation\">)<\/span> <span class=\"token punctuation\">{<\/span>\n    <span class=\"token comment\">\/\/ Add timeout for production resilience<\/span>\n    ctx<span class=\"token punctuation\">,<\/span> cancel <span class=\"token operator\">:=<\/span> context<span class=\"token punctuation\">.<\/span><span class=\"token function\">WithTimeout<\/span><span class=\"token punctuation\">(<\/span>ctx<span class=\"token punctuation\">,<\/span> <span class=\"token number\">30<\/span><span class=\"token operator\">*<\/span>time<span class=\"token punctuation\">.<\/span>Second<span class=\"token punctuation\">)<\/span>\n    <span class=\"token keyword\">defer<\/span> <span class=\"token function\">cancel<\/span><span class=\"token punctuation\">(<\/span><span class=\"token punctuation\">)<\/span>\n\n    <span class=\"token comment\">\/\/ 1. Get directory structure from Repomix (cached)<\/span>\n    repoStructure <span class=\"token operator\">:=<\/span> r<span class=\"token punctuation\">.<\/span>ChangeContext<span class=\"token punctuation\">.<\/span>Repomix<span class=\"token punctuation\">.<\/span>DirectoryStructure\n\n    <span class=\"token comment\">\/\/ 2. Build the prompt asking \"Which files do you need?\"<\/span>\n    phase1Prompt <span class=\"token operator\">:=<\/span> fmt<span class=\"token punctuation\">.<\/span><span class=\"token function\">Sprintf<\/span><span class=\"token punctuation\">(<\/span>SelectFilesForReviewPrompt<span class=\"token punctuation\">,<\/span> repoStructure<span class=\"token punctuation\">,<\/span> prDiff<span class=\"token punctuation\">)<\/span>\n\n    <span class=\"token comment\">\/\/ 3. Call Bedrock with the Tool Configuration<\/span>\n    toolConfig <span class=\"token operator\">:=<\/span> bedrock<span class=\"token punctuation\">.<\/span><span class=\"token function\">BuildSelectFilesTool<\/span><span class=\"token punctuation\">(<\/span><span class=\"token punctuation\">)<\/span>\n\n    response<span class=\"token punctuation\">,<\/span> err <span class=\"token operator\">:=<\/span> r<span class=\"token punctuation\">.<\/span>BedrockClient<span class=\"token punctuation\">.<\/span><span class=\"token function\">Converse<\/span><span class=\"token punctuation\">(<\/span>ctx<span class=\"token punctuation\">,<\/span> SystemPrompt<span class=\"token punctuation\">,<\/span> phase1Prompt<span class=\"token punctuation\">,<\/span> toolConfig<span class=\"token punctuation\">)<\/span>\n    <span class=\"token keyword\">if<\/span> err <span class=\"token operator\">!=<\/span> <span class=\"token boolean\">nil<\/span> <span class=\"token punctuation\">{<\/span>\n        <span class=\"token keyword\">return<\/span> <span class=\"token boolean\">nil<\/span><span class=\"token punctuation\">,<\/span> fmt<span class=\"token punctuation\">.<\/span><span class=\"token function\">Errorf<\/span><span class=\"token punctuation\">(<\/span><span class=\"token string\">\"bedrock converse failed: %w\"<\/span><span class=\"token punctuation\">,<\/span> err<span class=\"token punctuation\">)<\/span>\n    <span class=\"token punctuation\">}<\/span>\n\n    <span class=\"token comment\">\/\/ 4. Extract structured tool response<\/span>\n    toolJSON<span class=\"token punctuation\">,<\/span> err <span class=\"token operator\">:=<\/span> bedrock<span class=\"token punctuation\">.<\/span><span class=\"token function\">ExtractToolJSON<\/span><span class=\"token punctuation\">(<\/span>response<span class=\"token punctuation\">)<\/span>\n    <span class=\"token keyword\">if<\/span> err <span class=\"token operator\">!=<\/span> <span class=\"token boolean\">nil<\/span> <span class=\"token punctuation\">{<\/span>\n        <span class=\"token keyword\">return<\/span> <span class=\"token boolean\">nil<\/span><span class=\"token punctuation\">,<\/span> fmt<span class=\"token punctuation\">.<\/span><span class=\"token function\">Errorf<\/span><span class=\"token punctuation\">(<\/span><span class=\"token string\">\"failed to extract tool response: %w\"<\/span><span class=\"token punctuation\">,<\/span> err<span class=\"token punctuation\">)<\/span>\n    <span class=\"token punctuation\">}<\/span>\n\n    <span class=\"token comment\">\/\/ 5. Unmarshal into typed struct<\/span>\n    <span class=\"token keyword\">var<\/span> selectResponse <span class=\"token keyword\">struct<\/span> <span class=\"token punctuation\">{<\/span>\n        Files     <span class=\"token punctuation\">[<\/span><span class=\"token punctuation\">]<\/span><span class=\"token builtin\">string<\/span> <span class=\"token string\">`json:\"files\"`<\/span>\n        Reasoning <span class=\"token builtin\">string<\/span>   <span class=\"token string\">`json:\"reasoning\"`<\/span>\n    <span class=\"token punctuation\">}<\/span>\n\n    <span class=\"token keyword\">if<\/span> err <span class=\"token operator\">:=<\/span> json<span class=\"token punctuation\">.<\/span><span class=\"token function\">Unmarshal<\/span><span class=\"token punctuation\">(<\/span>toolJSON<span class=\"token punctuation\">,<\/span> <span class=\"token operator\">&amp;<\/span>selectResponse<span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span> err <span class=\"token operator\">!=<\/span> <span class=\"token boolean\">nil<\/span> <span class=\"token punctuation\">{<\/span>\n        <span class=\"token keyword\">return<\/span> <span class=\"token boolean\">nil<\/span><span class=\"token punctuation\">,<\/span> fmt<span class=\"token punctuation\">.<\/span><span class=\"token function\">Errorf<\/span><span class=\"token punctuation\">(<\/span><span class=\"token string\">\"invalid json structure: %w\"<\/span><span class=\"token punctuation\">,<\/span> err<span class=\"token punctuation\">)<\/span>\n    <span class=\"token punctuation\">}<\/span>\n\n    <span class=\"token comment\">\/\/ 6. Guard against runaway file selection<\/span>\n    <span class=\"token keyword\">if<\/span> <span class=\"token function\">len<\/span><span class=\"token punctuation\">(<\/span>selectResponse<span class=\"token punctuation\">.<\/span>Files<span class=\"token punctuation\">)<\/span> <span class=\"token operator\">><\/span> <span class=\"token number\">20<\/span> <span class=\"token punctuation\">{<\/span>\n        slog<span class=\"token punctuation\">.<\/span><span class=\"token function\">Warn<\/span><span class=\"token punctuation\">(<\/span><span class=\"token string\">\"AI selected too many files, truncating\"<\/span><span class=\"token punctuation\">,<\/span>\n            <span class=\"token string\">\"requested\"<\/span><span class=\"token punctuation\">,<\/span> <span class=\"token function\">len<\/span><span class=\"token punctuation\">(<\/span>selectResponse<span class=\"token punctuation\">.<\/span>Files<span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">,<\/span>\n            <span class=\"token string\">\"limit\"<\/span><span class=\"token punctuation\">,<\/span> <span class=\"token number\">20<\/span><span class=\"token punctuation\">)<\/span>\n        selectResponse<span class=\"token punctuation\">.<\/span>Files <span class=\"token operator\">=<\/span> selectResponse<span class=\"token punctuation\">.<\/span>Files<span class=\"token punctuation\">[<\/span><span class=\"token punctuation\">:<\/span><span class=\"token number\">20<\/span><span class=\"token punctuation\">]<\/span>\n    <span class=\"token punctuation\">}<\/span>\n\n    <span class=\"token keyword\">return<\/span> selectResponse<span class=\"token punctuation\">.<\/span>Files<span class=\"token punctuation\">,<\/span> <span class=\"token boolean\">nil<\/span>\n<span class=\"token punctuation\">}<\/span><\/code><\/pre>\n<p>Notice the defensive programming: timeouts with <code>context.WithTimeout<\/code>, explicit error wrapping with <code>fmt.Errorf<\/code>, structured logging with <code>slog.Warn<\/code>, and the guardrail cap that truncates oversized file selections. When you're running this on every PR across 30 repositories, you need to assume the AI will occasionally hallucinate, the network will flake, or someone will open a 500-file refactor. The first week we deployed this, the AI tried to select 47 files for a 3-line CSS change. Without the cap, we would have blown our context window budget. Production systems need guardrails.<\/p>\n<h3 id=\"step-3-packing-context-the-repomix-magic\">Step 3: Packing Context (The Repomix Magic)<\/h3>\n<p>Now that we have the <code>selectedFiles<\/code> list, we don't need to re-clone anything. We have the Repomix data structure in memory (or cached). We just filter it to extract the context we need.<\/p>\n<pre class=\"language-go\"><code class=\"language-go\"><span class=\"token comment\">\/\/ BuildFileContext filters RepomixData to selected files and returns a packed string.<\/span>\n<span class=\"token comment\">\/\/ Note: This example shows XML format for illustration. The production version outputs<\/span>\n<span class=\"token comment\">\/\/ JSON using structured serialization for better type safety and easier parsing.<\/span>\n<span class=\"token keyword\">func<\/span> <span class=\"token punctuation\">(<\/span>r <span class=\"token operator\">*<\/span>Runner<span class=\"token punctuation\">)<\/span> <span class=\"token function\">BuildFileContext<\/span><span class=\"token punctuation\">(<\/span>data <span class=\"token operator\">*<\/span>RepomixData<span class=\"token punctuation\">,<\/span> selectedFiles <span class=\"token punctuation\">[<\/span><span class=\"token punctuation\">]<\/span><span class=\"token builtin\">string<\/span><span class=\"token punctuation\">)<\/span> <span class=\"token punctuation\">(<\/span><span class=\"token builtin\">string<\/span><span class=\"token punctuation\">,<\/span> <span class=\"token builtin\">error<\/span><span class=\"token punctuation\">)<\/span> <span class=\"token punctuation\">{<\/span>\n    <span class=\"token comment\">\/\/ 1. Create a lookup map for O(1) checks<\/span>\n    keep <span class=\"token operator\">:=<\/span> <span class=\"token function\">make<\/span><span class=\"token punctuation\">(<\/span><span class=\"token keyword\">map<\/span><span class=\"token punctuation\">[<\/span><span class=\"token builtin\">string<\/span><span class=\"token punctuation\">]<\/span><span class=\"token builtin\">bool<\/span><span class=\"token punctuation\">)<\/span>\n    <span class=\"token keyword\">for<\/span> <span class=\"token boolean\">_<\/span><span class=\"token punctuation\">,<\/span> f <span class=\"token operator\">:=<\/span> <span class=\"token keyword\">range<\/span> selectedFiles <span class=\"token punctuation\">{<\/span>\n        keep<span class=\"token punctuation\">[<\/span>f<span class=\"token punctuation\">]<\/span> <span class=\"token operator\">=<\/span> <span class=\"token boolean\">true<\/span>\n    <span class=\"token punctuation\">}<\/span>\n\n    <span class=\"token comment\">\/\/ 2. Filter the pre-packed files from Repomix<\/span>\n    <span class=\"token comment\">\/\/ We assume data.Files contains the parsed output from Repomix<\/span>\n    <span class=\"token keyword\">var<\/span> filteredFiles <span class=\"token punctuation\">[<\/span><span class=\"token punctuation\">]<\/span>repomix<span class=\"token punctuation\">.<\/span>File\n    <span class=\"token keyword\">for<\/span> <span class=\"token boolean\">_<\/span><span class=\"token punctuation\">,<\/span> file <span class=\"token operator\">:=<\/span> <span class=\"token keyword\">range<\/span> data<span class=\"token punctuation\">.<\/span>Files <span class=\"token punctuation\">{<\/span>\n        <span class=\"token keyword\">if<\/span> keep<span class=\"token punctuation\">[<\/span>file<span class=\"token punctuation\">.<\/span>Path<span class=\"token punctuation\">]<\/span> <span class=\"token punctuation\">{<\/span>\n            filteredFiles <span class=\"token operator\">=<\/span> <span class=\"token function\">append<\/span><span class=\"token punctuation\">(<\/span>filteredFiles<span class=\"token punctuation\">,<\/span> file<span class=\"token punctuation\">)<\/span>\n        <span class=\"token punctuation\">}<\/span>\n    <span class=\"token punctuation\">}<\/span>\n\n    <span class=\"token keyword\">if<\/span> <span class=\"token function\">len<\/span><span class=\"token punctuation\">(<\/span>filteredFiles<span class=\"token punctuation\">)<\/span> <span class=\"token operator\">==<\/span> <span class=\"token number\">0<\/span> <span class=\"token punctuation\">{<\/span>\n        <span class=\"token keyword\">return<\/span> <span class=\"token string\">\"\"<\/span><span class=\"token punctuation\">,<\/span> fmt<span class=\"token punctuation\">.<\/span><span class=\"token function\">Errorf<\/span><span class=\"token punctuation\">(<\/span><span class=\"token string\">\"no relevant files found in context\"<\/span><span class=\"token punctuation\">)<\/span>\n    <span class=\"token punctuation\">}<\/span>\n\n    <span class=\"token comment\">\/\/ 3. Reconstruct the XML\/Context string<\/span>\n    <span class=\"token comment\">\/\/ This replicates Repomix's output format but only for the subset<\/span>\n    <span class=\"token keyword\">var<\/span> sb strings<span class=\"token punctuation\">.<\/span>Builder\n    sb<span class=\"token punctuation\">.<\/span><span class=\"token function\">WriteString<\/span><span class=\"token punctuation\">(<\/span><span class=\"token string\">\"&lt;repository>\\n\"<\/span><span class=\"token punctuation\">)<\/span>\n    <span class=\"token keyword\">for<\/span> <span class=\"token boolean\">_<\/span><span class=\"token punctuation\">,<\/span> f <span class=\"token operator\">:=<\/span> <span class=\"token keyword\">range<\/span> filteredFiles <span class=\"token punctuation\">{<\/span>\n        sb<span class=\"token punctuation\">.<\/span><span class=\"token function\">WriteString<\/span><span class=\"token punctuation\">(<\/span>fmt<span class=\"token punctuation\">.<\/span><span class=\"token function\">Sprintf<\/span><span class=\"token punctuation\">(<\/span><span class=\"token string\">\"&lt;file path=%q>\\n%s\\n&lt;\/file>\\n\"<\/span><span class=\"token punctuation\">,<\/span> f<span class=\"token punctuation\">.<\/span>Path<span class=\"token punctuation\">,<\/span> f<span class=\"token punctuation\">.<\/span>Content<span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">)<\/span>\n    <span class=\"token punctuation\">}<\/span>\n    sb<span class=\"token punctuation\">.<\/span><span class=\"token function\">WriteString<\/span><span class=\"token punctuation\">(<\/span><span class=\"token string\">\"&lt;\/repository>\"<\/span><span class=\"token punctuation\">)<\/span>\n\n    <span class=\"token keyword\">return<\/span> sb<span class=\"token punctuation\">.<\/span><span class=\"token function\">String<\/span><span class=\"token punctuation\">(<\/span><span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">,<\/span> <span class=\"token boolean\">nil<\/span>\n<span class=\"token punctuation\">}<\/span><\/code><\/pre>\n<p>This transforms a 50MB repo into a precise 10KB context window containing <em>only<\/em> the files that matter.<\/p>\n<h3 id=\"step-4-phase-2-the-review\">Step 4: Phase 2 - The Review<\/h3>\n<p>Finally, I run the actual review. I feed it the focused context and ask for specific actionable feedback.<\/p>\n<p>Crucially, we map the response to a Go struct that matches GitHub's comment API structure. We also make sure to handle errors gracefully here too\u2014no <code>_<\/code> assigns allowed in production!<\/p>\n<pre class=\"language-go\"><code class=\"language-go\"><span class=\"token keyword\">func<\/span> <span class=\"token punctuation\">(<\/span>r <span class=\"token operator\">*<\/span>CodeReviewAnalyzer<span class=\"token punctuation\">)<\/span> <span class=\"token function\">performCodeReview<\/span><span class=\"token punctuation\">(<\/span>ctx context<span class=\"token punctuation\">.<\/span>Context<span class=\"token punctuation\">,<\/span> fileContext <span class=\"token builtin\">string<\/span><span class=\"token punctuation\">,<\/span> prDiff <span class=\"token builtin\">string<\/span><span class=\"token punctuation\">)<\/span> <span class=\"token punctuation\">(<\/span><span class=\"token operator\">*<\/span>CodeReviewResult<span class=\"token punctuation\">,<\/span> <span class=\"token builtin\">error<\/span><span class=\"token punctuation\">)<\/span> <span class=\"token punctuation\">{<\/span>\n    <span class=\"token comment\">\/\/ 1. Build the prompt with the focused context<\/span>\n    phase2Prompt <span class=\"token operator\">:=<\/span> fmt<span class=\"token punctuation\">.<\/span><span class=\"token function\">Sprintf<\/span><span class=\"token punctuation\">(<\/span>ReviewPrompt<span class=\"token punctuation\">,<\/span> fileContext<span class=\"token punctuation\">,<\/span> prDiff<span class=\"token punctuation\">)<\/span>\n\n    <span class=\"token comment\">\/\/ 2. Call Bedrock with the \"perform_code_review\" tool<\/span>\n    toolConfig <span class=\"token operator\">:=<\/span> bedrock<span class=\"token punctuation\">.<\/span><span class=\"token function\">BuildCodeReviewTool<\/span><span class=\"token punctuation\">(<\/span><span class=\"token punctuation\">)<\/span>\n\n    response<span class=\"token punctuation\">,<\/span> err <span class=\"token operator\">:=<\/span> r<span class=\"token punctuation\">.<\/span>BedrockClient<span class=\"token punctuation\">.<\/span><span class=\"token function\">Converse<\/span><span class=\"token punctuation\">(<\/span>ctx<span class=\"token punctuation\">,<\/span> SystemPrompt<span class=\"token punctuation\">,<\/span> phase2Prompt<span class=\"token punctuation\">,<\/span> toolConfig<span class=\"token punctuation\">)<\/span>\n    <span class=\"token keyword\">if<\/span> err <span class=\"token operator\">!=<\/span> <span class=\"token boolean\">nil<\/span> <span class=\"token punctuation\">{<\/span>\n        <span class=\"token keyword\">return<\/span> <span class=\"token boolean\">nil<\/span><span class=\"token punctuation\">,<\/span> err\n    <span class=\"token punctuation\">}<\/span>\n\n    <span class=\"token comment\">\/\/ 3. Unmarshal the structured response<\/span>\n    <span class=\"token keyword\">var<\/span> reviewData <span class=\"token keyword\">struct<\/span> <span class=\"token punctuation\">{<\/span>\n        Summary        <span class=\"token builtin\">string<\/span>       <span class=\"token string\">`json:\"summary\"`<\/span>\n        KeyConcerns    <span class=\"token punctuation\">[<\/span><span class=\"token punctuation\">]<\/span>KeyConcern <span class=\"token string\">`json:\"key_concerns\"`<\/span>\n        InlineComments <span class=\"token punctuation\">[<\/span><span class=\"token punctuation\">]<\/span><span class=\"token keyword\">struct<\/span> <span class=\"token punctuation\">{<\/span>\n            FilePath      <span class=\"token builtin\">string<\/span>      <span class=\"token string\">`json:\"file_path\"`<\/span>\n            Line          <span class=\"token keyword\">interface<\/span><span class=\"token punctuation\">{<\/span><span class=\"token punctuation\">}<\/span> <span class=\"token string\">`json:\"line\"`<\/span>\n            Body          <span class=\"token builtin\">string<\/span>      <span class=\"token string\">`json:\"body\"`<\/span>\n            SuggestedCode <span class=\"token builtin\">string<\/span>      <span class=\"token string\">`json:\"suggested_code\"`<\/span>\n        <span class=\"token punctuation\">}<\/span> <span class=\"token string\">`json:\"inline_comments\"`<\/span>\n    <span class=\"token punctuation\">}<\/span>\n\n    toolJSON<span class=\"token punctuation\">,<\/span> err <span class=\"token operator\">:=<\/span> bedrock<span class=\"token punctuation\">.<\/span><span class=\"token function\">ExtractToolJSON<\/span><span class=\"token punctuation\">(<\/span>response<span class=\"token punctuation\">)<\/span>\n    <span class=\"token keyword\">if<\/span> err <span class=\"token operator\">!=<\/span> <span class=\"token boolean\">nil<\/span> <span class=\"token punctuation\">{<\/span>\n        <span class=\"token keyword\">return<\/span> <span class=\"token boolean\">nil<\/span><span class=\"token punctuation\">,<\/span> fmt<span class=\"token punctuation\">.<\/span><span class=\"token function\">Errorf<\/span><span class=\"token punctuation\">(<\/span><span class=\"token string\">\"failed to extract tool json: %w\"<\/span><span class=\"token punctuation\">,<\/span> err<span class=\"token punctuation\">)<\/span>\n    <span class=\"token punctuation\">}<\/span>\n\n    <span class=\"token keyword\">if<\/span> err <span class=\"token operator\">:=<\/span> json<span class=\"token punctuation\">.<\/span><span class=\"token function\">Unmarshal<\/span><span class=\"token punctuation\">(<\/span>toolJSON<span class=\"token punctuation\">,<\/span> <span class=\"token operator\">&amp;<\/span>reviewData<span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span> err <span class=\"token operator\">!=<\/span> <span class=\"token boolean\">nil<\/span> <span class=\"token punctuation\">{<\/span>\n        <span class=\"token keyword\">return<\/span> <span class=\"token boolean\">nil<\/span><span class=\"token punctuation\">,<\/span> fmt<span class=\"token punctuation\">.<\/span><span class=\"token function\">Errorf<\/span><span class=\"token punctuation\">(<\/span><span class=\"token string\">\"failed to unmarshal review data: %w\"<\/span><span class=\"token punctuation\">,<\/span> err<span class=\"token punctuation\">)<\/span>\n    <span class=\"token punctuation\">}<\/span>\n\n    <span class=\"token comment\">\/\/ Now we have structured data to send to GitHub API!<\/span>\n    <span class=\"token keyword\">return<\/span> <span class=\"token function\">processReviewData<\/span><span class=\"token punctuation\">(<\/span>reviewData<span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">,<\/span> <span class=\"token boolean\">nil<\/span>\n<span class=\"token punctuation\">}<\/span><\/code><\/pre>\n<p>The result? I get a list of <code>InlineComments<\/code> that I can loop over and POST directly to the GitHub Pull Request API. No regex parsing, no &quot;I hope the AI formatted the markdown block correctly.&quot; Just typed data.<\/p>\n<h2 id=\"why-not-use-an-existing-tool\">Why Not Use an Existing Tool?<\/h2>\n<p>Fair question. Tools like CodeRabbit and Codeium Enterprise exist and they are fantastic. But here's the deal: none of them worked with our specific constraints.<\/p>\n<p>The primary driver was infrastructure. We run GitHub Enterprise on-premise behind a strict VPN. While many SaaS tools claim enterprise support, they typically expect your git instance to be reachable from the public internet (or require complex tunnel setups that our security team vetoed immediately). We simply couldn't pipe our repositories into a cloud SaaS.<\/p>\n<p>Once we accepted we had to build, we realized four massive benefits that validated the decision:<\/p>\n<ol>\n<li><strong>Control<\/strong>: We could integrate deeply with our internal knowledge base. I'm talking about feeding the AI our specific architecture docs and Go style guides.<\/li>\n<li><strong>Cost<\/strong>: SaaS tools usually charge per-seat ($20-50\/user\/month). I wanted per-PR pricing I could control. If we have a quiet month, I pay less.<\/li>\n<li><strong>Data residency<\/strong>: This was non-negotiable. With a custom build using Bedrock in our VPC, zero code ever leaves our controlled environment.<\/li>\n<li><strong>Customization<\/strong>: We have specific architectural rules (like &quot;no direct DB calls in handlers&quot;). Phase 2 adds these as custom fitness functions\u2014something no off-the-shelf tool offered.<\/li>\n<\/ol>\n<p>If you can use a SaaS tool, use it. This post is for when you can't.<\/p>\n<h2 id=\"the-first-save-and-why-this-matters\">The First Save (And Why This Matters)<\/h2>\n<p>Catching syntax errors is cute. Catching logic bombs is why we built this.<\/p>\n<p>Two weeks after deploying Archbot, it caught a bug that would have turned a minor network hiccup into a SEV-1 outage.<\/p>\n<p>A Senior Engineer was implementing a &quot;Feature Flag&quot; loader. The pattern is common: fetch configuration from a remote service, but keep a local fallback for safety. The goal is resilience.<\/p>\n<p>Here is a simplified version of the code:<\/p>\n<pre class=\"language-go\"><code class=\"language-go\"><span class=\"token keyword\">func<\/span> <span class=\"token punctuation\">(<\/span>c <span class=\"token operator\">*<\/span>ConfigLoader<span class=\"token punctuation\">)<\/span> <span class=\"token function\">GetFeatureFlag<\/span><span class=\"token punctuation\">(<\/span>ctx context<span class=\"token punctuation\">.<\/span>Context<span class=\"token punctuation\">,<\/span> flag <span class=\"token builtin\">string<\/span><span class=\"token punctuation\">)<\/span> <span class=\"token punctuation\">(<\/span><span class=\"token builtin\">bool<\/span><span class=\"token punctuation\">,<\/span> <span class=\"token builtin\">error<\/span><span class=\"token punctuation\">)<\/span> <span class=\"token punctuation\">{<\/span>\n    <span class=\"token comment\">\/\/ 1. Try to fetch from the remote config service<\/span>\n    val<span class=\"token punctuation\">,<\/span> err <span class=\"token operator\">:=<\/span> c<span class=\"token punctuation\">.<\/span>remote<span class=\"token punctuation\">.<\/span><span class=\"token function\">Fetch<\/span><span class=\"token punctuation\">(<\/span>ctx<span class=\"token punctuation\">,<\/span> flag<span class=\"token punctuation\">)<\/span>\n    <span class=\"token keyword\">if<\/span> err <span class=\"token operator\">!=<\/span> <span class=\"token boolean\">nil<\/span> <span class=\"token punctuation\">{<\/span>\n         <span class=\"token comment\">\/\/ The \"Safe\" move? Fail fast.<\/span>\n         <span class=\"token keyword\">return<\/span> <span class=\"token boolean\">false<\/span><span class=\"token punctuation\">,<\/span> fmt<span class=\"token punctuation\">.<\/span><span class=\"token function\">Errorf<\/span><span class=\"token punctuation\">(<\/span><span class=\"token string\">\"remote config failed: %w\"<\/span><span class=\"token punctuation\">,<\/span> err<span class=\"token punctuation\">)<\/span>\n    <span class=\"token punctuation\">}<\/span>\n\n    <span class=\"token comment\">\/\/ 2. If remote returns nothing (e.g. 404), fallback to local defaults<\/span>\n    <span class=\"token keyword\">if<\/span> val <span class=\"token operator\">==<\/span> <span class=\"token boolean\">nil<\/span> <span class=\"token punctuation\">{<\/span>\n        <span class=\"token keyword\">return<\/span> c<span class=\"token punctuation\">.<\/span>local<span class=\"token punctuation\">.<\/span><span class=\"token function\">Get<\/span><span class=\"token punctuation\">(<\/span>flag<span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">,<\/span> <span class=\"token boolean\">nil<\/span>\n    <span class=\"token punctuation\">}<\/span>\n\n    <span class=\"token keyword\">return<\/span> <span class=\"token operator\">*<\/span>val<span class=\"token punctuation\">,<\/span> <span class=\"token boolean\">nil<\/span>\n<span class=\"token punctuation\">}<\/span><\/code><\/pre>\n<p>Do you see it? It looks clean. It compiles. It passes the linter. It follows the standard Go idiom: <code>if err != nil { return err }<\/code>.<\/p>\n<p>But Archbot saw the architectural implication:<\/p>\n<p><img src=\"https:\/\/platformtoolsmith.com\/assets\/blog\/archbot-code-review.d39fa626.900.webp\" alt=\"Example of PR review comment\" width=\"1672\" height=\"1768\" loading=\"lazy\" decoding=\"async\" srcset=\"https:\/\/platformtoolsmith.com\/assets\/blog\/archbot-code-review.d39fa626.400.webp 400w, https:\/\/platformtoolsmith.com\/assets\/blog\/archbot-code-review.d39fa626.900.webp 900w, https:\/\/platformtoolsmith.com\/assets\/blog\/archbot-code-review.d39fa626.1672.webp 1672w\" sizes=\"(min-width: 1024px) 773px, 100vw\" \/><\/p>\n<p>The engineer paused. &quot;Oh.&quot;<\/p>\n<p>We had inadvertently built a &quot;kill switch&quot; into our startup sequence. If the config service blinked, our main application would crash. The fallback code\u2014written specifically for that scenario\u2014was unreachable code during an outage.<\/p>\n<p>That's when the team stopped treating Archbot like a toy.<\/p>\n<h2 id=\"the-real-impact-shifting-code-review-from-find-problems-to-verify-intent\">The Real Impact: Shifting Code Review from &quot;Find Problems&quot; to &quot;Verify Intent&quot;<\/h2>\n<p>Now, don't get me wrong\u2014catching production bugs is the headline reason we built this. That's what gets management buy-in. That's what makes the case for the infrastructure investment.<\/p>\n<p>But here's what actually changed day-to-day: <strong>Archbot became the first filter on every PR, freeing human reviewers to focus on architecture instead of syntax.<\/strong><\/p>\n<p>Think about the traditional code review process. You open a PR. A senior engineer context-switches from their deep work, loads your branch, scans 15 files looking for imports, error handling, edge cases, architectural violations\u2014all while trying to remember what <em>you<\/em> were trying to accomplish in the first place. It's exhausting. By the time they get to the interesting architectural question (&quot;Should this live in the service layer or the handler?&quot;), their brain is already fried from scanning for nil checks.<\/p>\n<p>Archbot inverts that. It handles the cognitive load of the &quot;scan for obvious issues&quot; phase. It's not replacing human review\u2014it's <strong>augmenting<\/strong> it.<\/p>\n<p>When Archbot leaves a comment, it's doing one of two things:<\/p>\n<ol>\n<li><strong>Spotlighting complexity:<\/strong> &quot;Hey, this function has three nested error paths and I can't tell if all branches are covered.&quot; Even when Archbot is <em>wrong<\/em>, it's usually highlighting code that deserves a second look.<\/li>\n<li><strong>Catching the mundane:<\/strong> Missing error handling, unreachable fallback logic, off-by-one bugs in loops\u2014the stuff that's easy to miss when you're mentally juggling architectural intent.<\/li>\n<\/ol>\n<p>The result? Human reviewers spend less time hunting for bugs and more time asking the questions that matter: <em>&quot;Is this the right design? Does this scale? Is there a simpler way?&quot;<\/em><\/p>\n<p>And yes, Archbot nitpicks sometimes. It'll flag a variable name it doesn't like. It'll suggest a refactor that doesn't make sense in context. But the team learned quickly: <strong>if Archbot comments on it, it's probably worth at least reading twice.<\/strong> Complex code attracts AI attention. That's actually a feature, not a bug.<\/p>\n<p>The shift we saw was subtle but real. Code review went from &quot;find all the problems&quot; to &quot;verify the architectural intent.&quot; Archbot doesn't have architectural taste\u2014but it frees up the humans who do to actually apply it.<\/p>\n<h2 id=\"the-result\">The Result<\/h2>\n<p>The difference was night and day.<\/p>\n<ul>\n<li><strong>Naive Approach:<\/strong> Hallucinations, missed context.<\/li>\n<li><strong>Hammering:<\/strong> Slow, rate-limited.<\/li>\n<li><strong>Monolith (Full Repo):<\/strong> Context overflow, expensive.<\/li>\n<li><strong>Two-Phase Archbot:<\/strong> Fast, accurate, cheap.<\/li>\n<\/ul>\n<p><strong>What we're seeing:<\/strong><\/p>\n<ul>\n<li>Catching production-impacting logic bugs before merge<\/li>\n<li>Identifying missing test coverage in critical paths<\/li>\n<li>High acceptance rate for comments with minimal false positives<\/li>\n<li>Drastically lower API costs compared to per-seat SaaS pricing<\/li>\n<li>Zero data leaked outside our VPC<\/li>\n<\/ul>\n<p>But catching bugs is only half the battle. The next evolution is <strong>Architecture Fitness Functions<\/strong> \u2014 teaching the AI to enforce our specific architectural rules (like &quot;no direct DB calls in handlers&quot; or &quot;use circuit breakers for external APIs&quot;). That's a topic for another day.<\/p>\n<p>Want to build something similar for your platform team? Let's chat.<\/p>\n<a href=\"https:\/\/cal.com\/alexandrecastrotech\/mentoring\" class=\"text-primary-text-light dark:text-primary-text hover:opacity-80 transition-opacity flex items-center gap-2 font-bold uppercase tracking-wide min-h-11\">\n    <i class=\"pixelart-icons-font-calendar\"><\/i> Book a mentoring session\n<\/a>\n"},{"title":"You are a Senior Engineer, Mastering Software Architecture and Design (Part 2)","link":{"@attributes":{"href":"https:\/\/platformtoolsmith.com\/blog\/senior-engineer-part-2\/"}},"updated":"2024-05-14T00:00:00Z","id":"https:\/\/platformtoolsmith.com\/blog\/senior-engineer-part-2\/","content":"<p>Hey there! You've reached a significant milestone in your career journey. But if &quot;Architecture&quot; feels like a scary word reserved for people in ivory towers, I've got news for you: <strong>you're already an architect.<\/strong><\/p>\n<p>Every time you choose a library, decide between a SQL or NoSQL database, or structure your API response, you are making architectural decisions. The difference between a Junior and a Senior isn't that the Senior knows the &quot;right&quot; answer. It's that the Senior knows there <em>is no right answer<\/em>\u2014only trade-offs.<\/p>\n<h2 id=\"the-two-pillars-of-architecture\">The Two Pillars of Architecture<\/h2>\n<p>To evolve from a coder to an architect, you need to balance two things: <strong>Theory<\/strong> (standing on the shoulders of giants) and <strong>Practice<\/strong> (getting your hands dirty).<\/p>\n<p>And there's a secret third ingredient: <strong>Communication<\/strong>. You can design the best system in the world, but if you can't explain <em>why<\/em> it's the best system for <em>this specific problem<\/em>, it won't get built.<\/p>\n<p>Let's break down how to level up in these areas.<\/p>\n<h2 id=\"i-class-pixelart-icons-font-notebook-i-theory-read-the-manual-and-the-books\"><i class=\"pixelart-icons-font-notebook\"><\/i> Theory: Read the Manual (and the Books)<\/h2>\n<p><img src=\"https:\/\/platformtoolsmith.com\/assets\/blog\/gopher-reading.f868676d.900.webp\" alt=\"A mole reads a large open book under the cone of a desk lamp.\" width=\"1920\" height=\"1080\" loading=\"lazy\" decoding=\"async\" srcset=\"https:\/\/platformtoolsmith.com\/assets\/blog\/gopher-reading.f868676d.400.webp 400w, https:\/\/platformtoolsmith.com\/assets\/blog\/gopher-reading.f868676d.900.webp 900w, https:\/\/platformtoolsmith.com\/assets\/blog\/gopher-reading.f868676d.1800.webp 1800w\" sizes=\"(min-width: 1024px) 773px, 100vw\" \/><\/p>\n<p>You can't Google &quot;architecture for my specific startup problem.&quot; You need deep patterns in your head to match against real-world problems. Books are the fastest way to download decades of experience into your brain.<\/p>\n<p>Here are the three that actually changed my career:<\/p>\n<ul>\n<li>\n<p><strong><a href=\"https:\/\/amzn.to\/3UAChYK\">&quot;Head First Design Patterns&quot;<\/a><\/strong>: Don't let the comic sans fool you. This is the best introduction to why patterns exist.<\/p>\n<blockquote class=\"alert-blockquote\">\n<div class=\"alert-header\">\n                [ <i class=\"pixelart-icons-font-notebook\"><\/i> <span>MY TAKE<\/span> ]\n              <\/div><p>It teaches you the <em>vocabulary<\/em> of design. When you can say &quot;let's use a Strategy pattern here,&quot; you save 30 minutes of explaining.<\/p>\n<\/blockquote>\n<\/li>\n<li>\n<p><strong><a href=\"https:\/\/amzn.to\/3UJsQYa\">&quot;Fundamentals of Software Architecture&quot;<\/a><\/strong>: This is the modern bible. Mark Richards and Neal Ford define architecture not as boxes and arrows, but as &quot;the things that are hard to change later.&quot;<\/p>\n<blockquote class=\"alert-blockquote\">\n<div class=\"alert-header\">\n                [ <i class=\"pixelart-icons-font-notebook\"><\/i> <span>MY TAKE<\/span> ]\n              <\/div><p>Their concept of &quot;Architectural Characteristics&quot; (Scalability, Elasticity, Reliability) gave me a framework to evaluate every tech decision I make.<\/p>\n<\/blockquote>\n<\/li>\n<li>\n<p><strong><a href=\"https:\/\/amzn.to\/3WrZ4Ze\">&quot;Refactoring&quot;<\/a><\/strong>: Martin Fowler's classic. It's not just about cleaning code; it's about shifting your mindset from &quot;writing code&quot; to &quot;crafting a system.&quot;<\/p>\n<blockquote class=\"alert-blockquote\">\n<div class=\"alert-header\">\n                [ <i class=\"pixelart-icons-font-notebook\"><\/i> <span>MY TAKE<\/span> ]\n              <\/div><p>The &quot;Code Smells&quot; catalog in this book is basically a cheat sheet for code reviews.<\/p>\n<\/blockquote>\n<\/li>\n<\/ul>\n<h2 id=\"i-class-pixelart-icons-font-article-i-keeping-the-pulse\"><i class=\"pixelart-icons-font-article\"><\/i> Keeping the Pulse<\/h2>\n<p>Keeping up-to-date with the latest technologies is critical, as they will undoubtedly shape the future of software engineering. For example, Kubernetes, which was introduced less than a decade ago, has become the default choice for many in the industry. Therefore, it's crucial to stay informed about the latest tech news to remain relevant and competitive.<\/p>\n<p>When seeking out news, focus on topics that are specific to the technologies you want to learn, such as Golang, Kubernetes, and Microservices. Additionally, broad technology trends will give you a better understanding of the bigger picture, enabling you to anticipate and prepare for future developments. By staying on top of tech news, you'll be exposed to innovative ideas and emerging trends that will help you think outside the box and stay ahead of the curve. Some of my personal sources for news are:<\/p>\n<ul>\n<li>Newsletters:\n<ul>\n<li><a href=\"https:\/\/www.freecodecamp.org\/news\/tag\/newsletter\/\">freeCodeCamp<\/a><\/li>\n<li><a href=\"https:\/\/tldr.tech\/\">TLDR Newsletter - A Byte-Sized Daily Tech Newsletter<\/a><\/li>\n<li><a href=\"https:\/\/go.libhunt.com\/newsletter\">Awesome Go<\/a><\/li>\n<\/ul>\n<\/li>\n<li>Coding Blogs:\n<ul>\n<li>Like mine!<\/li>\n<li><a href=\"https:\/\/martinfowler.com\/\">Martin Fowler<\/a><\/li>\n<li><a href=\"https:\/\/dev.to\/\">Dev.to<\/a><\/li>\n<\/ul>\n<\/li>\n<li>Youtube Channels:\n<ul>\n<li><a href=\"https:\/\/www.youtube.com\/@Fireship\">Fireship<\/a><\/li>\n<li><a href=\"https:\/\/www.youtube.com\/@TechLinked\">TechLinked<\/a><\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<h2 id=\"practice-get-your-hands-dirty\">Practice: Get Your Hands Dirty<\/h2>\n<p><img src=\"https:\/\/platformtoolsmith.com\/assets\/blog\/gopher-practicing.bfd9a797.900.webp\" alt=\"A mole practices on a tiny keyboard while a metronome ticks beside it.\" width=\"1920\" height=\"1080\" loading=\"lazy\" decoding=\"async\" srcset=\"https:\/\/platformtoolsmith.com\/assets\/blog\/gopher-practicing.bfd9a797.400.webp 400w, https:\/\/platformtoolsmith.com\/assets\/blog\/gopher-practicing.bfd9a797.900.webp 900w, https:\/\/platformtoolsmith.com\/assets\/blog\/gopher-practicing.bfd9a797.1800.webp 1800w\" sizes=\"(min-width: 1024px) 773px, 100vw\" \/><\/p>\n<p>Reading is safe. Doing is scary. And that's exactly why you need to do it.<\/p>\n<p>If you're comfortable, you're not growing. Here is my <strong>&quot;Senior Engineer Workout Plan&quot;<\/strong>:<\/p>\n<ol>\n<li><strong>The &quot;Why&quot; Game:<\/strong> Pick a tool your company uses (e.g., Kafka, Redis). Ask &quot;Why did we choose this?&quot; digging until you find the <em>trade-off<\/em> that justified it. If you can't find it, that's a red flag (or a learning opportunity).<\/li>\n<li><strong>Architectural Katas:<\/strong> Don't just code; design. Take a prompt like &quot;Design a URL shortener&quot; or &quot;Design a ride-sharing app.&quot; Sketch the C4 model. List the trade-offs. Then\u2014and this is the key\u2014<em>throw it away<\/em>. The value is in the thinking, not the artifact.<\/li>\n<li><strong>Read Other People's Code:<\/strong> Go to a popular open-source repo (like <code>kubernetes\/kubernetes<\/code> or <code>moby\/moby<\/code>). Don't read the functions; look at the folder structure, the interface definitions, the package boundaries. Try to reverse-engineer the architecture in your head.<\/li>\n<\/ol>\n<blockquote class=\"alert-blockquote\">\n<div class=\"alert-header\">\n                [ <i class=\"pixelart-icons-font-zap\"><\/i> <span>TIP<\/span> ]\n              <\/div><p><strong>Pro Move:<\/strong> Volunteer to write the &quot;Design Doc&quot; for a feature you aren't building. It forces you to think about interfaces and contracts without getting bogged down in implementation details.<\/p>\n<\/blockquote>\n<h2 id=\"lowering-the-resolution-mental-models\">Lowering the Resolution: Mental Models<\/h2>\n<blockquote class=\"alert-blockquote\">\n<div class=\"alert-header\">\n                [ <i class=\"pixelart-icons-font-warning-diamond\"><\/i> <span>IMPORTANT<\/span> ]\n              <\/div><p>The smartest engineers I know don't use big words. They use simple metaphors.<\/p>\n<\/blockquote>\n<p>When you're faced with a terrifyingly complex system, your brain wants to panic. The trick is to <strong>lower the resolution<\/strong>. Blur the details until you see the shapes.<\/p>\n<p>Let's take <strong>Kafka<\/strong>.<\/p>\n<ul>\n<li><strong>High Resolution:<\/strong> &quot;A distributed streaming platform with partitions, replication factors, consumer groups, and Zookeeper coordination.&quot; (Panic level: High)<\/li>\n<li><strong>Low Resolution:<\/strong> &quot;It's a big pipe. We put stuff in one end, and take it out the other.&quot; (Panic level: Zero)<\/li>\n<\/ul>\n<p>Once you understand &quot;It's a pipe,&quot; you can ask: &quot;How big is the pipe? What happens if the pipe gets clogged?&quot;<\/p>\n<p>You can apply this to anything:<\/p>\n<ul>\n<li><strong>Kubernetes<\/strong> -&gt; &quot;A smart manager that restarts my app when it crashes.&quot;<\/li>\n<li><strong>Redis<\/strong> -&gt; &quot;A giant hash map shared by all my servers.&quot;<\/li>\n<\/ul>\n<p>Start with the simple mental model. Only add complexity when the simple model fails to explain reality.<\/p>\n<h2 id=\"keep-learning-keep-growing\">Keep Learning, Keep Growing<\/h2>\n<p>Becoming a senior engineer is an outstanding achievement, but remember: the goalpost just moved.<\/p>\n<p>Mastering architecture isn't about memorizing patterns; it's about developing the wisdom to know <em>when<\/em> to use them. It's a marathon, not a sprint.<\/p>\n<p>Need a hand navigating the transition? I'm here to help.<\/p>\n<a href=\"https:\/\/cal.com\/alexandrecastrotech\/mentoring\" class=\"text-primary-text-light dark:text-primary-text hover:opacity-80 transition-opacity flex items-center gap-2 font-bold uppercase tracking-wide min-h-11\">\n    <i class=\"pixelart-icons-font-calendar\"><\/i> Book a mentoring session\n<\/a>\n<h2 id=\"series-you-are-a-senior-engineer\">Series: You are a Senior Engineer<\/h2>\n<ul>\n<li><a href=\"https:\/\/platformtoolsmith.com\/blog\/senior-engineer-part-1\/\">You are a Senior Engineer, now what? (Part 1)<\/a><\/li>\n<li><strong>You are a Senior Engineer, Mastering Software Architecture and Design (Part 2)<\/strong><\/li>\n<li><a href=\"https:\/\/platformtoolsmith.com\/blog\/senior-engineer-part-3\/\">You are a Senior Engineer, Mastering Communication &amp; Influence (Part 3)<\/a><\/li>\n<\/ul>\n"},{"title":"You are a Senior Engineer, now what? (Part 1)","link":{"@attributes":{"href":"https:\/\/platformtoolsmith.com\/blog\/senior-engineer-part-1\/"}},"updated":"2024-05-08T00:00:00Z","id":"https:\/\/platformtoolsmith.com\/blog\/senior-engineer-part-1\/","content":"<p>Hey there! First off, take a moment to celebrate. You've earned the rank of Senior Engineer. That's no small feat, and it's proof of your technical chops and dedication.<\/p>\n<p>But if you're like most engineers I mentor, the champagne toast is quickly followed by a nagging thought: <em>&quot;Now what?&quot;<\/em><\/p>\n<p>It's the classic &quot;what got you here won't get you there&quot; moment. As a Principal Engineer, I see many talented seniors hit a plateau right here. They keep trying to just &quot;code harder,&quot; thinking that's the path to Staff or Principal. Spoiler alert: it's not.<\/p>\n<p>The truth is, becoming a Senior Engineer is just the tutorial level for the rest of your career. The game changes now. You need to shift from being a pure builder to being a force multiplier. Let's break down exactly how to navigate this transition without burning out.<\/p>\n<h2 id=\"technical-depth-and-breadth-the-t-shaped-engineer\">Technical Depth and Breadth: The T-Shaped Engineer<\/h2>\n<p><img src=\"https:\/\/platformtoolsmith.com\/assets\/blog\/technical-depth-breadth.65dc82ee.900.webp\" alt=\"A mole climbs a three-step pyramid: reading a book at the top, studying a tablet in the middle, and staring into a black hole at the bottom.\" width=\"1920\" height=\"1080\" loading=\"lazy\" decoding=\"async\" srcset=\"https:\/\/platformtoolsmith.com\/assets\/blog\/technical-depth-breadth.65dc82ee.400.webp 400w, https:\/\/platformtoolsmith.com\/assets\/blog\/technical-depth-breadth.65dc82ee.900.webp 900w, https:\/\/platformtoolsmith.com\/assets\/blog\/technical-depth-breadth.65dc82ee.1800.webp 1800w\" sizes=\"(min-width: 1024px) 773px, 100vw\" \/><\/p>\n<p>You've probably heard of the &quot;T-Shaped&quot; engineer. It's a clich\u00e9 for a reason\u2014because it's accurate. But let's look at it through a senior lens.<\/p>\n<h3 id=\"technical-depth-your-superpower\">Technical Depth: Your Superpower<\/h3>\n<blockquote class=\"alert-blockquote\">\n<div class=\"alert-header\">\n                [ <i class=\"pixelart-icons-font-info-box\"><\/i> <span>NOTE<\/span> ]\n              <\/div><p><strong>Technical depth<\/strong> is your anchor. It's the area where you are the undeniable expert. When production is on fire or a complex architectural decision needs to be made in this domain, eyes turn to you.<\/p>\n<\/blockquote>\n<p>Your depth includes your primary language ecosystem (e.g., Java\/Spring, Go\/Cloud Native), your database expertise, or your cloud provider mastery. This is your comfort zone, where you flow.<\/p>\n<p>For me, this was originally Java and distributed systems. I knew the JVM memory model inside out. I could debug garbage collection pauses in my sleep. This depth gave me the credibility to speak up in room full of smart people. You need this anchor to build trust.<\/p>\n<h3 id=\"technical-breadth-your-radar\">Technical Breadth: Your Radar<\/h3>\n<blockquote class=\"alert-blockquote\">\n<div class=\"alert-header\">\n                [ <i class=\"pixelart-icons-font-info-box\"><\/i> <span>NOTE<\/span> ]\n              <\/div><p><strong>Technical breadth<\/strong> is your radar system. It's knowing that tools exist, understanding what problems they solve, and knowing when to reach for them\u2014even if you've never used them in production.<\/p>\n<\/blockquote>\n<p>I like to explain breadth as &quot;knowing the menu.&quot; You don't need to know how to cook every dish, but you need to know what to order when you're hungry for a specific solution.<\/p>\n<p>For example, if you're a backend engineer, you should understand the basics of React or Vue\u2014not to write frontend code daily, but to empathy with your frontend peers and design better APIs for them. You should know what a Graph Database is good for, even if you only use Postgres.<\/p>\n<h3 id=\"visualizing-the-balance\">Visualizing the Balance<\/h3>\n<p>It's not about being an expert in everything. It's about strategic coverage.<\/p>\n<div class=\"mermaid-container\">\n<div class=\"mermaid-diagram\" tabindex=\"0\">\n<svg class=\"dg-se1\" viewBox=\"-2 -2 1068 720\" role=\"img\" aria-labelledby=\"dg-se1-title dg-se1-desc\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\">\n<title id=\"dg-se1-title\">The senior engineer profile: depth and breadth<\/title>\n<desc id=\"dg-se1-desc\">A senior engineer profile splits into technical depth and technical breadth. Depth covers core language mastery, framework internals and performance tuning. Breadth covers frontend basics, DevOps and CI\/CD, product and business context, and other languages. Core language mastery builds trust and frontend basics enable connection, and both lead to career impact.<\/desc>\n<style>.dg-se1{display:block;width:100%;min-width:680px;height:auto;font-family:'Space Grotesk',system-ui,sans-serif}.dg-se1 text{text-anchor:middle;fill:var(--mermaid-text, #000000)}.dg-se1 .b{fill:var(--mermaid-node-bg, #FFFFFF);stroke:var(--mermaid-node-border, #000000);stroke-width:1.5}.dg-se1 .grp{fill:none;stroke:var(--mermaid-node-border, #000000);stroke-width:1.5;stroke-dasharray:3 5}.dg-se1 .band{font-size:13.8px;font-weight:700;letter-spacing:.08em}.dg-se1 .t{font-size:17.5px;font-weight:700}.dg-se1 .gt{font-size:14.4px;font-weight:700;text-anchor:start;fill:var(--theme-text-muted, #6A6A6A);paint-order:stroke;stroke:var(--mermaid-node-bg, #FFFFFF);stroke-width:6px;stroke-linejoin:round}.dg-se1 .lab{font-size:14.4px;fill:var(--theme-text-muted, #6A6A6A);paint-order:stroke;stroke:var(--mermaid-node-bg, #FFFFFF);stroke-width:6px;stroke-linejoin:round}.dg-se1 .ln{fill:none;stroke:var(--mermaid-line, #FF5C00);stroke-width:2}.dg-se1 .life{fill:none;stroke:var(--mermaid-node-border, #000000);stroke-width:1.5;stroke-dasharray:3 5}.dg-se1 .dash{stroke-dasharray:7 5}.dg-se1 marker path{fill:var(--mermaid-line, #FF5C00)}<\/style>\n<defs><marker id=\"dg-se1-arrow\" viewBox=\"0 0 10 10\" refX=\"7\" refY=\"5\" markerWidth=\"7\" markerHeight=\"7\" orient=\"auto-start-reverse\"><path d=\"M0 0L10 5L0 10z\"><\/path><\/marker><\/defs>\n<rect class=\"grp\" x=\"426\" y=\"220\" width=\"638\" height=\"208\"><\/rect>\n<text class=\"gt\" x=\"438\" y=\"242\"><\/text>\n<path class=\"ln\" d=\"M180 335L200 335L200 174L218 174\" marker-end=\"url(#dg-se1-arrow)\"><\/path>\n<path class=\"ln\" d=\"M180 363L200 363L200 524L218 524\" marker-end=\"url(#dg-se1-arrow)\"><\/path>\n<path class=\"ln\" d=\"M400 174L420 174L420 74L438 74\" marker-end=\"url(#dg-se1-arrow)\"><\/path>\n<path class=\"ln\" d=\"M400 174L420 174L420 174L438 174\" marker-end=\"url(#dg-se1-arrow)\"><\/path>\n<path class=\"ln\" d=\"M400 174L420 174L420 274L438 274\" marker-end=\"url(#dg-se1-arrow)\"><\/path>\n<path class=\"ln\" d=\"M400 524L420 524L420 374L438 374\" marker-end=\"url(#dg-se1-arrow)\"><\/path>\n<path class=\"ln\" d=\"M400 524L420 524L420 474L438 474\" marker-end=\"url(#dg-se1-arrow)\"><\/path>\n<path class=\"ln\" d=\"M400 524L420 524L420 574L438 574\" marker-end=\"url(#dg-se1-arrow)\"><\/path>\n<path class=\"ln\" d=\"M400 524L420 524L420 674L438 674\" marker-end=\"url(#dg-se1-arrow)\"><\/path>\n<path class=\"ln\" d=\"M670 274L800 274L800 304L868 304\" marker-end=\"url(#dg-se1-arrow)\"><\/path>\n<path class=\"ln\" d=\"M670 374L800 374L800 344L868 344\" marker-end=\"url(#dg-se1-arrow)\"><\/path>\n<rect class=\"b\" x=\"440\" y=\"32\" width=\"230\" height=\"84\"><\/rect>\n<rect x=\"440\" y=\"32\" width=\"230\" height=\"29\" style=\"fill:var(--diagram-act, #10C840)\"><\/rect>\n<text class=\"band\" x=\"555.0\" y=\"52\" style=\"fill:var(--diagram-act-label, #000000)\">LEARN<\/text>\n<text class=\"t\" x=\"555.0\" y=\"94.5\">Framework Internals<\/text>\n<rect class=\"b\" x=\"440\" y=\"132\" width=\"230\" height=\"84\"><\/rect>\n<rect x=\"440\" y=\"132\" width=\"230\" height=\"29\" style=\"fill:var(--diagram-act, #10C840)\"><\/rect>\n<text class=\"band\" x=\"555.0\" y=\"152\" style=\"fill:var(--diagram-act-label, #000000)\">LEARN<\/text>\n<text class=\"t\" x=\"555.0\" y=\"194.5\">Performance Tuning<\/text>\n<rect class=\"b\" x=\"440\" y=\"232\" width=\"230\" height=\"84\"><\/rect>\n<rect x=\"440\" y=\"232\" width=\"230\" height=\"29\" style=\"fill:var(--diagram-act, #10C840)\"><\/rect>\n<text class=\"band\" x=\"555.0\" y=\"252\" style=\"fill:var(--diagram-act-label, #000000)\">LEARN<\/text>\n<text class=\"t\" x=\"555.0\" y=\"294.5\">Core Language Mastery<\/text>\n<rect class=\"b\" x=\"440\" y=\"332\" width=\"230\" height=\"84\"><\/rect>\n<rect x=\"440\" y=\"332\" width=\"230\" height=\"29\" style=\"fill:var(--diagram-act, #10C840)\"><\/rect>\n<text class=\"band\" x=\"555.0\" y=\"352\" style=\"fill:var(--diagram-act-label, #000000)\">LEARN<\/text>\n<text class=\"t\" x=\"555.0\" y=\"394.5\">Frontend Basics<\/text>\n<rect class=\"b\" x=\"440\" y=\"432\" width=\"230\" height=\"84\"><\/rect>\n<rect x=\"440\" y=\"432\" width=\"230\" height=\"29\" style=\"fill:var(--diagram-act, #10C840)\"><\/rect>\n<text class=\"band\" x=\"555.0\" y=\"452\" style=\"fill:var(--diagram-act-label, #000000)\">LEARN<\/text>\n<text class=\"t\" x=\"555.0\" y=\"494.5\">DevOps &amp; CI\/CD<\/text>\n<rect class=\"b\" x=\"440\" y=\"532\" width=\"230\" height=\"84\"><\/rect>\n<rect x=\"440\" y=\"532\" width=\"230\" height=\"29\" style=\"fill:var(--diagram-act, #10C840)\"><\/rect>\n<text class=\"band\" x=\"555.0\" y=\"552\" style=\"fill:var(--diagram-act-label, #000000)\">LEARN<\/text>\n<text class=\"t\" x=\"555.0\" y=\"582.5\">Product\/Business<\/text>\n<text class=\"t\" x=\"555.0\" y=\"606.5\">Context<\/text>\n<rect class=\"b\" x=\"440\" y=\"632\" width=\"230\" height=\"84\"><\/rect>\n<rect x=\"440\" y=\"632\" width=\"230\" height=\"29\" style=\"fill:var(--diagram-act, #10C840)\"><\/rect>\n<text class=\"band\" x=\"555.0\" y=\"652\" style=\"fill:var(--diagram-act-label, #000000)\">LEARN<\/text>\n<text class=\"t\" x=\"555.0\" y=\"694.5\">Other Languages<\/text>\n<rect class=\"b\" x=\"0\" y=\"299.0\" width=\"180\" height=\"100\"><\/rect>\n<rect x=\"0\" y=\"299.0\" width=\"180\" height=\"29\" style=\"fill:var(--diagram-origin, #A0694B)\"><\/rect>\n<text class=\"band\" x=\"90.0\" y=\"319.0\" style=\"fill:var(--diagram-origin-label, #000000)\">START<\/text>\n<text class=\"t\" x=\"90.0\" y=\"357.5\">Senior Engineer<\/text>\n<text class=\"t\" x=\"90.0\" y=\"381.5\">Profile<\/text>\n<rect class=\"b\" x=\"220\" y=\"134.0\" width=\"180\" height=\"80\"><\/rect>\n<rect x=\"220\" y=\"134.0\" width=\"180\" height=\"29\" style=\"fill:var(--diagram-define, #00A0C8)\"><\/rect>\n<text class=\"band\" x=\"310.0\" y=\"154.0\" style=\"fill:var(--diagram-define-label, #000000)\">DEPTH<\/text>\n<text class=\"t\" x=\"310.0\" y=\"194.5\">Technical Depth<\/text>\n<rect class=\"b\" x=\"220\" y=\"484.0\" width=\"180\" height=\"80\"><\/rect>\n<rect x=\"220\" y=\"484.0\" width=\"180\" height=\"29\" style=\"fill:var(--diagram-define, #00A0C8)\"><\/rect>\n<text class=\"band\" x=\"310.0\" y=\"504.0\" style=\"fill:var(--diagram-define-label, #000000)\">BREADTH<\/text>\n<text class=\"t\" x=\"310.0\" y=\"544.5\">Technical Breadth<\/text>\n<rect class=\"b\" x=\"870\" y=\"274.0\" width=\"180\" height=\"100\"><\/rect>\n<rect x=\"870\" y=\"274.0\" width=\"180\" height=\"29\" style=\"fill:var(--diagram-neutral, #F0F0DC)\"><\/rect>\n<text class=\"band\" x=\"960.0\" y=\"294.0\" style=\"fill:var(--diagram-neutral-label, #000000)\">IMPACT<\/text>\n<text class=\"t\" x=\"960.0\" y=\"344.5\">Career Impact<\/text>\n<text class=\"lab\" x=\"960\" y=\"244\" style=\"text-anchor:middle\">The Strategy<\/text>\n<text class=\"lab\" x=\"682\" y=\"265.0\" style=\"text-anchor:start\">Builds Trust<\/text>\n<text class=\"lab\" x=\"682\" y=\"365.0\" style=\"text-anchor:start\">Enables Connection<\/text>\n<\/svg>\n<\/div>\n<\/div>\n<p>The most successful senior engineers I know oscillate between these two. They dive deep when necessary but constantly scan the horizon for new tools that could solve old problems better.<\/p>\n<h2 id=\"the-senior-loop-learn-apply-teach\">The Senior Loop: Learn, Apply, Teach<\/h2>\n<p><img src=\"https:\/\/platformtoolsmith.com\/assets\/blog\/gopher-grinding.2ae60e70.900.webp\" alt=\"A mole sharpens a chisel on a spinning grindstone, sparks flying.\" width=\"1920\" height=\"1080\" loading=\"lazy\" decoding=\"async\" srcset=\"https:\/\/platformtoolsmith.com\/assets\/blog\/gopher-grinding.2ae60e70.400.webp 400w, https:\/\/platformtoolsmith.com\/assets\/blog\/gopher-grinding.2ae60e70.900.webp 900w, https:\/\/platformtoolsmith.com\/assets\/blog\/gopher-grinding.2ae60e70.1800.webp 1800w\" sizes=\"(min-width: 1024px) 773px, 100vw\" \/><\/p>\n<p>Here's the deal: &quot;Keep grinding&quot; is bad advice if it just means working longer hours. The grind changes from <em>doing<\/em> to <em>enabling<\/em>.<\/p>\n<p>The path to Staff\/Principal isn't linear anymore; it's circular. I call it the <strong>Senior Loop<\/strong>:<\/p>\n<ol>\n<li><strong>Learn:<\/strong> Pick up a new pattern, tool, or domain knowledge.<\/li>\n<li><strong>Apply:<\/strong> Use it to solve a real business problem (not just a resume stuffer).<\/li>\n<li><strong>Teach:<\/strong> Document it, present it, or mentor others on it.<\/li>\n<\/ol>\n<p>If you skip the &quot;Teach&quot; step, you're just a highly paid individual contributor. To advance, you must scale your knowledge.<\/p>\n<blockquote class=\"alert-blockquote\">\n<div class=\"alert-header\">\n                [ <i class=\"pixelart-icons-font-teach\"><\/i> <span>MENTOR<\/span> ]\n              <\/div><p>When I started mentoring, I thought it was taking time away from my &quot;real work&quot; (coding). I was wrong. Mentoring <em>is<\/em> the real work of a senior leader. It solidifies your own understanding and builds a team that can execute without you constantly holding their hand.<\/p>\n<\/blockquote>\n<h2 id=\"you-are-not-alone\">You Are Not Alone<\/h2>\n<p>The jump to Senior can feel isolating. You're expected to have answers, but often you just have better questions. That's normal. Being Senior isn't about knowing everything; it's about having the confidence to figure it out and the humility to ask for help.<\/p>\n<p>Stay tuned for Part 2, where we'll tackle the monster under the bed: <strong>Software Architecture and Design<\/strong>.<\/p>\n<p>Seeking more personalized guidance on your journey to seniority? I'm here to help.<\/p>\n<a href=\"https:\/\/cal.com\/alexandrecastrotech\/mentoring\" class=\"text-primary-text-light dark:text-primary-text hover:opacity-80 transition-opacity flex items-center gap-2 font-bold uppercase tracking-wide min-h-11\">\n    <i class=\"pixelart-icons-font-calendar\"><\/i> Book a mentoring session\n<\/a>\n<h2 id=\"series-you-are-a-senior-engineer\">Series: You are a Senior Engineer<\/h2>\n<ul>\n<li><strong>You are a Senior Engineer, now what? (Part 1)<\/strong><\/li>\n<li><a href=\"https:\/\/platformtoolsmith.com\/blog\/senior-engineer-part-2\/\">You are a Senior Engineer, Mastering Software Architecture and Design (Part 2)<\/a><\/li>\n<li><a href=\"https:\/\/platformtoolsmith.com\/blog\/senior-engineer-part-3\/\">You are a Senior Engineer, Mastering Communication &amp; Influence (Part 3)<\/a><\/li>\n<\/ul>\n"},{"title":"Kafka Retries: Implementing Consumer Retry with Go","link":{"@attributes":{"href":"https:\/\/platformtoolsmith.com\/blog\/kafka-retries-go\/"}},"updated":"2026-01-17T00:00:00Z","id":"https:\/\/platformtoolsmith.com\/blog\/kafka-retries-go\/","content":"<p><img src=\"https:\/\/platformtoolsmith.com\/assets\/blog\/intro-diagram.0645357a.900.webp\" alt=\"A mole plucks a red failed envelope from a stream of paper envelopes into a retry bin, with a grey dead-letter bin at the end.\" width=\"1920\" height=\"1080\" loading=\"lazy\" decoding=\"async\" srcset=\"https:\/\/platformtoolsmith.com\/assets\/blog\/intro-diagram.0645357a.400.webp 400w, https:\/\/platformtoolsmith.com\/assets\/blog\/intro-diagram.0645357a.900.webp 900w, https:\/\/platformtoolsmith.com\/assets\/blog\/intro-diagram.0645357a.1800.webp 1800w\" sizes=\"(min-width: 1024px) 773px, 100vw\" \/><\/p>\n<p>You don\u2019t \u201cneed retries in Kafka\u201d until the day one of your handlers starts failing and you\u2019re forced into a choice: block consumption (and watch lag climb) or keep consuming and retry somewhere else.<\/p>\n<p>This post is about one very pragmatic approach: <strong>commit the Kafka offset even when processing fails<\/strong>, then push the failed message into a Go retry queue. Kafka keeps moving, and your application owns the retry policy.<\/p>\n<p>Quick context (assuming you already speak Kafka): consumer groups split partitions across consumers for parallelism. Offsets are committed per partition. In this approach, an offset commit means \u201cKafka can move on,\u201d not necessarily \u201cside effects succeeded.\u201d<\/p>\n<p>If you\u2019re looking for the other style of \u201cconsumer retry\u201d (don\u2019t advance the offset until the handler succeeds), that\u2019s a different design with different tradeoffs. This post is explicitly about keeping consumption moving and absorbing failures in a retry pipeline.<\/p>\n<h2 id=\"working-with-examples\">Working with examples<\/h2>\n<p>Let's create an example. Imagine you work for a company named ACME, and you have a Kafka topic that receives a new message every time a new customer is created. This customer needs to receive an email saying that their account is fully created and that they need to verify their email.<\/p>\n<p>When we read something like &quot;when something happens&quot; do &quot;something else,&quot; always think about events! Events, in Kafka, are messages!<\/p>\n<p>We're going to create a microservice that will:<\/p>\n<ul>\n<li>Receive this new customer message from Kafka.<\/li>\n<li>Compose a nice email.<\/li>\n<li>Send it to the customer.<\/li>\n<li>Add a new message to another topic saying that the message was sent!<\/li>\n<\/ul>\n<p>Let's diagram that for more visibility:<\/p>\n<div class=\"mermaid-container\">\n<div class=\"mermaid-diagram\" tabindex=\"0\">\n<svg class=\"dg-kf1\" viewBox=\"-2 -2 874 670\" role=\"img\" aria-labelledby=\"dg-kf1-title dg-kf1-desc\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\">\n<title id=\"dg-kf1-title\">The email verification consumer before retries<\/title>\n<desc id=\"dg-kf1-desc\">The customer app publishes a new customer to the registration topic. The Verify Email app consumes the message, composes the email from templates, sends it through SMTP, and records it in the email sent store.<\/desc>\n<style>.dg-kf1{display:block;width:100%;min-width:560px;height:auto;font-family:'Space Grotesk',system-ui,sans-serif}.dg-kf1 text{text-anchor:middle;fill:var(--mermaid-text, #000000)}.dg-kf1 .b{fill:var(--mermaid-node-bg, #FFFFFF);stroke:var(--mermaid-node-border, #000000);stroke-width:1.5}.dg-kf1 .grp{fill:none;stroke:var(--mermaid-node-border, #000000);stroke-width:1.5;stroke-dasharray:3 5}.dg-kf1 .band{font-size:13.8px;font-weight:700;letter-spacing:.08em}.dg-kf1 .t{font-size:17.5px;font-weight:700}.dg-kf1 .gt{font-size:14.4px;font-weight:700;text-anchor:start;fill:var(--theme-text-muted, #6A6A6A);paint-order:stroke;stroke:var(--mermaid-node-bg, #FFFFFF);stroke-width:6px;stroke-linejoin:round}.dg-kf1 .lab{font-size:14.4px;fill:var(--theme-text-muted, #6A6A6A);paint-order:stroke;stroke:var(--mermaid-node-bg, #FFFFFF);stroke-width:6px;stroke-linejoin:round}.dg-kf1 .ln{fill:none;stroke:var(--mermaid-line, #FF5C00);stroke-width:2}.dg-kf1 .life{fill:none;stroke:var(--mermaid-node-border, #000000);stroke-width:1.5;stroke-dasharray:3 5}.dg-kf1 .dash{stroke-dasharray:7 5}.dg-kf1 marker path{fill:var(--mermaid-line, #FF5C00)}<\/style>\n<defs><marker id=\"dg-kf1-arrow\" viewBox=\"0 0 10 10\" refX=\"7\" refY=\"5\" markerWidth=\"7\" markerHeight=\"7\" orient=\"auto-start-reverse\"><path d=\"M0 0L10 5L0 10z\"><\/path><\/marker><\/defs>\n<rect class=\"grp\" x=\"0\" y=\"146\" width=\"870\" height=\"520\"><\/rect>\n<text class=\"gt\" x=\"12\" y=\"168\">Verify Email App<\/text>\n<path class=\"ln\" d=\"M220 54L378 54\" marker-end=\"url(#dg-kf1-arrow)\"><\/path>\n<path class=\"ln dash\" d=\"M120 330L120 128L440 128L440 110\" marker-end=\"url(#dg-kf1-arrow)\"><\/path>\n<path class=\"ln\" d=\"M220 364L285 364L285 244L348 244\" marker-end=\"url(#dg-kf1-arrow)\"><\/path>\n<path class=\"ln\" d=\"M220 384L348 384\" marker-end=\"url(#dg-kf1-arrow)\"><\/path>\n<path class=\"ln\" d=\"M120 438L120 564L348 564\" marker-end=\"url(#dg-kf1-arrow)\"><\/path>\n<path class=\"ln dash\" d=\"M550 244L638 244\" marker-end=\"url(#dg-kf1-arrow)\" marker-start=\"url(#dg-kf1-arrow)\"><\/path>\n<path class=\"ln dash\" d=\"M550 384L638 384\" marker-end=\"url(#dg-kf1-arrow)\"><\/path>\n<rect class=\"b\" x=\"20\" y=\"0\" width=\"200\" height=\"108\"><\/rect>\n<rect x=\"20\" y=\"0\" width=\"200\" height=\"29\" style=\"fill:var(--diagram-origin, #A0694B)\"><\/rect>\n<text class=\"band\" x=\"120.0\" y=\"20\" style=\"fill:var(--diagram-origin-label, #000000)\">START<\/text>\n<text class=\"t\" x=\"120.0\" y=\"74.5\">Customer App<\/text>\n<rect class=\"b\" x=\"380\" y=\"0\" width=\"240\" height=\"108\"><\/rect>\n<rect x=\"380\" y=\"0\" width=\"240\" height=\"29\" style=\"fill:var(--diagram-neutral, #F0F0DC)\"><\/rect>\n<text class=\"band\" x=\"500.0\" y=\"20\" style=\"fill:var(--diagram-neutral-label, #000000)\">TOPIC<\/text>\n<text class=\"t\" x=\"500.0\" y=\"62.5\">Customer Registration<\/text>\n<text class=\"t\" x=\"500.0\" y=\"86.5\">Topic<\/text>\n<rect class=\"b\" x=\"20\" y=\"330\" width=\"200\" height=\"108\"><\/rect>\n<rect x=\"20\" y=\"330\" width=\"200\" height=\"29\" style=\"fill:var(--diagram-act, #10C840)\"><\/rect>\n<text class=\"band\" x=\"120.0\" y=\"350\" style=\"fill:var(--diagram-act-label, #000000)\">CONSUME<\/text>\n<text class=\"t\" x=\"120.0\" y=\"404.5\">Consume Message<\/text>\n<rect class=\"b\" x=\"350\" y=\"190\" width=\"200\" height=\"108\"><\/rect>\n<rect x=\"350\" y=\"190\" width=\"200\" height=\"29\" style=\"fill:var(--diagram-act, #10C840)\"><\/rect>\n<text class=\"band\" x=\"450.0\" y=\"210\" style=\"fill:var(--diagram-act-label, #000000)\">COMPOSE<\/text>\n<text class=\"t\" x=\"450.0\" y=\"264.5\">Compose Email<\/text>\n<rect class=\"b\" x=\"640\" y=\"190\" width=\"200\" height=\"108\"><\/rect>\n<rect x=\"640\" y=\"190\" width=\"200\" height=\"29\" style=\"fill:var(--diagram-neutral, #F0F0DC)\"><\/rect>\n<text class=\"band\" x=\"740.0\" y=\"210\" style=\"fill:var(--diagram-neutral-label, #000000)\">STORE<\/text>\n<text class=\"t\" x=\"740.0\" y=\"264.5\">Email Templates<\/text>\n<rect class=\"b\" x=\"350\" y=\"330\" width=\"200\" height=\"108\"><\/rect>\n<rect x=\"350\" y=\"330\" width=\"200\" height=\"29\" style=\"fill:var(--diagram-act, #10C840)\"><\/rect>\n<text class=\"band\" x=\"450.0\" y=\"350\" style=\"fill:var(--diagram-act-label, #000000)\">SEND<\/text>\n<text class=\"t\" x=\"450.0\" y=\"404.5\">Send Email<\/text>\n<rect class=\"b\" x=\"640\" y=\"330\" width=\"200\" height=\"108\"><\/rect>\n<rect x=\"640\" y=\"330\" width=\"200\" height=\"29\" style=\"fill:var(--diagram-neutral, #F0F0DC)\"><\/rect>\n<text class=\"band\" x=\"740.0\" y=\"350\" style=\"fill:var(--diagram-neutral-label, #000000)\">TOOL<\/text>\n<text class=\"t\" x=\"740.0\" y=\"404.5\">SMTP<\/text>\n<rect class=\"b\" x=\"350\" y=\"510\" width=\"200\" height=\"108\"><\/rect>\n<rect x=\"350\" y=\"510\" width=\"200\" height=\"29\" style=\"fill:var(--diagram-neutral, #F0F0DC)\"><\/rect>\n<text class=\"band\" x=\"450.0\" y=\"530\" style=\"fill:var(--diagram-neutral-label, #000000)\">STORE<\/text>\n<text class=\"t\" x=\"450.0\" y=\"584.5\">Email Sent<\/text>\n<text class=\"lab\" x=\"300\" y=\"42\" style=\"text-anchor:middle\">New Customer<\/text>\n<text class=\"lab\" x=\"120\" y=\"240\" style=\"text-anchor:middle\">1<\/text>\n<text class=\"lab\" x=\"285\" y=\"300\" style=\"text-anchor:middle\">1<\/text>\n<text class=\"lab\" x=\"310\" y=\"375.0\" style=\"text-anchor:middle\">2<\/text>\n<text class=\"lab\" x=\"240\" y=\"555.0\" style=\"text-anchor:middle\">3<\/text>\n<\/svg>\n<\/div>\n<\/div>\n<h2 id=\"where-are-the-retries\">Where are the retries?<\/h2>\n<p>Cool, but, this is a blog post about retries right? Where are the retries in all of it?<\/p>\n<p>Well, any part of the processing can go wrong, and we don't want our customers to miss out on their account creation emails, do we? So, let's talk about a few things that could go wrong and how we can handle them with retries:<\/p>\n<ul>\n<li>The email template database might be down. No problem, we'll just keep trying to connect until it's back up.<\/li>\n<li>The template persisted inside of it might be invalid. We'll check for this and alert the team to update it if needed.<\/li>\n<li>The SMTP server might be down or drop a message. We'll keep trying to send the email until it goes through.<\/li>\n<li>The application might compose the SMTP message badly and the SMTP server reject it. We'll catch this error and fix the composition before trying again.<\/li>\n<li>Producing the message to the other Kafka topic might return an error on Kafka's server side. We'll keep trying until it's successfully sent.<\/li>\n<\/ul>\n<p>Some of the errors mentioned may require code or data modifications to resolve, such as updating a template or correcting the way the application composes an SMTP message. These types of errors may not be suitable for retries as they may require manual intervention.<\/p>\n<p>On the other hand, other errors such as temporary database downtime, flaky message production, or overloaded SMTP servers can potentially be resolved by retrying the operation.<\/p>\n<h2 id=\"unlocking-kafka-consumer-retries\">Unlocking Kafka Consumer Retries<\/h2>\n<p>This comes up constantly in event-driven systems: Kafka gives you a great log, but it doesn\u2019t give you a \u201cretry policy\u201d button.<\/p>\n<p>I\u2019ve tried a bunch of architectures for retrying message processing. None is perfect, but one is consistently the easiest to operate when your top priority is \u201ckeep consuming,\u201d as long as you\u2019re explicit about the semantics trade.<\/p>\n<p>Before diving into the solution, let me share some background on my previous attempts.<\/p>\n<h3 id=\"aws-sqs-a-great-option-but-with-limitations\">AWS SQS: A Great Option, But With Limitations<\/h3>\n<p>As a strong advocate for AWS, my first thought when considering retries was AWS SQS.<\/p>\n<p>Amazon Simple Queue Service (SQS) is a fully managed message queuing service that enables the decoupling and scaling of microservices, distributed systems, and serverless applications. It comes with built-in retry behavior (visibility timeout + redrive policies + DLQ) and supports a maximum message size of 256 KB.<\/p>\n<p>Now, don\u2019t get me wrong: the grown-up solution is the pointer pattern \u2014 put the real payload in object storage (like S3) and send a small pointer through SQS.<\/p>\n<p>That pattern works, but it adds moving parts:<\/p>\n<ul>\n<li>You\u2019ve introduced a second persistence system.<\/li>\n<li>You need retention\/lifecycle for blobs.<\/li>\n<li>You need consistency between \u201cpointer message\u201d and \u201cpayload object\u201d.<\/li>\n<\/ul>\n<p>Also, quick correction on Kafka: Kafka message size is configurable (defaults vary, but ~1MB is common). You <em>can<\/em> tune limits, but you still need an explicit payload contract, or producers will eventually surprise you in production.<\/p>\n<p>So while SQS is a great option, it wasn\u2019t the best fit for what I wanted here: keep the entire pipeline \u201cKafka-native\u201d without introducing a second storage path just to make retries easier.<\/p>\n<h3 id=\"kafka\">Kafka<\/h3>\n<p>Yes, that's exactly it, since SQS isn't a suitable solution, why not use Kafka itself?<\/p>\n<p>I attempted to create an internal application Kafka topic that would only be used by the application, where the application would push messages to it, and in case of a failure, it would enqueue the message again with a new count attribute in the header. Let me diagram that to make it more clear:<\/p>\n<div class=\"mermaid-container\">\n<div class=\"mermaid-diagram\" tabindex=\"0\">\n<svg class=\"dg-kf2\" viewBox=\"-2 -2 1094 704\" role=\"img\" aria-labelledby=\"dg-kf2-title dg-kf2-desc\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\">\n<title id=\"dg-kf2-title\">Retries through an internal Kafka topic<\/title>\n<desc id=\"dg-kf2-desc\">The application consumes a customer message and produces it to an internal topic with a count of 0. It consumes the message and processing fails, so it produces again with count 1, then count 2. After the third failure it sends the message to the dead letter topic.<\/desc>\n<style>.dg-kf2{display:block;width:100%;min-width:760px;height:auto;font-family:'Space Grotesk',system-ui,sans-serif}.dg-kf2 text{text-anchor:middle;fill:var(--mermaid-text, #000000)}.dg-kf2 .b{fill:var(--mermaid-node-bg, #FFFFFF);stroke:var(--mermaid-node-border, #000000);stroke-width:1.5}.dg-kf2 .grp{fill:none;stroke:var(--mermaid-node-border, #000000);stroke-width:1.5;stroke-dasharray:3 5}.dg-kf2 .band{font-size:13.8px;font-weight:700;letter-spacing:.08em}.dg-kf2 .t{font-size:17.5px;font-weight:700}.dg-kf2 .gt{font-size:14.4px;font-weight:700;text-anchor:start;fill:var(--theme-text-muted, #6A6A6A);paint-order:stroke;stroke:var(--mermaid-node-bg, #FFFFFF);stroke-width:6px;stroke-linejoin:round}.dg-kf2 .lab{font-size:14.4px;fill:var(--theme-text-muted, #6A6A6A);paint-order:stroke;stroke:var(--mermaid-node-bg, #FFFFFF);stroke-width:6px;stroke-linejoin:round}.dg-kf2 .ln{fill:none;stroke:var(--mermaid-line, #FF5C00);stroke-width:2}.dg-kf2 .life{fill:none;stroke:var(--mermaid-node-border, #000000);stroke-width:1.5;stroke-dasharray:3 5}.dg-kf2 .dash{stroke-dasharray:7 5}.dg-kf2 marker path{fill:var(--mermaid-line, #FF5C00)}<\/style>\n<defs><marker id=\"dg-kf2-arrow\" viewBox=\"0 0 10 10\" refX=\"7\" refY=\"5\" markerWidth=\"7\" markerHeight=\"7\" orient=\"auto-start-reverse\"><path d=\"M0 0L10 5L0 10z\"><\/path><\/marker><\/defs>\n<path class=\"life\" d=\"M1000 84L1000 700\"><\/path>\n<path class=\"life\" d=\"M770 84L770 700\"><\/path>\n<path class=\"life\" d=\"M430 84L430 700\"><\/path>\n<path class=\"life\" d=\"M100 84L100 700\"><\/path>\n<path class=\"ln dash\" d=\"M430 118L102 118\" marker-end=\"url(#dg-kf2-arrow)\"><\/path>\n<path class=\"ln dash\" d=\"M430 164L768 164\" marker-end=\"url(#dg-kf2-arrow)\"><\/path>\n<path class=\"ln dash\" d=\"M430 210L768 210\" marker-end=\"url(#dg-kf2-arrow)\"><\/path>\n<path class=\"ln dash\" d=\"M430 334L768 334\" marker-end=\"url(#dg-kf2-arrow)\"><\/path>\n<path class=\"ln dash\" d=\"M430 380L768 380\" marker-end=\"url(#dg-kf2-arrow)\"><\/path>\n<path class=\"ln dash\" d=\"M430 504L768 504\" marker-end=\"url(#dg-kf2-arrow)\"><\/path>\n<path class=\"ln dash\" d=\"M430 550L768 550\" marker-end=\"url(#dg-kf2-arrow)\"><\/path>\n<path class=\"ln dash\" d=\"M430 674L998 674\" marker-end=\"url(#dg-kf2-arrow)\"><\/path>\n<rect class=\"b\" x=\"0.0\" y=\"0\" width=\"200\" height=\"84\"><\/rect>\n<rect x=\"0.0\" y=\"0\" width=\"200\" height=\"29\" style=\"fill:var(--diagram-neutral, #F0F0DC)\"><\/rect>\n<text class=\"band\" x=\"100.0\" y=\"20\" style=\"fill:var(--diagram-neutral-label, #000000)\">TOPIC<\/text>\n<text class=\"t\" x=\"100.0\" y=\"62.5\">Kafka Topic<\/text>\n<rect class=\"b\" x=\"330.0\" y=\"0\" width=\"200\" height=\"84\"><\/rect>\n<rect x=\"330.0\" y=\"0\" width=\"200\" height=\"29\" style=\"fill:var(--diagram-act, #10C840)\"><\/rect>\n<text class=\"band\" x=\"430.0\" y=\"20\" style=\"fill:var(--diagram-act-label, #000000)\">EXECUTE<\/text>\n<text class=\"t\" x=\"430.0\" y=\"62.5\">Application<\/text>\n<rect class=\"b\" x=\"670.0\" y=\"0\" width=\"200\" height=\"84\"><\/rect>\n<rect x=\"670.0\" y=\"0\" width=\"200\" height=\"29\" style=\"fill:var(--diagram-neutral, #F0F0DC)\"><\/rect>\n<text class=\"band\" x=\"770.0\" y=\"20\" style=\"fill:var(--diagram-neutral-label, #000000)\">TOPIC<\/text>\n<text class=\"t\" x=\"770.0\" y=\"62.5\">Internal Topic<\/text>\n<rect class=\"b\" x=\"910.0\" y=\"0\" width=\"180\" height=\"84\"><\/rect>\n<rect x=\"910.0\" y=\"0\" width=\"180\" height=\"29\" style=\"fill:var(--diagram-hold, #FAC800)\"><\/rect>\n<text class=\"band\" x=\"1000.0\" y=\"20\" style=\"fill:var(--diagram-hold-label, #000000)\">PARKED<\/text>\n<text class=\"t\" x=\"1000.0\" y=\"62.5\">DLQ Topic<\/text>\n<rect class=\"b\" x=\"335.0\" y=\"236\" width=\"190\" height=\"60\"><\/rect>\n<text class=\"lab\" x=\"430\" y=\"258\">Process message<\/text>\n<text class=\"lab\" x=\"430\" y=\"280\">Error Processing<\/text>\n<rect class=\"b\" x=\"335.0\" y=\"406\" width=\"190\" height=\"60\"><\/rect>\n<text class=\"lab\" x=\"430\" y=\"428\">Process message<\/text>\n<text class=\"lab\" x=\"430\" y=\"450\">Error Processing<\/text>\n<rect class=\"b\" x=\"335.0\" y=\"576\" width=\"190\" height=\"60\"><\/rect>\n<text class=\"lab\" x=\"430\" y=\"598\">Process message<\/text>\n<text class=\"lab\" x=\"430\" y=\"620\">Error Processing<\/text>\n<text class=\"lab\" x=\"265.0\" y=\"109\" style=\"text-anchor:middle\">Consume new Customer Message<\/text>\n<text class=\"lab\" x=\"600.0\" y=\"155\" style=\"text-anchor:middle\">Produce message with header &quot;count = 0&quot;<\/text>\n<text class=\"lab\" x=\"600.0\" y=\"201\" style=\"text-anchor:middle\">Consume message<\/text>\n<text class=\"lab\" x=\"600.0\" y=\"325\" style=\"text-anchor:middle\">Produce message with header &quot;count = 1&quot;<\/text>\n<text class=\"lab\" x=\"600.0\" y=\"371\" style=\"text-anchor:middle\">Consume message<\/text>\n<text class=\"lab\" x=\"600.0\" y=\"495\" style=\"text-anchor:middle\">Produce message with header &quot;count = 2&quot;<\/text>\n<text class=\"lab\" x=\"600.0\" y=\"541\" style=\"text-anchor:middle\">Consume message<\/text>\n<text class=\"lab\" x=\"715.0\" y=\"665\" style=\"text-anchor:middle\">Produce message<\/text>\n<\/svg>\n<\/div>\n<\/div>\n<p>While this approach does work, it has some significant downsides. Firstly, setting up Kafka topics is not as straightforward as SQS, creating multiple topics can be somewhat cumbersome, and dealing with all the consumers and producers within the code can become quite messy.<\/p>\n<p>Kafka also doesn\u2019t give you a first-class \u201cretry this message N times\u201d feature at the broker level, and that\u2019s mostly a good thing: Kafka can\u2019t know whether your side effects are safe to repeat. Retries are an application-level decision tied to offsets, commits, backpressure, and idempotency.<\/p>\n<h3 id=\"databases\">Databases<\/h3>\n<p>Desperate Times Call for Desperate Measures, right?<\/p>\n<p>As a last resort, I turned to use databases for message retries. Modern SQL databases have LOCKING mechanisms that can be leveraged to use a table as a queue.<\/p>\n<div class=\"mermaid-container\">\n<div class=\"mermaid-diagram\" tabindex=\"0\">\n<svg class=\"dg-kf3\" viewBox=\"-2 -2 789 408\" role=\"img\" aria-labelledby=\"dg-kf3-title dg-kf3-desc\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\">\n<title id=\"dg-kf3-title\">Retries through a database table<\/title>\n<desc id=\"dg-kf3-desc\">The application inserts a row with process_times 0 and created_at now. In a loop it queries the oldest row where process_times is below 2, locks it and processes it. When processing fails it adds 1 to process_times.<\/desc>\n<style>.dg-kf3{display:block;width:100%;min-width:640px;height:auto;font-family:'Space Grotesk',system-ui,sans-serif}.dg-kf3 text{text-anchor:middle;fill:var(--mermaid-text, #000000)}.dg-kf3 .b{fill:var(--mermaid-node-bg, #FFFFFF);stroke:var(--mermaid-node-border, #000000);stroke-width:1.5}.dg-kf3 .grp{fill:none;stroke:var(--mermaid-node-border, #000000);stroke-width:1.5;stroke-dasharray:3 5}.dg-kf3 .band{font-size:13.8px;font-weight:700;letter-spacing:.08em}.dg-kf3 .t{font-size:17.5px;font-weight:700}.dg-kf3 .gt{font-size:14.4px;font-weight:700;text-anchor:start;fill:var(--theme-text-muted, #6A6A6A);paint-order:stroke;stroke:var(--mermaid-node-bg, #FFFFFF);stroke-width:6px;stroke-linejoin:round}.dg-kf3 .lab{font-size:14.4px;fill:var(--theme-text-muted, #6A6A6A);paint-order:stroke;stroke:var(--mermaid-node-bg, #FFFFFF);stroke-width:6px;stroke-linejoin:round}.dg-kf3 .ln{fill:none;stroke:var(--mermaid-line, #FF5C00);stroke-width:2}.dg-kf3 .life{fill:none;stroke:var(--mermaid-node-border, #000000);stroke-width:1.5;stroke-dasharray:3 5}.dg-kf3 .dash{stroke-dasharray:7 5}.dg-kf3 marker path{fill:var(--mermaid-line, #FF5C00)}<\/style>\n<defs><marker id=\"dg-kf3-arrow\" viewBox=\"0 0 10 10\" refX=\"7\" refY=\"5\" markerWidth=\"7\" markerHeight=\"7\" orient=\"auto-start-reverse\"><path d=\"M0 0L10 5L0 10z\"><\/path><\/marker><\/defs>\n<path class=\"life\" d=\"M680 84L680 404\"><\/path>\n<path class=\"life\" d=\"M110 84L110 404\"><\/path>\n<rect class=\"grp\" x=\"5\" y=\"146\" width=\"780\" height=\"256\"><\/rect>\n<text class=\"gt\" x=\"17\" y=\"168\">loop: process_times<\/text>\n<path class=\"ln dash\" d=\"M110 118L678 118\" marker-end=\"url(#dg-kf3-arrow)\"><\/path>\n<path class=\"ln dash\" d=\"M110 208L678 208\" marker-end=\"url(#dg-kf3-arrow)\"><\/path>\n<path class=\"ln dash\" d=\"M110 254L678 254\" marker-end=\"url(#dg-kf3-arrow)\"><\/path>\n<path class=\"ln dash\" d=\"M110 378L678 378\" marker-end=\"url(#dg-kf3-arrow)\"><\/path>\n<rect class=\"b\" x=\"10.0\" y=\"0\" width=\"200\" height=\"84\"><\/rect>\n<rect x=\"10.0\" y=\"0\" width=\"200\" height=\"29\" style=\"fill:var(--diagram-act, #10C840)\"><\/rect>\n<text class=\"band\" x=\"110.0\" y=\"20\" style=\"fill:var(--diagram-act-label, #000000)\">EXECUTE<\/text>\n<text class=\"t\" x=\"110.0\" y=\"62.5\">Application<\/text>\n<rect class=\"b\" x=\"580.0\" y=\"0\" width=\"200\" height=\"84\"><\/rect>\n<rect x=\"580.0\" y=\"0\" width=\"200\" height=\"29\" style=\"fill:var(--diagram-neutral, #F0F0DC)\"><\/rect>\n<text class=\"band\" x=\"680.0\" y=\"20\" style=\"fill:var(--diagram-neutral-label, #000000)\">STORE<\/text>\n<text class=\"t\" x=\"680.0\" y=\"62.5\">DB<\/text>\n<rect class=\"b\" x=\"15.0\" y=\"280\" width=\"190\" height=\"60\"><\/rect>\n<text class=\"lab\" x=\"110\" y=\"302\">Process row<\/text>\n<text class=\"lab\" x=\"110\" y=\"324\">Error Processing<\/text>\n<text class=\"lab\" x=\"395.0\" y=\"109\" style=\"text-anchor:middle\">Insert new row with process_times = 0 and created_at = now()<\/text>\n<text class=\"lab\" x=\"395.0\" y=\"199\" style=\"text-anchor:middle\">Query order by created_at where process_times &lt; 2 top 1 row<\/text>\n<text class=\"lab\" x=\"395.0\" y=\"245\" style=\"text-anchor:middle\">Lock row<\/text>\n<text class=\"lab\" x=\"395.0\" y=\"369\" style=\"text-anchor:middle\">Update row process_times column +1<\/text>\n<\/svg>\n<\/div>\n<\/div>\n<p>To my surprise, this approach worked! However, it's not a perfect solution. Databases are not designed to function as queues, and using them in this way can be a stretch. There are a few downsides to this approach:<\/p>\n<ul>\n<li>Databases are built for consistency using transactions, which can make them performant, but they will never perform as well as a specialized tool like Kafka. This can easily become a bottleneck in the system.<\/li>\n<li>Managing all of this code can easily become problematic, and it is not an easy pattern to share across an entire company.<\/li>\n<\/ul>\n<h3 id=\"so-what-now-memory\">So, what now? Memory<\/h3>\n<p>After trying all of the above solutions and being unsatisfied with the results, I decided to look at how other companies and frameworks handle retries.<\/p>\n<p>One framework that stood out to me is Spring, an open-source Java framework that provides a comprehensive programming and configuration model for building modern enterprise applications.<\/p>\n<h3 id=\"the-spring-way\">The Spring way<\/h3>\n<p>One of the modules within Spring is Spring Kafka, which provides several ways to handle retries when consuming messages. These include using the <code>RetryTemplate<\/code> and <code>RetryCallback<\/code> interfaces from the Spring Retry library to define a retry policy, using the <code>@KafkaListener<\/code> annotation to configure retry behavior on a per-method basis, or using AOP to handle retries by using a <code>RetryInterceptor<\/code>.<\/p>\n<p>Spring Kafka allows developers to choose the approach that best fits their use case, whether it is a global retry policy or a more fine-grained per-method retry configuration.<\/p>\n<p>It's been a long time since I worked with Java, and Spring code is far from easy to understand (it's pretty advanced stuff), but after diving deeper into the Spring Kafka framework, I discovered that it implements an error handler using a strategy for handling exceptions thrown by the consumers.<\/p>\n<p>When an exception that is not a <code>BatchListenerFailedException<\/code> (which Spring knows is impossible to retry) is thrown, the error handler will retry the batch of records from memory. This prevents a consumer rebalance during an extended retry sequence and allows for a more elegant solution.<\/p>\n<p>In my honest opinion, the Spring Kafka framework offers a robust and flexible approach to handling retries that is well worth considering for any enterprise application.<\/p>\n<p>It provides a range of options for developers to choose from and allows for more fine-grained control over retry behavior, making it a great choice for any organization looking to implement a retry mechanism for their messaging system.<\/p>\n<h3 id=\"inspired-by-spring-kafka-but-different-semantics-in-go\">Inspired by Spring Kafka (but different semantics) in Go<\/h3>\n<p>I currently work with (and love <i class=\"pixelart-icons-font-heart text-primary-text-light dark:text-primary-text\"><\/i>) Go, and, as far as my research went, I didn't find any rewrite of the Spring Kafka and Retry framework in Go, so, let's build our own!<\/p>\n<p>Here\u2019s the key difference in my implementation compared to Spring Kafka\u2019s approach: I\u2019m not trying to pause a partition and \u201chold the offset line\u201d until processing succeeds.<\/p>\n<p>Instead, I commit the offset and move on, then retry in the background.<\/p>\n<p>My approach includes:<\/p>\n<ul>\n<li>Creating a consumer that reads from the topic.<\/li>\n<li>Creating a Go channel that handles retries.<\/li>\n<li>If a message fails to process, it is sent to the retry channel.<\/li>\n<li>The main consumer continues to process the main topic while the other messages are retried.<\/li>\n<li>If retries are exhausted, the message is sent to a persisted DLQ for later investigation.<\/li>\n<\/ul>\n<blockquote class=\"alert-blockquote\">\n<div class=\"alert-header\">\n                [ <i class=\"pixelart-icons-font-square-alert\"><\/i> <span>WARNING<\/span> ]\n              <\/div><p>This pattern intentionally decouples Kafka offsets from processing success.<\/p>\n<p>If you commit a message and your process crashes before the retry succeeds, Kafka will not redeliver that message. With an in-memory Go channel, that means a restart can drop \u201cin-flight retries.\u201d<\/p>\n<p>If you need stronger guarantees than that, the retry queue must be durable (retry topic, DB table, external queue) and your handler must be idempotent.<\/p>\n<p>Also note that retries can happen later than subsequent messages, so you\u2019re trading away strict ordering for throughput.<\/p>\n<\/blockquote>\n<h4 id=\"when-not-to-use-this-pattern\">When not to use this pattern<\/h4>\n<p>If you need strict ordering guarantees, or you must be able to restart at any time without losing \u201cin-flight retries,\u201d don\u2019t use an in-memory retry queue behind an early commit. In those cases, you either need a durable retry queue, or you need an offset-holding strategy (pause\/seek and commit only after success).<\/p>\n<h4 id=\"here-s-the-full-example\">Here\u2019s the full example<\/h4>\n<p>This example uses an in-memory Go channel as the retry queue. That keeps Kafka consumption moving, but it also means retries are lost on process restart. In real systems, I usually evolve this into a durable retry queue.<\/p>\n<p>One more practical note: your retry queue is part of your backpressure story. If it fills up, enqueueing can block and slow consumption. The &quot;keep consuming no matter what&quot; version of this pattern needs an explicit overflow strategy (drop, spill to durable storage, or fail fast into DLQ). Also consider adding jitter to your backoff to avoid retry storms when multiple messages fail simultaneously.<\/p>\n<p><strong><code>kafka_retry_dlq\/kafka-retry.go<\/code><\/strong><\/p>\n<pre class=\"language-go\"><code class=\"language-go\"><span class=\"token keyword\">package<\/span> kafka_retry_dlq\n\n<span class=\"token keyword\">import<\/span> <span class=\"token punctuation\">(<\/span>\n    <span class=\"token string\">\"context\"<\/span>\n    <span class=\"token string\">\"fmt\"<\/span>\n    <span class=\"token string\">\"time\"<\/span>\n\n    <span class=\"token string\">\"github.com\/cenkalti\/backoff\/v4\"<\/span>\n    <span class=\"token string\">\"github.com\/segmentio\/kafka-go\"<\/span>\n<span class=\"token punctuation\">)<\/span>\n\n<span class=\"token keyword\">type<\/span> ProcessRetryHandler <span class=\"token keyword\">interface<\/span> <span class=\"token punctuation\">{<\/span>\n    <span class=\"token function\">Process<\/span><span class=\"token punctuation\">(<\/span>context<span class=\"token punctuation\">.<\/span>Context<span class=\"token punctuation\">,<\/span> kafka<span class=\"token punctuation\">.<\/span>Message<span class=\"token punctuation\">)<\/span> <span class=\"token builtin\">error<\/span>\n    <span class=\"token function\">MoveToDLQ<\/span><span class=\"token punctuation\">(<\/span>context<span class=\"token punctuation\">.<\/span>Context<span class=\"token punctuation\">,<\/span> kafka<span class=\"token punctuation\">.<\/span>Message<span class=\"token punctuation\">)<\/span>\n<span class=\"token punctuation\">}<\/span>\n\n<span class=\"token keyword\">type<\/span> ConsumerWithRetryOptions <span class=\"token keyword\">struct<\/span> <span class=\"token punctuation\">{<\/span>\n    Handler    ProcessRetryHandler\n    Reader     <span class=\"token operator\">*<\/span>kafka<span class=\"token punctuation\">.<\/span>Reader\n    MaxRetries <span class=\"token builtin\">int<\/span>\n    RetryQueue <span class=\"token keyword\">chan<\/span> kafka<span class=\"token punctuation\">.<\/span>Message\n    Backoff    backoff<span class=\"token punctuation\">.<\/span>BackOff\n<span class=\"token punctuation\">}<\/span>\n\n<span class=\"token keyword\">func<\/span> <span class=\"token function\">NewConsumerWithRetry<\/span><span class=\"token punctuation\">(<\/span>ctx context<span class=\"token punctuation\">.<\/span>Context<span class=\"token punctuation\">,<\/span> options <span class=\"token operator\">*<\/span>ConsumerWithRetryOptions<span class=\"token punctuation\">)<\/span> <span class=\"token punctuation\">{<\/span>\n    <span class=\"token keyword\">go<\/span> <span class=\"token keyword\">func<\/span><span class=\"token punctuation\">(<\/span><span class=\"token punctuation\">)<\/span> <span class=\"token punctuation\">{<\/span>\n        <span class=\"token keyword\">for<\/span> <span class=\"token punctuation\">{<\/span>\n            <span class=\"token keyword\">select<\/span> <span class=\"token punctuation\">{<\/span>\n            <span class=\"token keyword\">case<\/span> <span class=\"token operator\">&lt;-<\/span>ctx<span class=\"token punctuation\">.<\/span><span class=\"token function\">Done<\/span><span class=\"token punctuation\">(<\/span><span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">:<\/span>\n                <span class=\"token keyword\">return<\/span>\n            <span class=\"token keyword\">case<\/span> msg <span class=\"token operator\">:=<\/span> <span class=\"token operator\">&lt;-<\/span>options<span class=\"token punctuation\">.<\/span>RetryQueue<span class=\"token punctuation\">:<\/span>\n                options<span class=\"token punctuation\">.<\/span>Backoff<span class=\"token punctuation\">.<\/span><span class=\"token function\">Reset<\/span><span class=\"token punctuation\">(<\/span><span class=\"token punctuation\">)<\/span>\n                retries <span class=\"token operator\">:=<\/span> <span class=\"token number\">1<\/span>\n\n                <span class=\"token keyword\">for<\/span> <span class=\"token punctuation\">{<\/span>\n                    fmt<span class=\"token punctuation\">.<\/span><span class=\"token function\">Printf<\/span><span class=\"token punctuation\">(<\/span><span class=\"token string\">\"Retry %v message %v\\n\"<\/span><span class=\"token punctuation\">,<\/span> retries<span class=\"token punctuation\">,<\/span> msg<span class=\"token punctuation\">.<\/span>Key<span class=\"token punctuation\">)<\/span>\n\n                    <span class=\"token keyword\">if<\/span> retries <span class=\"token operator\">>=<\/span> options<span class=\"token punctuation\">.<\/span>MaxRetries <span class=\"token punctuation\">{<\/span>\n                        options<span class=\"token punctuation\">.<\/span>Handler<span class=\"token punctuation\">.<\/span><span class=\"token function\">MoveToDLQ<\/span><span class=\"token punctuation\">(<\/span>ctx<span class=\"token punctuation\">,<\/span> msg<span class=\"token punctuation\">)<\/span>\n                        <span class=\"token keyword\">break<\/span>\n                    <span class=\"token punctuation\">}<\/span>\n\n                    <span class=\"token keyword\">if<\/span> err <span class=\"token operator\">:=<\/span> options<span class=\"token punctuation\">.<\/span>Handler<span class=\"token punctuation\">.<\/span><span class=\"token function\">Process<\/span><span class=\"token punctuation\">(<\/span>ctx<span class=\"token punctuation\">,<\/span> msg<span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span> err <span class=\"token operator\">!=<\/span> <span class=\"token boolean\">nil<\/span> <span class=\"token punctuation\">{<\/span>\n                        fmt<span class=\"token punctuation\">.<\/span><span class=\"token function\">Printf<\/span><span class=\"token punctuation\">(<\/span><span class=\"token string\">\"Error processing message, retrying: %v\\n\"<\/span><span class=\"token punctuation\">,<\/span> err<span class=\"token punctuation\">)<\/span>\n                        time<span class=\"token punctuation\">.<\/span><span class=\"token function\">Sleep<\/span><span class=\"token punctuation\">(<\/span>options<span class=\"token punctuation\">.<\/span>Backoff<span class=\"token punctuation\">.<\/span><span class=\"token function\">NextBackOff<\/span><span class=\"token punctuation\">(<\/span><span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">)<\/span>\n                        retries<span class=\"token operator\">++<\/span>\n                        <span class=\"token keyword\">continue<\/span>\n                    <span class=\"token punctuation\">}<\/span>\n\n                    <span class=\"token keyword\">break<\/span>\n                <span class=\"token punctuation\">}<\/span>\n            <span class=\"token punctuation\">}<\/span>\n        <span class=\"token punctuation\">}<\/span>\n    <span class=\"token punctuation\">}<\/span><span class=\"token punctuation\">(<\/span><span class=\"token punctuation\">)<\/span>\n\n    <span class=\"token keyword\">for<\/span> <span class=\"token punctuation\">{<\/span>\n        <span class=\"token keyword\">select<\/span> <span class=\"token punctuation\">{<\/span>\n        <span class=\"token keyword\">case<\/span> <span class=\"token operator\">&lt;-<\/span>ctx<span class=\"token punctuation\">.<\/span><span class=\"token function\">Done<\/span><span class=\"token punctuation\">(<\/span><span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">:<\/span>\n            <span class=\"token keyword\">return<\/span>\n        <span class=\"token keyword\">default<\/span><span class=\"token punctuation\">:<\/span>\n        <span class=\"token punctuation\">}<\/span>\n\n        msg<span class=\"token punctuation\">,<\/span> err <span class=\"token operator\">:=<\/span> options<span class=\"token punctuation\">.<\/span>Reader<span class=\"token punctuation\">.<\/span><span class=\"token function\">ReadMessage<\/span><span class=\"token punctuation\">(<\/span>ctx<span class=\"token punctuation\">)<\/span>\n        <span class=\"token keyword\">if<\/span> err <span class=\"token operator\">!=<\/span> <span class=\"token boolean\">nil<\/span> <span class=\"token punctuation\">{<\/span>\n            fmt<span class=\"token punctuation\">.<\/span><span class=\"token function\">Println<\/span><span class=\"token punctuation\">(<\/span><span class=\"token string\">\"Error reading message: \"<\/span><span class=\"token punctuation\">,<\/span> err<span class=\"token punctuation\">)<\/span>\n            <span class=\"token keyword\">return<\/span>\n        <span class=\"token punctuation\">}<\/span>\n\n        <span class=\"token keyword\">if<\/span> err <span class=\"token operator\">:=<\/span> options<span class=\"token punctuation\">.<\/span>Handler<span class=\"token punctuation\">.<\/span><span class=\"token function\">Process<\/span><span class=\"token punctuation\">(<\/span>ctx<span class=\"token punctuation\">,<\/span> msg<span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">;<\/span> err <span class=\"token operator\">!=<\/span> <span class=\"token boolean\">nil<\/span> <span class=\"token punctuation\">{<\/span>\n            <span class=\"token keyword\">select<\/span> <span class=\"token punctuation\">{<\/span>\n            <span class=\"token keyword\">case<\/span> options<span class=\"token punctuation\">.<\/span>RetryQueue <span class=\"token operator\">&lt;-<\/span> msg<span class=\"token punctuation\">:<\/span>\n            <span class=\"token keyword\">default<\/span><span class=\"token punctuation\">:<\/span>\n                fmt<span class=\"token punctuation\">.<\/span><span class=\"token function\">Println<\/span><span class=\"token punctuation\">(<\/span><span class=\"token string\">\"Retry queue full, sending directly to DLQ\"<\/span><span class=\"token punctuation\">)<\/span>\n                options<span class=\"token punctuation\">.<\/span>Handler<span class=\"token punctuation\">.<\/span><span class=\"token function\">MoveToDLQ<\/span><span class=\"token punctuation\">(<\/span>ctx<span class=\"token punctuation\">,<\/span> msg<span class=\"token punctuation\">)<\/span>\n            <span class=\"token punctuation\">}<\/span>\n        <span class=\"token punctuation\">}<\/span>\n    <span class=\"token punctuation\">}<\/span>\n<span class=\"token punctuation\">}<\/span><\/code><\/pre>\n<p><strong><code>example.go<\/code><\/strong><\/p>\n<pre class=\"language-go\"><code class=\"language-go\"><span class=\"token keyword\">package<\/span> main\n\n<span class=\"token keyword\">import<\/span> <span class=\"token punctuation\">(<\/span>\n    <span class=\"token string\">\"context\"<\/span>\n    <span class=\"token string\">\"crypto\/tls\"<\/span>\n    <span class=\"token string\">\"encoding\/json\"<\/span>\n    <span class=\"token string\">\"fmt\"<\/span>\n    <span class=\"token string\">\"math\/rand\"<\/span>\n    <span class=\"token string\">\"time\"<\/span>\n\n    <span class=\"token string\">\"github.com\/acastro2\/go-retries\/kafka_retry_dlq\"<\/span>\n    <span class=\"token string\">\"github.com\/cenkalti\/backoff\/v4\"<\/span>\n    <span class=\"token string\">\"github.com\/segmentio\/kafka-go\"<\/span>\n    <span class=\"token string\">\"github.com\/segmentio\/kafka-go\/sasl\/plain\"<\/span>\n<span class=\"token punctuation\">)<\/span>\n\n<span class=\"token keyword\">type<\/span> exampleHandler <span class=\"token keyword\">struct<\/span> <span class=\"token punctuation\">{<\/span><span class=\"token punctuation\">}<\/span>\n\n<span class=\"token keyword\">func<\/span> <span class=\"token punctuation\">(<\/span>h <span class=\"token operator\">*<\/span>exampleHandler<span class=\"token punctuation\">)<\/span> <span class=\"token function\">Process<\/span><span class=\"token punctuation\">(<\/span>ctx context<span class=\"token punctuation\">.<\/span>Context<span class=\"token punctuation\">,<\/span> msg kafka<span class=\"token punctuation\">.<\/span>Message<span class=\"token punctuation\">)<\/span> <span class=\"token builtin\">error<\/span> <span class=\"token punctuation\">{<\/span>\n    <span class=\"token keyword\">var<\/span> jsonData <span class=\"token keyword\">map<\/span><span class=\"token punctuation\">[<\/span><span class=\"token builtin\">string<\/span><span class=\"token punctuation\">]<\/span><span class=\"token keyword\">interface<\/span><span class=\"token punctuation\">{<\/span><span class=\"token punctuation\">}<\/span>\n    err <span class=\"token operator\">:=<\/span> json<span class=\"token punctuation\">.<\/span><span class=\"token function\">Unmarshal<\/span><span class=\"token punctuation\">(<\/span>msg<span class=\"token punctuation\">.<\/span>Value<span class=\"token punctuation\">,<\/span> <span class=\"token operator\">&amp;<\/span>jsonData<span class=\"token punctuation\">)<\/span>\n    fmt<span class=\"token punctuation\">.<\/span><span class=\"token function\">Println<\/span><span class=\"token punctuation\">(<\/span>jsonData<span class=\"token punctuation\">)<\/span>\n\n    <span class=\"token keyword\">if<\/span> rand<span class=\"token punctuation\">.<\/span><span class=\"token function\">Intn<\/span><span class=\"token punctuation\">(<\/span><span class=\"token number\">100<\/span><span class=\"token punctuation\">)<\/span> <span class=\"token operator\">&lt;<\/span> <span class=\"token number\">25<\/span> <span class=\"token punctuation\">{<\/span>\n        <span class=\"token keyword\">return<\/span> fmt<span class=\"token punctuation\">.<\/span><span class=\"token function\">Errorf<\/span><span class=\"token punctuation\">(<\/span><span class=\"token string\">\"Throw random error to simulate processing failure and test retry.\"<\/span><span class=\"token punctuation\">)<\/span>\n    <span class=\"token punctuation\">}<\/span>\n\n    <span class=\"token keyword\">return<\/span> err\n<span class=\"token punctuation\">}<\/span>\n\n<span class=\"token keyword\">func<\/span> <span class=\"token punctuation\">(<\/span>h <span class=\"token operator\">*<\/span>exampleHandler<span class=\"token punctuation\">)<\/span> <span class=\"token function\">MoveToDLQ<\/span><span class=\"token punctuation\">(<\/span>ctx context<span class=\"token punctuation\">.<\/span>Context<span class=\"token punctuation\">,<\/span> msg kafka<span class=\"token punctuation\">.<\/span>Message<span class=\"token punctuation\">)<\/span> <span class=\"token punctuation\">{<\/span>\n    fmt<span class=\"token punctuation\">.<\/span><span class=\"token function\">Println<\/span><span class=\"token punctuation\">(<\/span><span class=\"token string\">\"Moving message to DLQ\"<\/span><span class=\"token punctuation\">)<\/span>\n<span class=\"token punctuation\">}<\/span>\n\n<span class=\"token keyword\">func<\/span> <span class=\"token function\">main<\/span><span class=\"token punctuation\">(<\/span><span class=\"token punctuation\">)<\/span> <span class=\"token punctuation\">{<\/span>\n    ctx <span class=\"token operator\">:=<\/span> context<span class=\"token punctuation\">.<\/span><span class=\"token function\">Background<\/span><span class=\"token punctuation\">(<\/span><span class=\"token punctuation\">)<\/span>\n\n    readerConfig <span class=\"token operator\">:=<\/span> kafka<span class=\"token punctuation\">.<\/span>ReaderConfig<span class=\"token punctuation\">{<\/span>\n        Brokers<span class=\"token punctuation\">:<\/span> <span class=\"token punctuation\">[<\/span><span class=\"token punctuation\">]<\/span><span class=\"token builtin\">string<\/span><span class=\"token punctuation\">{<\/span><span class=\"token string\">\"localhost:9092\"<\/span><span class=\"token punctuation\">}<\/span><span class=\"token punctuation\">,<\/span>\n        GroupID<span class=\"token punctuation\">:<\/span> <span class=\"token string\">\"&lt;consumer-group-id>\"<\/span><span class=\"token punctuation\">,<\/span>\n        Topic<span class=\"token punctuation\">:<\/span> <span class=\"token string\">\"&lt;topic>\"<\/span><span class=\"token punctuation\">,<\/span>\n        MaxBytes<span class=\"token punctuation\">:<\/span> <span class=\"token number\">10e6<\/span><span class=\"token punctuation\">,<\/span>\n        WatchPartitionChanges<span class=\"token punctuation\">:<\/span> <span class=\"token boolean\">true<\/span><span class=\"token punctuation\">,<\/span>\n        Dialer<span class=\"token punctuation\">:<\/span> <span class=\"token operator\">&amp;<\/span>kafka<span class=\"token punctuation\">.<\/span>Dialer<span class=\"token punctuation\">{<\/span>\n            SASLMechanism<span class=\"token punctuation\">:<\/span> plain<span class=\"token punctuation\">.<\/span>Mechanism<span class=\"token punctuation\">{<\/span>\n                Username<span class=\"token punctuation\">:<\/span> <span class=\"token string\">\"&lt;username>\"<\/span><span class=\"token punctuation\">,<\/span>\n                Password<span class=\"token punctuation\">:<\/span> <span class=\"token string\">\"&lt;password>\"<\/span><span class=\"token punctuation\">,<\/span>\n            <span class=\"token punctuation\">}<\/span><span class=\"token punctuation\">,<\/span>\n            TLS<span class=\"token punctuation\">:<\/span> <span class=\"token operator\">&amp;<\/span>tls<span class=\"token punctuation\">.<\/span>Config<span class=\"token punctuation\">{<\/span>\n                MinVersion<span class=\"token punctuation\">:<\/span> tls<span class=\"token punctuation\">.<\/span>VersionTLS12<span class=\"token punctuation\">,<\/span>\n            <span class=\"token punctuation\">}<\/span><span class=\"token punctuation\">,<\/span>\n        <span class=\"token punctuation\">}<\/span><span class=\"token punctuation\">,<\/span>\n    <span class=\"token punctuation\">}<\/span>\n\n    reader <span class=\"token operator\">:=<\/span> kafka<span class=\"token punctuation\">.<\/span><span class=\"token function\">NewReader<\/span><span class=\"token punctuation\">(<\/span>readerConfig<span class=\"token punctuation\">)<\/span>\n    <span class=\"token keyword\">defer<\/span> reader<span class=\"token punctuation\">.<\/span><span class=\"token function\">Close<\/span><span class=\"token punctuation\">(<\/span><span class=\"token punctuation\">)<\/span>\n\n    retryBackoff <span class=\"token operator\">:=<\/span> backoff<span class=\"token punctuation\">.<\/span><span class=\"token function\">NewExponentialBackOff<\/span><span class=\"token punctuation\">(<\/span><span class=\"token punctuation\">)<\/span>\n    retryBackoff<span class=\"token punctuation\">.<\/span>MaxElapsedTime <span class=\"token operator\">=<\/span> time<span class=\"token punctuation\">.<\/span>Minute <span class=\"token operator\">*<\/span> <span class=\"token number\">5<\/span>\n\n    options <span class=\"token operator\">:=<\/span> kafka_retry_dlq<span class=\"token punctuation\">.<\/span>ConsumerWithRetryOptions<span class=\"token punctuation\">{<\/span>\n        Reader<span class=\"token punctuation\">:<\/span> reader<span class=\"token punctuation\">,<\/span>\n        Backoff<span class=\"token punctuation\">:<\/span> retryBackoff<span class=\"token punctuation\">,<\/span>\n        MaxRetries<span class=\"token punctuation\">:<\/span> <span class=\"token number\">3<\/span><span class=\"token punctuation\">,<\/span>\n        Handler<span class=\"token punctuation\">:<\/span> <span class=\"token operator\">&amp;<\/span>exampleHandler<span class=\"token punctuation\">{<\/span><span class=\"token punctuation\">}<\/span><span class=\"token punctuation\">,<\/span>\n        RetryQueue<span class=\"token punctuation\">:<\/span> <span class=\"token function\">make<\/span><span class=\"token punctuation\">(<\/span><span class=\"token keyword\">chan<\/span> kafka<span class=\"token punctuation\">.<\/span>Message<span class=\"token punctuation\">,<\/span> <span class=\"token number\">1000<\/span><span class=\"token punctuation\">)<\/span><span class=\"token punctuation\">,<\/span>\n    <span class=\"token punctuation\">}<\/span>\n\n    kafka_retry_dlq<span class=\"token punctuation\">.<\/span><span class=\"token function\">NewConsumerWithRetry<\/span><span class=\"token punctuation\">(<\/span>ctx<span class=\"token punctuation\">,<\/span> <span class=\"token operator\">&amp;<\/span>options<span class=\"token punctuation\">)<\/span>\n\n    <span class=\"token keyword\">select<\/span> <span class=\"token punctuation\">{<\/span><span class=\"token punctuation\">}<\/span>\n<span class=\"token punctuation\">}<\/span><\/code><\/pre>\n<p><strong>Quick orientation<\/strong>: <code>ProcessRetryHandler<\/code> is the interface your handler implements \u2014 <code>Process<\/code> does the work, <code>MoveToDLQ<\/code> handles exhausted retries. <code>ConsumerWithRetryOptions<\/code> wires everything together. The main loop reads from Kafka and enqueues failures; the goroutine retries them with backoff.<\/p>\n<h2 id=\"semantics-don-t-skip-this\">Semantics (don\u2019t skip this)<\/h2>\n<p>This pattern is \u201cKafka keeps moving,\u201d which means you\u2019re making a deliberate trade: Kafka offsets can advance even when processing hasn\u2019t succeeded yet.<\/p>\n<p><strong>What Kafka guarantees (in this pattern)<\/strong><\/p>\n<p>If you commit before success (or your client auto-commits on read), Kafka won\u2019t redeliver on restart. That\u2019s effectively <em>at-most-once<\/em> for your side effects.<\/p>\n<p>If you\u2019re following along with <code>kafka-go<\/code>, note that <code>Reader.ReadMessage<\/code> with a <code>GroupID<\/code> advances committed offsets independently of whether your handler succeeds.<\/p>\n<p><strong>How to get stronger guarantees<\/strong><\/p>\n<p>If you want at-least-once for the side effects, the retry queue can\u2019t be in-memory. It needs to be durable (retry topic, DB table, external queue) and your handler needs to be idempotent.<\/p>\n<p><strong>Idempotency (practical version)<\/strong><\/p>\n<p>If processing record X twice causes two emails, two charges, or two rows, you\u2019re going to have a bad week. The fix is usually an idempotency key + write-once constraint (or upsert).<\/p>\n<p><strong>Poison pills<\/strong><\/p>\n<p>A poison pill is a record that will never succeed without code\/data changes. This pattern is nice because poison pills don\u2019t block Kafka consumption \u2014 they show up as retry storms and DLQ events instead.<\/p>\n<h2 id=\"decision-table\">Decision table<\/h2>\n<p>In my head, this \u201ccommit early + retry elsewhere\u201d idea has a few maturity levels:<\/p>\n<div class=\"my-8 border-2 border-black dark:border-border-color bg-background-light dark:bg-background-dark font-mono text-lg relative shadow-[4px_4px_0_rgba(0,0,0,1)] dark:shadow-none\"><div class=\"absolute -top-3 left-4 bg-background-light dark:bg-background-dark px-2 text-primary-text-light dark:text-primary-text font-bold uppercase text-sm border border-black dark:border-border-color tracking-widest\">Comparison of Kafka Retry Strategies<\/div><div class=\"overflow-x-auto p-4 pt-6\" tabindex=\"0\"><table>\n<thead>\n<tr>\n<th>Option<\/th>\n<th>Kafka consumption<\/th>\n<th>Failure semantics<\/th>\n<th>Operational complexity<\/th>\n<th>When I\u2019d pick it<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>In-memory retry channel<\/td>\n<td>Never blocks consumption<\/td>\n<td>Retries are lost on restart<\/td>\n<td>Low<\/td>\n<td>Quick wins, low blast radius workflows<\/td>\n<\/tr>\n<tr>\n<td>Durable retry queue (Kafka retry topic \/ external queue)<\/td>\n<td>Never blocks consumption<\/td>\n<td>At-least-once is achievable (with idempotency)<\/td>\n<td>Medium<\/td>\n<td>Most production systems<\/td>\n<\/tr>\n<tr>\n<td>DB-backed state machine \/ outbox<\/td>\n<td>Never blocks consumption<\/td>\n<td>Strongest auditability and control<\/td>\n<td>High<\/td>\n<td>Complex workflows, compliance, long retries<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div><\/div><h2 id=\"in-conclusion\">In conclusion<\/h2>\n<p>Kafka retries are not a broker feature; they\u2019re a semantics decision.<\/p>\n<p>This \u201ccommit early + retry elsewhere\u201d approach is great when your top priority is keeping Kafka consumption unimpaired. The cost is that you\u2019re now running a second system: your retry pipeline. Treat it like a first-class dependency.<\/p>\n<p>If I were shipping this pattern, I\u2019d page on:<\/p>\n<ul>\n<li>Retry queue depth (and time-in-queue).<\/li>\n<li>Retry exhaustion rate (messages going to DLQ).<\/li>\n<li>DLQ age (DLQ isn\u2019t success; it\u2019s a promise you\u2019ll look).<\/li>\n<li>Processing success latency (p95\/p99) for the retry worker.<\/li>\n<li>Consumer lag as a sanity check (it should stay flat even during downstream outages).<\/li>\n<\/ul>\n<p>Dealing with distributed systems challenges or building reliable event-driven architectures? Let's talk.<\/p>\n<a href=\"https:\/\/cal.com\/alexandrecastrotech\/mentoring\" class=\"text-primary-text-light dark:text-primary-text hover:opacity-80 transition-opacity flex items-center gap-2 font-bold uppercase tracking-wide min-h-11\">\n    <i class=\"pixelart-icons-font-calendar\"><\/i> Book a mentoring session\n<\/a>\n"}]}