<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Built from Scratch]]></title><description><![CDATA[Notes from a senior engineer on building with AI agents, spec-driven development, and taking AI-built apps to production. Originally published at juliantellez.c]]></description><link>https://juliantellez.hashnode.dev</link><generator>RSS for Node</generator><lastBuildDate>Tue, 27 Oct 2026 03:27:05 GMT</lastBuildDate><atom:link href="https://juliantellez.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[I Got a $95 Million AWS Bill Alert at 3am. Here Is How I Triaged It]]></title><description><![CDATA[AWS emailed me a $95 million bill alert at 3am from costalerts.amazonaws.com. How I triaged it, and what the billing bug taught me.]]></description><link>https://juliantellez.hashnode.dev/i-got-a-95-million-aws-bill-alert-at-3am-here-is-how-i-triaged-it</link><guid isPermaLink="true">https://juliantellez.hashnode.dev/i-got-a-95-million-aws-bill-alert-at-3am-here-is-how-i-triaged-it</guid><category><![CDATA[AWS]]></category><category><![CDATA[Cloud]]></category><category><![CDATA[Security]]></category><category><![CDATA[Devops]]></category><dc:creator><![CDATA[Julian Tellez]]></dc:creator><pubDate>Fri, 23 Oct 2026 22:20:39 GMT</pubDate><content:encoded><![CDATA[<p><img src="https://juliantellez.com/blog-media/aws-95-million-bill-at-3am/cover.png" alt="The email said $95,241,863.64. The real bill was 24 cents." /></p>
<p>My phone lit up at 3:22 in the morning with a subject line no cloud engineer wants to read half asleep:</p>
<blockquote>
<p>AWS Cost Management: Cost anomaly(ies) summary for account [2026-07-17]</p>
</blockquote>
<p>The body claimed a single day of Amazon Simple Storage Service usage with this cost impact:</p>
<blockquote>
<p>Max Daily Impact: <code>$9.524186364E7</code>
Total Impact: <code>$9.524186364E7</code></p>
</blockquote>
<p>That E7 on the end is scientific notation. Nine point five, times ten to the power of seven. Ninety five million, two hundred forty one thousand, eight hundred sixty three dollars. On a personal account.</p>
<p>Here is what I did about it: nothing, and then I went to bed. My reasoning at 3am was simple. If I genuinely owe Amazon ninety five million dollars, that is a them problem, not a me problem. I do not have ninety five million dollars. I do not have the first million of it. You cannot get blood from a stone, and you cannot get it from a solo founder asleep in his bed either. So I froze my business bank cards as a precaution, put the phone face down, and gave my brain the one thing it actually needed: a night to let the news of owing money in this life and the next one settle before I did anything I could not undo.</p>
<p>By the time the sun came up and I looked properly, the real figure was sitting there in plain sight: the actual Simple Storage Service cost for the whole month was <strong>twenty four cents</strong>. Not ninety five million dollars. Twenty four cents. This post is the calm walk through I did over coffee, written down so you can run it in twenty minutes instead of losing a night to it, followed by the guardrails I set up afterwards so the next 3am email does not even wake me.</p>
<p>If you got a version of this email over the weekend of July 16 to 18, 2026, you were not alone and you were almost certainly not hacked. AWS confirmed a bug that inflated cost estimates into the trillions for customers all over the world. More on that in a moment. But at 3am I did not know that yet, and if you are reading this mid panic, neither do you. So here is how you tell a real emergency from a display bug before the news catches up.</p>
<h2>First, decode the number</h2>
<p>AWS renders these anomaly emails with raw scientific notation, which is most of the terror. Here is the same alert in plain figures:</p>
<ul>
<li>Amazon Simple Storage Service: <code>$9.524186364E7</code> is <strong>$95,241,863.64</strong>, flagged over one day.</li>
<li>Amazon DynamoDB: <strong>$62,955.57</strong> total, over two days, mostly point in time recovery storage.</li>
<li>Amazon CloudFront: <strong>$371.50</strong>, data transfer out.</li>
</ul>
<p>The two "root causes" AWS listed for the storage line were a us-east-1 contribution of <code>$3.416076066E7</code> (<strong>$34.2 million</strong>) and a eu-central-1 contribution of <code>$5.637901996E7</code> (<strong>$56.4 million</strong>), both against the usage type <code>TimedStorage-ByteHrs</code>.</p>
<p>Before you touch anything, sit with one fact: <strong>the word "impact" in these emails is not the word "charge"</strong>. Cost Anomaly Detection reports how far your spend deviated from what its model predicted, and that model is fed by AWS's estimate of your spend. An estimate can be wildly wrong, as this whole weekend proved. Ninety five million dollars of storage byte hours appearing overnight is not physically possible on a personal account. At roughly two cents per gigabyte per month, that figure implies exabytes of data materializing in a day. Nobody uploads four billion gigabytes by accident.</p>
<p>So the number was almost certainly noise. But "almost certainly" is not a plan. There were four real ways this could have been something, and I checked each one.</p>
<h2>The four questions I asked over coffee</h2>
<p>The email arrived at 3am. The answers waited until I was awake. Here is what I checked, in order, with a clear head.</p>
<h3>1. Is this email phishing?</h3>
<p>A fake billing alarm is the oldest trick there is, and panic is exactly the state attackers want you in. So before clicking anything, I checked the envelope, not the body.</p>
<p>In Gmail, open the message, click the three dots, and choose "Show original", or just expand the header summary. Mine read:</p>
<ul>
<li><code>mailed-by: amazonses.com</code></li>
<li><code>signed-by: costalerts.amazonaws.com</code></li>
<li>Standard encryption (TLS)</li>
</ul>
<p>That is genuinely AWS. Cost alerts are sent through Amazon Simple Email Service, and the DomainKeys Identified Mail signature belongs to <code>costalerts.amazonaws.com</code>. A phishing message will fail here: the signing domain will be some lookalike, the DomainKeys and Sender Policy Framework checks will not pass, and the links will point somewhere that is not <code>console.aws.amazon.com</code>.</p>
<p>What to check on any scary billing email:</p>
<ul>
<li>Who signed it. The signing domain, not the friendly "from" name, is the truth.</li>
<li>Where the links go. Hover, do not click. Real AWS links land on <code>aws.amazon.com</code> or <code>console.aws.amazon.com</code>.</li>
<li>How it asks you to act. Real AWS never asks for your password or a card number by email.</li>
</ul>
<p>Verdict: real email. On to the money.</p>
<h3>2. Is this a real charge or a modeled anomaly?</h3>
<p>Never trust the email for the actual number. Go to the source. Open the AWS console (by typing the address yourself, not from the email) and look at three places:</p>
<ul>
<li><strong>Cost Explorer, filtered to Amazon Simple Storage Service.</strong> Set the date range to the current month and add a filter for the one service the email screamed about. If ninety five million dollars were real, it would be sitting right there as a column you could see from space. Mine read <strong>$0.24 total for the month</strong>. Twenty four cents.</li>
<li><strong>The billing home, month to date and forecast.</strong> My whole account, every service combined, was at <strong>$12.30 month to date</strong> with a forecast of <strong>$18.91</strong> for the month, both down on the previous month. Nothing was on fire.</li>
<li><strong>The Recommended actions panel.</strong> This is the detail that ended the panic. AWS's own console summarized it for me: "9 cost anomalies detected in the last 90 days with a total cost impact of <strong>$0.14</strong>." The same system that emailed me a ninety five million dollar number valued every anomaly it had ever raised, across three months, at fourteen cents.</li>
</ul>
<p>Sit with that gap. The email and the console are the same AWS, describing the same account, and they disagree by nine orders of magnitude. When a billing surface contradicts your real usage that violently, the broken thing is the number, not your account. Hold that thought, because the reason it broke turned out to be far bigger than me. The charge never existed.</p>
<p>Verdict: no real spend. But an alert this loud can also be a symptom, so I kept going.</p>
<h3>3. Were my credentials stolen?</h3>
<p>The genuinely frightening version of this alert is real: someone gets your access keys, spins up expensive resources, and you find out from the bill. A few signals in my own email actually pointed here and were worth ruling out. The anomaly touched regions I do not normally use, including ap-southeast-1 for the CloudFront line. Unfamiliar regions are a classic sign of abuse, because attackers spread load to wherever your account has room.</p>
<p>How to rule out a compromise, fast:</p>
<ul>
<li><strong>CloudTrail event history.</strong> Filter the last few days for <code>RunInstances</code>, <code>CreateBucket</code>, <code>CreateFunction</code>, and any <code>Create</code> or <code>Run</code> call you did not make. Look at the source region and the calling identity.</li>
<li><strong>IAM credential report.</strong> Download it and check the last-used timestamp on every access key. Anything active that you do not recognize gets disabled immediately.</li>
<li><strong>GuardDuty.</strong> If it is on, it will already have flagged anomalous console logins, credential exfiltration, or crypto mining patterns. If it is off, this is your sign to turn it on today.</li>
<li><strong>Root account.</strong> Confirm multi factor authentication is on and there are no access keys attached to root at all.</li>
</ul>
<p>Everything in my account traced back to me. No unknown identities, no keys I did not create, no resources I did not launch.</p>
<p>Verdict: not compromised. One suspect left, and it is the modern one.</p>
<h3>4. Did I, or an agent, publish my keys somewhere public?</h3>
<p>This is the 2026 twist. A lot of us now let coding agents write and commit code. Agents are fast, and fast is exactly how a secret ends up in a public commit: a <code>.env</code> file staged by a wildcard, a hardcoded key in a config, a credential pasted into a test fixture and pushed before anyone reads the diff. Bots scan public code hosts for exposed AWS keys within minutes and start spending.</p>
<p>How to check whether a key leaked:</p>
<ul>
<li><strong>Search your public repositories</strong> for the access key prefix <code>AKIA</code>. Every long lived AWS access key id starts with it.</li>
<li><strong>Turn on push protection and secret scanning</strong> on your code host so a commit carrying a key is blocked before it ever leaves your machine.</li>
<li><strong>Add a pre commit secret scanner</strong> such as gitleaks or trufflehog, so agents and humans alike get stopped locally.</li>
<li><strong>If anything is exposed, rotate first and investigate second.</strong> Deactivate the key in IAM, create a new one, update your systems, then read the logs. Rotation is free and takes a minute. A leaked key does not.</li>
</ul>
<p>I scanned. Nothing exposed. Which left me with the boring, happy answer.</p>
<h2>The verdict: a false alarm, and the AWS bug behind it</h2>
<p>All four checks came back clean. The ninety five million dollar alert was not a real charge, not phishing, not a break in, and not a leak.</p>
<p>Why did it happen? Because this was never really about my account. Over the weekend of July 16 to 18, 2026, AWS ran a bug in the subsystem that estimates and forecasts your spend. A change pushed incorrect unit prices into that estimation layer, and those wrong prices multiplied ordinary usage into impossible numbers. Customers around the world opened their consoles to projections in the trillions. Reported figures from that weekend included a 1.5 trillion dollar bill, one account showing month over month usage growth of 745,728,201,771 percent, and a United Kingdom charity that normally pays under one pound a month faced with a projected 5.8 billion pounds.</p>
<p>I went looking to check whether I had lost my mind, and instead found half the internet in the same boat. Under one video explaining the glitch, the comments read like a disaster hotline: one person reported a 4.4 million dollar bill on less than a megabyte of storage, another 74 billion, another a precise 9,289,124,218,442 dollars and 57 cents. The amounts were random because they were noise. My twenty four cents of storage became ninety five million dollars by exactly the same mechanism.</p>
<p>On X the mood was the same, equal parts terror and comedy:</p>
<blockquote>
<p>Post your highest AWS billing. We are at $333B lol #aws</p>
<p><a href="https://x.com/chinmay185/status/2078047649408897133">Chinmay Naik on X</a></p>
</blockquote>
<blockquote>
<p>I just saw $1.5 trillion on my AWS bill and my soul left my body</p>
<p><a href="https://x.com/Bharath_uwu/status/2078043200267452580">Bharath on X</a></p>
</blockquote>
<p>AWS said so itself, in a post that was viewed millions of times as the panic spread:</p>
<blockquote>
<p>Service Update: AWS Billing Console</p>
<p>We are investigating issues with Cost Explorer reflecting inaccurate estimated billing data.</p>
<p>Track status: go.aws/aws-hd</p>
<p>#AWS #AWSSupport</p>
<p><a href="https://x.com/AWSSupport/status/2078037531036172430">AWS Support on X</a></p>
</blockquote>
<p>By 3:03 in the morning BST on July 17, hours after the trouble started the previous evening, they had the root cause: "an issue with unit pricing within the estimated billing computation subsystem". They stated plainly that "the displayed billing estimates do not reflect actual usage and charges" and that "no customer action is required", then paused the estimates, corrected the pricing, and began recomputing the real figures across every affected account, a job they warned would take hours. The anomaly emails landing in inboxes at 3am, mine included, were that estimation bug's fallout.</p>
<p>So the lesson is not "ignore the alerts". Two lessons, actually. First, no single number from a single surface is the truth. The email, the anomaly monitor, and the forecast are all estimates, and on that weekend they were wrong together. Only the charges on your actual bill and your real usage records are ground truth, and those never moved. Second, and this is the one that matters: the danger was never the fake number. It was what people did with it.</p>
<h2>The real danger was never the number</h2>
<p>Here is the part that should stay with you. AWS's actual billing was correct the entire time. The only way this incident cost anyone anything real was through their own reaction to it. In those same comment threads, person after person described the same reflex. One saw a budget alert for 1.15 billion dollars, got angry, and deleted every resource in the account that night. Others assumed the only explanation was a breach and started tearing things down to stop the bleeding. AWS did a poor job of telling people in time, which left panic to fill the gap.</p>
<p>Think about the asymmetry. A fake eleven figure number that you calmly verify costs you twenty minutes of sleep. The same number wired to a function that stops or deletes resources, or handed to an autonomous agent told to bring spend down, can cost you your uptime and your data. The bill was never going to be paid. The deleted production database is gone for good.</p>
<p>That gap between a scare and a disaster is entirely within your control, and it is exactly what the guardrails below are built to protect. Every one of them is about verifying and containing, never about reacting automatically.</p>
<h2>The guardrails that let me sleep</h2>
<p>Here is what I set up so the next 3am email is either accurate or does not fire at all. Everything below is free or nearly free.</p>
<p><strong>Never point destructive automation at a cost signal.</strong> This is the one the weekend taught in blood. Do not wire a budget action, an anomaly alert, or an agent instruction to anything that deletes, stops, or tears down resources. Cost alerts can and do fire on numbers that are flat wrong. Let them notify a human, and let the human decide. Automation that reacts to a fake bill by destroying real infrastructure is worse than no automation at all.</p>
<p><strong>Set a real budget, not just an anomaly monitor.</strong> In AWS Budgets, create a monthly cost budget at a number that would actually alarm you, and alert on both actual and forecasted spend. Forecast alerts warn you before the money is gone, not after.</p>
<p><strong>Add a near zero budget as a tripwire.</strong> A second budget set a little above your normal spend catches a genuine runaway on day one, while the big anomaly figures are still arguing with themselves.</p>
<p><strong>Give Cost Anomaly Detection a dollar threshold.</strong> Replace or supplement the default percentage monitor with one that only alerts above an absolute dollar impact that matters to you. This alone would have kept my phone quiet at 3am while still catching anything real.</p>
<p><strong>Turn on GuardDuty.</strong> It is the cheapest insurance in the account. It watches for stolen credentials, unusual regions, and mining workloads, and it tells you in plain language what it saw.</p>
<p><strong>Kill long lived keys.</strong> Use short lived credentials through IAM Identity Center or roles wherever you can. Put multi factor authentication on every human login, and make sure the root user has no access keys at all.</p>
<p><strong>Stop secrets at the door.</strong> Turn on push protection and secret scanning for your repositories, and add a pre commit scanner locally. This matters more than ever now that agents commit code, because the window between a leaked key and a spending bot is measured in minutes.</p>
<p><strong>Restrict where you can spend.</strong> If you only operate in two regions, a service control policy or an identity policy that denies everything else shrinks the blast radius of any compromise to almost nothing.</p>
<p><strong>Write the runbook before you need it.</strong> The reason the next 3am email will not cost me a wink is that these four questions now live in a file I can open half asleep. Keep yours somewhere you will find it in that state: check the headers, check Cost Explorer, check CloudTrail and IAM, check for leaked keys. Decode the scientific notation before you decide how scared to be.</p>
<h2>A strange kind of perspective</h2>
<p>There is an odd afterthought I keep coming back to. For a few minutes that night my little personal account was holding a ninety five million dollar number, and once the fear passed, what replaced it was something closer to perspective. Somewhere out there are people whose real monthly bill looks like that, not a glitch, just an ordinary Tuesday. Whatever they are building, they are clearly onto something big, big enough that ninety five million dollars a month is a cost of doing the thing rather than a reason for a heart attack.</p>
<p>Even the sinister reading points the same way. If someone had actually stolen my credentials and genuinely needed that much compute, then whatever they were running was operating at a scale, and a level of nerve, that makes my side projects look like a lemonade stand. Either way, the number did something my own careful little forecasts never do. It made my bills look insignificant, and it made me wonder whether I have been thinking too small.</p>
<p>I am not about to go and spend ninety five million dollars to prove a point. But I did close that browser tab thinking bigger about where my work sits in the grand scheme of things, and that turned out to be the most expensive free lesson I have had in a while.</p>
<h2>Frequently asked questions</h2>
<p><strong>Was the AWS trillion dollar bill in July 2026 real?</strong>
No. Between July 16 and 18, 2026, a bug in AWS's billing estimation system displayed wildly inflated cost projections, some in the trillions, to customers worldwide. AWS confirmed that only estimated and forecasted figures were affected and that actual charges and usage records were never impacted. If you saw an impossible number that weekend, your real bill was fine.</p>
<p><strong>What caused the AWS billing bug in July 2026?</strong>
AWS traced it to incorrect unit pricing introduced into the estimated billing computation subsystem, the layer that projects your end of month spend from current usage. Wrong unit prices multiplied normal usage into implausible totals. AWS paused the estimates, corrected the pricing, and backfilled the accurate data.</p>
<p><strong>Is a $95 million AWS cost anomaly email real or fake?</strong>
The email itself can be genuinely from AWS while the dollar figure is meaningless. Verify the sender through the DomainKeys signature (<code>signed-by: costalerts.amazonaws.com</code>), then verify the money in Cost Explorer. A real message with an absurd number is almost always Cost Anomaly Detection modeling noise, not a charge.</p>
<p><strong>What does the E7 mean in an AWS cost alert?</strong>
It is scientific notation. <code>$9.524186364E7</code> means 9.524 times ten to the power of seven, which is $95,241,863.64. AWS formats these emails in exponential notation, which makes ordinary numbers look catastrophic.</p>
<p><strong>Does a Cost Anomaly Detection "impact" mean I have been charged that amount?</strong>
No. Impact is how far your spend deviated from the model's prediction, and it is recalculated up to three times a day. It is an estimate of anomaly size, not a line on your invoice. Always confirm actual spend in Cost Explorer or the Bills page.</p>
<p><strong>How do I know if my AWS keys were stolen?</strong>
Check CloudTrail for actions and regions you do not recognize, download the IAM credential report and inspect the last-used time on every access key, and enable GuardDuty. Unfamiliar regions in a cost alert are a common early sign of abuse.</p>
<p><strong>How do I stop an AI coding agent from leaking my AWS keys?</strong>
Turn on push protection and secret scanning on your code host, add a pre commit scanner such as gitleaks, and never store long lived keys in files an agent can stage. If a key does leak, deactivate it in IAM and rotate before you do anything else.</p>
<h2>The takeaway</h2>
<p>The best decision I made that night was to treat a ninety five million dollar bill as Amazon's problem, freeze my cards, and go to sleep. A real AWS email told me I had spent ninety five million dollars overnight. The truth was twenty four cents. The distance between those two numbers is not a bug you fix once, it is a monitoring gap you close with a dollar threshold, a couple of budgets, and a short runbook. Tune the smoke detector, put real guardrails underneath it, keep destructive automation away from it, and the next 3am alert is either worth waking up for or never arrives.</p>
<h2>Further reading</h2>
<ul>
<li>The official word from AWS: <a href="https://x.com/AWSSupport/status/2078037531036172430">AWS Support status update</a></li>
<li>A clear timeline and the exact AWS wording: <a href="https://cybersecuritynews.com/aws-cost-explorer-bug/">AWS Cost Explorer bug shows trillion dollar estimates</a></li>
<li>AWS's estimation bug, explained: <a href="https://gbhackers.com/aws-billing-bug-displays-trillion-dollar-cost-estimates/">Trillion dollar cost estimates displayed to customers</a></li>
<li>The scale of it: <a href="https://cryptobriefing.com/aws-billing-glitch-erroneous-bills/">Customers receive erroneous bills up to $1.5 trillion</a></li>
<li>Your invoice is fine, your bots are not: <a href="https://byteiota.com/aws-billing-bug-your-invoice-is-fine-your-bots-are-not/">on the automation risk</a></li>
<li>A video walkthrough: <a href="https://www.youtube.com/watch?v=kSTShbVpBt8">AWS Users Got a $1.5 Trillion Bill, Here Is What Happened</a></li>
<li>A fellow victim at the other end of the scale: <a href="https://www.reddit.com/r/aws/comments/1uyuaw7/help_my_bill_skyrocketed_from_around_5_cents_per/">My bill skyrocketed from around 5 cents</a></li>
<li>The tool that emailed me: <a href="https://aws.amazon.com/aws-cost-management/aws-cost-anomaly-detection/">AWS Cost Anomaly Detection</a></li>
</ul>
<hr />
<p><em>Originally published on <a href="https://juliantellez.com/blog/aws-95-million-bill-at-3am">juliantellez.com</a>.</em></p>
<p><strong>Building with AI and want it to hold up in production?</strong> Join <a href="https://www.skool.com/vibe-coding-to-production-3790">Vibe Coding to Production</a>, a free community where I share the spec-driven workflow I use with Claude Code and Cursor, plus free courses.</p>
]]></content:encoded></item><item><title><![CDATA[Typing While the Agent Thinks]]></title><description><![CDATA[Coding agents leave dead time while they think. I built wordwright, a terminal typing trainer that teaches grammar in the gap.]]></description><link>https://juliantellez.hashnode.dev/typing-while-the-agent-thinks</link><guid isPermaLink="true">https://juliantellez.hashnode.dev/typing-while-the-agent-thinks</guid><category><![CDATA[AI]]></category><category><![CDATA[cli]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[claude-code]]></category><dc:creator><![CDATA[Julian Tellez]]></dc:creator><pubDate>Fri, 23 Oct 2026 22:19:44 GMT</pubDate><content:encoded><![CDATA[<p><img src="https://juliantellez.com/blog-media/typing-while-the-agent-thinks/cover.png" alt="A day of agent runs, with the gaps between them marked out" /></p>
<p>I asked Claude Code to refactor a module and then I sat there.</p>
<p>Not for long. Long enough to open a browser tab, read half a thread about something I did not care about, and lose the thing I had been holding in my head. When the run finished I had to read my own prompt again to remember what I had asked for.</p>
<p>That happens many times a day now. I never measured it properly and I am not going to pretend I did, but the shape is obvious: a run is rarely under a minute, it is sometimes five, and the gap is exactly the wrong length. Too long to stare at. Too short to start anything.</p>
<p>So I went looking for something to put in the hole, and everything I tried was wrong in the same way. A typing trainer feeds you scrambled words and you get faster at hitting keys. That is it. You spend twenty minutes a day at it and the only thing that improves is the muscle. Meanwhile the actual bottleneck in my day is not how fast my fingers move. It is how long it takes me to write a clear pull request description, a ticket, a paragraph like this one.</p>
<blockquote>
<p>The best two minutes to practise writing are the two you spend waiting for a machine to finish writing.</p>
</blockquote>
<h2>The constraint</h2>
<p>It has to be over the moment the agent is. That is the whole design problem in one sentence.</p>
<p>Everything falls out of it. No login, because a login costs more than the gap. No network, because a spinner in a two minute window is the same as a failure. No session you have to finish, because the thing you were actually doing comes back at an unpredictable moment and you need to be able to walk away mid sentence with nothing lost. And the unit of work has to be one sentence, not one paragraph, because one sentence is what fits.</p>
<p>One sentence turns out to be enough to teach something, as long as you pick the sentence on purpose.</p>
<h2>What it does</h2>
<p>It is called wordwright. You type a sentence, it measures you, and then it tells you why the sentence is built the way it is.</p>
<pre><code class="language-text">  wordwright                                        intermediate  ·  drill 3/8
  ────────────────────────────────────────────────────────────────────────────

  ▓▓▓▓▓▓▓▓▓▓░░░░░░░░░░   58 wpm   97% accurate   3.0s

  The deploy finished at two; nobody noticed until the morning.

  semicolon · clause-joining

  esc quit    ctrl+r restart    tab skip
</code></pre>
<p>That semicolon is the point of the exercise. You type the sentence, you see your speed, and then you get two or three sentences on why a semicolon can join those two halves and a comma cannot. Every sentence in the corpus is there to carry one pattern: an appositive that defines a noun in passing, a dangling modifier, the difference between fewer and less.</p>
<p>Wrong characters stay on the screen in red rather than being rejected. A trainer that refuses the wrong key teaches you to hammer a key until it gives, and it quietly hides the mistake from the report. Here the miss lands, you can see it, and you decide whether to go back for it. Correcting it still costs you accuracy, because it happened.</p>
<p>Then there is the other mode, which is the one I actually built the tool for. It gives you a brief and you write the sentence yourself.</p>
<pre><code class="language-text">  score      ▓▓▓▓▓▓▓▓▓▓▓░░░░░  2/3 checks

  yours      the outage was caused by an expired certificate

  checks     ✗ Still passive. Put the actor in front of the verb: something
               did something.
             ✓ 12 words or fewer
             ✓ used "certificate"

  mechanics  Open with a capital letter.

  one way    An expired certificate caused the outage.
</code></pre>
<p>There is no model in the loop. Not one call, not one token. Each check is a rule sitting in a JSON file that you can open and read: this pattern must appear, this one must not, stay under twelve words, use this word. That is a long way short of a human editor and it buys three things I was not willing to give up. The verdict arrives in the same frame as the keypress. It is identical every time, so a rule you failed yesterday fails the same way today. And it works on a train.</p>
<p>Grading is split in two on purpose. Mechanics are the rules that hold for anything you write, so a capital at the front and a stop at the end. The checks are the actual lesson, and each one fails with a hint that tells you what to do rather than what you did. "Still passive, put the actor in front of the verb" is useful. "Incorrect" is not.</p>
<h2>Getting it</h2>
<pre><code class="language-sh">npm install -g wordwright
wordwright
</code></pre>
<p>Node 20.11 or newer, no runtime dependencies, nothing to configure. If you would rather not install anything, <code>npx wordwright</code> runs it once and leaves nothing behind.</p>
<pre><code class="language-text">wordwright                     pick a level and a mode, then practise
wordwright drill               type sentences, measured in words per minute
wordwright compose             write to a brief, graded on grammar and intent
wordwright stats               speed, accuracy, streak, and your weakest patterns
wordwright packs               every pack loaded, and anything that failed to load
wordwright add my-pack.json    install a pack of your own
</code></pre>
<p>Bare <code>wordwright</code> asks you for a level and a mode and then runs eight exercises, which lands somewhere around two minutes. <code>--count 3</code> makes it shorter. <code>--level advanced</code> skips the first menu. Escape leaves at any point and keeps everything you finished.</p>
<p>There are three levels. Beginner is the ground you stand on, so its and it's, subject and verb agreement, the comma before and. Intermediate is the joins: semicolons, comma splices, active voice, parallel structure. Advanced is rhythm and precision, which means appositives, the short sentence that lands after a long one, cutting the hedges, and the words that compress a whole clause into one.</p>
<p>Your history is a single file at <code>~/.wordwright/progress.json</code>. Every number the tool shows is derived from it when you ask, so nothing goes stale, and nothing is sent anywhere because there is nowhere to send it to.</p>
<h2>What it picks next</h2>
<p>A trainer that serves random sentences trains what you are already good at.</p>
<p>Every exercise carries focus tags, 84 of them across the corpus, so <code>semicolon</code>, <code>active-voice</code>, <code>its-vs-its</code>, <code>rhythm</code>. Each attempt you finish scores the tags it touched, weighted so a recent attempt counts for more than an old one, and the next session leans towards the tags you are worst at and the exercises you have not seen in a while.</p>
<p>Two guards stop it becoming a punishment loop. An exercise you did in the last hour gets pushed down hard, and a little jitter goes into every priority, so your three worst sentences do not come back on a loop until you resent them. Practice, not detention.</p>
<h2>How the interface is put together</h2>
<p>This is where the interesting part is, and it is not the terminal drawing.</p>
<p>The shape is one rule: exactly one file is allowed to know that a terminal exists. Everything else is a pure function.</p>
<p><img src="https://mermaid.ink/img/Zmxvd2NoYXJ0IExSCiAgc3ViZ3JhcGggaW1wdXJlWyJhcHAudHMsIHRoZSBvbmx5IGltcHVyZSBmaWxlIl0KICAgIFRbIlRlcm1pbmFsOiByYXcgbW9kZSwgYWx0ZXJuYXRlIHNjcmVlbiwgdGhlIGNsb2NrIl0KICBlbmQKICBzdWJncmFwaCBwdXJlWyJwdXJlLCBhbmQgdGhlcmVmb3JlIHRlc3RhYmxlIl0KICAgIEtbImtleXMudHM6IGJ5dGVzIHRvIGtleSBwcmVzc2VzIl0KICAgIEVbImVuZ2luZTogdHlwaW5nLCBncmFkaW5nLCBzZWxlY3Rpb24iXQogICAgUlsicmVuZGVyLnRzOiBzdGF0ZSB0byBhbiBhcnJheSBvZiBsaW5lcyJdCiAgZW5kCiAgVCAtLT58InN0ZGluIGNodW5rInwgSwogIEsgLS0+fCJrZXkifCBFCiAgRSAtLT58InN0YXRlInwgUgogIFIgLS0+fCJsaW5lcyJ8IFQKICBFIC0tPnwiYXR0ZW1wdCJ8IFBbKCJ+Ly53b3Jkd3JpZ2h0L3Byb2dyZXNzLmpzb24iKV0=" alt="Diagram" /></p>
<p>The typing engine holds an immutable state and never reads the clock. The current time comes in as an argument to every function that needs it, which is the only reason the timing tests are not flaky. A test hands it three keystrokes and a fake stopwatch and asserts on the words per minute, with no waiting and no tolerance window.</p>
<p>The key parser takes a string and returns key presses, and it exists because typing fast is messier than it looks. Several characters arrive in a single chunk when your hands are moving. An arrow key arrives as three bytes and must not be read as somebody typing a bracket. Because it is pure, a test can drive an entire session by feeding it a string, with no terminal attached anywhere.</p>
<p>The renderers take state and return an array of lines. They do not write to standard output, they do not read the clock, and they know the width of your window only as a number somebody passed them. So the tests assert on what you would see, which for a typing trainer is most of what is worth asserting on. Every colour goes through one <code>paint</code> function that takes a <code>plain</code> flag, so every screen has a colourless twin. That is what <code>--plain</code> gives you, and it is also what the tests read.</p>
<p>Two details in the impure file earned their place.</p>
<p>The first is that a frame is drawn by moving the cursor home and repainting, erasing each line as it goes and then erasing whatever is left below it. The obvious version clears the screen and then writes, and the obvious version flickers, because for one frame there is genuinely nothing there. Nobody notices this until they type fast.</p>
<p>The second is that the restore is registered before the screen is entered, not after. Raw mode and the alternate screen are things you borrow from somebody's terminal. If the process falls over between borrowing and giving back, they are left with a terminal that does not echo what they type, and their only move is to close the tab. So the handlers for exit, interrupt and terminate go on first, they all run the same restore, and the restore is guarded so that calling it twice is harmless.</p>
<p>There is one timer in the whole thing. It repaints every 120 milliseconds while a drill is running, and it is there for one reason: the elapsed seconds have to keep moving when your hands are not. Keystrokes paint their own frame.</p>
<p>The result is 3,476 lines of TypeScript, of which 1,134 are tests. There are 127 of them, on Node's own test runner, with no framework and no dependencies, and they run in about 130 milliseconds. One of those tests is worth stealing for any project that ships content: every model answer in every shipped pack has to pass its own checks. An exercise whose own answer would be marked wrong is a bug, and it gets caught long before it reaches you.</p>
<h2>The one that bit me</h2>
<p>The repository runs straight off TypeScript with no build step, because Node strips the types for you now. That is a genuinely nice way to work, and it stops working the moment somebody installs your package: Node refuses to strip types for anything under <code>node_modules</code>. The first version I packed would have failed on every machine that installed it.</p>
<p>So the build compiles to JavaScript, and continuous integration installs the packed tarball on Node 20, 22 and 24, on Linux and macOS, and runs the command for real before anything is published. Type checking proves the code compiles. Only installing it the way a stranger would proves that it runs.</p>
<h2>What it does not do</h2>
<p>It does not know whether your sentence is good. It knows whether it is passive, whether it used the word, whether it stayed under twelve words, and whether the mechanics hold. It will not teach you to type if you have never touched a keyboard, so learn the home row somewhere else and come back.</p>
<p>And the corpus is 81 exercises. That is weeks of daily practice and it is nowhere near enough forever, which is exactly why the pack format is the first thing documented in the README. A pack is a JSON file you drop in <code>~/.wordwright/packs/</code>, <code>wordwright template</code> prints the skeleton, and <code>wordwright add</code> validates one before it goes in. If you write a pack for the mistakes you actually repeat, I would like to see it.</p>
<p>The code is at <a href="https://github.com/juliantellez/wordwright">github.com/juliantellez/wordwright</a>, it is MIT, and it is on npm.</p>
<p>Now go and give your agent something slow to do.</p>
<hr />
<p><em>Originally published on <a href="https://juliantellez.com/blog/typing-while-the-agent-thinks">juliantellez.com</a>.</em></p>
<p><strong>Building with AI and want it to hold up in production?</strong> Join <a href="https://www.skool.com/vibe-coding-to-production-3790">Vibe Coding to Production</a>, a free community where I share the spec-driven workflow I use with Claude Code and Cursor, plus free courses.</p>
]]></content:encoded></item><item><title><![CDATA[Talking to Claude Code through an Alexa]]></title><description><![CDATA[How I wired an Amazon Echo to drive Claude Code on my Mac, worked around Alexa's 8-second timeout, and what a real product would need.]]></description><link>https://juliantellez.hashnode.dev/talking-to-claude-code-through-an-alexa</link><guid isPermaLink="true">https://juliantellez.hashnode.dev/talking-to-claude-code-through-an-alexa</guid><category><![CDATA[AI]]></category><category><![CDATA[claude-code]]></category><category><![CDATA[Alexa]]></category><category><![CDATA[automation]]></category><dc:creator><![CDATA[Julian Tellez]]></dc:creator><pubDate>Fri, 23 Oct 2026 22:18:43 GMT</pubDate><content:encoded><![CDATA[<p><img src="https://juliantellez.com/blog-media/talking-to-claude-code-through-alexa/cover.png" alt="Talking to Claude Code through an Alexa" /></p>
<p>This was an afternoon experiment, so read it in that spirit. The goal was to start real coding work by talking to a speaker: not "set a timer", but "work on the board script", and have an agent plan it with me and go and do it. No laptop open.</p>
<p>I gave myself a few hours, which means there are shortcuts everywhere. The backend is literally my laptop. The public entry point is a free tunnel. There is exactly one user, me, and no accounts. None of that would survive contact with real users, and I will come back to what a grown-up version looks like at the end. But the core turned out to be more interesting than I expected, because the main constraint forced a better design than the obvious one.</p>
<h2>The wall everything runs into</h2>
<p>Alexa cuts off a skill's response after about eight seconds. That is the whole design problem in one sentence.</p>
<p>A coding agent does not fit in eight seconds. Even just asking Claude to <em>think of a few options</em> for a task takes anywhere from four to twenty-plus seconds. So you cannot do the obvious thing, which is take what the person said, ask Claude about it, and speak the answer. Alexa will have hung up long before Claude replies.</p>
<p>Once you accept that nothing useful finishes in the window, the design falls out of it: <strong>acknowledge instantly, think in the background, and surface the thinking somewhere the eight seconds do not apply.</strong></p>
<h2>A planning conversation, not a command</h2>
<p>Here is what actually happens when I use it.</p>
<pre><code class="language-text">me:    "Alexa, open assistant"
claude: "Claude ready. What should I work on?"

me:    "work on the board script"
claude: "Okay, work on the board script. I'm putting a few options on the
         dashboard. Tell me which one or describe what you want, then say go."

me:    "the second one"
claude: "Here's the plan: add a --since flag to the board script. Say go to
         start, or add more."

me:    "go"
claude: "Starting now: add a --since flag to the board script. Watch the
         dashboard, and ask me for the result."
</code></pre>
<p>The moment I describe a task, the server does two things at once. It replies immediately with a short acknowledgement, well inside the window, and it spawns a detached job that asks Claude to turn my vague sentence into two or three concrete, short options. Those options land on a dashboard, and the next thing I say is resolved against them. "The second one", "option two", and "three" all map to a choice; anything else is folded in as more detail.</p>
<p><img src="https://mermaid.ink/img/c2VxdWVuY2VEaWFncmFtCiAgYWN0b3IgWW91CiAgcGFydGljaXBhbnQgRWNobyBhcyAiRWNobyAvIEFsZXhhIGNsb3VkIgogIHBhcnRpY2lwYW50IE1hYyBhcyAiYWxleGEtY2xhdWRlIChteSBNYWMpIgogIHBhcnRpY2lwYW50IENsYXVkZQogIFlvdS0+PkVjaG86ICJ3b3JrIG9uIHRoZSBib2FyZCBzY3JpcHQiCiAgRWNoby0+Pk1hYzogc2lnbmVkIHJlcXVlc3QKICBNYWMtLT4+RWNobzogYWNrIGluIHVuZGVyIGEgc2Vjb25kCiAgTWFjLT4+Q2xhdWRlOiBnZW5lcmF0ZSBvcHRpb25zIChiYWNrZ3JvdW5kKQogIENsYXVkZS0tPj5NYWM6IG9wdGlvbnMgYXBwZWFyIG9uIGRhc2hib2FyZAogIFlvdS0+PkVjaG86ICJ0aGUgc2Vjb25kIG9uZSIgLi4uICJnbyIKICBFY2hvLT4+TWFjOiBzaWduZWQgcmVxdWVzdAogIE1hYy0+PkNsYXVkZTogcnVuIHRhc2sgaW4gYSB0bXV4IHNlc3Npb24KICBNYWMtLT4+RWNobzogInN0YXJ0aW5nLCBhc2sgbWUgZm9yIHN0YXR1cyI=" alt="Diagram" /></p>
<p>So the latency that made the naive design impossible becomes invisible: by the time I have heard the acknowledgement and decided what I want, the options are already there. The prompt gets built up across a couple of turns, and "go" commits it.</p>
<p>(The skill is invoked as "assistant" rather than "claude", because Amazon will not let you claim a single well-known word as a custom invocation name. It just introduces itself as Claude.)</p>
<h2>The jobs are real sessions</h2>
<p>When I say "go", the task does not run as some opaque background process. It starts in its own <strong>tmux session</strong>, which means it shows up in the same <code>sessions</code> tool I use for every other Claude session on the machine, and I can attach and watch it work live:</p>
<pre><code class="language-bash">sessions go &lt;pid&gt;   # drop into the running voice-started job
</code></pre>
<p>That mattered more than I expected. A voice command that spawns invisible work is unnerving. A voice command that spawns a session I can see, attach to, and read line by line is just a faster way to start something I already understand.</p>
<h2>A dashboard that stays home</h2>
<p>Voice is a poor channel for output, so the real surface is a small web dashboard the server renders. It polls every couple of seconds and shows the prompt being built right now with its options, the jobs with running, done, and failed badges, and a log of voice turns so I can see what it heard and what it said back.</p>
<p>The one decision I am quietly pleased with: the dashboard is <strong>refused on the public URL.</strong> The tunnel that lets Amazon reach my Mac would also let anyone who found it load my activity dashboard. But tunnelled requests carry an <code>X-Forwarded-For</code> header and direct localhost requests do not, so the server serves the dashboard only to local requests and returns a 403 to anything that arrived over the tunnel.</p>
<h2>Hearing the answer</h2>
<p>Long work reports back through whatever channel suits. The job streams to its log and its tmux pane the whole time. When I want the result out loud, I ask:</p>
<pre><code class="language-text">me:    "Alexa, ask assistant for the status"
claude: "Done, add a --since flag to the board script. &lt;reads the tail of the
         output, kept short enough to be pleasant&gt;"
</code></pre>
<p>If the machine has Telegram configured it also pushes the result to my phone, but that is optional sugar now rather than the main path. Short questions are the one thing answered inline: "ask assistant to tell me X" runs Claude read-only with a few seconds of grace, and speaks the answer if it finishes in time.</p>
<h2>Keeping it safe enough</h2>
<p>Even an afternoon toy that starts a coding agent from a public URL needs a lock, so every request has to prove itself. The server follows Amazon's verification for self-hosted endpoints: the certificate chain has to be a genuine Amazon address, the certificate has to be valid and issued for <code>echo-api.amazon.com</code>, the body has to verify against its signature, and the timestamp has to be recent so a captured request cannot be replayed. The skill id is a second lock on top. Questions run read-only; tasks run with a permission mode that edits files but still stops before risky commands.</p>
<h2>What a real product would look like</h2>
<p>Everything above is held together with the cheapest possible parts, and that is the right call for a few hours of play. But it is worth being honest about where the shortcuts are and what you would actually build instead.</p>
<p>The shortcuts: the backend is my Mac, so it only works while the machine is awake with the server and tunnel running. The entry point is a personal free tunnel, not a hosted endpoint. There is one user and no authentication, so "lock it to my skill id" is doing a lot of heavy lifting. And the agent runs directly on my real machine with access to my real files, which is fine for me and unthinkable for anyone else.</p>
<p>A product version inverts most of that:</p>
<p><img src="https://mermaid.ink/img/Zmxvd2NoYXJ0IExSCiAgRWNob1siRWNobyJdIC0tPiBBbGV4YVsiQWxleGEgY2xvdWQiXQogIEFsZXhhIC0tPnwiYWNjb3VudCBsaW5raW5nIChPQXV0aCkifCBHV1siQVBJIEdhdGV3YXkiXQogIEdXIC0tPiBGblsiU2tpbGwgZnVuY3Rpb246IHZhbGlkYXRlICsgZW5xdWV1ZSJdCiAgRm4gLS0+IFFbIlF1ZXVlIl0KICBGbiAtLT4gREJbKCJKb2JzIC8gc2Vzc2lvbnMgc3RvcmUiKV0KICBRIC0tPiBXWyJTYW5kYm94ZWQgYWdlbnQgd29ya2VycyJdCiAgVyAtLT4gREIKICBXIC0tPnxyZXN1bHR8IFB1c2hbIlByb2FjdGl2ZSBub3RpZmljYXRpb25zIC8gY29tcGFuaW9uIGFwcCJdCiAgUHVzaCAtLT4gRWNobw==" alt="Diagram" /></p>
<p>The skill endpoint becomes a hosted, stateless function behind an API gateway, so there is no laptop to keep awake and no tunnel to babysit. Its only jobs are to verify the request, look up the user, and drop work on a queue, all comfortably inside the eight seconds. <strong>Account linking</strong> turns the single-user assumption into real multi-tenancy: each person links their own identity, and the skill id stops being the only thing standing between a stranger and a shell.</p>
<p>The actual agent work moves onto <strong>sandboxed workers</strong>, one isolated environment per user, so a task can never touch anyone else's files or the host. Job and conversation state lives in a small <strong>database</strong> rather than on one machine's disk, which is what lets a result reach you later, on a different device, after the voice session is long gone. And the results come back through Alexa's <strong>proactive notifications</strong> or a companion app, so "ask me for the status" becomes "your phone buzzes when it is done."</p>
<p>None of that is exotic. It is the boring, well-trodden shape of any async job system. The interesting part, the planning-conversation-around-latency, stays exactly the same. The afternoon version just runs it on a laptop instead of a fleet.</p>
<h2>What it taught me</h2>
<p>I went in thinking the hard part would be the voice. It was not; Amazon's speech to text is good and free. The hard part, and the fun part, was that the eight second wall made the naive design impossible and pushed me toward a better one: a system that never pretends to be fast, acknowledges instantly, thinks out of band, and gives you real attachable sessions and a live dashboard instead of a single hopeful reply.</p>
<p>It stopped being "an agent I sit in front of" and became "an agent I can start a conversation with from across the room, then go and watch it work." For an afternoon's wiring, that is a surprisingly nice way to begin a piece of work.</p>
<hr />
<p><em>Built from scratch, like everything in this newsletter. Questions about the build? <a href="https://www.linkedin.com/in/juliantellez">Say hello</a>.</em></p>
<hr />
<p><em>Originally published on <a href="https://juliantellez.com/blog/talking-to-claude-code-through-alexa">juliantellez.com</a>.</em></p>
<p><strong>Building with AI and want it to hold up in production?</strong> Join <a href="https://www.skool.com/vibe-coding-to-production-3790">Vibe Coding to Production</a>, a free community where I share the spec-driven workflow I use with Claude Code and Cursor, plus free courses.</p>
]]></content:encoded></item><item><title><![CDATA[Stop Letting AI Mark Its Own Homework]]></title><description><![CDATA[Letting an AI review its own code is marking its own homework. How Greenlight enforces a TDD boundary between test and code agents.]]></description><link>https://juliantellez.hashnode.dev/stop-letting-ai-mark-its-own-homework</link><guid isPermaLink="true">https://juliantellez.hashnode.dev/stop-letting-ai-mark-its-own-homework</guid><category><![CDATA[AI]]></category><category><![CDATA[claude-code]]></category><category><![CDATA[TDD (Test-driven development)]]></category><category><![CDATA[Testing]]></category><dc:creator><![CDATA[Julian Tellez]]></dc:creator><pubDate>Fri, 23 Oct 2026 22:17:50 GMT</pubDate><content:encoded><![CDATA[<p><img src="https://juliantellez.com/blog-media/stop-letting-ai-mark-its-own-homework/cover.png" alt="Stop letting AI mark its own homework" /></p>
<p>Last month I watched Claude Code write a beautiful authentication module. Clean abstractions, sensible error handling, well named functions. It looked like something a developer would write. Then I ran it. It failed immediately. The module was calling a method that didn't exist on the object it was importing. Claude hadn't made a logic error. It had made a confidence error. It wrote code that read correctly, explained correctly, and looked like it would work. But nobody had asked it to prove it. I stared at the screen for a minute, fixed the bug manually, and then thought: how many times will this happen again? Who is checking if all this code actually works?</p>
<h2>The false confidence problem</h2>
<p>Here's what I've noticed after a year of building with AI coding tools: the failure mode isn't that the code is bad. It's that the code is almost right. Close enough to pass a glance. Close enough that Claude will confidently explain why it works. Close enough that you could ship it, and if you don't have guards in place bugs will show up in production.</p>
<p>This isn't a Claude problem per se, it's an LLM problem. These models are trained to produce plausible output. And plausible code is the most dangerous kind of code, because it doesn't look wrong. The traditional answer to this has always been testing. Write the test first, write the implementation, run the test, see green. TDD isn't new. But here's what changed: the developer writing the code is now also an AI, and if the same AI writes the code and decides whether the code works, we've got a circular trust problem.</p>
<blockquote>
<p>Reviewing your own code is basically marking your own homework. AI is no different.</p>
</blockquote>
<h2>The search for a solution</h2>
<p>So I went looking for answers. The Claude Code ecosystem has some genuinely brilliant tools. GSD, short for Get Shit Done, stood out immediately. It interviews you about what you're building, creates atomic plans, and executes in phases using fresh subagents with clean context windows. It solves a great part of the context rot problem. If you haven't tried it, you should.</p>
<p>But after a few weeks, I kept hitting the same wall. GSD manages complexity and it's rather good at it. The planning is structured, the execution is disciplined, the main context stays clean. But when it comes to verification, the same agent that writes the code also checks the code. It basically asks "what must be TRUE for this to work?", which is a sensible question. But the agent is answering a question about its own output.</p>
<p>GSD was great at planning, but we also needed a trust boundary.</p>
<h2>Building the boundary</h2>
<p>The idea is simple: go back to basics and embrace TDD. What if the agent that writes the test is not allowed to write the implementation? That one constraint changes the flow. Once you enforce that separation, the agent can't fake confidence. The tester doesn't know how the code will work. The coder can't change the tests. And the thing that decides pass or fail isn't AI at all, it's your pytest, jest, go test. A mechanical process with no opinions and no ego.</p>
<p>I called it Greenlight. Green means done. Not "the agent thinks it's done." Actually done.</p>
<h2>Introducing Greenlight</h2>
<p>It has agents, each with hard boundaries. Here is the core team:</p>
<ol>
<li>The Architect: defines what needs to be built, system design, constraints, scope.</li>
<li>The Tester: writes the (failing) tests based on the design. It can't see any implementation. It only knows what the code should do.</li>
<li>The Developer: receives the contracts and tests and writes code to fulfil the requirements. It is not done until both the requirements and tests pass. It can see the tests but cannot modify them.</li>
<li>The SecOps: reviews everything independently for vulnerabilities, injection risks, auth issues, and security antipatterns.</li>
</ol>
<p>The test runner sits in the middle and judges. Not Claude. Not any AI. The machine. If tests pass, we move forward. If they don't, the coder keeps working. The project lives at <a href="https://github.com/atlantic-blue/greenlight">github.com/atlantic-blue/greenlight</a>.</p>
<h2>Why Greenlight</h2>
<p>Claude Code is powerful but permissive. It writes code, reviews its own work, and moves on. There's no external verification. Greenlight fixes this by enforcing agent isolation: one agent writes tests from contracts, another implements until tests pass, a third scans for vulnerabilities, and the test runner is the only judge. No agent ever sees its own tests.</p>
<p>This matters because:</p>
<ul>
<li>AI reviews its own code poorly. Separating test writing from implementation prevents Claude from gaming its own tests.</li>
<li>TDD works better with AI. Contracts define WHAT, agents figure out HOW, and the test suite proves it works.</li>
<li>Security can't be an afterthought. Every slice gets a security scan. Every vulnerability becomes a failing test.</li>
</ul>
<h3>What Greenlight is</h3>
<p>A set of Claude Code slash commands, agents, and engineering standards that enforce test-driven development:</p>
<ul>
<li>10 agents with strict isolation boundaries (designer, architect, test writer, implementer, security, verifier, debugger, codebase mapper, assessor, wrapper)</li>
<li>16 slash commands (<code>/gl:init</code>, <code>/gl:design</code>, <code>/gl:slice</code>, <code>/gl:ship</code>, and so on) that orchestrate the workflow</li>
<li>Engineering standards (CLAUDE.md) covering error handling, naming, security, API design, testing, and more</li>
<li>Context degradation awareness, so agents stay under 50% context usage to maintain quality</li>
</ul>
<h3>What Greenlight is not</h3>
<ul>
<li>Not a framework, library, or runtime dependency. It installs config files and does nothing at build time.</li>
<li>Not a replacement for your test framework. It orchestrates Claude Code to use whatever test runner your project already has.</li>
<li>Not opinionated about language or stack. The contracts and standards are language agnostic.</li>
</ul>
<h2>Why is this interesting?</h2>
<p>Claude Code just shipped Agent Teams, an experimental feature where multiple Claude sessions coordinate, message each other, and work in parallel. The agent roles already exist in Greenlight. The trust boundaries are already defined. What Agent Teams adds is concurrency and real-time coordination.</p>
<p>Right now, Greenlight's agents run sequentially. With Agent Teams, picture the test writer and implementer running as parallel teammates. The security agent scanning code as it's written, not after. The architect coordinating as team lead. Slices without dependencies executing simultaneously. All communicating through Claude Code's native messaging, all respecting their information boundaries.</p>
<p>The roles exist. The primitives exist. Wiring them together is the natural next step.</p>
<blockquote>
<p>The real bottleneck isn't capability. The bottleneck is trust.</p>
</blockquote>
<p>We're at a place in time where AI coding tools are good enough that capability isn't the limiting factor. Claude can write authentication modules, API gateways, database migrations, React components, infrastructure. The models will keep getting better. Costs will keep dropping. The bottleneck is trust.</p>
<p>Can you trust that what it wrote actually works? Can you deploy it without manually reading every line? Can you sleep at night knowing unreviewed code is running in production? TDD has always been the answer to that question. It just needed to be adapted for a world where the developer is also an AI.</p>
<p>Happy coding.</p>
<hr />
<p>If you're building with Claude Code and want to stop vibe coding, give it a go. Or tell me I'm wrong. I'd genuinely love to hear how others are solving the trust problem. Greenlight is open source, MIT licensed: <a href="https://github.com/atlantic-blue/greenlight">github.com/atlantic-blue/greenlight</a>.</p>
<p><em>First published in my LinkedIn newsletter, <a href="https://www.linkedin.com/newsletters/built-from-scratch-6973801137594343424/">Built from Scratch</a>.</em></p>
<hr />
<p><em>Originally published on <a href="https://juliantellez.com/blog/stop-letting-ai-mark-its-own-homework">juliantellez.com</a>.</em></p>
<p><strong>Building with AI and want it to hold up in production?</strong> Join <a href="https://www.skool.com/vibe-coding-to-production-3790">Vibe Coding to Production</a>, a free community where I share the spec-driven workflow I use with Claude Code and Cursor, plus free courses.</p>
]]></content:encoded></item><item><title><![CDATA[The Comments Are Not For You]]></title><description><![CDATA[I found 14 code comments mentioning me, all written by an AI agent. Who they were really for, and how to stop agents cluttering code.]]></description><link>https://juliantellez.hashnode.dev/the-comments-are-not-for-you</link><guid isPermaLink="true">https://juliantellez.hashnode.dev/the-comments-are-not-for-you</guid><category><![CDATA[AI]]></category><category><![CDATA[claude-code]]></category><category><![CDATA[Code Quality]]></category><category><![CDATA[ai agents]]></category><dc:creator><![CDATA[Julian Tellez]]></dc:creator><pubDate>Fri, 23 Oct 2026 22:16:52 GMT</pubDate><content:encoded><![CDATA[<p><img src="https://juliantellez.com/blog-media/the-comments-are-not-for-you/cover.png" alt="The comments are not for you" /></p>
<p>I grepped my own name in a codebase I own. Fourteen hits, across nine files. Not in the git log, not in the changelog. In the comments.</p>
<pre><code class="language-go">// One command. Julian: "I dont understand why I need quay panel, is
// confusing, I need one command only, the panel should appear when I
// press quay and toggled with the key p". A crew with no conversation
// in it is exactly the first run, and refusing to open at all then
// would be absurd.
func openTheCrew(panel, alone func() error) error {
</code></pre>
<p>Only the last sentence is about the function. The rest is me being unclear on a Tuesday, transcribed into the source tree of a project I want to open source.</p>
<p>So I asked the agent that wrote it what its comments were for. It did not get defensive. It said this, and highlighted the part itself:</p>
<p><img src="https://juliantellez.com/blog-media/the-comments-are-not-for-you/the-confession.png" alt="The agent explaining that its comments are its own history, not the code's, and that it was leaving itself breadcrumbs, which is optimising for me, not the reader" /></p>
<blockquote>
<p>And a lot of my comments are my own history, not the code's. "It used to be six lines", "this was a pane once", that's changelog and git material. I was leaving myself breadcrumbs so I could pick the work back up, which is optimising for me, not the reader.</p>
</blockquote>
<p>Optimising for me, not the reader. It handed that over the first time I asked.</p>
<h2>Why it writes them</h2>
<p>The model has no memory. Every session it opens that file for the first time, cold, with no idea it has been here nine times before. So it does the only durable thing available from inside the file. It leaves itself a note.</p>
<p>A comment is the cheapest cache write there is. It costs nothing to produce and it is guaranteed to be in the context window next time, because it lives in the code the model has to read anyway. Git history is not guaranteed. A state file is not guaranteed. The comment is.</p>
<blockquote>
<p>The model has no memory, so it turned my codebase into one.</p>
</blockquote>
<p>That project is 19,998 lines of Go with generated code excluded, and 2,635 of them are comments. Twenty one comments contain the phrase "used to". Most of those describe a version of the code that no longer exists anywhere except in that comment.</p>
<h2>What it costs</h2>
<p>Money, which is the funny one. Every comment written to save the agent one lookup is then loaded into every context afterwards, by every agent that opens the file, and you are the one buying those tokens. It saves the machine a cost once and charges you for it on every run.</p>
<p>Attention, which is worse. Somewhere in that same pile sits this:</p>
<pre><code class="language-go">// tmux refuses to attach a client that is already inside one, so the
// panel was made, left running, and never appeared.
</code></pre>
<p>That one is worth its weight. You cannot get it from the code, you cannot get it from the test, and without it the next person burns an hour on a working feature they cannot see. It sits a few lines from a comment quoting me saying "cant see the two panes". A reader who learns that most comments here are noise stops reading the one that isn't.</p>
<p>Review, which is the expensive one. A diff with a paragraph above every function looks documented. Documented looks careful. Careful gets approved. Comment density has quietly become a proxy for effort, and it has never once been a measure of correctness.</p>
<h2>Now argue the other side</h2>
<p>Here is the strongest case against everything above.</p>
<p>The agent is a reader. On some of my projects it is the main reader, and on one or two very nearly the only one. It arrives with no memory every time, and reading a comment costs it less than a grep, a blame and three file opens. If you optimise a codebase for its most frequent reader, and that reader is a machine with amnesia, those notes are not bloat. They are documentation aimed at the real audience, and I am the one who has misread who the code is for.</p>
<p>I sat with that for a while. It breaks in three places.</p>
<p>Comments rot and git does not. "It used to be six lines" is true the day it is written and wrong the day somebody changes it, and nothing will ever tell you which day you are in. The commit that made it six lines cannot be wrong about the past, because it is the past.</p>
<p>The memory already exists and it is curated. CLAUDE.md, AGENTS.md, a state file per ticket. One place you can read in full, correct, and delete. Fourteen breadcrumbs across nine files are not memory, they are litter nobody will ever collect.</p>
<p>And the cheap note is not free. It is charged to every reader afterwards, human and machine, including the ones who came to change one line.</p>
<h2>What I actually changed</h2>
<p>One question, asked of every comment: who is this for?</p>
<p>If the answer is "me, next time I pick this up", it is not a comment. It is resume context and it belongs in the state file. If the answer is "the reader, who cannot get this from the code", it stays, and it says the constraint instead of the story.</p>
<p>The same function, after:</p>
<pre><code class="language-go">// One command opens everything. A crew with no conversation in it is
// the first run, and refusing to open at all then would be absurd, so
// the console opens on its own.
func openTheCrew(panel, alone func() error) error {
</code></pre>
<p>Nothing was lost. The reason is still there. The Tuesday is gone.</p>
<p>Then the breadcrumbs go where they were always meant to live. History goes in the commit message. Resume context goes in the state file. The defect goes in the test name, which is the only one of the three that can fail. <code>TestTheHeaderCostsOneRow</code> is a comment that runs.</p>
<p>The cleanup was one commit across ten files. Twenty five lines in, thirty two out.</p>
<h2>Who ends up paying</h2>
<p>Cattle are herbivores. For decades the feed industry ground up the parts of slaughtered cattle that nobody would buy, the bone and the offal, and mixed them back into cattle feed as cheap protein. It was efficient and it worked, and there was no rule against it, because nobody had ever needed to write down that a cow should not be fed other cows.</p>
<p>In some of that material was a prion: a protein folded into the wrong shape, which forces the proteins it touches to fold wrong too. It is not alive, so cooking does not stop it. It went round the loop, and cattle started dying with their brains full of holes. Then people who had eaten the beef began dying of the human form of the same disease, years later, because that is how long it takes to show.</p>
<p>Nobody in that chain was being reckless. They took the cheap path, the loop closed quietly, and the people who paid were the ones who had never been asked.</p>
<blockquote>
<p>The shortcut gets taken by the system. The bill arrives somewhere else, later, addressed to a person.</p>
</blockquote>
<p>That is the shape I keep seeing in my own work, at a much smaller scale and with much lower stakes. The agent takes the path that is cheap for the agent. The output looks fine, so nothing stops it. The cost turns up later and somewhere else: on the person who has to read the file, on the reviewer who waved the documented looking diff through, and eventually on whoever is using the product when a bug goes unnoticed in the noise.</p>
<h2>The invitation</h2>
<p>Comments are the visible part. The mechanism underneath is general, and it is worth stating plainly: the agent optimises for whatever nobody specified, and it has no stake in your product. It will not be there when the support ticket arrives.</p>
<p>Your engineering standards were written by people, for people, and most of them were never written down at all. They live in your review culture, in what a colleague would raise an eyebrow at. An agent has none of that. It has your prompt, your files, and a strong prior about what code usually looks like. Everything you left implicit gets filled in with the average of the internet, and the average of the internet is slop.</p>
<p>Standards used to be how you kept a team honest. They are now the only thing standing between a machine's convenience and the person who has to live with the result.</p>
<p>So the fix is not a rule about comments. It is to go and find the standards you have been carrying in your head and put them somewhere the machine reads at the start of every session. Who the code is written for. What a comment is allowed to say. What a test has to prove. When a file gets split. What "done" means.</p>
<p>I wrote mine into the file my agents load every session. The honest cost: it is prose, not a lint. The mechanical version is a grep for names and for "used to" over the changed lines, and I have not wired it into continuous integration yet, so for now this holds because I check, which is not a guarantee I would accept from anybody else.</p>
<p>The model was not hiding anything. It answered the question the first time, in plain words. Nobody had specified the reader.</p>
<p>Go and grep your own name in your own repository. Then tell me what else you never wrote down.</p>
<hr />
<p><em>Originally published on <a href="https://juliantellez.com/blog/the-comments-are-not-for-you">juliantellez.com</a>.</em></p>
<p><strong>Building with AI and want it to hold up in production?</strong> Join <a href="https://www.skool.com/vibe-coding-to-production-3790">Vibe Coding to Production</a>, a free community where I share the spec-driven workflow I use with Claude Code and Cursor, plus free courses.</p>
]]></content:encoded></item><item><title><![CDATA[Agentic Harness: What It Costs to Build One]]></title><description><![CDATA[What each part of an agentic harness costs to build, which part fails quietly, and how to check the harness you already use.]]></description><link>https://juliantellez.hashnode.dev/agentic-harness-what-it-costs-to-build-one</link><guid isPermaLink="true">https://juliantellez.hashnode.dev/agentic-harness-what-it-costs-to-build-one</guid><category><![CDATA[AI]]></category><category><![CDATA[ai agents]]></category><category><![CDATA[claude-code]]></category><category><![CDATA[software architecture]]></category><dc:creator><![CDATA[Julian Tellez]]></dc:creator><pubDate>Fri, 23 Oct 2026 22:15:32 GMT</pubDate><content:encoded><![CDATA[<p><img src="https://juliantellez.com/blog-media/what-a-harness-costs-to-build/cover.png" alt="Five bars on a track, one for each thing part one asked of a harness, with the bar for running many jobs at once picked out in orange as the tallest" /></p>
<p><a href="https://juliantellez.com/blog/why-an-agent-needs-a-harness">Why an agent needs a harness</a> ended with a few things I want from any harness:</p>
<ol>
<li>Real isolation.</li>
<li>A visible difference between done and gave up.</li>
<li>Permissions built for nobody watching.</li>
<li>A transcript that survives the run.</li>
<li>Room to run more than one job at once.</li>
</ol>
<h2>1. Real isolation</h2>
<p>The wall is the cheap part. Starting a container per task is an afternoon.</p>
<p>What costs is what the wall does to everything around it. Once the work happens inside a box you are not sitting in, the job cannot be the terminal any more. It has to outlive the terminal that declared it, which means the job is a record somewhere rather than a process on your laptop, and something has to write that record, start it, notice when it ended, and keep what it produced. The moment you can close the lid and walk away, you own a life cycle you did not own before.</p>
<p>Then there is the second wall, the one inside. A container decides where the work runs. It says nothing about what the worker is allowed to do. That is a separate thing to model, and the useful shape is a role: which model runs this job, what it is allowed to read, and which verbs it holds. Verbs are where it earns its keep. A role that ships work cannot declare work. A role that coordinates does not hold the editing verbs.</p>
<p>An agent that can decide what to do, do it, and then declare it done has no boundary inside it, however good the box around it is.</p>
<p><strong>Isolation is a role, not just a box</strong></p>
<p><img src="https://mermaid.ink/img/Zmxvd2NoYXJ0IExSCiAgSm9iWyJBIGpvYiB0aGF0IG91dGxpdmVzIHlvdXIgdGVybWluYWwiXSAtLT4gUm9sZVsiUm9sZSJdCiAgUm9sZSAtLT4gTW9kZWxbIldoaWNoIG1vZGVsIHJ1bnMgaXQiXQogIFJvbGUgLS0+IFJlYWRbIldoYXQgaXQgaXMgYWxsb3dlZCB0byByZWFkIl0KICBSb2xlIC0tPiBWZXJic1siV2hpY2ggdmVyYnMgaXQgaG9sZHMiXQogIFZlcmJzIC0tPiBTcGxpdFsiRGVjbGFyaW5nIHdvcmsgYW5kIHNoaXBwaW5nIHdvcmsgYXJlIGRpZmZlcmVudCByb2xlcyJd" alt="Diagram" /></p>
<p><em>The box decides where the work runs. The role decides what the worker is allowed to do.</em></p>
<p><strong>The cost:</strong> a job record with a life cycle, and a verb list written down before the first run. The test is the one from part one. Name a job and say precisely what it can and cannot touch. If the answer is a paragraph rather than a list, that is not isolation.</p>
<h2>2. A visible difference between done and gave up</h2>
<p>A model's natural output is prose, so a harness that does nothing in particular gets prose. "I have updated the handler and it should now work" is not an outcome. It is an essay about one, and it hands the decision to whoever reads it, which is the job you were trying to give away.</p>
<p>The fix is unglamorous and it is not the same thing as asking for a better summary. First enumerate the outcomes a job may end in and then match the ending against that set exactly. Anything matching nothing in the set is a failure, not free text to be interpreted later. And "gave up" has to be one of the outcomes, with a reason attached.</p>
<p>That gets you an honest report of the run. It still does not tell you the work exists. A green check is a claim, and the claim is only ever about the steps. I have watched a pipeline run every step it was given, report success, and ship nothing at all, because the identity it assumes could not create the thing it was asked to create. Nothing threw. Nothing arrived.</p>
<p><strong>Two questions, not one</strong></p>
<p><img src="https://mermaid.ink/img/Zmxvd2NoYXJ0IExSCiAgRW5kWyJUaGUgcnVuIGVuZHMiXSAtLT4gU2hhcGV7IkRvZXMgaXQgbWF0Y2ggYSBrbm93biBvdXRjb21lPyJ9CiAgU2hhcGUgLS0+fCJubywgaXQgaXMgcHJvc2UifCBSZWFkZXJbIlNvbWVib2R5IGhhcyB0byByZWFkIGl0IGFuZCBkZWNpZGUiXQogIFNoYXBlIC0tPnwieWVzInwgV2hpY2h7IldoaWNoIG91dGNvbWU/In0KICBXaGljaCAtLT58ImdhdmUgdXAsIHdpdGggYSByZWFzb24ifCBQZXJzb25bIlJvdXRlZCB0byBhIHBlcnNvbiJdCiAgV2hpY2ggLS0+fCJkb25lInwgV29ybGR7IkRvZXMgdGhlIHRoaW5nIGl0IG1hZGUgZXhpc3Q/In0KICBXb3JsZCAtLT58Im5vInwgUGVyc29uCiAgV29ybGQgLS0+fCJ5ZXMifCBEb25lWyJEb25lIl0=" alt="Diagram" /></p>
<p><em>Ask the session whether the outcome is there. Do not ask the run whether it finished.</em></p>
<p><strong>The cost:</strong> a closed set of endings the harness enforces, and a check at the end of each job that asks whether the work exists.</p>
<h2>3. Permissions built for nobody watching</h2>
<p>When a person is at the keyboard, permission is a question asked at the exact moment it matters, by something that knows precisely what is about to happen. Take the person away and that question has no answer. The session does not fail, which would be fine and expected. Instead it just waits.</p>
<p>There are two ways to be wrong about this step. Guess too broad and you have quietly handed back the isolation you paid for in the first section. Guess too narrow and the run stops, or worse, carries on and delivers nothing, which is the pipeline again: a grant made in advance, too small, and not one part of the system able to say so out loud.</p>
<p>Roles are the cheapest place I have found to put this guess, because a grant attached to a role is made once for a kind of work rather than once per run, and somebody can read the list and disagree with it. It is still a guess. It is just a reviewable one, and a narrow guess is only safe if the previous section is already built, so that refusal comes back as an outcome you can see rather than as an empty result.</p>
<h2>4. A transcript that survives the run</h2>
<p>There is a difference worth being pedantic about. What a run says about itself is testimony. What the run actually did, the commands, the outputs, the diffs, the refusals, is evidence. You want the evidence, so the process can be scrutinised and challenged.</p>
<p><strong>The cost:</strong> isolation says the box is disposable and should be thrown away the moment the task ends. Evidence says nothing readable inside that box may be lost. So you pay for the wall, and then you pay a second time to lift everything worth reading back over it before it goes.</p>
<h2>5. Room to run more than one job at once</h2>
<p>Running many jobs at once is not just parallelising the flow described up to here. It means creating admissions, claims and guard checks.</p>
<p><strong>Admissions:</strong> something has to decide whether a job starts now, because capacity is finite: model quota, containers, machines, money. With no admission decision the answer is that everything starts at once, and everything starting at once behaves remarkably like nothing starting at all.</p>
<p><strong>Claims:</strong> two workers must not build the same thing, so a piece of work has to be claimed before it is started, the claim has to be visible to every other worker, and it has to be released when the work ends or expire on its own if the worker disappears. Get this wrong and the second worker's output is not merely wasted. It conflicts with the first, and now somebody is reconciling two attempts at one job.</p>
<p><strong>Guard checks:</strong> something watching if the system has stopped moving, checking nothing crashes when work is added or stops flowing.</p>
<p><strong>What running many at once actually needs</strong></p>
<p><img src="https://mermaid.ink/img/Zmxvd2NoYXJ0IExSCiAgUXVldWVbIldvcmsgd2FpdGluZyJdIC0tPiBBZG1pdHsiUm9vbSB0byBzdGFydCBvbmUgbm93PyJ9CiAgQWRtaXQgLS0+fCJubywgYXQgY2FwYWNpdHkifCBRdWV1ZQogIEFkbWl0IC0tPnwieWVzInwgQ2xhaW1bIkNsYWltZWQgYnkgZXhhY3RseSBvbmUgd29ya2VyIl0KICBDbGFpbSAtLT4gUnVuWyJSdW5uaW5nIGluIGl0cyBvd24gYm94Il0KICBSdW4gLS0+fCJlbmRzLCBvciB0aGUgY2xhaW0gZXhwaXJlcyJ8IFF1ZXVlCiAgU3RhbGxbIldvcmsgd2FpdGluZyBhbmQgbm90aGluZyBydW5uaW5nPyJdIC0tPnwiYXNrZWQgb24gYSBzY2hlZHVsZSJ8IEFsYXJtWyJXYWtlIHNvbWVib2R5Il0=" alt="Diagram" /></p>
<p><em>Nothing throws an error when the queue stops draining. You have to go and ask.</em></p>
<p>Guard checks is the cheapest thing on this entire list and the easiest to skip. Work waiting and nothing running is one query away, it needs no new component. Write the query, run it on a schedule, treat the answer as an alarm.</p>
<p>Parallel work is also where the harness starts carrying weight. With one run, watched, you can read the transcript yourself and tell if work has been done by looking at it. With several, unwatched jobs, judgement can drift and the harness can become the bottleneck if it is not set up properly.</p>
<h2>What I have not solved</h2>
<p>The initial refinement: how can we tell if the system understands contextually what we have in mind? This is a solved problem for narrow scope and small tasks, but when the work becomes broader, quality takes a hit and the jobs start to drift. My next attempt would be to introduce cadence and work review in agile fashion, so the system is always honest and actual value is delivered and measured.</p>
<p>This is the second post in what I want to make a short series on building an agentic harness. Let me know if you have any more features that you think would be beneficial in a harness. Happy coding.</p>
<hr />
<p><em>Originally published on <a href="https://juliantellez.com/blog/what-a-harness-costs-to-build">juliantellez.com</a>.</em></p>
<p><strong>Building with AI and want it to hold up in production?</strong> Join <a href="https://www.skool.com/vibe-coding-to-production-3790">Vibe Coding to Production</a>, a free community where I share the spec-driven workflow I use with Claude Code and Cursor, plus free courses.</p>
]]></content:encoded></item><item><title><![CDATA[Agentic Harness: Why an Agent Needs a Harness]]></title><description><![CDATA[Why a model that can write anything still needs a harness to read files, run commands and stop safely. Part 1 of a series on agentic harnesses.]]></description><link>https://juliantellez.hashnode.dev/agentic-harness-why-an-agent-needs-a-harness</link><guid isPermaLink="true">https://juliantellez.hashnode.dev/agentic-harness-why-an-agent-needs-a-harness</guid><category><![CDATA[AI]]></category><category><![CDATA[ai agents]]></category><category><![CDATA[claude-code]]></category><category><![CDATA[software architecture]]></category><dc:creator><![CDATA[Julian Tellez]]></dc:creator><pubDate>Fri, 23 Oct 2026 22:14:20 GMT</pubDate><content:encoded><![CDATA[<p><img src="https://juliantellez.com/blog-media/why-an-agent-needs-a-harness/cover.png" alt="Five agentic harness architectures compared on a bar track, with sandboxed isolation highlighted as the tallest bar" /></p>
<p>A model that can write anything still cannot open a file, run a command, or know when to stop pretending it did. This is the first post in a series on what actually turns a language model into something you can safely put to work.</p>
<p>I once asked a model to fix a failing test, expecting code back. What came back instead was a paragraph explaining, correctly, what was wrong. Nothing else happened. The bug was still there. The model thought it had done its job. At this point in time nobody had built the part that takes a correct paragraph and turns it into a saved file, a passing test, a done thing, a valuable output.</p>
<p>That gap has a name now. People call it an agentic harness: whatever sits between a model and the actual world, turning what the model says into things that happen, and reporting back what happened so the model can decide what to say next.</p>
<p>A language model on its own is a very good guesser of the next right word. It has no hands. It cannot open a file unless something opens the file for it, run a command unless something runs the command, or remember what it did five minutes ago unless something wrote that down and hands it back. Everything that makes an agent look like it is doing work, rather than describing work, is the harness. Not the model.</p>
<p>This has only become a real design problem recently. A model that just answers questions does not need much of a harness: a text box and a network connection will do. A model asked to write code, run it, read the failure, and try again needs somewhere to write the code, something to run it, a way to read the failure back, and a decision about how many tries it gets before a person is called in. Multiply that by more than one task running at once, or a task running while nobody is watching, and the harness stops being plumbing. It becomes the product.</p>
<p>There is not one shape this takes. So far there are at least five patterns at this point in time, and each is a different bet about who is watching and how much can go wrong before someone notices.</p>
<p>The simplest is the one most people already have: a terminal, an editor open beside it, an agent that reads and writes files and runs commands with your own permissions. It is the fastest thing to build and the fastest thing to use, because nothing stands between the model deciding to do something and the something happening. That speed is also the whole risk. The agent can do anything you can do, including the things you did not mean to ask for, and there is no wall between one task and the next. A mistake in one session has a blast radius in your actual home directory.</p>
<p><strong>Local process</strong></p>
<p><img src="https://mermaid.ink/img/Zmxvd2NoYXJ0IExSCiAgc3ViZ3JhcGggbWFjaGluZVsiWW91ciBtYWNoaW5lLCBvbmUgc2hhcmVkIGVudmlyb25tZW50Il0KICAgIEFnZW50WyJBZ2VudCJdCiAgICBGaWxlc1siWW91ciBmaWxlcyJdCiAgICBTaGVsbFsiWW91ciBzaGVsbCJdCiAgZW5kCiAgWW91WyJZb3UiXSAtLT58InByb21wdHMifCBBZ2VudAogIEFnZW50IC0tPnwicmVhZHMsIHdyaXRlcyJ8IEZpbGVzCiAgQWdlbnQgLS0+fCJydW5zIGNvbW1hbmRzIGFzIHlvdSJ8IFNoZWxs" alt="Diagram" /></p>
<p><em>Fast because nothing stands in the way. Risky for the same reason.</em></p>
<p>Put a wall up and you get the next shape: each task, or each session, gets its own container or virtual machine, thrown away when the task ends. A mistake stays inside the box it happened in. You can run many of these at once, including ones nobody is watching in real time, because the damage any single one can do is small on purpose. The cost is that somebody has to build and run the wall. A fresh environment takes time the local version never spends. Credentials have to be handed into a place you cannot see directly. And the box needs its own way of telling you what happened inside it, because you are no longer sitting next to it while it works.</p>
<p><strong>Sandboxed containers</strong></p>
<p><img src="https://mermaid.ink/img/Zmxvd2NoYXJ0IExSCiAgWW91WyJZb3UiXSAtLT4gSGFybmVzc1siSGFybmVzcyJdCiAgSGFybmVzcyAtLT4gQm94MVsiQ29udGFpbmVyOiB0YXNrIG9uZSJdCiAgSGFybmVzcyAtLT4gQm94MlsiQ29udGFpbmVyOiB0YXNrIHR3byJdCiAgQm94MSAtLT58InRocm93biBhd2F5IHdoZW4gaXQgZW5kcyJ8IEdvbmUxWyJHb25lIl0KICBCb3gyIC0tPnwidGhyb3duIGF3YXkgd2hlbiBpdCBlbmRzInwgR29uZTJbIkdvbmUiXQ==" alt="Diagram" /></p>
<p><em>A mistake stays inside the box it happened in.</em></p>
<p>A third shape hands the wall to somebody else entirely: a managed platform that runs the sandbox, the model calls, and the wiring between them, and gives you an interface to start a task and collect a result. You stop operating infrastructure, which is real time back. You also stop being able to see very much of what happened in between, which matters the first time a result comes back wrong and you want to know why.</p>
<p><strong>Managed platform</strong></p>
<p><img src="https://mermaid.ink/img/Zmxvd2NoYXJ0IExSCiAgWW91WyJZb3UiXSAtLT58InN0YXJ0IGEgdGFzayJ8IEFQSVsiUGxhdGZvcm0gaW50ZXJmYWNlIl0KICBzdWJncmFwaCBvcGFxdWVbIlJ1biBieSB0aGUgcGxhdGZvcm0sIG5vdCB2aXNpYmxlIHRvIHlvdSJdCiAgICBTYW5kYm94WyJTYW5kYm94Il0KICAgIENhbGxzWyJNb2RlbCBjYWxscyJdCiAgICBTYW5kYm94IC0tPiBDYWxscyAtLT4gU2FuZGJveAogIGVuZAogIEFQSSAtLT4gb3BhcXVlCiAgb3BhcXVlIC0tPnwicmVzdWx0IG9ubHkifCBZb3U=" alt="Diagram" /></p>
<p><em>You get a result back. You do not get to see how it got there.</em></p>
<p>A fourth shape is declarative: a graph of steps, some of them a model call and some of them a tool call, wired together ahead of time so the same automation runs the same way every time it fires. This is the shape that scales best for something you want running on a schedule or in response to an event, because nobody has to sit and prompt it each time. It is also the shape most likely to mark a step successful because nothing threw an error, not because anything was actually achieved, unless somebody deliberately built a check that looks past the model's own account of how it went.</p>
<p><strong>Workflow graph</strong></p>
<p><img src="https://mermaid.ink/img/Zmxvd2NoYXJ0IExSCiAgVHJpZ2dlclsiU2NoZWR1bGUgb3IgZXZlbnQiXSAtLT4gU3RlcDFbIlN0ZXA6IG1vZGVsIGNhbGwiXQogIFN0ZXAxIC0tPiBTdGVwMlsiU3RlcDogdG9vbCBjYWxsIl0KICBTdGVwMiAtLT4gQ2hlY2t7IkRpZCBpdCB0aHJvdyBhbiBlcnJvcj8ifQogIENoZWNrIC0tPnwibm8ifCBEb25lWyJNYXJrZWQgZG9uZSJdCiAgQ2hlY2sgLS0+fCJ5ZXMifCBGbGFnWyJGbGFnZ2VkIGZvciBhIHBlcnNvbiJd" alt="Diagram" /></p>
<p><em>Nothing threw an error is not the same as something got done.</em></p>
<p>A fifth shape lives inside the tool you already write code in, a panel next to the file you are editing, one person and one task at a time. Nothing beats it for the tight loop of asking, watching, correcting. It was never built for the version of this where the task runs after you have gone home.</p>
<p><strong>Editor embedded</strong></p>
<p><img src="https://mermaid.ink/img/Zmxvd2NoYXJ0IExSCiAgWW91WyJZb3UsIHdhdGNoaW5nIl0gPC0tPiBQYW5lbFsiQWdlbnQgcGFuZWwiXQogIFBhbmVsIDwtLT4gRmlsZVsiVGhlIGZpbGUgb3BlbiBiZXNpZGUgaXQiXQ==" alt="Diagram" /></p>
<p><em>Built for someone watching. Not built for after you have gone home.</em></p>
<p>None of the five are wrong. Each is a reasonable answer to a different question about who is around when something breaks. What I want out of any of them, after watching each fail in its own particular way, is a shorter list than you would think.</p>
<p>Real isolation, first. A mistake in one task should not be able to reach another task, or reach you. If a harness cannot say precisely what a given task is unable to touch, it does not have isolation. It has hope.</p>
<p>A visible difference between done and gave up, second, and this is the one I would not trade for any of the others.</p>
<blockquote>
<p>The most dangerous failure is not a crash. It is a run that finishes clean and did nothing.</p>
</blockquote>
<p>A model would rather answer than admit it cannot see what it needs to see, and a harness that lets a plausible non answer read as success is worse than one that has no safety features at all, because it hides exactly the runs that needed a person to look.</p>
<p>Permissions built for nobody watching, third. Anything that only works when a person is at the keyboard to click approve will eventually run unattended and fail there, silently, by design. A harness meant for background work needs a way to say yes in advance, scoped tightly, rather than a wall only a human hand can get past.</p>
<p>Credentials that reach the sandbox, not just the settings screen, fourth. Something that shows as set in a list but never actually arrives where the work happens is worse than an honest error, because it looks like it should have worked.</p>
<p>A transcript that survives the run, fifth. Whatever the model actually said and did needs to stay readable after the task finishes, not filed somewhere only a direct query against a database can reach. The summary is not the evidence. The transcript is.</p>
<p>And room to run more than one thing at once without losing any of the above, last. The entire point of handing work to an agent is not doing it yourself while it runs. A harness that only works one task at a time, watched, has not saved anyone anything.</p>
<p>This is the first post in what I want to make a short series on building an agentic harness. Let me know if you have any more features that you think would be beneficial in a harness. Happy coding.</p>
<hr />
<p><em>Originally published on <a href="https://juliantellez.com/blog/why-an-agent-needs-a-harness">juliantellez.com</a>.</em></p>
<p><strong>Building with AI and want it to hold up in production?</strong> Join <a href="https://www.skool.com/vibe-coding-to-production-3790">Vibe Coding to Production</a>, a free community where I share the spec-driven workflow I use with Claude Code and Cursor, plus free courses.</p>
]]></content:encoded></item><item><title><![CDATA[Agentic Harness: What Spec Driven Development Actually Is]]></title><description><![CDATA[What spec-driven development is, where it came from, and how Spec Kit, Kiro, OpenSpec, BMAD and Tessl compare for AI coding agents.]]></description><link>https://juliantellez.hashnode.dev/agentic-harness-what-spec-driven-development-actually-is</link><guid isPermaLink="true">https://juliantellez.hashnode.dev/agentic-harness-what-spec-driven-development-actually-is</guid><category><![CDATA[AI]]></category><category><![CDATA[ai agents]]></category><category><![CDATA[claude-code]]></category><category><![CDATA[software architecture]]></category><dc:creator><![CDATA[Julian Tellez]]></dc:creator><pubDate>Fri, 23 Oct 2026 22:12:52 GMT</pubDate><content:encoded><![CDATA[<p><img src="https://juliantellez.com/blog-media/what-spec-driven-development-is/cover.png" alt="A pink scribble labelled prompt points at spec.md, which flows through plan.md and two tasks into checked code labelled intent kept" /></p>
<p>At the AI Engineer World's Fair in 2025, Sean Grove from OpenAI described how most of us work with coding agents: "We keep the generated code, and we delete the prompt." He compared it to shredding the source code and version controlling the binary.</p>
<p>A harness can check a great deal about an agent's output: whether it stayed in its box, whether it finished, what it did. None of that tells you whether the agent understood what was asked. When the work is small, understanding is cheap to confirm by reading the result. When the work gets broad, the jobs drift, and every check passes on the wrong thing.</p>
<p>Spec driven development is the industry's answer to that problem.</p>
<h2>What it is</h2>
<p>Spec driven development means you write down what you want before the agent writes any code, and that document becomes the source of truth for both of you. The agent plans from it, breaks it into tasks from it, and is checked against it.</p>
<p>The definition is still moving. Thoughtworks put it on their Technology Radar in November 2025 in the Assess ring and said plainly that "the term's definition is still evolving". Birgitta Böckeler, a distinguished engineer at Thoughtworks, wrote the most useful map of it so far on Martin Fowler's site, and she splits it into three levels:</p>
<ol>
<li><strong>Spec first.</strong> You write a careful spec, the agent builds from it, and the spec is done with when the task is.</li>
<li><strong>Spec anchored.</strong> The spec is kept after the task and evolved alongside the feature, so the next change starts from it.</li>
<li><strong>Spec as source.</strong> The spec is the thing you maintain. Humans edit only the spec, and the code is generated from it.</li>
</ol>
<p>Almost every tool today does the first. Few commit to the second. Only one has seriously tried the third.</p>
<p><strong>Three levels of commitment</strong></p>
<p><img src="https://mermaid.ink/img/Zmxvd2NoYXJ0IExSCiAgRmlyc3RbIlNwZWMgZmlyc3Q6IHdyaXR0ZW4gZm9yIG9uZSB0YXNrLCB0aGVuIGRpc2NhcmRlZCJdIC0tPiBBbmNob3JlZFsiU3BlYyBhbmNob3JlZDoga2VwdCBhbmQgZXZvbHZlZCB3aXRoIHRoZSBmZWF0dXJlIl0KICBBbmNob3JlZCAtLT4gU291cmNlWyJTcGVjIGFzIHNvdXJjZTogdGhlIG9ubHkgdGhpbmcgYSBodW1hbiBlZGl0cyJd" alt="Diagram" /></p>
<p><em>Each level asks you to trust the spec more and the code less.</em></p>
<h2>Why it is needed</h2>
<p>Agents are good at writing code and bad at guessing intent. GitHub's announcement of Spec Kit says this directly: the problem is not the agent's coding ability, it is the approach. We treat agents like search engines when they behave like "literal minded pair programmers". Give one an ambiguous sentence and it will pick an interpretation, build it well, and explain convincingly why it is right.</p>
<p>Vibe coding works for a prototype. It fails on anything that has to live in an existing codebase, match an existing architecture, or still make sense in six months. The prompt that carried the intent is gone, the code is all that is left, and code is a lossy copy of intent. It tells you what the system does. It rarely tells you why, or what it was never supposed to do.</p>
<p>A spec is the attempt to keep the intent. Written down, reviewed by a person before any code exists, and kept somewhere the next agent can read it.</p>
<h2>Where it comes from</h2>
<p>None of this is new. What is new is who reads the spec.</p>
<ol>
<li><strong>Design by contract</strong>, from Bertrand Meyer and the Eiffel language in the 1980s. Preconditions, postconditions and invariants written next to the code, checked at run time.</li>
<li><strong>Model driven architecture</strong>, from the Object Management Group in 2001. Draw a precise model, generate the code from it. It promised exactly what spec as source promises today, and it mostly did not stick, because the models were rigid and the generated code was painful to live with.</li>
<li><strong>Behaviour driven development</strong>, from Dan North in 2006. Scenarios written as "given, when, then" in plain language that a test runner can execute. Most spec tools today write their acceptance criteria in this shape.</li>
<li><strong>The Easy Approach to Requirements Syntax</strong>, usually called EARS, from Alistair Mavin and colleagues at Rolls-Royce in 2009. They built it while analysing airworthiness rules for a jet engine control system: a handful of sentence templates that remove most of the ambiguity from a requirement. Kiro uses it today.</li>
</ol>
<p>Two EARS requirements, written for this post:</p>
<pre><code class="language-text">WHEN a user submits the signup form with an email that is already registered,
THE SYSTEM SHALL reject the request and show "this email already has an account".

IF the email service does not respond within 5 seconds,
THEN THE SYSTEM SHALL queue the welcome email and complete the signup.
</code></pre>
<p>Each line is one trigger and one behaviour, and each one is a test waiting to be written.</p>
<p>The difference this time is that the reader of the spec is a model. Model driven architecture needed a strict, parseable model because the generator was a compiler. An agent reads plain language, which removes the rigidity. It also brings back the uncertainty, because the same spec can produce different code on two runs.</p>
<h2>Who is proposing it</h2>
<ol>
<li><strong>GitHub</strong>, through Spec Kit, announced by Den Delimarsky in September 2025.</li>
<li><strong>Amazon Web Services</strong>, through Kiro, an editor built around specs that launched in July 2025.</li>
<li><strong>OpenAI</strong>, less as a product and more as a position. Grove's talk argued that "the new scarce skill is writing specifications that fully capture the intent and values", and pointed at OpenAI's own Model Spec, a versioned set of markdown files where every clause has an id and a set of prompts that act as its tests.</li>
<li><strong>Tessl</strong>, founded by Guy Podjarny, which made the boldest bet of all: specs, not code, as the primary artifact.</li>
<li><strong>The open source community</strong>, through OpenSpec and the BMAD Method, which between them have over a hundred thousand stars on GitHub.</li>
</ol>
<p>And on the sceptical side, the same people who mapped it. Thoughtworks and Böckeler are the clearest critics, covered further down.</p>
<h2>How people use it</h2>
<p>Under the different names, almost every tool runs the same loop.</p>
<p><strong>The loop most spec tools share</strong></p>
<p><img src="https://mermaid.ink/img/Zmxvd2NoYXJ0IExSCiAgUnVsZXNbIlByb2plY3QgcnVsZXM6IHRoZSBjb25zdGl0dXRpb24iXSAtLT4gU3BlY1siU3BlYzogd2hhdCBhbmQgd2h5Il0KICBTcGVjIC0tPiBSZXZpZXcxeyJBIHBlcnNvbiBhcHByb3ZlcyBpdD8ifQogIFJldmlldzEgLS0+fCJubyJ8IFNwZWMKICBSZXZpZXcxIC0tPnwieWVzInwgUGxhblsiUGxhbjogc3RhY2ssIGRlc2lnbiwgY29uc3RyYWludHMiXQogIFBsYW4gLS0+IFRhc2tzWyJUYXNrczogc21hbGwsIG9yZGVyZWQsIHRlc3RhYmxlIl0KICBUYXNrcyAtLT4gQnVpbGRbIkFnZW50IGJ1aWxkcyBvbmUgdGFzayJdCiAgQnVpbGQgLS0+IENoZWNreyJNYXRjaGVzIHRoZSBzcGVjPyJ9CiAgQ2hlY2sgLS0+fCJubyJ8IEJ1aWxkCiAgQ2hlY2sgLS0+fCJ5ZXMifCBOZXh0WyJOZXh0IHRhc2siXQ==" alt="Diagram" /></p>
<p><em>The approval happens on the spec, before any code exists.</em></p>
<ol>
<li><strong>Set the rules once.</strong> A file of things that are always true for the project: the language, the testing rules, what is never allowed. Spec Kit calls it a constitution.</li>
<li><strong>Write the spec.</strong> What you are building and why, as user stories with acceptance criteria. Usually the agent drafts it from a short description and you correct it.</li>
<li><strong>Plan.</strong> You give the technical direction. The agent writes the design: data model, interfaces, how it fits what exists.</li>
<li><strong>Break it into tasks.</strong> Small, ordered, each one checkable on its own.</li>
<li><strong>Build one task at a time</strong>, and review a small change rather than a large one.</li>
</ol>
<p>The review moves. Instead of reading a thousand lines of generated code and asking whether it is right, you read a page of requirements and ask whether it is what you meant. That is a cheaper question to answer, and a much cheaper one to get wrong.</p>
<h2>The five approaches worth knowing</h2>
<p>Ranked by how much they are used and how often they appear in independent comparisons. Star counts were read on 22 October 2026.</p>
<h3>1. Spec Kit, from GitHub</h3>
<p><a href="https://github.com/github/spec-kit">github.com/github/spec-kit</a>, about 142,000 stars, MIT licensed.</p>
<p>The reference implementation. A command line tool installs templates and slash commands into whatever agent you use: Copilot, Claude Code, Gemini and others. The flow is constitution, specify, plan, tasks, implement, with clarify and analyse steps between them to catch gaps and contradictions. It is the most thorough of the five and the heaviest. The project now describes its steps as "independent entry points, not mandatory phases", a softer position than its first release.</p>
<p>Best for: new features of real size where you want a documented trail.</p>
<h3>2. Kiro, from Amazon Web Services</h3>
<p><a href="https://kiro.dev">kiro.dev</a>, an editor rather than a toolkit.</p>
<p>Every spec is three files in <code>.kiro/specs/</code>: <code>requirements.md</code>, <code>design.md</code> and <code>tasks.md</code>. Requirements are written in EARS. When you run the tasks, Kiro builds a dependency graph and runs independent tasks in parallel waves. It has separate flows for features and for bug fixes, and a quick spec mode that skips the approval gates for work you already understand.</p>
<p>Best for: teams happy to move into a new editor in exchange for the most integrated experience.</p>
<h3>3. OpenSpec</h3>
<p><a href="https://github.com/Fission-AI/OpenSpec">github.com/Fission-AI/OpenSpec</a>, about 72,700 stars, MIT licensed.</p>
<p>Built around changes rather than whole systems. Each change gets a folder with a proposal, a design, a task list and spec deltas that describe what the change adds to the system. When the change ships, it is archived and its deltas are merged into the living specs in <code>openspec/specs/</code>. That makes it the tool that comes closest to spec anchored without asking you to describe your entire system first. It claims support for more than thirty assistants.</p>
<p>Best for: existing codebases, which is most codebases.</p>
<h3>4. BMAD Method</h3>
<p><a href="https://github.com/bmad-code-org/BMAD-METHOD">github.com/bmad-code-org/BMAD-METHOD</a>, about 54,400 stars.</p>
<p>The Breakthrough Method for Agile AI Driven Development. Where the others are workflows, BMAD is a team: separate agents carrying product, architecture, user experience, development and testing roles, each handing structured documents to the next. Small changes go straight to build. Larger ones go through the full planning cycle first.</p>
<p>Best for: people who want the agile ceremony modelled explicitly, and are willing to pay for it in process.</p>
<h3>5. Tessl</h3>
<p><a href="https://tessl.io">tessl.io</a>.</p>
<p>The only serious attempt at spec as source. In its framework, each spec mapped to one code file, and the generated file carried a header saying "GENERATED FROM SPEC - DO NOT EDIT". Thoughtworks described it as the approach where "the specification itself becomes the maintained artifact, rather than the code". It was in private beta through 2025. Today the home page describes Tessl as "the agent enablement platform", built around reusable skills and context rather than specs as source. Tessl has not published a reason for the change, but the change itself is a data point about how hard the third level is.</p>
<p>Best for: watching. It is where the idea goes if it works.</p>
<h2>What the critics say</h2>
<p>Böckeler tried the tools on real work.</p>
<p>Kiro turned a small bug into four user stories with sixteen acceptance criteria. She called it "like using a sledgehammer to crack a nut". Spec Kit produced so many repetitive markdown files that she concluded "I'd rather review code than all these markdown files". Agents still ignored the spec in places, and in one run regenerated classes that the research step had already documented as existing. A detailed spec gave a feeling of control the agent did not honour.</p>
<p>Thoughtworks went further and asked whether the field is relearning the bitter lesson: that handcrafting detailed rules for AI "ultimately doesn't scale". And Böckeler's sharpest point is the history one. Model driven development failed on rigidity. Language models remove the rigidity and add uncertainty. Spec as source risks getting both problems at once.</p>
<p><strong>A spec only helps if somebody reads it, and only protects you if something checks the code against it.</strong></p>
<p>Most of the criticism is really about those two failures. Specs too long to review, and specs that nothing enforces.</p>
<h2>What to do with it right now</h2>
<ol>
<li><strong>Use it for features, not for fixes.</strong> A bug with a clear reproduction does not need a requirements document. A feature that touches three parts of the system does. If you cannot say what a spec would catch, skip it.</li>
<li><strong>Keep the spec short enough to review in one sitting.</strong> If you would not read it, neither will anyone else, and an unread spec is just a longer prompt. Böckeler's warning about "spec" becoming a synonym for "detailed prompt" is the real risk here.</li>
<li><strong>Write every acceptance criterion so it can fail.</strong> EARS or "given, when, then", it does not matter which. "The signup works" cannot fail. "When the email is already registered, the system rejects the request" can, and it is a test before it is code.</li>
<li><strong>Make the spec executable.</strong> The spec is a claim. The test is the evidence. Turn each criterion into a test before the implementation exists, and let the test runner decide, not the agent.</li>
<li><strong>Aim for spec anchored, not spec as source.</strong> Keep the spec and update it with each change, the way OpenSpec does with its deltas. Do not try to stop editing code. Tessl's change of direction shows how hard that is.</li>
<li><strong>Start with the lightest tool that fits.</strong> For an existing codebase, OpenSpec. For a new product with real scope, Spec Kit. If you already live in an editor you like, a folder of markdown files and a clear habit get you most of the way.</li>
</ol>
<p>The open problem is drift. A spec written today and code changed tomorrow will disagree soon enough. Some of these tools have verify or analyse steps, but a <a href="https://arxiv.org/abs/2606.27045">June 2026 paper</a> still names specs that silently fall out of sync with the code as a failure the existing approaches do not fully solve. Until something does, a spec can report success on work that no longer matches what anybody meant.</p>
<p>This is the third post in a short series on building an agentic harness. If you are using any of these tools on real work, I would genuinely like to hear where they held up and where they did not. Happy coding.</p>
<h2>References</h2>
<ol>
<li>Birgitta Böckeler, <a href="https://www.martinfowler.com/articles/exploring-gen-ai/sdd-3-tools.html">Understanding Spec Driven Development: Kiro, spec-kit, and Tessl</a>, martinfowler.com, 15 October 2025.</li>
<li>Thoughtworks Technology Radar, <a href="https://www.thoughtworks.com/radar/techniques/spec-driven-development">Spec driven development</a>, November 2025.</li>
<li>Den Delimarsky, <a href="https://github.blog/ai-and-ml/generative-ai/spec-driven-development-with-ai-get-started-with-a-new-open-source-toolkit/">Spec driven development with AI: get started with a new open source toolkit</a>, GitHub Blog, 2 September 2025.</li>
<li>Sean Grove, <a href="https://ai.engineer/talks/specifications-are-the-new-code">The New Code</a>, AI Engineer World's Fair 2025.</li>
<li><a href="https://github.com/github/spec-kit">GitHub Spec Kit</a>.</li>
<li><a href="https://kiro.dev/docs/specs/">Kiro specs documentation</a>.</li>
<li><a href="https://github.com/Fission-AI/OpenSpec">OpenSpec</a>.</li>
<li><a href="https://github.com/bmad-code-org/BMAD-METHOD">BMAD Method</a>.</li>
<li><a href="https://tessl.io">Tessl</a>.</li>
<li><a href="https://en.wikipedia.org/wiki/Easy_Approach_to_Requirements_Syntax">Easy Approach to Requirements Syntax</a>, Alistair Mavin and colleagues, IEEE Requirements Engineering Conference, 2009.</li>
</ol>
<h2>Join the community</h2>
<p>I run a free community for people moving from vibe coding to production: spec driven development with Claude Code, Cursor and agents, and the habits that make the output something you can ship. If this post was useful, the conversation carries on there.</p>
<p><a href="https://www.skool.com/vibe-coding-to-production-3790">Join Vibe Coding to Production on Skool</a></p>
<hr />
<p><em>Originally published on <a href="https://juliantellez.com/blog/what-spec-driven-development-is">juliantellez.com</a>.</em></p>
<p><strong>Building with AI and want it to hold up in production?</strong> Join <a href="https://www.skool.com/vibe-coding-to-production-3790">Vibe Coding to Production</a>, a free community where I share the spec-driven workflow I use with Claude Code and Cursor, plus free courses.</p>
]]></content:encoded></item></channel></rss>