Architecture — the blueprint, in the open
This site is the argument. This page is the blueprint — how it's built, and how you'd build your own.
I started the year lost
A project that was not going well, a pile of catch-up obligations on AI tooling, and things degraded until the end of the year. Kiro had been within reach for a while by then, and I started the year willing to learn how to use it. And there is a detail I suspect a lot of senior engineers are living through and not saying out loud: I had the agentic development tool in hand — and still felt outside the hype.
Because the problem was not the tool; it was where I was going to use it. At that point in the year, all the AI work I was close to split into two halves: the modelling, which is strong, and the rest — systems integration, legacy that cannot be replaced, the ordinary complications of corporate IT. That second half is where I have spent eighteen years, and it is the one with no ready-made use case to learn on — the case has to turn up on its own, in real work.
The case that turned it around was not this site. At the beginning of the year, in January, I started building an authentication and authorization mechanism on the side — dense business rules, custom-built on Spring Boot and Spring Security, integrating legacy systems. I would never have delivered that without an agentic development tool — and it was not only the deadline: I was carrying tech-lead responsibilities on that project at the same time. That is what the tool bought. Not typing speed: both of those fitting into the same week. And nothing is more fun to me than seeing an application running beautifully — at a scale I could not reach on my own. That was where I saw something I had not seen in a long time: if the requirement is where I stay and the code is worked by AI-DLC, a software engineering project can be more audacious. Not as a forecast — as what I saw at that moment, with the thing running in front of me.
"Computer programming is an art, because it applies accumulated knowledge to the world, because it requires skill and ingenuity, and especially because it produces objects of beauty."
— Donald Knuth, 1974

The holiday was in May, in San Francisco and the Valley, and the rest of this page comes out of it. There was not a place I passed through without some AI offering in it — on the train, on the street, in a shop window, on the lanyard of the person next to me. I came back with the idea of what to do, and since then I have run it on two fronts: an internal one, at work, with Kiro, and this one, in public, with Claude Code. Two harnesses running the same kind of work is what lets me separate what comes from the model from what comes from the setup around it.
One morning I took the Caltrain south — 8:57, next stop Palo Alto. The carriage was open laptops end to end, loops running, people trading ideas out loud on the way to work. It was not an event, not a community, nothing arranged. What I concluded from that, rather than saw: a lot of people doing the same kind of work, in the same place, at the same time — close enough to overhear without asking and to answer without scheduling. I was inside that for one week, in May. The rest of the year, I am not.

Outside that week the carriage does not exist, and it is what the public front stands in for — there is a reason the front exists rather than a notebook. There are far too many configuration options — which harness, which hooks, which persona, which gate, which model — and nobody has enough sessions to test them all alone. Trading each other's experience of using AI is what will speed that learning up, and that is why what is here is the whole setup, not only the conclusion it reached.
What the requirement demanded, and the architecture it justified
The requirement for this public front is short: publish content, in two languages, with the whole build in the open. That is what decides the architecture, the bill, and the rest of this page.
And it was not built lean. It was built full and then cut: there was a backend platform — BFF on Lambda, DynamoDB, Cognito, SES —, a Lambda@Edge rendering OG images per request, a link-unfurl service, GitFlow with staging and production, and an offline-first PWA. A database with nothing to store. Auth with nobody to authenticate. A staging environment for a site whose revert is a merge. Each one was defensible when it was decided, and none survived the question "what is this for, here" — and every reversal is on the record with the decision that replaced it: 0025, 0026, 0027, 0028, 0029.
The cut is not this page's subject; it is the consequence. What is left is three things — a static site, a dev-loop plugin and an agent runtime — and they are not tiers of one system: each one exists without the other two. The site runs without the plugin. The plugin installs in any repository. The runtime is not mine. What sits in the middle is the one thing none of the three delivers on its own — and it is where the way I build this lives, which is the way I want to be hired to build: AI-native development with the SDLC rigor most AI work skips, a loop built on Context & Harness Engineering, with AI-DLC running inside it.
What sits in the intersection is the actual work: deciding what the harness refuses, what it advises and what it only documents — and then proving the inventory of that is still true.
The launch showed the output. The case shows the machine.
Pillar 1 · the solution
A fully static SPA — React + Vite + TypeScript — served from S3 behind CloudFront, with a small CloudFront Function rewriting clean URLs. No server, no database, no auth. The content is markdown in the repository itself, and every published route is prerendered at build, in both languages — an article can be held back before publication, and a held one is compiled into the bundle without ever being prerendered.
(→ ADR-0002 fully static / no backend · ADR-0013 S3 + CloudFront)
End to end, backstage included — and the interesting thing about this picture is what is not in it:
The absence is the design, not a gap. A picture like this, for a system like this, usually carries an application column, a database and internal integrations between the frontend and the infrastructure; here there is no column there at all, and what the reader asks for comes whole out of a bucket. The only third party at runtime is analytics, and it is consent-gated. And "no backend" raises one question before all others — how does a crawler see this — whose answer is that nothing has to be rendered for it to: what it asks for comes back as complete HTML, with the OG tags already in it, straight from a static file. No SSR, no edge rendering — the edge's rewrite function runs on every request for a page and touches the URL, nothing else.
The limit travels with the claim, because it is the part a reader can falsify: a URL that does not exist answers 200, not 404 — and what comes back is the landing page, complete with the landing page's own OG tags, under an address that was never real. CloudFront maps 403 and 404 onto /index.html, which is what lets a SPA work on deep routes and is a real trade rather than a detail. It has bitten here once: a path misroute sent the per-article OG images into that same fallback, and each one answered 200 text/html to every scraper that asked.
The only logic that runs between a reader and a file is that function: ten executable lines, with their own unit tests and a post-deploy check that the live function still matches this repo. And the bucket is not public in any sense: it answers s3:GetObject only from this distribution.
(→ ADR-0004 build-time render, no SSR · ADR-0005 every URL OG-complete · ADR-0033 consent-gated analytics · iac/frontend.tf the distribution and the policies)
USD 6.57 a month
This figure measures what the site added, not what it depends on, and it measures what the site runs on, not what I build it with. "Near-zero" is the easiest claim on this page to make and the easiest to leave unchecked — so here is the bill, with the serving lines read from the account's daily cost in late July 2026 and the registration read from the registrar's price list. Neither estimated:
- The domain — USD 71.00/yr for the
.io, an annual charge that lands in one month. USD 5.92/month amortized. I picked the.iofor branding, not for cost: that is the honest reason, and the single line here you can decline. - Route 53 — USD 0.50/month, fixed. The hosted zone, whether or not anyone visits.
- S3 — about USD 0.15/month, and it is deploy writes, not reads.
- CloudFront — effectively USD 0.00 at this traffic.
Outside AWS the criterion is the same. GitHub Team and Claude Max are paid and stay outside the total — the GitHub Team subscription predates the site, though the CI load on it is entirely the site's own; GitHub Actions and SonarCloud are zero because the repositories are public — a property of the repos, not of the plan — and Terraform Cloud is zero because the infrastructure is small. And iCloud+ is the line that shows the criterion being applied rather than announced: it predates the site, but it carries the custom-domain email at the apex and iac/email.tf provisions its MX, DKIM and SPF records — so it is not adjacent to this infrastructure, it is inside it. (→ ADR-0016)
Outside the total sits every hour of mine as well: USD 6.57 a month is what it costs to keep this running, not what it cost to build. In people, it cost one — weekends, alongside consulting work. And the same reading turned up roughly USD 12.80 a month the site was not using: WAF web ACLs and idle public IPv4 addresses, left behind when the backend was retired. I found them by reading the bill, which is late — infrastructure you stop using does not stop billing — and what watches now is a budget in iac/budget.tf deliberately not scoped to this project's tags: otherwise it would only ever see spend this repo created, and this was exactly the kind it did not.
Pillar 2 · the customization
The interesting part isn't the stack — it's how it's built: agent-led verification, human-residual. The agent proves "done" with mechanical gates and real evidence (lint, types, tests ≥85%, a green build, SonarCloud, functional E2E, a fresh-context reviewer); the human keeps the irreversible and architectural calls. That loop lives in a separate plugin — tadeumendonca-skills — so it's a methodology you can adopt, not something bespoke to this site.
I appear at both ends, and they are different jobs: at the start, opening the Issue, and at the end, on boundary-class work only, deciding whether it ships. Between one end and the other there is no human on the path. And the drawing claims something stricter than "at the plan": I am the only origin of demand — nothing enters the queue on its own — and what closes intake is the ready label, the artifact saying the description was closed; from ready downwards, only the safe class crosses alone. No lane goes round the gate, including the one that builds the loop itself. A change to the machinery is written by agents-lead and still reaches the same box, and it answers for more there rather than less: the same Definition of Done, the same question about production, and on top of both a requirement no other lane carries — that agents-lead left its verdict marker before the gate may merge at all. A reviewer that has to have been present, not a review that is skipped. And the cost of it, since the rest of this page states its own: what decides a change is safe is the same kind of thing that wrote the change. Mis-classify one and it takes the empty path. What makes that acceptable here is blast radius, not confidence — it is a static site, and a revert is a merge.
(→ ADR-0003 trunk-based, one environment · ADR-0018 the CI gates)
What the harness is made of
Of the plugin's own components, exactly one kind can stop you, and that is the honest version of the adoption pitch: three of the fourteen registrations in hooks.json return a denial before the act, and the act does not happen. They do it in two different shapes, which is the part worth having: permission-guard on Bash and mcp-guard on the MCP tools sit on PreToolUse and refuse a tool call; preflight sits on UserPromptSubmit and refuses the prompt, before the turn starts at all. the three — struck: of those four claims, one survives. Two of those guards were deleted in the plugin and PreToolUse hooks — permission-guard and wip-guard on Bash, and orchestrator-write-guard on the file-writing toolsmcp-guard had never appeared on this page at all. And nothing here caught it — which is the late leg described two paragraphs down, arriving as an instance rather than as a caveat: the drawing agreed with the manifest, so that check stayed green while both sides were stale together, and a deletion over there fires nothing on this side. And preflight is drawn in two columns of the grid above, because it is registered twice with a different class on each event — which is the one thing here nobody authoring by hand would have drawn.
The other eleven registrations only report, and not for the same reason — a distinction this page owes you rather than one it can round off. Five of them sit on events that refuse nothing, SessionStart and SubagentStart, so those five could not block if they wanted to; note that the reason is the event, not the absence of a tool call, because UserPromptSubmit has no tool call in front of it either and denies anyway. The other six could. Stop can block the turn from ending and SubagentStop can block a subagent from stopping, and every one of those scripts declines: every one of their exit paths is a success. zombie-loop-detect notices, one turn late and once per session for that head, that a branch is sitting on a gate verdict nobody acted on; dispatch-metrics-stop files the dispatch's numbers and gets out of the way; orchestrator-tool-census names what the orchestrator did with its own hands, write and post calls apart from reads, and decides nothing about it. Six hooks choosing not to be mechanisms says more about this harness than one did. And the personas advise — their judgement is checked by nothing, and this repo's own guide says in as many words that a lens nobody dispatches fails silently. That is the guarantee the loop gives — and it is worth exactly what the inventory in the drawing above is worth.
And that drawing's inventory is checkable — that is the second guarantee, and it is a different kind of thing. Rename a persona in the plugin and this repository's build goes red. The drawing above is authored by hand: a test compares it, node by node and count by count, against a committed manifest, in both editions; and a CI job compares that manifest against the plugin's live tree. That is exactly the difference between drawing a harness and proving the drawing is still it, and it is mechanical. And it has two legs, with different limits, stated here rather than later. From drawing to manifest, the comparison includes the enforcement class of all twelve cells, in both editions: give a persona denies in the manifest and this goes red, and that is the grid's central claim. From manifest to the plugin's live tree, the check arrives late, since nothing on this side can see a merge over there, and each component's class comes from a rule about its shape — which event a hook is registered on — rather than from reading what the script does: on re-reading the manifest, what is checked is that the class is a legal value, not that it is true of that component.
And that is why I call this one thing and not another. AI-DLC is not mine — it is AWS's name for a delivery lifecycle whose stages are run and verified by agents; I adopt it, I did not coin it, and it runs inside this rather than beside it. Context & Harness Engineering is the claim I am making, and the two words are two different things to build. The context is what an agent reads before it acts: the skills, the records, the briefs that tell it what was already decided, so it does not decide it again differently. The harness is what stands between it and the act: the hooks, the gates, the personas that read what it produced — and the grid above is the inventory of which of those can actually stop it. Neither half is the practice on its own — context with nothing holding it is advice, and a harness with nothing to read is a wall — and building, versioning and proving both is the work. Adopting a methodology costs nothing to say — which is precisely why saying it is worth nothing. This one is paid for, and the payment is in the paragraph above: a build that breaks when the inventory stops being true. It is the same agent-led verification ruler the rest of this page applies to code, turned on the methodology: whoever makes the claim is who produces the evidence.
(→ ADR-0043 the inventory pinned to the plugin)
Eight personas, what each one argues against — and what each one carries when it is dispatched. The last column is each brief's preload: the skills that enter the persona's session before it reads the first line of the task.
| who | what it owns | what it argues against | which skills it carries when dispatched |
|---|---|---|---|
product-lead | the reader, value, order, slice size — and positioning, voice, and the truth of anything published outside the content lane | tech-lead; and on a product or loop diff it blocks a merge when it finds a published claim that is untrue — by convention rather than by hook. On content that veto is gone since 2026-09-03: there it holds intake and nothing else, and no persona holds a copy block on that lane at all | agents-configuration · engineering-standards · definition-of-ready · shell |
tech-lead | architecture, measurement, sequencing — and it writes the product and system decision records; the ones about the loop itself are agents-lead's | product-lead, by design: product-and-market and system are genuinely different optimisations | agents-configuration · engineering-standards · definition-of-ready · documentation-standard · devops · shell |
developer | the slice end to end — app, infrastructure, pipeline, and the tests written as it goes | nothing. It builds, and it is what the gate is pointed at | agents-configuration · engineering-standards · code-review · quality-gates · devops · shell |
quality-assurance | delivery against the Definition of Done, and separately whether a change can break production | developer, on both axes in one pass — and it is the only one the permission hook lets merge | agents-configuration · engineering-standards · definition-of-done · quality-gates · devops · shell |
content-writer | drafts articles, site copy and social-post language in the owner's voice — shapes, cuts, structures and translates an experience he already has, never originates one | content-reviewer, and that is the whole of it: it reads the draft against the very ruler it was written against, and since 2026-09-03 product-lead does not run the copy lens on this lane at all — its role in content is intake, and nothing else | agents-configuration · engineering-standards · shell · published-voice · content-publishing |
content-reviewer | raising a draft's bar before it reaches me — at most two rounds against published-voice, and it blocks nothing at all — it repairs the draft in place, on two grounds and only two: where it can quote a clause of that skill, or where the claim is false against the source | content-writer, and this is the roster's first true pair: everything that meets neither ground comes back labelled advisory-and-droppable, with the prose left alone | agents-configuration · engineering-standards · shell · published-voice · content-publishing |
agents-lead | the machinery itself: hooks, permissions, briefs, skills and commands, the plugin — and it writes the decision records about the loop, the other half of the split above | me — and that is the interesting case: its counterpart is not another persona, it is the one seat in this loop that had nobody to argue with | agents-configuration · engineering-standards · documentation-standard · definition-of-ready · devops · shell |
scrum-master | that the rites happen, in order, and that nothing is skipped — it ranks the eligible pool and names, in a record, which profile acts next | nobody, and it holds no tools at all: it cannot dispatch, edit, run a command or apply a label, so what it produces is a record the main session executes — and nothing reads it | agents-configuration · engineering-standards |
Two things in that last column are worth saying. agents-configuration and engineering-standards are in all eight — the universal preload: understanding the loop itself, and the engineering standards it is run against, belong to no specialty. shell — the file-and-command discipline — is in seven of the eight, and the one it is missing from is scrum-master, which holds neither Write nor Bash, so the rule would have nothing to govern there. And only 11 of the library's 15 skills are preloaded by anyone: the four nobody carries — backend, cloud-infrastructure, frontend and planning-poker — only reach a session if the model finds them on its own, through their description.
And this table is authored by hand — the new column included. The persona names in the drawing above are compared against the manifest and against the plugin's live tree, so retiring a persona reddens a build here. Down here nothing compares anything: check-harness-drift checks persona names and counts, and does not check which skills each one loads. Someone changes a brief's skills: block and this column starts lying the next day, with no signal at all. And a role changing hands reddens nothing either: what that check compares of a persona is its name, and how many personas there are — never what a row claims one does. Decision-record authorship split between two of these rows and no build anywhere noticed; those two cells were corrected by hand, like everything else in this table.
One line runs under all of that, and it is a line about consent rather than about tooling. Everything in the drawing above is committed — hooks, personas, skills, commands — and that is deliberate: it is what makes the work reproducible, so a fork inherits the floor itself rather than a description of it. What is not committed, and will not be, is the setting that makes the operator reachable. Every session I run has Remote Control on from the first second — remoteControlAtStartup, which opens a bridge so I can watch the live local session and approve its tool calls from my phone. It is not the cloud sandbox Claude Code also offers, and the two are not interchangeable: inside a cloud session Remote Control reports itself unavailable, because the local session it exists to reach is not there. That key lives in ~/.claude/settings.json, on my machine, in the one file this repository cannot ship.
That asymmetry looks like a gap in the harness, and the tool had already settled the argument — I had just never read it. So I read it: not the name of the key, which is not a specification, but the shipped binary. Repo-scoped settings cannot turn Remote Control on. Commit "remoteControlAtStartup": true to a project's .claude/settings.json and the CLI ignores it and says why — "repo-scoped settings cannot enable Remote Control; set it at user scope". The one thing a repository can do with that key is set it to false, and that direction is honoured immediately, ahead of whatever the person running the session prefers. Enabling is a person's decision; disabling is a project's; and only the second one travels through a clone.
Which is the right way round for a repository whose whole premise is being forked. Committing the true would have handed a session capability to everyone who cloned this and to CI, without one of them choosing it — and, per the paragraph above, it would not even have worked: a line that does nothing while reading like it does something is the exact failure this page keeps naming. So the setup step is in the README, both methods, with what each one costs. This page keeps the line and not the instructions.
Pillar 3 · the runtime
The orchestrator is the part of the harness you cannot install. It is in none of the inventory above — not in the components grid, not in the manifest, even though the tier flow draws it right in the middle of the AFK stretch — and it is the main session: the context that reads an Issue, decides which persona to dispatch, and weighs what comes back. The actor is not a plugin component, its policy partly is, and what you supply is the context that runs it. It is also the party the boundaries above are drawn against: the middle column can say no more than that a persona advises, and the reason it can say no more is that nothing forces a dispatch — which names the failure mode without naming who dispatches. That is the orchestrator, and a lens it forgets is a lens nobody ran.
And its context runs out. That is what a subagent buys: it reads, runs, gets it wrong and redoes it inside its own session, and what reaches the orchestrator is the conclusion. A task costs the orchestrator its verdict, not its execution, which is why the one real lever this harness has is verdict length, turned by writing the persona briefs. I measured it once, on this repo's own session, on 7–8 August 2026, by parsing the transcripts: what stayed inside the subagents was over an order of magnitude more than what came back. And the saving has a ceiling — even so, the returned verdicts were a large slice of everything the orchestrator took in from a tool. It is not an escape: that session compacted twice anyway. The number is not published, because the input is a private session transcript that no gate can reach.
And the ground under it moves. This is the part the rest of this page does not have: I control the site, I control the plugin, I do not control the runtime. Whoever produces it ships change constantly, and every new model changes which configuration still makes sense — not because the configuration became wrong, but because it was compensating for a weakness that is gone.
That is not my inference. When Opus 5 shipped, the Claude Code team deleted more than 80% of their own system prompt — their product's, not somebody's personal config — and the model got better without the scaffolding. And not as a one-off: every major model upgrade needs less scaffolding, so you delete rules and re-add them only where the model still fails. That is a cycle, not a spring clean.
The thesis that comes with it is the part I care about, and it is a hard one: frontier models are being hobbled by products built for yesterday's weaker models, and the advantage goes to whoever puts engineering effort into verification rather than instruction. That is the person who built the tool saying it — and the name I give that move, agent-led verification, is mine, not his. I am not quoting it for decoration: it is independent corroboration of a choice I had already made, from someone holding data I do not have.
And it is why this loop is made of hooks and gates rather than of a giant prompt explaining to the agent how to behave. Instruction ages with every new model, and it ages silently. A gate does not: it checks the result, and the result is the same thing before and after the upgrade. If the argument above is right, the part of my harness that survives is the part that verifies — and the part that instructs is the part I will be deleting.
Boris Cherny, who built Claude Code, on the Y Combinator channel:
The decision record IS the documentation
The classic argument for ADRs is the human of the future: record why the decision was taken, so that two years from now somebody does not undo it without knowing what was at stake. Here the argument is a different one, and it is what decides the format.
In a repository where the developing is done by agents, the record is inference context. An agent has no memory of what was discussed — it has the repository, and that is what it infers from. If the architecture that formed over time is not anchored somewhere in the code itself, every new change is decided without it, and the result is not one isolated wrong call: it is a new decision that contradicts a decision nobody remembers making. That is why a reversed decision stays here and says it was reversed. Without that mark, the record of a retired architecture reads as instruction — which is the cheapest way there is to get an agent to rebuild the very thing that was cut on purpose. With one exception, and it is the only one: there were two WAF web ACLs and only the regional one has an ADR — the CloudFront-edge one was built, was cut, and its record is this sentence rather than a file.
That purpose is what picks the format, not the other way round. MADR: context, the options that were on the table, the one decided, and the consequence. One short document per decision, one file per decision, all in the same repository the agent already reads — no wiki, no separate tool. What a format like that gives a human reader is traceability; what it gives an agent is what it needs in order not to contradict.
There are 49 decisions — and what is mechanical here is the index, not this number. The index is generated from docs/adr/, committed as an artifact, and checked in CI: adding or superseding a decision without regenerating it turns the pipeline red, so the artifact and the directory cannot drift apart. The 49 in this sentence is typed by hand: while the table was rendered here it came checked for free; cutting it removed that tie, and what holds the number up now is the link below, one click from counting them yourself. The rows are deliberately not printed here — this page points at canonical detail instead of restating it, and a 49-row copy would be that rule broken in the one section that exists to defend it.
(→ the decision library · ADR-0001 lean by design)
Replicate it for your own context
It's all public — two repos, no secrets.
tedeuxx/tadeumendonca-ioTypeScriptThis site and the Terraform that serves it — a static SPA on S3 + CloudFront, prerendered in both locales, with the decision library that records the load-bearing decisions and what each one cost.→ View on GitHub tedeuxx/tadeumendonca-skillsShellThe reusable dev-loop plugin: the principles layer, the agent personas, the permission guards. The half you can adopt with no cloud account at all.→ View on GitHubThe fork-to-live steps are in the READMEs, not on this page: this repo's has the cloud path end to end, from the domain to the first merge, and the plugin's has the loop half, which installs with no cloud account at all. And the bar a project has to clear to be listed in the portfolio here is public too, in docs/catalog-ready.md — the proof-of-engineering gate.
One thing I would copy without thinking twice. The deploy enters AWS over OIDC, so there is no stored secret: a leaked key is access until somebody revokes it, and a leaked token is access until it expires — and only if whoever took it can also satisfy the trust condition, which here is that repository's immutable subject, by numeric ID rather than by name, because a name can be transferred to someone else and the IDs cannot. The trade is that the root of that trust has to be created outside: Terraform here does not create the OIDC provider, nor the role that runs Terraform itself. It is a documented hole in a floor, and no plan will ever tell you it has drifted.
(→ ADR-0042 trust root outside Terraform · ADR-0015 immutable subject)
The part I would be nervous seeing someone copy without the rest is merging straight to production. Trunk-based with a single environment is fast and unforgiving in equal measure; without the gates in front of it, only the second half survives.
This is in the open because there are more configuration choices here than one person has sessions to run. If you have run any of those choices differently, you are the one holding the half this page is missing: tell me your counter-example, or share the page and say what you would change.