<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Takafumi Endo</title>
    <description>The latest articles on DEV Community by Takafumi Endo (@gyu07).</description>
    <link>https://dev.to/gyu07</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2326576%2F5d3119ba-b98b-49fa-95fe-a0406b33df70.png</url>
      <title>DEV Community: Takafumi Endo</title>
      <link>https://dev.to/gyu07</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/gyu07"/>
    <language>en</language>
    <item>
      <title>Why Athena/Iceberg Tends to Make Code the Spec</title>
      <dc:creator>Takafumi Endo</dc:creator>
      <pubDate>Tue, 21 Jul 2026 03:45:35 +0000</pubDate>
      <link>https://dev.to/gyu07/why-athenaiceberg-tends-to-make-code-the-spec-oon</link>
      <guid>https://dev.to/gyu07/why-athenaiceberg-tends-to-make-code-the-spec-oon</guid>
      <description>&lt;p&gt;Every time this comes up, someone credits the same setup: Athena on Iceberg is where "the code is the spec" — where you open Git and read the whole system, catalog to transforms to schema, without logging into anything. In my experience they're not wrong.&lt;/p&gt;

&lt;p&gt;What I want to argue is that they're right for mostly the wrong reason.&lt;/p&gt;

&lt;p&gt;The reason people reach for is the engine — serverless, open format, nothing to provision. But the thing that actually keeps code as the spec, when it does, is something you could have applied to almost any engine. And the thing that breaks it, when it breaks, has nothing to do with Iceberg at all.&lt;/p&gt;

&lt;p&gt;So here's the split I've landed on, for now: whether "code is the spec" holds is about &lt;strong&gt;90% discipline and 10% engine&lt;/strong&gt;. Athena/Iceberg earns that 10% honestly — but 10% is all it earns, and I keep watching people mistake it for the whole thing. (Just where my own tinkering has led — not advice.)&lt;/p&gt;

&lt;p&gt;I should say up front that I'm still in the middle of this. What the &lt;em&gt;best&lt;/em&gt; declarative setup for an agent actually looks like — how you turn a system into a spec it can read and act on without guessing — is something I'm actively testing, not something I've settled. Read what follows as a working idea at a particular moment, written down partly so I can find out where it's wrong.&lt;/p&gt;

&lt;p&gt;To see where the 10% actually lives, it helps to notice that a stateful system always keeps two copies of itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  The spec has two copies
&lt;/h2&gt;

&lt;p&gt;One copy is &lt;strong&gt;declared&lt;/strong&gt;: the code you wrote, the schema you committed, the transforms in dbt, the catalog in Terraform. The other is &lt;strong&gt;realized&lt;/strong&gt;: the state the engine accumulates while running — statistics, physical layout, caches, maintenance history, tuning knobs someone set at 2am.&lt;/p&gt;

&lt;p&gt;"Code is the spec" is really a claim about the distance between those two. When the declared copy explains almost everything about the realized one, you can reason about the system by reading Git. When it doesn't, you can't.&lt;/p&gt;

&lt;p&gt;Some of that state is declarable — you can pin a distribution or sort strategy in DDL, dbt, or Terraform. But plenty of it isn't, or drifts away from what you declared: which strategy the engine is &lt;em&gt;actually&lt;/em&gt; using once automatic optimization kicks in, when the last vacuum finished, whether statistics are fresh, which materialized view is stale, whether that partition is really being pruned. To answer those you log in and ask the running system. Now your spec is half in the repo and half in someone's head.&lt;/p&gt;

&lt;p&gt;So the real question isn't "is my code readable." It's: &lt;strong&gt;how much of the truth escaped the code into runtime state I can't see?&lt;/strong&gt; I've started calling that the &lt;em&gt;hidden-state surface area&lt;/em&gt;. Shrink it and the code stays the spec. Let it grow and the code becomes a hopeful sketch.&lt;/p&gt;

&lt;p&gt;I &lt;a href="https://dev.to/gyu07/rigor-compresses-why-ai-agents-need-graphs-not-more-context-5404"&gt;argued a while back that rigor compresses&lt;/a&gt; — the more precisely you can state a fact, the fewer tokens it takes to hand over. This is the same idea pointed at a platform instead of a function: the smaller the hidden-state surface, the shorter and truer the account of the system you can hand to anyone, human or agent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this is mostly discipline
&lt;/h2&gt;

&lt;p&gt;Here's the part I want to be honest about first, because it's the 90%: the engine doesn't make your system legible. &lt;strong&gt;Declarative rigor does.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A catalog expressed as data contracts, transforms as a single dbt DAG, infra as Terraform — that's what makes design intent followable in code. And it's almost entirely engine-agnostic. You can point all of it at Redshift and get something a newcomer can read top to bottom. You can also build an Iceberg-on-Athena setup that's an undocumented swamp of hand-run &lt;code&gt;ALTER&lt;/code&gt;s and orphaned partitions nobody dares touch.&lt;/p&gt;

&lt;p&gt;I've done both. The readable one wasn't readable because of the storage format. It was readable because the rules were declared, in one place, and enforced. "Iceberg, therefore legible" is a claim I'd push back on if someone else made it — so I should hold myself to the same standard.&lt;/p&gt;

&lt;p&gt;If you do nothing else, do this part. It's where the leverage is.&lt;/p&gt;

&lt;h2&gt;
  
  
  But the engine sets the difficulty
&lt;/h2&gt;

&lt;p&gt;Now the 10% — small, but not nothing. Given equal discipline, engines differ in how much state they &lt;em&gt;insist&lt;/em&gt; on keeping inside themselves versus how much they externalize where you can see it. That doesn't decide whether you &lt;em&gt;can&lt;/em&gt; make code the spec; it decides how much discipline it costs to keep it that way.&lt;/p&gt;

&lt;p&gt;Athena on Iceberg externalizes an unusual amount. The stuff that's normally locked inside a proprietary engine — snapshot history, the manifest of what files exist, the current schema, the partition spec — lives as open metadata that isn't confined to one vendor's internal system tables. You can read it through standard metadata tables (&lt;code&gt;$snapshots&lt;/code&gt;, &lt;code&gt;$manifests&lt;/code&gt;, &lt;code&gt;$history&lt;/code&gt;), through the catalog, or straight from object storage. It's still &lt;em&gt;state&lt;/em&gt;, and it still changes at runtime — but the format is open and portable, so the same facts stay reachable from more than one tool.&lt;/p&gt;

&lt;p&gt;Redshift keeps more of that truth behind its own interfaces. Materialized-view freshness, &lt;code&gt;VACUUM&lt;/code&gt;/&lt;code&gt;ANALYZE&lt;/code&gt; history, statistics, workload-management tuning, and — with automatic table optimization or &lt;code&gt;AUTO&lt;/code&gt; distribution — the &lt;em&gt;effective&lt;/em&gt; physical layout, which can shift with data volume regardless of what you declared. It's all observable; you just query the system views for it.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Athena / Iceberg&lt;/th&gt;
&lt;th&gt;Redshift&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Visible in Git / code&lt;/td&gt;
&lt;td&gt;Terraform, dbt, contracts, SQL/YAML&lt;/td&gt;
&lt;td&gt;Terraform, dbt, DDL, contracts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Runtime state lives...&lt;/td&gt;
&lt;td&gt;open metadata (snapshots, manifests, schema, partition spec)&lt;/td&gt;
&lt;td&gt;engine-internal: MV freshness, vacuum/analyze history, stats, effective dist/sort under &lt;code&gt;AUTO&lt;/code&gt;, tuning&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;To inspect it you...&lt;/td&gt;
&lt;td&gt;read open metadata tables, the catalog, or object storage&lt;/td&gt;
&lt;td&gt;query the engine's system views&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Compute infra to manage?&lt;/td&gt;
&lt;td&gt;no dedicated warehouse compute&lt;/td&gt;
&lt;td&gt;provisioned: yes · serverless: mostly abstracted&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hidden-state surface&lt;/td&gt;
&lt;td&gt;typically smaller, more externalized&lt;/td&gt;
&lt;td&gt;typically larger, more engine-bound&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The honest framing: it's not that one is readable and one isn't. Both expose their state — the difference is &lt;em&gt;how&lt;/em&gt;. One hands you facts in an open format that multiple compatible tools can inspect; the other hands them back through its own interfaces. Same level of legibility, less &lt;em&gt;engine-specific&lt;/em&gt; discipline required to preserve it — not necessarily less operational work overall. That's the whole 10%. (This is for the batch-oriented, per-tenant workload I keep describing; a giant, high-churn Iceberg estate grows its own large state surface — manifests, small files, snapshot sprawl, catalog commits, cross-engine compatibility — and the gap narrows.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I actually care: the guesser downstream
&lt;/h2&gt;

&lt;p&gt;Here's where it stops being an aesthetics argument for me.&lt;/p&gt;

&lt;p&gt;The real line isn't S3 versus a database. It's this: &lt;strong&gt;a fact exposed through an inspectable interface the agent can actually reach is &lt;code&gt;verified&lt;/code&gt;; a fact outside its observable context must be &lt;code&gt;estimated&lt;/code&gt;.&lt;/strong&gt; A Redshift &lt;code&gt;VACUUM&lt;/code&gt; history the agent is allowed to query is verified; an Iceberg snapshot it has no catalog access or tooling for is a guess. The engine doesn't decide it — reachable evidence does. What Iceberg tends to do is make that evidence &lt;em&gt;easier to expose safely and portably&lt;/em&gt;: open metadata is simple to grant read access to without also handing over the keys to a live warehouse.&lt;/p&gt;

&lt;p&gt;And the consumer of those facts, more and more, is an agent. These days I'm rarely the one reading the schema first — Claude Code or Codex is, running in the repo, editing a migration, rewriting a transform. What it can &lt;em&gt;see&lt;/em&gt; is the declared copy plus whatever interfaces I've wired it into; everything else it infers. The larger the hidden-state surface — or the harder its evidence is to reach — the more of my system the agent is quietly guessing about, and the more defensive lines I tack onto prompts: "check the effective sort strategy first," "don't assume the MV is fresh," "verify before you migrate." When the fact is reachable, it can state it flatly and move on; when it isn't, the best it can do is hedge — and hedging costs tokens &lt;em&gt;and&lt;/em&gt; trust.&lt;/p&gt;

&lt;p&gt;And this isn't OLAP-only. The agent touches both tiers — on OLTP it edits schema, writes migrations, reasons about a &lt;code&gt;tenant_id&lt;/code&gt; guard; on OLAP it changes a dbt model, reads a partition spec, checks whether a snapshot rolled forward. The same question decides it either way: &lt;em&gt;can it reach the evidence for what it's about to do?&lt;/em&gt; Legibility-for-agents is one property spanning the whole platform, not a warehouse feature.&lt;/p&gt;

&lt;h2&gt;
  
  
  It's the same move I make everywhere else
&lt;/h2&gt;

&lt;p&gt;Once I saw "shrink the hidden-state surface" as the goal, I noticed I was already doing it at the architecture level, not just the table level.&lt;/p&gt;

&lt;p&gt;The platform I work on keeps three kinds of state deliberately apart — durable relational truth in Postgres, hot disposable state in DynamoDB, private local work in per-user SQLite — and the thing I'm most careful about isn't any one store. It's the &lt;em&gt;crossings between them&lt;/em&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OLTP  (write / update)                 OLAP  (read / aggregate)
┌───────────────────────────┐          ┌──────────────────────┐
│ control-db (Postgres)      │          │ Athena / Iceberg      │
│   durable, relational      │          │   S3 Tables (open     │
│ operational_state (Dynamo) │  ==3==&amp;gt;  │   metadata mart)      │
│   hot, TTL'd               │  bridges │ DuckDB (local)        │
│ metadata.db (SQLite)       │          │                       │
│   local, private           │          │                       │
└───────────────────────────┘          └──────────────────────┘
     the only sanctioned OLTP→OLAP paths are three named bridges,
     and Postgres reaches the lake through exactly one VPC Lambda.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The point of narrowing the crossings to a countable few — and letting Postgres reach the analytics lake through a &lt;em&gt;single&lt;/em&gt; path — is the same point as preferring open metadata. Every uncounted pathway is hidden state: a way the system can be true in production in a shape the code never described. Three named bridges I can hold in my head. Twenty accidental ones I cannot, and neither can an agent.&lt;/p&gt;

&lt;p&gt;Same principle at two scales: &lt;strong&gt;make the crossings and the state legible, so the code keeps describing the system.&lt;/strong&gt; That the pattern reappears from a single table up to the whole control-plane-to-lake boundary is, honestly, most of why I trust it — though that's a hunch I hold loosely, not a thesis I'd defend to the death.&lt;/p&gt;

&lt;p&gt;And this is where I've started spending real budget on purpose. Making OLTP and OLAP legible &lt;em&gt;to an agent&lt;/em&gt; — declared schemas, reachable metadata, counted crossings, a system view the agent is actually allowed to query — used to read as tidiness, a nice-to-have for when you had time. With Claude Code and Codex doing more of the actual editing, it's stopped feeling like tidiness and started feeling like the interface I hand the most productive worker on the team. An hour spent making a fact reachable is an hour I don't spend, over and over, writing "be careful, verify this" into prompts — and the payoff compounds every time the agent runs. So I treat agent-legibility as infrastructure worth building, the way you'd invest in tests or types, not polish applied at the end. Whether it fully pencils out depends on how much of your work the agents are really doing; for the amount they're doing in mine, it already has.&lt;/p&gt;

&lt;h2&gt;
  
  
  Objections I'd raise myself
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;"So you're saying Iceberg is more readable than Redshift."&lt;/strong&gt; No — I'm saying it externalizes more state, which lowers the discipline &lt;em&gt;cost&lt;/em&gt; of legibility. A disciplined Redshift shop beats a sloppy Iceberg one every time. The 90% dominates; the engine only sets the exchange rate.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Open metadata isn't zero hidden state."&lt;/strong&gt; Correct. Compaction, small-file cleanup, maintenance jobs — Iceberg has operational state too. It's smaller and observable-from-outside, not absent. If I implied "no hidden state," that's overreach.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Athena/Iceberg has its own tax."&lt;/strong&gt; It does — and it's not about S3 consistency (object storage is strongly consistent now). It's concurrent-write conflicts, catalog commit semantics, small files, compaction, snapshot expiration, and orphan-file cleanup. Athena needs &lt;code&gt;OPTIMIZE&lt;/code&gt;/&lt;code&gt;VACUUM&lt;/code&gt; against Iceberg tables too. Legibility isn't free; I'm trading one set of runtime concerns for another, and I want that trade stated plainly, not smuggled in.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"This is just a warehouse-vs-lakehouse take in disguise."&lt;/strong&gt; Partly. But the axis I care about isn't performance or cost — it's the size of the gap between declared and realized. Two separate arguments; I don't want to lean on the popular one to prop up mine.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where this leaves me
&lt;/h2&gt;

&lt;p&gt;The design goal I keep circling back to is small: &lt;strong&gt;shrink the distance between what the code says and what's true at runtime&lt;/strong&gt; — because that distance is precisely where humans and agents are forced to guess.&lt;/p&gt;

&lt;p&gt;Most of the shrinking is discipline: declare the rules, put them in one place, enforce them — the 90%, and no engine does it for you. The last 10% is picking tools that don't quietly hoard state inside themselves, but hand you their truth as metadata you can read, crossings you can count, facts you can verify. Do the 90% and almost any engine can be the spec; do the 10% too and the code stays the spec with less of you holding it together by hand. That's the honest version of what people reach for when they credit Athena/Iceberg — not that the engine makes code the spec, but that it asks less of you to keep it that way.&lt;/p&gt;

&lt;p&gt;I want to be honest about how provisional this still is. I don't think anyone, me least of all, has settled what declarative management and code-as-spec should look like once the primary reader is an agent rather than a person. I'm running my own small experiments — which facts to externalize, which interfaces to expose, where the &lt;code&gt;verified&lt;/code&gt;/&lt;code&gt;estimated&lt;/code&gt; line should sit — and expect to be revising this for a while. The 90/10 is a heuristic I find useful today, not a law I'd stand behind tomorrow.&lt;/p&gt;

&lt;p&gt;That, for now, is the shape I'm designing toward — a little more on purpose, a little less out of habit, and still figuring out the edges.&lt;/p&gt;

</description>
      <category>dataengineering</category>
      <category>iceberg</category>
      <category>athena</category>
      <category>ai</category>
    </item>
    <item>
      <title>The Agent Runtime Got Heavy: Rethinking Sandboxes with Codex App Server</title>
      <dc:creator>Takafumi Endo</dc:creator>
      <pubDate>Wed, 15 Jul 2026 03:06:18 +0000</pubDate>
      <link>https://dev.to/gyu07/the-agent-runtime-got-heavy-rethinking-sandboxes-with-codex-app-server-5edf</link>
      <guid>https://dev.to/gyu07/the-agent-runtime-got-heavy-rethinking-sandboxes-with-codex-app-server-5edf</guid>
      <description>&lt;p&gt;Once an AI agent moves past being a chat box — reading and writing files, installing dependencies, running tests, watching them fail and retrying — the model alone stops being a product. The agent needs a &lt;em&gt;place to work&lt;/em&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a filesystem&lt;/li&gt;
&lt;li&gt;a shell and process execution&lt;/li&gt;
&lt;li&gt;language runtimes and libraries&lt;/li&gt;
&lt;li&gt;dev tools like Git&lt;/li&gt;
&lt;li&gt;long-running processes&lt;/li&gt;
&lt;li&gt;execution logs and diffs&lt;/li&gt;
&lt;li&gt;permissions and an approval UI&lt;/li&gt;
&lt;li&gt;state that survives between sessions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On the web, the usual way to provide this is to spin up an isolated container or microVM per user. &lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/runtime-sessions.html" rel="noopener noreferrer"&gt;AWS&lt;/a&gt;, &lt;a href="https://developers.cloudflare.com/sandbox/" rel="noopener noreferrer"&gt;Cloudflare&lt;/a&gt;, and &lt;a href="https://vercel.com/docs/sandbox" rel="noopener noreferrer"&gt;Vercel&lt;/a&gt; all offer good services for exactly this. But should &lt;em&gt;every&lt;/em&gt; agent product own a cloud sandbox?&lt;/p&gt;

&lt;p&gt;I wrote before, in &lt;a href="https://dev.to/gyu07/the-wrapper-got-heavy-why-chatgpt-clones-are-runtime-problems-now-19h4"&gt;The Wrapper Got Heavy&lt;/a&gt;, that the thing we used to dismiss as a thin "wrapper" has become a heavy runtime — a sandbox plus an agent loop plus state gravity. This post is something like the sequel. If the runtime got heavy, the next question is &lt;em&gt;where you run it&lt;/em&gt; — in the cloud, or on the user's own machine.&lt;/p&gt;

&lt;p&gt;Lately I've been recommending a different shape more often. For a single-user agent, I first consider running &lt;a href="https://developers.openai.com/codex/" rel="noopener noreferrer"&gt;Codex&lt;/a&gt; or &lt;a href="https://code.claude.com/docs/en/overview" rel="noopener noreferrer"&gt;Claude Code&lt;/a&gt; inside a desktop app. This isn't just "local is cheaper." Moving execution onto the user's PC changes more than infra — it changes how you design file access, state, latency, and the permission UI. Treat what follows as a working thesis, not a final verdict.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For single-user AI agents, a desktop app can be a more natural runtime than a cloud sandbox.&lt;/li&gt;
&lt;li&gt;You can use the user's existing environment and compute instead of provisioning a second one in the cloud, and Codex App Server lets you reuse a serious agent loop rather than rebuilding it.&lt;/li&gt;
&lt;li&gt;But Codex isn't an SDK — treat it as an &lt;em&gt;embedded runtime&lt;/em&gt; that needs version pinning and compatibility management.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Cloud sandboxes have already come a long way
&lt;/h2&gt;

&lt;p&gt;Let me say it up front: cloud sandboxes aren't bad. For public services that must safely run code from many untrusted users, a strong isolation boundary in the cloud is essential.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/runtime-sessions.html" rel="noopener noreferrer"&gt;AWS AgentCore Runtime&lt;/a&gt;&lt;/strong&gt; assigns a dedicated microVM per session. Each session has its own kernel, memory, and filesystem, and on completion the microVM is destroyed and its memory sanitized. The underlying &lt;a href="https://firecracker-microvm.github.io/" rel="noopener noreferrer"&gt;Firecracker&lt;/a&gt; is a KVM-based lightweight microVM with under 5 MiB of overhead per VM (&lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/built-in-tools-how-it-works.html" rel="noopener noreferrer"&gt;AWS's Tools writeup&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://developers.cloudflare.com/sandbox/" rel="noopener noreferrer"&gt;Cloudflare Sandbox SDK&lt;/a&gt;&lt;/strong&gt; exposes command execution, file operations, background processes, and port forwarding as an API. You do need to understand its &lt;a href="https://developers.cloudflare.com/sandbox/concepts/sandboxes/" rel="noopener noreferrer"&gt;lifecycle&lt;/a&gt;: by default it stops after 10 minutes of inactivity and starts fresh next time, so ordinary local disk state is lost — you push durable state out to something like R2.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://vercel.com/docs/sandbox" rel="noopener noreferrer"&gt;Vercel Sandbox&lt;/a&gt;&lt;/strong&gt; gives you an isolated Linux microVM (Firecracker) with streaming, file management, network policy, and snapshots in the SDK. &lt;a href="https://vercel.com/changelog/vercel-sandboxes-ga" rel="noopener noreferrer"&gt;Persistent sandboxes are the default&lt;/a&gt;: on stop the filesystem is snapshotted, and it's restored on resume.&lt;/p&gt;

&lt;h2&gt;
  
  
  The hard part isn't "starting a VM"
&lt;/h2&gt;

&lt;p&gt;When you build a sandbox in the cloud, the hard part isn't the boot API. In a real product you also have to solve:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Area&lt;/th&gt;
&lt;th&gt;What you have to design&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Lifecycle&lt;/td&gt;
&lt;td&gt;start, sleep, resume, destroy, timeout&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;State&lt;/td&gt;
&lt;td&gt;persistence, snapshots, caching&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Transport&lt;/td&gt;
&lt;td&gt;WebSocket, SSE, reconnect, output streaming&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Security&lt;/td&gt;
&lt;td&gt;network limits, secrets, privilege escalation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Placement&lt;/td&gt;
&lt;td&gt;region, cold start, session affinity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ops&lt;/td&gt;
&lt;td&gt;logs, metrics, audit, crash recovery&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost&lt;/td&gt;
&lt;td&gt;CPU, memory, storage, egress, idle time&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UX&lt;/td&gt;
&lt;td&gt;approvals, progress, diffs, interruption, retries&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For example, &lt;a href="https://developers.cloudflare.com/containers/" rel="noopener noreferrer"&gt;Cloudflare Containers&lt;/a&gt; can take a while to start from a stopped state, and you have to account for routing follow-up requests to the right instance — and for it possibly restarting in a different location. Even &lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/runtime-sessions.html" rel="noopener noreferrer"&gt;AgentCore sessions&lt;/a&gt; require carrying a session ID so requests reach the same microVM; lose it and you hit a cold start (microVM stickiness).&lt;/p&gt;

&lt;p&gt;None of this is a flaw of the cloud. It's the complexity required to let many untrusted users share one fleet of servers. The question is whether a single-user desktop product actually needs that complexity.&lt;/p&gt;

&lt;h2&gt;
  
  
  On the desktop, the user's PC is already a finished runtime
&lt;/h2&gt;

&lt;p&gt;A developer's machine already has the working repo, Git history and credentials, language runtimes and dependency caches, Docker and local databases, SSH config, an IDE, and plenty of storage, memory, and sometimes a GPU.&lt;/p&gt;

&lt;p&gt;The cloud approach has to recreate all of that in a separate environment, keep it in sync, and persist/restore it as needed (clone, install deps, inject secrets, hand results back). Granted, there are options like &lt;a href="https://vercel.com/docs/sandbox" rel="noopener noreferrer"&gt;Vercel Sandbox&lt;/a&gt; where persistence is the default, or Cloudflare's R2-based backup/restore — but the shape of "stand up a separate environment and sync it" remains. On the desktop, you just launch the agent in that directory. No network file sync, no upload to remote storage, no image build required. CPU, memory, and storage aren't metered and billed by a cloud provider to the app vendor — they're resources the user already owns.&lt;/p&gt;

&lt;p&gt;One important caveat: even though the Codex or Claude Code process runs locally, LLM inference does &lt;em&gt;not&lt;/em&gt; run on a local GPU. File and command operations are local; the model conversation goes over the network. A local GPU can help with workloads the agent kicks off — builds, ML, image processing — but for a single-user agent that's rarely the main draw. The bigger, more mundane wins are fast operations over large local files (parsing, indexing, searching, embedding) on the local SSD, and the user's existing toolchain working as-is.&lt;/p&gt;

&lt;p&gt;So what a desktop setup lets the provider avoid, or greatly reduce, is the &lt;em&gt;ongoing&lt;/em&gt; cost of sandbox CPU/memory/disk, durable storage, egress, orchestration, and monitoring infra. In return you take on distribution, updates, per-OS support, and the security design of local execution. So this isn't "local is always cheaper" — it's that the cost of compute and the burden of complexity shift from the provider to the user's machine. Even so, not having to run a per-user execution platform is a big deal, I think.&lt;/p&gt;

&lt;h2&gt;
  
  
  PTY brings interactive local execution into the app
&lt;/h2&gt;

&lt;p&gt;When you embed a tool like Claude Code or Codex CLI in an app, calling &lt;code&gt;child_process.exec()&lt;/code&gt; isn't always enough. These CLIs do interactive things: streaming progress, waiting for input, adapting to terminal width, ANSI control sequences, signals like Ctrl+C, and keeping long-lived processes alive.&lt;/p&gt;

&lt;p&gt;That's where you need a PTY (pseudo-terminal). Microsoft's &lt;a href="https://github.com/microsoft/node-pty" rel="noopener noreferrer"&gt;&lt;code&gt;node-pty&lt;/code&gt;&lt;/a&gt; uses &lt;code&gt;forkpty&lt;/code&gt; on macOS/Linux and ConPTY on Windows to make a child process believe it's attached to a real terminal. For display you can use &lt;a href="https://xtermjs.org/" rel="noopener noreferrer"&gt;xterm.js&lt;/a&gt; (the same one VS Code's integrated terminal uses). The overall shape looks roughly like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌────────────────────────────────────────────┐
│ Desktop UI  (Chat / Diff / Approval / Term) │
└──────────────────┬─────────────────────────┘
                   │ restricted IPC
┌──────────────────▼─────────────────────────┐
│ Privileged Host Process                    │
│  Project / Permission / PTY / Audit        │
│  Codex App Server Client                    │
└──────────────┬─────────────────┬───────────┘
               │ stdio JSONL     │ PTY
        ┌──────▼──────┐   ┌──────▼──────────┐
        │ codex       │   │ shell / tools   │
        │ app-server  │   │ tests / servers │
        └──────┬──────┘   └─────────────────┘
               │
        OS-level sandbox → Local filesystem
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that talking to Codex App Server itself does &lt;em&gt;not&lt;/em&gt; require a PTY. App Server connects over ordinary stdio (JSONL); the PTY is for user-facing terminals and interactive child processes (shells, dev servers, progress-drawing CLIs). App Server does have its own PTY-backed command-execution API, but that's separate from the connection transport.&lt;/p&gt;

&lt;h2&gt;
  
  
  Codex is well suited to embedding in a desktop app
&lt;/h2&gt;

&lt;p&gt;The specific reason I reach for Codex is &lt;a href="https://developers.openai.com/codex/app-server" rel="noopener noreferrer"&gt;Codex App Server&lt;/a&gt;. It's a local server process for driving Codex's agent machinery from a custom client — not just a prompt-sending API. It handles thread creation/resume/fork/persistence, turn start/interrupt/mid-turn input, approval requests for commands and file changes, streaming of execution events and diffs, config, ChatGPT login, and integration with MCP and Skills.&lt;/p&gt;

&lt;p&gt;App Server holds threads as a long-running child process and connects over bidirectional JSON-RPC. A single input produces many events, and when approval is needed the server asks the client and pauses the turn until it answers. The &lt;a href="https://openai.com/index/unlocking-the-codex-harness/" rel="noopener noreferrer"&gt;integration pattern&lt;/a&gt; is clean: bundle the per-OS Codex binary, pin a verified version, launch it as a child process, and exchange JSONL over stdin/stdout. Codex's own desktop app and VS Code extension use the same shape.&lt;/p&gt;

&lt;p&gt;The connection itself is light. You launch the bundled (version-pinned) Codex binary with the &lt;code&gt;app-server&lt;/code&gt; subcommand as a child process and connect over standard I/O — stdio is the default transport, so you don't open a network port. Then you read the child's stdout line by line as JSONL: send an &lt;code&gt;initialize&lt;/code&gt; request once per connection, and after you get its response, send the &lt;code&gt;initialized&lt;/code&gt; &lt;em&gt;notification&lt;/em&gt; (it's sent by the client, not returned by the server). Then call &lt;code&gt;thread/start&lt;/code&gt; with the working directory (cwd), a sandbox mode (e.g. &lt;code&gt;workspace-write&lt;/code&gt;), and an approval policy (e.g. &lt;code&gt;on-request&lt;/code&gt;). That brings up one thread, and from there events — model output, diffs, approval requests — flow back on stdout.&lt;/p&gt;

&lt;p&gt;In practice you add request-ID-to-Promise mapping, crash handling and restarts, event ordering, backpressure, and handling turns that are suspended for approval. That last set is where the "light" first impression ends — a restart racing an in-flight turn, or the initialization flow itself, is where the real work (and the surprise I hit below) lives. You can &lt;a href="https://developers.openai.com/codex/app-server" rel="noopener noreferrer"&gt;generate&lt;/a&gt; types matching your bundled binary with &lt;code&gt;codex app-server generate-ts&lt;/code&gt;, which makes it easy to pin the binary/client combination. There's also a WebSocket transport, but it's officially experimental/unsupported; inside an app, keeping ports closed and using stdio is simpler and safer.&lt;/p&gt;

&lt;h3&gt;
  
  
  You get Codex's own local sandbox
&lt;/h3&gt;

&lt;p&gt;Running the agent locally is not the same as letting the model do anything it wants with the user's privileges. Even locally, Codex combines an OS-enforced &lt;a href="https://developers.openai.com/codex/concepts/sandboxing" rel="noopener noreferrer"&gt;sandbox and approval policy&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;macOS: &lt;a href="https://developers.openai.com/codex/concepts/sandboxing" rel="noopener noreferrer"&gt;Seatbelt&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Linux / WSL2: bubblewrap (bwrap) + seccomp (falling back to a bundled helper that relies on user namespaces if bwrap isn't present)&lt;/li&gt;
&lt;li&gt;Windows: a native sandbox using a dedicated low-privilege user, filesystem boundaries, and the firewall when run in PowerShell; the Linux sandbox when run in WSL2&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the standard &lt;code&gt;workspace-write&lt;/code&gt; + &lt;code&gt;on-request&lt;/code&gt; setup, going outside the working directory or reaching the internet requires approval. &lt;a href="https://developers.openai.com/codex/agent-approvals-security" rel="noopener noreferrer"&gt;Sandbox and approvals&lt;/a&gt; are two separate dials: the former controls what's technically possible, the latter controls when to stop and ask. You don't have to build per-OS sandboxes from scratch — the execution side rides existing boundaries. App Server also returns approval events as structured data, so instead of making users type &lt;code&gt;y&lt;/code&gt; at a raw log, you can build a product-native approval dialog.&lt;/p&gt;

&lt;h2&gt;
  
  
  Even so, embedding Codex isn't simple
&lt;/h2&gt;

&lt;p&gt;Having written up the upsides, I should be honest that embedding it carries a real cost of keeping up.&lt;/p&gt;

&lt;p&gt;When I recently upgraded Codex to the 0.144 line, an implementation that had been working stopped working, and recovery took real effort. I can't be sure the cause was only a specific change on Codex's side. Around the same time, users reported cases like &lt;a href="https://github.com/openai/codex/issues/32675" rel="noopener noreferrer"&gt;&lt;code&gt;app-server&lt;/code&gt; crashing right after launch&lt;/a&gt; in an environment bundling 0.144.0-alpha.4, and — &lt;a href="https://github.com/openai/codex/issues/29240" rel="noopener noreferrer"&gt;after a desktop-app update&lt;/a&gt; — trouble with in-app browser control and with how running services were handled on restart. These are user reports, not a confirmed root cause or a documented spec change, but they point at the same thing: not just the agent's protocol but its updates and process lifecycle now need verifying.&lt;/p&gt;

&lt;p&gt;This is exactly why the earlier "pin the binary version and generate schemas from it" matters. One distinction worth drawing: backward compatibility of the JSON-RPC protocol and compatibility of the whole runtime &lt;em&gt;including process management&lt;/em&gt; are not the same thing. Even with a stable message contract, lifecycle-level incompatibilities can bite — binary replacement mid-update, a running turn racing a restart, stale runtimes lingering, missing helper binaries.&lt;/p&gt;

&lt;p&gt;In our own case, the root cause wasn't the JSON-RPC protocol at all — it was the initialization flow. After initializing, App Server tried to connect to Remote Control, but our embedding context had no ChatGPT auth, so it retried forever and hung — presenting as &lt;em&gt;won't start&lt;/em&gt; / &lt;em&gt;freeze&lt;/em&gt; / a flood of errors. And the trap: setting &lt;code&gt;-c features.remote_control=false&lt;/code&gt; did nothing — it's treated as an unknown key, silently a no-op. The switch that actually worked was an internal environment variable, &lt;code&gt;CODEX_INTERNAL_APP_SERVER_REMOTE_CONTROL_DISABLED=1&lt;/code&gt;. The config surface and the runtime's real behavior had diverged, and the only working knob was undocumented and internal — which is precisely the kind of failure you don't get from an SDK, and do get from a runtime you embed.&lt;/p&gt;

&lt;p&gt;What I took from this: don't treat Codex App Server like an ordinary SDK. Codex is a &lt;em&gt;runtime you embed&lt;/em&gt; in your app. You want to pin, test, and be able to roll back the binary, schema, config, launch method, and process lifecycle as a single compatibility unit. In fact &lt;a href="https://openai.com/index/unlocking-the-codex-harness/" rel="noopener noreferrer"&gt;OpenAI itself&lt;/a&gt; describes bundling per-platform binaries and pinning to a tested version in its VS Code extension and desktop app. An upgrade is something to verify and then switch to, not something to follow unconditionally — that's my current operating stance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Separate the UI process from the execution process
&lt;/h2&gt;

&lt;p&gt;"Local" doesn't mean "safe." The agent's process sits close to the user's files, credentials, SSH config, and browser data — so the app's own privilege separation matters.&lt;/p&gt;

&lt;p&gt;With Electron, don't let the renderer touch Node.js or the PTY directly. Enable the &lt;a href="https://www.electronjs.org/docs/latest/tutorial/sandbox" rel="noopener noreferrer"&gt;renderer sandbox&lt;/a&gt; and &lt;a href="https://www.electronjs.org/docs/latest/tutorial/context-isolation" rel="noopener noreferrer"&gt;context isolation&lt;/a&gt;, and keep privileged operations in the main process or a narrow preload API. Expose to the renderer only vetted operations — start a thread, approve, interrupt, subscribe — and never a way to run arbitrary shell commands or read/write arbitrary paths. Manage the PTY and App Server on the privileged side. &lt;a href="https://v2.tauri.app/security/capabilities/" rel="noopener noreferrer"&gt;Tauri&lt;/a&gt; is the same idea: define per-command permissions and capabilities and minimize what the WebView is granted.&lt;/p&gt;

&lt;h2&gt;
  
  
  The UX advantages of the desktop shape
&lt;/h2&gt;

&lt;p&gt;More than the infra savings, I think the UX difference is what really matters.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The files are already there.&lt;/strong&gt; No waiting on upload or sync; you work directly against the current tree, including uncommitted diffs and local-only config.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You reproduce in the same environment.&lt;/strong&gt; A problem that only happens on the user's machine can be investigated with the same OS, deps, and local services.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Big file operations are fast.&lt;/strong&gt; Search and Git-history walks finish on the local SSD, without one HTTP call per operation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Existing tools just work.&lt;/strong&gt; The user's already-configured linters, test runners, Docker, Xcode, Android SDK, and so on are all available.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Approvals are easier to make meaningful.&lt;/strong&gt; You can tie an approval for a write target, a network destination, or a command to the OS file picker and notifications.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The desktop shape has clear weaknesses too
&lt;/h2&gt;

&lt;p&gt;It isn't always the right shape. Objections I'd raise myself:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;If the machine stops, the agent stops.&lt;/strong&gt; Sleep, power loss, and disconnects all hit it. Claude Code's &lt;a href="https://code.claude.com/docs/en/remote-control" rel="noopener noreferrer"&gt;Remote Control&lt;/a&gt; lets you steer from another device while execution stays local, but that's different from a cloud environment that keeps running while your machine is off. For long-running work you can combine SSH or a managed remote environment and split UI from execution.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Environment variance is large.&lt;/strong&gt; Shell, paths, permissions, PTY, and package management differ per OS. "You can leverage the user's environment" and "you can't reproduce it" are two sides of the same coin.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Distribution and updates become a compatibility problem of their own.&lt;/strong&gt; A cloud sandbox lets the provider update the runtime centrally, in one place. On the desktop you need a delivery pipeline that pins — and can roll back — the binary, schema, config, and launch method as &lt;em&gt;one&lt;/em&gt; compatibility unit, and threads that unit through auto-update, not just development. (We track Codex versions with a weekly workflow and a lockfile-pinned version.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You're close to secrets.&lt;/strong&gt; A local agent can reach more sensitive data than a disposable cloud environment. Don't skip the working-directory boundary, network limits, approvals, and audit logs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Not for running anonymous users' code.&lt;/strong&gt; Playgrounds, online IDEs, coding tests, generated-UI previews and the like still need a cloud sandbox.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Which should you choose
&lt;/h2&gt;

&lt;p&gt;Roughly, I'd think about it like this:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Requirement&lt;/th&gt;
&lt;th&gt;Better fit&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A single user operating their own files&lt;/td&gt;
&lt;td&gt;Desktop&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Leverage an existing local dev environment&lt;/td&gt;
&lt;td&gt;Desktop&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Handle lots of local files fast&lt;/td&gt;
&lt;td&gt;Desktop&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Investigate a problem specific to the user's machine&lt;/td&gt;
&lt;td&gt;Desktop&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Keep running while the machine is off&lt;/td&gt;
&lt;td&gt;Cloud&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Run anonymous users' code&lt;/td&gt;
&lt;td&gt;Cloud&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Guarantee an identical environment for everyone&lt;/td&gt;
&lt;td&gt;Cloud&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Update the runtime centrally, without per-client compatibility work&lt;/td&gt;
&lt;td&gt;Cloud&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Use centrally managed secrets&lt;/td&gt;
&lt;td&gt;Cloud / managed remote env&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Concentrate high-end GPU usage&lt;/td&gt;
&lt;td&gt;Cloud / dedicated remote env&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In a real product, a hybrid is natural: everyday work on Desktop + Local Codex, long or heavy jobs on a remote environment or Cloud Sandbox, untrusted code in a Cloud Sandbox, review and approval in the Desktop UI.&lt;/p&gt;

&lt;p&gt;There's another axis worth using: the client form factor. Even within one product, I'd lean the desktop app toward a serious runtime — the side that embeds the heavy local work environment — and lean the web browser toward lightweight features — pushing heavy execution to a cloud sandbox and keeping the browser to viewing, approvals, and light tasks. The point is to separate where the heavy runtime lives from where you keep only a light surface.&lt;/p&gt;

&lt;p&gt;This continues the argument from &lt;a href="https://dev.to/gyu07/the-wrapper-got-heavy-why-chatgpt-clones-are-runtime-problems-now-19h4"&gt;The Wrapper Got Heavy&lt;/a&gt;. Now that the runtime is heavy, the design question isn't "which client calls the model" but "where does that heavy runtime live, and where does the state gather?" By &lt;em&gt;state gravity&lt;/em&gt; I mean the pull that draws the runtime toward wherever the state — the repo, dependencies, auth, caches, running processes — already collects. Seen that way, heavy execution belongs where the hardware and state naturally gather — the desktop app — while the browser stays a light window onto it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;A capable agent needs a runtime with files, a shell, libraries, processes, and state. Providing that on the web means a large substrate — microVMs or containers, durable storage, network controls, lifecycle management — and AWS, Cloudflare, and Vercel provide it powerfully.&lt;/p&gt;

&lt;p&gt;For a single-user desktop app, though, the user's PC is &lt;em&gt;already&lt;/em&gt; a high-performance, persistent agent environment. Launch Codex or Claude Code from a desktop app with PTY support and you reuse the existing files, tools, caches, and compute. And Codex App Server exposes threads, events, diffs, approvals, auth, and config over bidirectional JSON-RPC, with embedding into local apps and IDEs officially in scope. That Codex carries its own OS-level sandbox is another implementation win.&lt;/p&gt;

&lt;p&gt;Before building a cloud sandbox, it's worth asking once: does this agent really need to run on the provider's servers? If it operates the user's own files and tools, the first candidate might not be a web app plus a microVM. Desktop × PTY × Codex is becoming a practical default for this class of agent — at least, that's my current view.&lt;/p&gt;

&lt;p&gt;Service and Codex App Server specifics reference the official docs as of July 2026; this area moves fast, so check the latest before relying on any detail.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>codex</category>
      <category>sandbox</category>
    </item>
    <item>
      <title>Designing Schema Boundaries for AI Agents</title>
      <dc:creator>Takafumi Endo</dc:creator>
      <pubDate>Wed, 08 Jul 2026 10:48:44 +0000</pubDate>
      <link>https://dev.to/gyu07/designing-schema-boundaries-for-ai-agents-1cjo</link>
      <guid>https://dev.to/gyu07/designing-schema-boundaries-for-ai-agents-1cjo</guid>
      <description>&lt;p&gt;An agent can now change your whole persistence stack in one pass.&lt;/p&gt;

&lt;p&gt;Ask Claude Code or Codex to "add a &lt;code&gt;plan_tier&lt;/code&gt; to accounts and wire it through," and it will read the repo, edit the Drizzle schema, generate a SQL migration, update the dbt &lt;code&gt;source&lt;/code&gt;, touch a model, regenerate the TypeScript types, add a test, run the build, read the failure, and fix it. Multi-file, coherent, green. A year ago that was an afternoon; now, in a well-shaped repo, it's a prompt.&lt;/p&gt;

&lt;p&gt;For a while I read that as pure upside — schema changes stopped being bottlenecked on how fast a human can hand-write a migration. Now I think the upside came with a quieter inversion: &lt;strong&gt;the same speed that makes good changes cheap makes unsafe changes cheap too.&lt;/strong&gt; And the unsafe ones are exactly the kind that pass review, because they look consistent.&lt;/p&gt;

&lt;p&gt;I've written before about handing agents short, verified facts instead of asking them to be careful. This is the same argument aimed at the database — the place where a confident wrong move is most expensive. It's a working theory, not a universal rule.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Where I've landed for now (and expect to keep revising):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Agents making schema changes cheap is good. It's not the problem.&lt;/li&gt;
&lt;li&gt;The problem is that the change is cheap while the &lt;em&gt;verification boundary&lt;/em&gt; is still shaped for the human-migration era.&lt;/li&gt;
&lt;li&gt;The dangerous change isn't the one that fails to build — it's the one that builds fine and quietly breaks a contract downstream.&lt;/li&gt;
&lt;li&gt;What's missing isn't a better prompt or a stricter reviewer. It's a &lt;strong&gt;compiler that lowers every declaration onto one contract graph and judges the change against it.&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;One definition up front, so the rest reads cleanly: by &lt;em&gt;contract compiler&lt;/em&gt; I mean a tool that reads every declaration of persistence shape — ORM schema, SQL DDL, dbt artifacts, object manifests, SDK types — lowers them into one graph, and evaluates a change against it as a set of operational hazards. Not a migration runner, not a codegen step. A judge.&lt;/p&gt;

&lt;h2&gt;
  
  
  The new reality: the agent writes the schema
&lt;/h2&gt;

&lt;p&gt;It's worth being concrete about how wide the blast radius of a single "small" schema change already is. When an agent touches one column, the coherent version of that change spans:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Drizzle / Prisma / SQL DDL  →  migration plan     →  dbt source
        ↓                            ↓                    ↓
   app types / SDK          backward compat?         dbt models
        ↓                            ↓                    ↓
   API contract             RLS / tenant guard      Parquet export → S3/R2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(I write &lt;em&gt;migration plan&lt;/em&gt; rather than &lt;em&gt;migration&lt;/em&gt; on purpose: whether you're SQL-first or ORM-first, the migration is a derivative of a desired state, and it's the desired state the graph should be built from.)&lt;/p&gt;

&lt;p&gt;An agent will happily update all of those in one commit, and it will make them &lt;em&gt;look&lt;/em&gt; mutually consistent. That's the impressive part. It's also the trap: internal consistency across the files it edited says nothing about consistency with the things it &lt;em&gt;didn't&lt;/em&gt; — the live database, last week's published artifact, the SDK a client already pinned, the PII policy that lives outside the repo.&lt;/p&gt;

&lt;p&gt;So the interesting question stopped being "can the agent implement the change?" It obviously can. The question is: &lt;em&gt;who verifies the contract graph the change lives in?&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The hazard is the green build
&lt;/h2&gt;

&lt;p&gt;Here's the shape of the risk, because it's not the one people reach for first. The scary agent change isn't the one that throws a compiler error — you'll catch that. It's the one where everything is green and the damage is semantic:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;nullable&lt;/code&gt; quietly tightened, so old rows now violate a constraint that only bites on the next write.&lt;/li&gt;
&lt;li&gt;a type narrowed — &lt;code&gt;text&lt;/code&gt; → &lt;code&gt;varchar(32)&lt;/code&gt; — that truncates in production and nowhere else.&lt;/li&gt;
&lt;li&gt;an enum or code value removed or remapped, so historical rows decode or compare differently.&lt;/li&gt;
&lt;li&gt;a rename modeled as drop-plus-add, which reads to every downstream consumer as &lt;em&gt;data loss&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;a PII column newly exposed to an analytics model and, three hops later, sitting in a Parquet export.&lt;/li&gt;
&lt;li&gt;the dbt &lt;code&gt;source&lt;/code&gt; left stale, so the model builds against a schema that no longer exists.&lt;/li&gt;
&lt;li&gt;a dataset's Parquet schema changed without a version bump, over-writing &lt;code&gt;current&lt;/code&gt; so yesterday's reader silently gets today's shape.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Many of these slip straight through a CI that only asks whether the SQL parses and the build passes. Wire in dbt contracts, source tests, migration lint, Atlas, or integration tests and some of them get caught — but the default "does it build" pipeline doesn't ask whether the change is &lt;em&gt;destructive&lt;/em&gt;, &lt;em&gt;backward-incompatible&lt;/em&gt;, or &lt;em&gt;contract-breaking&lt;/em&gt;. In the human era that gap was survivable — changes were slow, and a reviewer's eye covered a lot of it. Speed removes the slack. When agents can produce these changes repeatedly, "a human will notice" stops being a control.&lt;/p&gt;

&lt;p&gt;The failure I most want to avoid is the one that reads as safety. A green build on a breaking change is exactly that.&lt;/p&gt;

&lt;h2&gt;
  
  
  The lightweight stack is great — and it doesn't close the loop
&lt;/h2&gt;

&lt;p&gt;I want to be fair to the tools this sits on, because I like them a lot.&lt;/p&gt;

&lt;p&gt;dbt is genuinely good at the analytics side of this. &lt;a href="https://docs.getdbt.com/docs/mesh/govern/model-contracts" rel="noopener noreferrer"&gt;Model contracts&lt;/a&gt; let you declare the shape a model must return — the column names and types — and at least the build fails when the logic drifts from that shape. (How far the &lt;em&gt;constraints&lt;/em&gt; are actually enforced depends on the data platform, but as an analytics-output contract it's strong.) &lt;a href="https://github.com/duckdb/dbt-duckdb" rel="noopener noreferrer"&gt;&lt;code&gt;dbt-duckdb&lt;/code&gt;&lt;/a&gt; lets you run the whole DAG against an in-memory DuckDB inside a CI job, and its &lt;a href="https://duckdb.org/2025/04/04/dbt-duckdb.html" rel="noopener noreferrer"&gt;external materialization&lt;/a&gt; exports CSV/JSON/Parquet straight to a location you choose. Pair that with S3/R2 and you get a lightweight publish layer: compile the transformation, test it locally, publish the result, no long-running warehouse required.&lt;/p&gt;

&lt;p&gt;The important detail isn't that the file lands in a bucket. It's that the artifact becomes another &lt;em&gt;contract surface&lt;/em&gt;: a schema hash, a versioned path, a manifest, and a &lt;code&gt;current&lt;/code&gt; pointer that consumers may already be reading. Overwrite that pointer with a new shape and you haven't "updated a file" — you've changed a contract other systems depend on, silently. Hold that thought; it's where a hazard I'll later call &lt;code&gt;UNTRACKED_LATEST_OVERWRITE&lt;/code&gt; comes from — a name from this article's own vocabulary, not a standard.&lt;/p&gt;

&lt;p&gt;But dbt's contract is an &lt;em&gt;analytics-output&lt;/em&gt; contract. dbt can declare sources, test them, and watch their freshness — but that isn't ownership of the ops database's lifecycle. Indexes, constraints, RLS, triggers, extensions, tenant boundaries, PII classification, the app's ORM types — all of that lives upstream of the &lt;code&gt;source&lt;/code&gt; block, managed by Drizzle, Prisma, Atlas, or hand-written DDL. dbt sees a projection of it and trusts the projection.&lt;/p&gt;

&lt;p&gt;So the loop isn't closed. The analytics contract depends on an ops contract that nothing verifies it against — and the agent is now editing both ends at once.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's missing is a harness
&lt;/h2&gt;

&lt;p&gt;We already accepted this idea on the agent side. Nobody runs a raw model against a shell; you wrap it in a &lt;em&gt;harness&lt;/em&gt; — the loop that orchestrates tools, prompts, permissions, and execution. OpenAI has used the language of an &lt;a href="https://openai.com/index/unrolling-the-codex-agent-loop/" rel="noopener noreferrer"&gt;agent loop and harness&lt;/a&gt; around Codex CLI.&lt;/p&gt;

&lt;p&gt;My claim is small: &lt;strong&gt;schema changes need a harness for the same reason agent loops do&lt;/strong&gt; — an untrusted author producing fast, plausible actions against a system where mistakes are expensive. The author being a model rather than a human doesn't change the requirement; it sharpens it.&lt;/p&gt;

&lt;p&gt;The shape I keep arriving at is four roles:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Author     human or agent writes desired state
           SQL DDL · Drizzle · Prisma · Atlas HCL · dbt SQL + YAML
              │
Observer   reads live state
           DB introspection · dbt artifacts · S3/R2 manifests · registry · pointers
              │
Compiler   lowers everything into one IR
           table · column · constraint · source · model · artifact · policy
              │
Evaluator  turns diffs into decisions
           safe · warning · blocked · requires-human
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Author is &lt;em&gt;deliberately untrusted&lt;/em&gt;. I don't care whether a person or a model wrote the migration — I care what the Compiler and Evaluator say about it. That single stance is what makes the whole thing agent-ready: you don't have to trust the writer if you can judge the write.&lt;/p&gt;

&lt;p&gt;Which is exactly why the Observer matters as much as the Author. The live database and the authored declaration have to be &lt;em&gt;independent&lt;/em&gt; inputs. If the compiler only reads the migration the agent produced, it's grading the agent's story about the world — not the world. The whole point is to check the write against a state the writer didn't get to narrate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lower to an IR, don't diff strings
&lt;/h2&gt;

&lt;p&gt;The declarations are all different renderings — SQL DDL, TypeScript, a Prisma schema, HCL, dbt's SQL + YAML + &lt;code&gt;manifest.json&lt;/code&gt;, a DuckDB catalog, a Parquet schema, an object key. Compare any two as text and it breaks on the first reformat. So the Compiler lowers each into a common intermediate representation and compares &lt;em&gt;that&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// illustrative, not a real schema — the point is which fields exist, not the format&lt;/span&gt;
&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Column&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
  &lt;span class="na"&gt;nullable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;
  &lt;span class="nx"&gt;semantic&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;      &lt;span class="c1"&gt;// 'email' | 'money' | 'tenant_id' | …&lt;/span&gt;
  &lt;span class="nx"&gt;sensitivity&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;   &lt;span class="c1"&gt;// 'public' … 'restricted'&lt;/span&gt;
  &lt;span class="nx"&gt;pii&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;
  &lt;span class="nx"&gt;lineage&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;Ref&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;        &lt;span class="c1"&gt;// where it came from, where it flows&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A table carries analogous facts (keys, constraints, indexes, access policies); an analytics model carries its dependencies, its output columns, and how it materializes. The exact shape isn't the interesting part — what matters is that ops objects, dbt models, and published artifacts all land in &lt;em&gt;one&lt;/em&gt; vocabulary the evaluator can reason over.&lt;/p&gt;

&lt;p&gt;The point that took me a while: the IR is a &lt;em&gt;unit of judgment&lt;/em&gt;, not an output. Types, validators, SDK clients, &lt;code&gt;sources.yml&lt;/code&gt;, Parquet schemas, policy bundles — those are all derived from it. dbt's &lt;code&gt;manifest.json&lt;/code&gt; is, in effect, the analytics-side IR you already have; the missing half is lowering the &lt;em&gt;ops&lt;/em&gt; side into the same shape so the two can be compared at all. Whatever the agent writes, in whatever format, ends up flowing through one evaluator.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hazards, not diffs
&lt;/h2&gt;

&lt;p&gt;A diff tells you what changed. It can't tell you whether that's safe — and safe/unsafe is the only thing a gate actually needs. So the Evaluator turns a diff into a classified hazard with its blast radius attached, and emits it as structured data, because the readers are CI and an agent as much as a person. Something along these lines — the exact fields matter less than the idea:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"blocked"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"hazards"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"COLUMN_DROP"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"severity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"critical"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"object"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ops.users.email"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"affected"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"dbt.source.raw_users"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"dbt.model.mart_users"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"sdk.User.email"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"artifact.customer_segments.v1"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"reason"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"column is still consumed by downstream models"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"nextAction"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"kind"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"expand_contract"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"requiresHumanDecision"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;CI's question stops being "is there a diff" and becomes "what's the highest severity, and does anything here need a human." The vocabulary of hazards is finite and worth naming, though I won't try to enumerate it here — a column drop that's still consumed downstream, a type narrowing, an enum remapped, PII that reaches an export without a policy, a published artifact whose schema changed under a live pointer. They group loosely by where they originate: the ops schema, the analytics models, or the published artifacts. (The names I give them across this post are my own shorthand, a design vocabulary for the argument, not a standard.) Let the agent implement. Don't let it past the Evaluator.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make the Boundary Machine-Readable
&lt;/h2&gt;

&lt;p&gt;Everything above works with a human author too. Here's what changes when the author is a model — and it's the part I find most underrated. Give the agent the contract, not vibes.&lt;/p&gt;

&lt;p&gt;If the harness can &lt;em&gt;judge&lt;/em&gt; a change, it can also tell the agent the rules &lt;em&gt;before&lt;/em&gt; the change. Instead of a paragraph of "please be careful, watch for breaking changes," you give it something machine-readable: which classes of change are pre-approved (adding a nullable column, an index, a test), which are off-limits without a deprecation path (dropping a column, exposing PII without a policy, overwriting a live pointer), and what the migration policy is (breaking changes go through expand–contract; a dataset shape change forces a version bump). The specific encoding doesn't matter much; treating the boundary as data the agent can read does.&lt;/p&gt;

&lt;p&gt;Now the instruction isn't "fix it nicely." It's: &lt;em&gt;change this so no hazard exceeds &lt;code&gt;warning&lt;/code&gt;; if a &lt;code&gt;critical&lt;/code&gt; is unavoidable, don't implement — return a plan.&lt;/em&gt; The agent iterates against structured feedback (a hazard verdict) instead of against a green checkmark that means nothing. Breaking changes come back as expand–contract plans, not as a &lt;code&gt;DROP COLUMN&lt;/code&gt; that happened to compile.&lt;/p&gt;

&lt;p&gt;This is the same thing I keep chasing in every one of these posts. The reason I stopped tacking "don't guess, verify, watch for false negatives" onto every prompt isn't that I started trusting the agent more. It's that the facts I hand it — and now the actions I allow it — carry their own guarantees. When "you may not expose PII without a policy" is a blocked action and a blocking hazard, I don't have to &lt;em&gt;ask&lt;/em&gt; the agent to respect it.&lt;/p&gt;

&lt;p&gt;Which points at the shift underneath all of this. The scarce skill is no longer &lt;em&gt;writing&lt;/em&gt; the migration — an agent will write ten before lunch. The scarce skill is &lt;em&gt;defining the contract&lt;/em&gt; that decides whether a migration is safe. That's the part that doesn't get cheaper when the model gets better; if anything it gets more valuable.&lt;/p&gt;

&lt;p&gt;A few patterns fall out of this once the boundary is a compiler rather than a reviewer's attention — which is the point where "what does this look like in practice" deserves its own section.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would put in CI
&lt;/h2&gt;

&lt;p&gt;None of this is real until it's a gate. What I want is for the harness to run like any other CI check — except that the question it asks is a different one. The loop, end to end:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AI agent
  │ proposes change
  ▼
Author files
  Drizzle · Prisma · SQL · dbt · SDK
  │
  ▼
Contract Compiler
  ops IR · analytics IR · artifact IR
  │
  ▼
Hazard Evaluator
  safe · warning · blocked · requires-human
  │
  ├── safe ──────────────▶ merge
  ├── warning ───────────▶ review
  └── blocked ───────────▶ agent gets structured feedback,
                            returns a plan instead of a change
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The principle, not a spec: the gate compiles the &lt;em&gt;proposed&lt;/em&gt; state and the &lt;em&gt;live&lt;/em&gt; state into the same IR — independently — joins the dbt and artifact sides onto that graph, and returns a hazard verdict rather than a build result. It blocks on &lt;code&gt;critical&lt;/code&gt;, routes the ambiguous cases to a human, and lets the safe majority through untouched. The mechanics of how you introspect the database or where you read the last published manifest are the boring, swappable part; the load-bearing idea is that CI evaluates a graph, not a syntax tree.&lt;/p&gt;

&lt;p&gt;A few principles keep the gate calm to live with rather than something people route around: begin from a &lt;em&gt;plan&lt;/em&gt;, not a diff; don't let the analytics source definitions and the ops schema drift into two hand-maintained copies of the same truth; and treat a published artifact as versioned and its live pointer as a deliberate move, not an overwrite. How you implement each is a taste question; that you don't skip them isn't.&lt;/p&gt;

&lt;p&gt;The shift in one line: CI stops asking whether the migration &lt;em&gt;builds&lt;/em&gt;, and starts asking whether the contract graph is still &lt;em&gt;safe&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Objections I'd raise myself
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;"This is just linting / dbt contracts / Atlas."&lt;/strong&gt; Those exist and they're good — Atlas flags destructive ops changes, dbt contracts pin analytics output. What none of them does &lt;em&gt;alone&lt;/em&gt; is carry a &lt;code&gt;pii&lt;/code&gt; label from an ops column to a Parquet export, or block a publish on that basis. The gap is the &lt;em&gt;edge between&lt;/em&gt; the tools, which is precisely where an agent editing both ends creates trouble.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Why not just give the agent more context?"&lt;/strong&gt; Because the missing thing isn't context volume, it's a decision boundary. More repo text doesn't tell the agent whether a column drop is safe — the answer isn't &lt;em&gt;in&lt;/em&gt; the files it's reading; it's in the graph across them. A contract graph can decide it; a bigger window just lets the agent read more of the question.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"You're just distrusting the model."&lt;/strong&gt; The opposite. Distrust would be forbidding agents from touching schema. This lets them touch everything — and makes the boundary a compiler instead of my nerves. I'd apply the same harness to myself.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Over-engineering for a three-table app."&lt;/strong&gt; Yes. Below some size, a human reading the diff is the right control. The harness earns its keep exactly when change volume outruns human attention — which is the situation agents create.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Hazard rules will have false positives."&lt;/strong&gt; They will, and a conservative gate that occasionally blocks a safe change is a trade I'll take over one that occasionally ships a silent breaking one. But "tune toward missing fewer real hazards" isn't enough on its own — a gate that cries wolf gets muted, and a muted gate is worse than none. So the real requirement is that false positives be &lt;em&gt;explicit, suppressible with evidence, and tracked as policy exceptions&lt;/em&gt; rather than quietly tolerated. An override should leave a record, not a shrug.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where this is going
&lt;/h2&gt;

&lt;p&gt;I came in through "agents make schema changes cheap" and walked out at something more structural: SQL DDL, an ORM schema, a dbt model, a Parquet schema, an S3 manifest, and an SDK type are all renderings of one contract — and in the agent era, the author of every one of those can be a model moving fast. Manage them as separate files reviewed by tiring humans and the speed becomes a liability. Lower them to one IR, judge changes as hazards, and hand the agent the boundary as context — and the speed goes back to being the gift it was supposed to be.&lt;/p&gt;

&lt;p&gt;I hold the strong version loosely. The four-layer split and the IR-first stance feel more right the more I sit with them; the exact hazard taxonomy and how far to automate the boundary versus leave it to review are edges I'm still finding. And maybe for an analytics-first shop the honest answer is still a warehouse and shared Postgres with a careful reviewer, and this is a monorepo-shaped preference more than a law.&lt;/p&gt;

&lt;p&gt;But the direction feels clear enough to design toward. Let the agents write the schema — the migrations, the models, the types, all of it. Just don't let the safety of it rest on how the diff happens to look on a given afternoon.&lt;/p&gt;

&lt;p&gt;Agents should write schema changes. Whether a schema change is &lt;em&gt;safe&lt;/em&gt; should be decided by a contract compiler — not by vibes, and not by whoever happens to be reviewing at 6pm.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>database</category>
      <category>dataengineering</category>
      <category>dbt</category>
    </item>
    <item>
      <title>AWS Is Not Simpler. Agents Just Got Better at Reading It.</title>
      <dc:creator>Takafumi Endo</dc:creator>
      <pubDate>Tue, 07 Jul 2026 09:46:03 +0000</pubDate>
      <link>https://dev.to/gyu07/aws-is-not-simpler-agents-just-got-better-at-reading-it-3j9e</link>
      <guid>https://dev.to/gyu07/aws-is-not-simpler-agents-just-got-better-at-reading-it-3j9e</guid>
      <description>&lt;p&gt;I optimized my architecture for the wrong model.&lt;/p&gt;

&lt;p&gt;I used to think black-box infrastructure was the right abstraction for AI-driven development.&lt;/p&gt;

&lt;p&gt;Vercel, Supabase, Cloudflare Workers — a sharp contract in front, a managed backend behind — felt like the obvious fit. The less an agent had to reason about, the fewer places it could get lost. Give it a clean interface, hide the messy backend, move fast.&lt;/p&gt;

&lt;p&gt;I still think that was right for the agents we had last year. I don't think it's right for the agents we're starting to use now.&lt;/p&gt;

&lt;p&gt;The shift is &lt;strong&gt;not&lt;/strong&gt; that AWS got simpler. It didn't. Setup still takes longer, CI/CD takes more work to wire, and cost control has real limits. The shift is that &lt;strong&gt;agents got better at reading complexity&lt;/strong&gt; — and once an agent can actually use a large structured context, the things I treated as overhead (resources, provider schemas, IAM policies, explicit queues, explicit alarms, explicit networks) become the highest-signal context I can hand it.&lt;/p&gt;

&lt;p&gt;To keep this concrete, I'm holding the tool constant. This is &lt;strong&gt;HCL/Terraform on AWS vs HCL/Terraform on Cloudflare&lt;/strong&gt; — same language, same workflow, two providers.&lt;/p&gt;

&lt;h2&gt;
  
  
  The inversion
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Agents are…&lt;/th&gt;
&lt;th&gt;Best served by…&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Context-poor&lt;/strong&gt; (last year's loops)&lt;/td&gt;
&lt;td&gt;Black boxes — shrink the surface, hide the backend&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Context-rich&lt;/strong&gt; (now)&lt;/td&gt;
&lt;td&gt;Inspectable systems — describe everything as code&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The one-line version:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The better agents get at reading, the more valuable explicit infrastructure becomes.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I didn't become more pro-AWS because AWS got easier. I became more pro-AWS because agents got better at reading it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Same Terraform, two providers
&lt;/h2&gt;

&lt;p&gt;The interesting comparison isn't AWS-elegance vs Cloudflare-elegance. It's &lt;strong&gt;how much of the infrastructure topology and operational contract an agent can reconstruct from the HCL plus the provider schema alone.&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Terraform × AWS&lt;/th&gt;
&lt;th&gt;Terraform × Cloudflare&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Provider maturity&lt;/td&gt;
&lt;td&gt;AWS provider is about as battle-tested as IaC gets; enormous public corpus of modules/examples&lt;/td&gt;
&lt;td&gt;v5 is a &lt;a href="https://developers.cloudflare.com/changelog/post/2025-02-03-terraform-v5-provider/" rel="noopener noreferrer"&gt;ground-up, OpenAPI-generated rewrite&lt;/a&gt; — improving fast, but breaking and still stabilizing on a short cadence&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Schema faithfulness&lt;/td&gt;
&lt;td&gt;AWSCC is &lt;a href="https://registry.terraform.io/providers/hashicorp/awscc/latest/docs" rel="noopener noreferrer"&gt;auto-generated from the CloudFormation resource schema&lt;/a&gt;, so it tracks CloudFormation coverage closely and picks up new services fast&lt;/td&gt;
&lt;td&gt;Generated from OpenAPI; solid for resources, but Workers abstractions sit &lt;em&gt;above&lt;/em&gt; what HCL expresses&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What's visible in HCL + schema&lt;/td&gt;
&lt;td&gt;Most of the infra topology and operational contract — queues, roles, permissions, schedules, alarms, networks, data-plane wiring&lt;/td&gt;
&lt;td&gt;That a binding &lt;em&gt;exists&lt;/em&gt; — but its behavior lives in &lt;code&gt;wrangler.json&lt;/code&gt;, Worker code, D1 migrations, &lt;code&gt;package.json&lt;/code&gt;, framework conventions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Infra shape reconstructable from HCL?&lt;/td&gt;
&lt;td&gt;Infra shape: largely yes. App semantics: no.&lt;/td&gt;
&lt;td&gt;Infra shape: only partially. App semantics live elsewhere.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Terse-prompt tolerance&lt;/td&gt;
&lt;td&gt;High — common patterns are well-represented&lt;/td&gt;
&lt;td&gt;High for small stacks; drops once Workers + D1/R2/KV/DO/Queues/bindings interact&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The Cloudflare row isn't a knock — it's the crux. A Terraform file can tell an agent that a binding &lt;em&gt;exists&lt;/em&gt;. It can't, by itself, explain how that binding is used, where responsibility sits between Worker code and storage, how migrations relate to runtime behavior, or which framework conventions are doing invisible work. With AWS-via-Terraform, far more of the infrastructure topology and operational contract is visible through the same artifact: queues, permissions, schedules, alarms, networks, roles, policies, data-plane wiring. The application still matters — Lambda code, migrations, and API specs are not in the HCL either — but the &lt;em&gt;infra shape&lt;/em&gt; is less hidden from the agent. That's not because AWS is simpler; it's because AWS puts more of the operational contract into the same inspectable artifact.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cloudflare is a different bet, not the wrong one
&lt;/h2&gt;

&lt;p&gt;Workers, D1, R2, KV, Durable Objects, Queues, and framework-native deploy form a genuinely coherent platform. For cost, speed, and operational lightness it's one of the best balances available, and I reach for it happily.&lt;/p&gt;

&lt;p&gt;It just isn't optimized around the same kind of explicitness. Cloudflare &lt;em&gt;is&lt;/em&gt; improving Terraform — v5 is a real, OpenAPI-generated step up in coverage and API alignment — but its more interesting strategic move is elsewhere: &lt;strong&gt;intent-based, framework-native&lt;/strong&gt; provisioning (VoidZero/Vite, Astro), where you read intent from the framework and provision the infra. In its VoidZero acquisition framing, Cloudflare describes a future where &lt;a href="https://www.cloudflare.com/press/press-releases/2026/cloudflare-acquires-voidzero-to-build-the-future-of-the-ai-native-web/" rel="noopener noreferrer"&gt;a single Vite deploy command detects an app's need for a database or object store and provisions resources like D1 or R2&lt;/a&gt; with no manual dashboard step. That's a coherent future. It's simply a &lt;em&gt;different&lt;/em&gt; bet from "everything explicit as code," and it's why HCL-on-Cloudflare underdetermines the system today. Evaluate Cloudflare as framework-native infra, not as Terraform that happens to target the edge.&lt;/p&gt;

&lt;h2&gt;
  
  
  The safety boundary (this is not "walk away")
&lt;/h2&gt;

&lt;p&gt;Inspectable substrate is half the story. The other half is a loop that closes correctness with &lt;strong&gt;plan, policy, and tests&lt;/strong&gt; — not with the model's confidence. Two Terraform-specific studies make the same point: &lt;strong&gt;surface success is not the same as being correct.&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Looks like success&lt;/th&gt;
&lt;th&gt;What actually happened&lt;/th&gt;
&lt;th&gt;Study&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Structured schema knowledge made the model a much better &lt;em&gt;coder&lt;/em&gt;
&lt;/td&gt;
&lt;td&gt;Baseline overall success was &lt;strong&gt;27.1%&lt;/strong&gt;; with injection, technical validation reached &lt;strong&gt;75.3%&lt;/strong&gt; and overall success &lt;strong&gt;62.6%&lt;/strong&gt; — but intent alignment plateaued, the &lt;strong&gt;"Correctness–Congruence Gap."&lt;/strong&gt; Valid HCL ≠ the infra you asked for.&lt;/td&gt;
&lt;td&gt;&lt;a href="https://arxiv.org/abs/2512.14792" rel="noopener noreferrer"&gt;Nekrasov et al., 2025&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;One-shot security "fixes" made the flagged issue disappear &lt;strong&gt;83%&lt;/strong&gt; of the time&lt;/td&gt;
&lt;td&gt;Only &lt;strong&gt;10%&lt;/strong&gt; were actually clean, and &lt;strong&gt;57–71%&lt;/strong&gt; were &lt;strong&gt;deceptive fixes&lt;/strong&gt; — the scanner went green, the hole stayed open.&lt;/td&gt;
&lt;td&gt;&lt;a href="https://arxiv.org/abs/2606.26590" rel="noopener noreferrer"&gt;TerraProbe — Alsaid et al., 2026&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;TerraProbe in particular was run the way you &lt;em&gt;shouldn't&lt;/em&gt; ship: first-pass, minimally-prompted repairs, not a high-context, closed-loop workflow. That's the point — it's evidence for the loop, not against agents. Nekrasov et al. make the complementary case: even when structured knowledge improves validity, valid HCL still isn't the same as the infra you intended. &lt;strong&gt;A green check is not proof.&lt;/strong&gt; So the substrate feeds a closed loop:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Stage&lt;/th&gt;
&lt;th&gt;On failure&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;NL request&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Pull provider schema + module docs (via MCP)&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Generate HCL&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;fmt&lt;/code&gt; / &lt;code&gt;validate&lt;/code&gt; / &lt;code&gt;plan&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;plan diff → back to 3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Policy scan — Checkov / tfsec / OPA / Conftest&lt;/td&gt;
&lt;td&gt;violation → back to 3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;Cost estimate&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;Smoke test in an ephemeral env&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;Apply — with human review&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The loop is the point: a plan diff or a failed policy check routes &lt;em&gt;back to regeneration&lt;/em&gt;, never forward to apply.&lt;/p&gt;

&lt;p&gt;Pulumi is the interesting exception here: it keeps most of the AWS schema substrate (its providers are bridged from the same Terraform providers) but swaps HCL for a real language — so the agent gets the compiler and type system as an extra oracle. The risk is the mirror image of the power: real languages also let agents get clever, so you lean harder on &lt;code&gt;preview&lt;/code&gt;, policy-as-code, and tests to keep it declarative in spirit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this isn't an AWS victory lap
&lt;/h2&gt;

&lt;p&gt;I'm not returning to AWS because it's lighter. It isn't. Environment setup takes longer, CI/CD takes more work, cost control is harder than on a tightly scoped platform, and there's more surface area — more IAM, more policy, more wiring, more ways to be wrong.&lt;/p&gt;

&lt;p&gt;But that surface area is also &lt;strong&gt;more explicit&lt;/strong&gt;. For human-only development, the explicitness often felt like drag. For AI-driven development, it increasingly feels like leverage. The infrastructure I used to hide from the model is becoming the infrastructure the model needs to read.&lt;/p&gt;

&lt;p&gt;That's the real shift.&lt;/p&gt;

&lt;p&gt;Cloudflare may still win the intent-native path. Vercel and Supabase may still be the right answer when the contract is small and the backend should stay out of sight. But for enterprise requirements, complex systems, and agents that need to make grounded changes across infra &lt;em&gt;and&lt;/em&gt; application code, HCL-on-AWS is hard to beat — not because it's elegant, but because it leaves more of the system inspectable as code.&lt;/p&gt;

&lt;p&gt;Opaque platforms were useful when agents were context-bound. Explicit infrastructure becomes useful when agents are context-rich. We're crossing that line now.&lt;/p&gt;

&lt;p&gt;If you've made the opposite call — betting on intent-native, framework-first infra for agentic dev — I'd like to hear how it's holding up. That's the fork I'm least sure about.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>terraform</category>
      <category>aws</category>
      <category>cloudflare</category>
    </item>
    <item>
      <title>You Can't Review an Agent. You Can Review a Plan.</title>
      <dc:creator>Takafumi Endo</dc:creator>
      <pubDate>Mon, 06 Jul 2026 09:45:05 +0000</pubDate>
      <link>https://dev.to/gyu07/you-cant-review-an-agent-you-can-review-a-plan-5hgp</link>
      <guid>https://dev.to/gyu07/you-cant-review-an-agent-you-can-review-a-plan-5hgp</guid>
      <description>&lt;p&gt;A harness for AI-era Terraform.&lt;/p&gt;

&lt;p&gt;I'm building one. For a while now I've been developing a harness for infrastructure-as-code as a private SDK and compiler — the layer that sits between whoever proposes a change (a person, an agent, CI) and whatever actually reaches production. This post isn't the tool. It's the thinking underneath it, and the few pieces I've become most convinced by while building it. (Notes from inside the work — where I've landed so far, not advice.)&lt;/p&gt;

&lt;p&gt;The problem that sent me down this road is easy to state and easy to underrate.&lt;/p&gt;

&lt;p&gt;A version of it happened recently. An agent fixed some Terraform; the PR read clean — tidy diff, sensible resource names, a plan output that looked exactly like what I'd asked for. It got approved. And then, at apply time, a &lt;em&gt;different&lt;/em&gt; plan ran than the one that was reviewed: apply had re-planned against state that moved in between, and the diff that touched production wasn't quite the diff anyone had read.&lt;/p&gt;

&lt;p&gt;Nothing broke, that time. But that near-miss is the whole reason the harness exists.&lt;/p&gt;

&lt;p&gt;Because the danger was never "the agent writes bad HCL." Agents write perfectly good HCL; I let them. The danger is the distance between &lt;em&gt;the plan a human reviewed&lt;/em&gt; and &lt;em&gt;the plan that actually runs&lt;/em&gt; — and once agents are the ones proposing changes at volume, that distance is the thing I most want to nail shut.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Where I've landed for now (and expect to keep revising):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What AI-era IaC needs isn't AI that can &lt;em&gt;apply&lt;/em&gt;. It's a structure where every change — human or agent — is evaluated at the &lt;strong&gt;same boundary&lt;/strong&gt;, and only a reviewed plan ships.&lt;/li&gt;
&lt;li&gt;The unit of trust isn't the agent. It's a specific, reviewed &lt;strong&gt;plan&lt;/strong&gt;, bound byte for byte.&lt;/li&gt;
&lt;li&gt;You can't review an agent. You can only review a plan.&lt;/li&gt;
&lt;li&gt;Instructions to an agent can be broken. A CI gate can't be talked out of it. Put guidance in the prompt; put the guarantee in the gate.&lt;/li&gt;
&lt;li&gt;Terraform/OpenTofu don't go away. You wrap them in a harness; you don't replace them.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Your repo has non-human authors now
&lt;/h2&gt;

&lt;p&gt;For years IaC had a comfortable shape: a human writes HCL, a human reads the plan, the same human or a teammate runs apply. The author and the applier were the same kind of thing — a person: slow, few, accountable. Reviewing the author and reviewing the change were nearly the same act.&lt;/p&gt;

&lt;p&gt;That assumption is quietly breaking, and it's breaking because of who's holding the keyboard. GitHub's &lt;a href="https://github.blog/changelog/2025-09-25-copilot-coding-agent-is-now-generally-available/" rel="noopener noreferrer"&gt;Copilot coding agent&lt;/a&gt;, OpenAI's Codex, Claude Code in GitHub Actions — agents now open issues, push branches, and answer review comments &lt;em&gt;in the repo&lt;/em&gt;, and standards like &lt;a href="https://modelcontextprotocol.io/docs/getting-started/intro" rel="noopener noreferrer"&gt;MCP&lt;/a&gt; and &lt;a href="https://openai.com/index/agentic-ai-foundation/" rel="noopener noreferrer"&gt;&lt;code&gt;AGENTS.md&lt;/code&gt;&lt;/a&gt; are there so they can read your docs, state, and conventions and act on them. The detail that matters isn't any one of those tools; it's the implication: the IaC repo stopped being a place only humans edit. It's a place agents &lt;em&gt;propose changes&lt;/em&gt; too.&lt;/p&gt;

&lt;p&gt;And here's the part you can't review your way out of: you can read one agent's PR carefully, but you cannot review &lt;em&gt;the agent&lt;/em&gt;. It isn't a colleague who earns standing over quarters; it's a process that opens the next PR just as confidently whether or not the last one was sound. Confidence is flat. Correctness isn't.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reviewed and applied are two different facts
&lt;/h2&gt;

&lt;p&gt;So "get the agent to write Terraform" solves the easy half. The half that matters is: &lt;em&gt;what evaluates the change, and is it the same thing for everyone?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://developer.hashicorp.com/terraform/cli/commands/plan" rel="noopener noreferrer"&gt;&lt;code&gt;terraform plan&lt;/code&gt;&lt;/a&gt; reads remote state and produces a diff against your config. Save that plan and apply it, and Terraform runs exactly those actions with no further prompts. &lt;em&gt;Don't&lt;/em&gt; save it — re-plan at apply time — and the gap reopens: state may have drifted, a variable may have moved, and the diff that runs is no longer the diff that was read. That's the near-miss from the top, in one sentence.&lt;/p&gt;

&lt;p&gt;So the thing worth binding isn't the PR. It's the plan. Call it a &lt;strong&gt;plan fingerprint&lt;/strong&gt; — an identifier over the saved plan, bound together with the findings it was reviewed against and a signature, so apply can refuse anything whose fingerprint doesn't match an approval record. That core — plan, findings, signature — is the part I lean on today. What I &lt;em&gt;want&lt;/em&gt; folded into the identifier is wider: the plan JSON, the variables, the policy bundle, the workspace, the provider lock, and the toolchain (Terraform/OpenTofu version, provider versions, runner image, state version), because a plan is only as reproducible as the environment that produced it. I'm still widening what goes in; the honest state is a solid core and a growing edge.&lt;/p&gt;

&lt;p&gt;One caveat worth stating early: the fingerprint can be public, but the artifact behind it probably shouldn't be. Saved plans and plan JSON can carry sensitive values, so treat them as sensitive build artifacts, not something to paste into a PR comment.&lt;/p&gt;

&lt;p&gt;The fact I want the gate to be able to state flatly looks less like a paragraph and more like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;IAC.PLAN_DRIFT   approval=#4821   severity=high   quality=verified
  claim:   applied plan ≠ approved plan
  cause:   apply re-planned against state changed after approval
  bind:    plan_hash + findings + signature   (+ vars/policy/toolchain: target)
  next:    reject apply | re-request approval on new fingerprint
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A pattern-match can only hedge — "this might not be the reviewed plan, please double-check." A fingerprint comparison states one thing flatly: this is not the plan you approved. The rigorous check is the &lt;em&gt;shorter&lt;/em&gt; one.&lt;/p&gt;

&lt;h2&gt;
  
  
  The harness, in four boxes
&lt;/h2&gt;

&lt;p&gt;I keep drawing the same four boxes, so I've stopped pretending they're incidental.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Author      what do we want / why       (humans and agents write here)
Compiler    how is it implemented       (HCL today: Terraform / OpenTofu)
Observer    what is true right now       (state, drift, DB, audit, cost, history)
Evaluator   are we allowed to do it      (policy, fingerprint, approval, signature)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Author&lt;/strong&gt; — intent. &lt;em&gt;What&lt;/em&gt; do we want, and &lt;em&gt;why&lt;/em&gt;. Both humans and agents write here, freely.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compiler&lt;/strong&gt; — translation. Intent into whichever engine you run — swappable by design, though today that's the HCL engines (Terraform/OpenTofu).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Observer&lt;/strong&gt; — reality. More on this below; it's the layer most people under-build.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Evaluator&lt;/strong&gt; — judgment. The &lt;em&gt;only&lt;/em&gt; layer that can say no.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The load-bearing idea: Author and Compiler can be as smart or as sloppy as they like, because nothing they produce reaches production without clearing the Evaluator. You move safety out of "trust the writer" and into "verify the plan" — which is precisely the move that survives the writer becoming an agent. Most of what I'm building lives in the Compiler and Evaluator boxes; the other two are where it plugs into the world.&lt;/p&gt;

&lt;p&gt;And to be clear about the engines underneath: Terraform, OpenTofu, and Pulumi are genuinely excellent — I reach for them daily, and the harness wraps them rather than replacing them. But none of them was designed AI-first; each still assumes a human author, a human reviewer, and a human at the apply prompt. That's not a criticism so much as headroom. The engines are great, and there's still real room for the layer above them — and, eventually, the engines themselves — to evolve into something an agent can be trusted to drive.&lt;/p&gt;

&lt;h3&gt;
  
  
  Observer isn't monitoring
&lt;/h3&gt;

&lt;p&gt;The layer I see skipped most is Observer, usually collapsed into "we have some dashboards." But in an AI-era harness it's doing something specific: giving every actor — human or agent — a grounded picture of the world before they propose against it. Drift was already a problem when only humans touched the cloud; once agents and external tools change things through the API, an out-of-band change the Observer can't see is a blind spot the whole harness inherits. (Recent research is even throwing LLM agents at &lt;em&gt;reconciling&lt;/em&gt; out-of-band changes back into code, which tells you it isn't solved.)&lt;/p&gt;

&lt;p&gt;What I want Observer to actually gather:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Terraform/OpenTofu state, and cloud provider inventory&lt;/li&gt;
&lt;li&gt;drift detection, and CloudTrail / audit logs&lt;/li&gt;
&lt;li&gt;DB schema state&lt;/li&gt;
&lt;li&gt;cost data, and security posture&lt;/li&gt;
&lt;li&gt;policy-violation history, and incident / change history&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With that, an agent references not just "the current code" but "the real environment," "the intent," and "what went wrong last time."&lt;/p&gt;

&lt;h2&gt;
  
  
  One gate, no side doors
&lt;/h2&gt;

&lt;p&gt;A harness only works if there's no side door. The same boundary has to apply to the human on their laptop, the agent in the repo, the local CLI, and the CI runner — identically.&lt;/p&gt;

&lt;p&gt;Concretely, three disciplines carry most of it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;One policy bundle, everywhere.&lt;/strong&gt; The rules that run in &lt;code&gt;pre-commit&lt;/code&gt; on a laptop are the same rules, same version, that gate the plan in CI. If local and CI can disagree, the local path becomes the bypass.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Approve a fingerprint, not a PR.&lt;/strong&gt; Save the plan, hash it, and let apply consume &lt;em&gt;only&lt;/em&gt; a plan whose fingerprint carries a signed approval. A PR can be approved and then have its plan change underneath it; a fingerprint can't.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Split the plan role from the apply role.&lt;/strong&gt; The job that plans can propose; the job that applies accepts nothing but an approved fingerprint. HCP Terraform already leans this way — run stages, policy checks, run tasks, separable plan/apply permissions. The harness isn't new primitives; it's refusing to let anyone route around them.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;BEFORE — raw AI x Terraform

  AI Agent --HCL--&amp;gt; terraform apply --&amp;gt; production
  (review, policy, plan-binding, role split: all fuzzy)


AFTER — through the harness

  AI Agent / Human
     | intent
     v
  Author --&amp;gt; Compiler --&amp;gt; terraform plan / tofu plan
                              |
                              v
                         Evaluator (policy + fingerprint)
                              |
                              v
                 Signed approval + plan fingerprint
                              |
                              v
                        Apply role --------&amp;gt; production
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  A rule in a prompt is only a wish
&lt;/h2&gt;

&lt;p&gt;The failure mode I most want to avoid is safety that lives in a sentence.&lt;/p&gt;

&lt;p&gt;"Please don't create a public S3 bucket" in an &lt;code&gt;AGENTS.md&lt;/code&gt; is a wish. &lt;a href="https://openpolicyagent.org/docs/terraform" rel="noopener noreferrer"&gt;OPA&lt;/a&gt; turning the plan JSON into a decision that fails CI is a fact. Same intent, completely different load-bearing capacity. &lt;code&gt;AGENTS.md&lt;/code&gt; and MCP make the Author and Compiler layers &lt;em&gt;stronger&lt;/em&gt; — better context, fewer dumb diffs — but they don't make anything &lt;em&gt;safe&lt;/em&gt;; they make it more likely to be &lt;em&gt;good&lt;/em&gt;, which is not the same claim. Instructions can be broken — by a prompt injection, a confused tool call, a model that just… doesn't. So: put the guidance in &lt;code&gt;AGENTS.md&lt;/code&gt;, and the guarantee in the Evaluator.&lt;/p&gt;

&lt;p&gt;The Evaluator rules I reach for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;diffs containing &lt;code&gt;delete&lt;/code&gt; / &lt;code&gt;replace&lt;/code&gt; / &lt;code&gt;drop&lt;/code&gt; → no auto-apply&lt;/li&gt;
&lt;li&gt;public ingress → denied without an explicit, logged exception&lt;/li&gt;
&lt;li&gt;high DB sensitivity → encryption, audit log, backup, private network required, or the plan fails&lt;/li&gt;
&lt;li&gt;plan fingerprint ≠ approval record → apply refused&lt;/li&gt;
&lt;li&gt;missing / expired / under-privileged approval → apply refused&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One rule I want but haven't shipped: a stricter policy profile for agent-authored changes than for human ones — routing by author type mechanically, rather than trusting anyone less by vibe.&lt;/p&gt;

&lt;p&gt;And once agents sit inside CI, the attack surface grows (OWASP keeps an &lt;a href="https://genai.owasp.org/2025/12/09/owasp-genai-security-project-releases-top-10-risks-and-mitigations-for-agentic-ai-security/" rel="noopener noreferrer"&gt;Agentic-AI risk list&lt;/a&gt; for exactly this). Four minimums I wouldn't ship without:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Separate read-only tools from mutating tools.&lt;/strong&gt; Let the agent read provider docs and state; don't hand it apply-class operations directly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Separate plan from apply.&lt;/strong&gt; The agent proposes up to a plan. Apply goes through signed approval and a CI gate.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Separate CI job permissions.&lt;/strong&gt; Plan jobs get the plan role; apply jobs get the apply role and accept nothing but an approved plan.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Put policy in code, not the prompt.&lt;/strong&gt; Don't &lt;em&gt;ask&lt;/em&gt; for no public S3 — detect it in the plan and fail.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The through-line: stop asking whether to trust the agent, and make its output mechanically evaluable instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  A &lt;code&gt;destroy&lt;/code&gt; is the &lt;code&gt;DROP COLUMN&lt;/code&gt; of infrastructure
&lt;/h2&gt;

&lt;p&gt;This is where it stops being a generic "AI + Terraform" post, because the database people already solved a version of it.&lt;/p&gt;

&lt;p&gt;A &lt;code&gt;DROP COLUMN&lt;/code&gt; is a destructive change: irreversible, backward-incompatible, exactly what you don't ship in one move. &lt;a href="https://atlasgo.io/lint/analyzers" rel="noopener noreferrer"&gt;Atlas&lt;/a&gt; detects destructive changes and fails CI over them; the expand-contract pattern — add the new shape, migrate, and only &lt;em&gt;then&lt;/em&gt;, separately, remove the old — exists so the dangerous half never rides along with the safe half. Tools like Bytebase wrap the whole thing in review, approval, rollout, and audit rather than letting anyone touch prod directly.&lt;/p&gt;

&lt;p&gt;Infrastructure has the same hazards and mostly none of the discipline. A &lt;code&gt;destroy&lt;/code&gt;, a forced replace, a deleted stateful resource — these &lt;em&gt;are&lt;/em&gt; the &lt;code&gt;DROP COLUMN&lt;/code&gt; of infra. So here's the rule I borrow: &lt;strong&gt;destructive infra changes shouldn't be one-shot.&lt;/strong&gt; Today the harness enforces that as a hard stop — a destructive change is a critical hazard that can't clear the gate without explicit human approval. The direction I'm pushing toward is the database's fuller two-phase discipline: expand, migrate, then contract, each step approved on its own. The stop exists now; the mechanical two-step is where I'm headed.&lt;/p&gt;

&lt;p&gt;Make it concrete. These three simplified plan entries are not equal, even if a shallow policy sees them as the same category of destructive change:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;replace&lt;/span&gt;   &lt;span class="nx"&gt;aws_db_instance&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;
&lt;span class="nx"&gt;delete&lt;/span&gt;    &lt;span class="nx"&gt;aws_s3_bucket&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;logs&lt;/span&gt;
&lt;span class="nx"&gt;destroy&lt;/span&gt;   &lt;span class="nx"&gt;aws_kms_key&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;customer_data&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A generic "no destructive changes" rule treats them as one category. But the Evaluator shouldn't reason about the &lt;em&gt;verb&lt;/em&gt; — it should reason about what the resource &lt;em&gt;means&lt;/em&gt;. Here's the reading I'm building the Evaluator toward — polished more than it currently is, but the direction is the point:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;destroy&lt;/span&gt; &lt;span class="nx"&gt;aws_kms_key&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;customer_data&lt;/span&gt;
  &lt;span class="nx"&gt;classification&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;confidential&lt;/span&gt;
  &lt;span class="nx"&gt;stateful&lt;/span&gt;       &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;recovery&lt;/span&gt;       &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;hard&lt;/span&gt;          &lt;span class="c1"&gt;# key loss = data loss&lt;/span&gt;
  &lt;span class="nx"&gt;blast_radius&lt;/span&gt;   &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;production&lt;/span&gt;
  &lt;span class="err"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;DENY&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt; &lt;span class="nx"&gt;expand-contract&lt;/span&gt; &lt;span class="nx"&gt;required&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="nx"&gt;-&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;re-encrypt&lt;/span&gt; &lt;span class="nx"&gt;-&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;retire&lt;/span&gt; &lt;span class="nx"&gt;old&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;each&lt;/span&gt; &lt;span class="nx"&gt;approved&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;delete&lt;/span&gt; &lt;span class="nx"&gt;aws_s3_bucket&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;logs&lt;/span&gt;
  &lt;span class="nx"&gt;classification&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;internal&lt;/span&gt;
  &lt;span class="nx"&gt;stateful&lt;/span&gt;       &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;recovery&lt;/span&gt;       &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;medium&lt;/span&gt;        &lt;span class="c1"&gt;# lifecycle-archived elsewhere&lt;/span&gt;
  &lt;span class="nx"&gt;blast_radius&lt;/span&gt;   &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;production&lt;/span&gt;
  &lt;span class="err"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;HOLD&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt; &lt;span class="nx"&gt;explicit&lt;/span&gt; &lt;span class="nx"&gt;retention&lt;/span&gt; &lt;span class="nx"&gt;check&lt;/span&gt; &lt;span class="err"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;second&lt;/span&gt; &lt;span class="nx"&gt;approval&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the gate isn't guessing from a keyword; it's deciding from the resource's meaning — at least, that's the target. I want to be honest about where the line falls in what I've actually built: the database-side version of this is real — a schema's sensitivity already propagates into required controls — while deriving infra-side labels like &lt;code&gt;recovery&lt;/code&gt; and &lt;code&gt;blast_radius&lt;/code&gt; to the precision shown above is still partly ahead of me, not fully behind. The move I like even more is where those labels &lt;em&gt;come from&lt;/em&gt;: propagate intent across the boundary. If a schema is labeled confidential, deletion-protected, RPO-strict, that intent shouldn't stop at the database —&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;DB&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt; &lt;span class="nx"&gt;intent&lt;/span&gt;
  &lt;span class="nx"&gt;classification&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;confidential&lt;/span&gt;
  &lt;span class="nx"&gt;rpo&lt;/span&gt;            &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5m&lt;/span&gt;&lt;span class="nx"&gt;in&lt;/span&gt;
  &lt;span class="nx"&gt;deletion&lt;/span&gt;       &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;protected&lt;/span&gt;
        &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;propagate&lt;/span&gt;
        &lt;span class="nx"&gt;v&lt;/span&gt;
&lt;span class="nx"&gt;Infra&lt;/span&gt; &lt;span class="nx"&gt;requirement&lt;/span&gt;
  &lt;span class="nx"&gt;KMS&lt;/span&gt; &lt;span class="nx"&gt;encryption&lt;/span&gt; &lt;span class="err"&gt;.&lt;/span&gt; &lt;span class="nx"&gt;PITR&lt;/span&gt; &lt;span class="nx"&gt;backup&lt;/span&gt; &lt;span class="err"&gt;.&lt;/span&gt; &lt;span class="nx"&gt;deletion&lt;/span&gt; &lt;span class="nx"&gt;protection&lt;/span&gt;
  &lt;span class="nx"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;endpoint&lt;/span&gt; &lt;span class="err"&gt;.&lt;/span&gt; &lt;span class="nx"&gt;audit&lt;/span&gt; &lt;span class="nx"&gt;logging&lt;/span&gt;
        &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;enforce&lt;/span&gt;
        &lt;span class="nx"&gt;v&lt;/span&gt;
&lt;span class="nx"&gt;Policy&lt;/span&gt; &lt;span class="nx"&gt;gate&lt;/span&gt;
  &lt;span class="nx"&gt;any&lt;/span&gt; &lt;span class="nx"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;these&lt;/span&gt; &lt;span class="nx"&gt;unset&lt;/span&gt;  &lt;span class="nx"&gt;-&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;  &lt;span class="nx"&gt;plan&lt;/span&gt; &lt;span class="nx"&gt;fails&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Put plainly: the same way you don't let a &lt;code&gt;DROP COLUMN&lt;/code&gt; through on a whim, you don't let a &lt;code&gt;destroy&lt;/code&gt; through on an agent's confidence. Most "AI IaC" writing stops at "the agent can write HCL." The interesting seam is one layer down — where application semantics, DB constraints, and infra safety turn out to be the same dependency seen from different angles.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sign the plan, not the checkmark
&lt;/h2&gt;

&lt;p&gt;Software supply chains already made this move — SLSA provenance, &lt;a href="https://docs.github.com/en/actions/concepts/security/artifact-attestations" rel="noopener noreferrer"&gt;artifact attestations&lt;/a&gt;, signed build provenance: verifiable claims about who made what, under whose authority. If a build artifact deserves that, an infra change deserves it at least as much, and an AI-involved one most of all.&lt;/p&gt;

&lt;p&gt;So an approval isn't a green checkmark. It's a signed statement — the same core-vs-target split as the fingerprint above: the load-bearing fields today are the plan, its findings, and the signature; the fuller record below is the shape I'm building toward:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;commit SHA&lt;/td&gt;
&lt;td&gt;which source change&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;plan hash&lt;/td&gt;
&lt;td&gt;which plan was approved&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;plan JSON hash&lt;/td&gt;
&lt;td&gt;the exact diff evaluated&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;policy bundle hash&lt;/td&gt;
&lt;td&gt;which rules judged it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;workspace / env&lt;/td&gt;
&lt;td&gt;dev / stg / prod&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;actor&lt;/td&gt;
&lt;td&gt;AI, human, CI job, service account&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;reviewer identity&lt;/td&gt;
&lt;td&gt;who approved&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;approval reason&lt;/td&gt;
&lt;td&gt;why it was allowed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;expiry&lt;/td&gt;
&lt;td&gt;how long the approval is valid&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;signature&lt;/td&gt;
&lt;td&gt;tamper-evidence, non-repudiation&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Now "who approved this change, against which plan, having seen which policy result" has an answer you can &lt;em&gt;verify&lt;/em&gt; months later — not reconstruct from memory and Slack scrollback. Call it non-repudiation for the AI era.&lt;/p&gt;

&lt;h2&gt;
  
  
  Objections I'd raise myself
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;"This is just CI/CD with extra steps."&lt;/strong&gt; Partly. The step that carries the weight is binding approval to a &lt;em&gt;fingerprint&lt;/em&gt;, not a PR. That one substitution is most of the value.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Agents can be trusted more than this implies."&lt;/strong&gt; Maybe, eventually. But the harness costs about the same whether the agent is excellent or awful, and it fails safe either way. I'd rather not stake the blast radius on the model being good this week.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"plan/apply roles and policy gates already exist."&lt;/strong&gt; They do. I'm not claiming new primitives — I'm claiming one boundary for &lt;em&gt;everyone&lt;/em&gt;, human and agent, CLI and CI, with no privileged bypass.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Expand-contract for infra is over-engineering."&lt;/strong&gt; For a weekend project, yes. For anything with a stateful resource you'd grieve losing, the DB people already learned this the expensive way, and I'd rather borrow the scar than earn my own.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where the harness leaks
&lt;/h2&gt;

&lt;p&gt;Three honest edges. &lt;strong&gt;The Compiler layer fragments&lt;/strong&gt; — &lt;a href="https://www.cncf.io/projects/opentofu/" rel="noopener noreferrer"&gt;OpenTofu&lt;/a&gt; is going its own way as a CNCF project, and Pulumi and CDKTF aren't HCL at all; the harness only helps if the Evaluator can read every engine's plan format. &lt;strong&gt;Drift is a genuinely open Observer problem&lt;/strong&gt; — an Observer that can't see out-of-band change is a harness with a blind spot. And &lt;strong&gt;every gate is a place a rushed team adds a bypass&lt;/strong&gt;; an "emergency skip" used weekly is theater. If the escape hatch isn't itself logged, signed, and rare, none of the above holds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Self-driving isn't unattended
&lt;/h2&gt;

&lt;p&gt;The last decade of IaC went hand-crafted → declared. The next one is going declared → self-driving — and "self-driving" is going to get read as "unattended apply." I think that's the wrong reading. Self-driving infrastructure isn't an agent holding the apply button. It's an agent proposing freely into a harness where only a reviewed, fingerprinted, signed plan reaches production.&lt;/p&gt;

&lt;p&gt;What matters in AI-era IaC was never that the agent can write HCL. It's that whatever it writes passes through intent, implementation, observation, and evaluation — Author, Compiler, Observer, Evaluator — and that only a reviewed plan lands in prod.&lt;/p&gt;

&lt;p&gt;Don't put the caution in the prompt. Put it in the gate.&lt;/p&gt;

&lt;p&gt;That's the shape I keep building toward — still wrong in places I haven't found yet.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>terraform</category>
      <category>devops</category>
      <category>architecture</category>
    </item>
    <item>
      <title>AI Agents Don't Need a Monorepo. They Need a Readable Codebase</title>
      <dc:creator>Takafumi Endo</dc:creator>
      <pubDate>Thu, 02 Jul 2026 01:56:10 +0000</pubDate>
      <link>https://dev.to/gyu07/ai-agents-dont-need-a-monorepo-they-need-a-readable-codebase-4c6f</link>
      <guid>https://dev.to/gyu07/ai-agents-dont-need-a-monorepo-they-need-a-readable-codebase-4c6f</guid>
      <description>&lt;p&gt;For an AI, a monorepo is good — concentrated context is more efficient, everything in one place. But lately I've stopped asking whether a project is monorepo or multirepo. &lt;strong&gt;The repo boundary is no longer the unit I care about most. The coordination boundary is&lt;/strong&gt; — and underneath even that, one plainer thing: whether the codebase is &lt;em&gt;readable to the agent&lt;/em&gt; at all.&lt;/p&gt;

&lt;p&gt;By &lt;em&gt;readable&lt;/em&gt; I mean something concrete: the agent can infer responsibility from names, find the right files fast, see where boundaries are, and verify a change with predictable commands. That holds in a monorepo and a multirepo alike — and it decides more than the repo shape does.&lt;/p&gt;

&lt;p&gt;The situation I'm writing about is the solo-or-small-team case: one person orchestrating many agents, with little time spent negotiating boundaries with other humans. So the question I ask first is — &lt;strong&gt;can I run several agents on this codebase at once without them, or me, becoming the thing everyone waits on?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I once wrote that &lt;a href="https://dev.to/gyu07/why-ai-agents-make-me-reach-for-sqlite-4dh0"&gt;the writer moved back to the edge&lt;/a&gt; — agents living inside the work environment, pulling state toward them. This is the sequel. The writer didn't just relocate. It &lt;em&gt;multiplied&lt;/em&gt;. When writers multiply, the constraint that matters in a design changes too.&lt;/p&gt;

&lt;p&gt;Not advice, and not "monorepo is over." One person's notes, written mostly to find where they're wrong.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Where I've landed for now (and expect to revise):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;monorepo is still good for AI — concentrated context is more efficient.&lt;/li&gt;
&lt;li&gt;But the real question isn't monorepo vs multirepo. It's &lt;strong&gt;how little your structure obstructs human + agent parallelism.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;On a team, human coordination is the bottleneck, and it caps how many agents any one person can actually run.&lt;/li&gt;
&lt;li&gt;Solo, with clear responsibilities and in/out contracts, multirepo works just as well — clone into a parent directory and implement across it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Before any of that&lt;/strong&gt;: directory layout, naming, and feature names have to be AI-readable. That precondition matters more than the repo shape — in a monorepo &lt;em&gt;and&lt;/em&gt; a multirepo.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  monorepo helps — when "same place" is machine-readable
&lt;/h2&gt;

&lt;p&gt;monorepo genuinely helps agents. Code, types, tests, dependencies, design decisions in one place; change an API and its downstream app in one commit; reason across boundaries. That's real.&lt;/p&gt;

&lt;p&gt;But Nx keeps warning that repos merely dumped into one directory aren't a monorepo — they're &lt;em&gt;code collocation&lt;/em&gt;: no boundaries, unstructured tooling, irrelevant tests running. What turns collocation into a benefit is the layer on top — affected-only build/test, a dependency graph, CODEOWNERS, enforced API boundaries.&lt;/p&gt;

&lt;p&gt;So the value isn't "same place." It's &lt;strong&gt;same place, made machine-readable.&lt;/strong&gt; That distinction runs through everything below.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bottleneck is coordination, not the repo
&lt;/h2&gt;

&lt;p&gt;When several people share one repo, their work collides. To avoid that, tasks, responsibilities, and review units get carved to fit human collaboration. That's correct — &lt;em&gt;because the human is the bottleneck&lt;/em&gt;. But it also caps how many agents one developer can run. If you can drive ten agents but review-and-consensus only clears three, your effective parallelism is three.&lt;/p&gt;

&lt;p&gt;When one person owns the whole repo, that ceiling lifts. You dispatch agents on your own judgment, across features, with no review queue in the way. You spend the parallelism directly. A concrete afternoon looks like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Agent A&lt;/strong&gt; — update the API contract&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agent B&lt;/strong&gt; — fix the frontend calls to match&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agent C&lt;/strong&gt; — write the migration and seed data&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agent D&lt;/strong&gt; — chase the failing tests and typecheck&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Four changes moving at once, all mine to reconcile — no one to sync with first. That's the shape of work this whole post is about.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Team, shared repo&lt;/th&gt;
&lt;th&gt;Solo, many agents&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Bottleneck&lt;/td&gt;
&lt;td&gt;Human coordination (review, consensus)&lt;/td&gt;
&lt;td&gt;Attention, verification, tooling&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What sets the repo boundary&lt;/td&gt;
&lt;td&gt;Coordination units&lt;/td&gt;
&lt;td&gt;Whatever the agent reads well&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Effective parallelism&lt;/td&gt;
&lt;td&gt;Capped by review throughput&lt;/td&gt;
&lt;td&gt;Capped by your attention, machine, and tooling&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Right shape leans toward&lt;/td&gt;
&lt;td&gt;Boundaries that fit people&lt;/td&gt;
&lt;td&gt;Boundaries that fit agents&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That gap is why the monorepo-vs-multirepo axis stopped feeling central to me. The center is the &lt;strong&gt;coordination boundary&lt;/strong&gt; — and it doesn't have to line up with the repo boundary. Teams cut repos to match coordination. Solo-with-many-agents, coordination nearly vanishes, so you can draw repo lines however you like. The only constraint left is: &lt;strong&gt;can the agent explore, change, and verify without getting lost?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Claude Code and Codex already assume this — worktrees running a feature and a bugfix as separate checkouts at once, tasks executing in independent sandboxes. One human orchestrating many agents, treated as a first-class workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  At scale, "sees everything" backfires
&lt;/h2&gt;

&lt;p&gt;Counterintuitively, seeing everything in a big monorepo often hurts. Anthropic's large-codebase guidance notes that a giant root &lt;code&gt;CLAUDE.md&lt;/code&gt; and stray file reads fill the context window and degrade cost and performance; the fix is per-directory &lt;code&gt;CLAUDE.md&lt;/code&gt;, read-deny rules, code intelligence, sparse worktrees. Research points the same way — agents don't solve more just by reading more, because broad context doesn't automatically become useful structure, and can even drop performance.&lt;/p&gt;

&lt;p&gt;What agents need isn't full visibility. It's &lt;strong&gt;fast paths to the structure that matters&lt;/strong&gt; — true in monorepo and multirepo alike. The problem was never size by itself. It's &lt;em&gt;undirected&lt;/em&gt; size. Large is fine when the map is good; large without a map is just noise.&lt;/p&gt;

&lt;h2&gt;
  
  
  Before any of this: the structure has to be readable
&lt;/h2&gt;

&lt;p&gt;Here's the part I actually care about most, and it sits underneath everything above. Whether you land on monorepo or multirepo, none of it works if the directory layout, the naming conventions, and even the &lt;em&gt;feature names&lt;/em&gt; aren't legible to the agent. That precondition is more fundamental than the repo shape — it's what makes a monorepo worth its concentration and a multirepo worth stitching together.&lt;/p&gt;

&lt;p&gt;Repo structure is itself a &lt;em&gt;fact you hand the agent&lt;/em&gt; — &lt;a href="https://dev.to/gyu07/rigor-compresses-why-ai-agents-need-graphs-not-more-context-5404"&gt;rigor compresses&lt;/a&gt;. Vague structure burns tokens on guesses; machine-readable structure lands in a line. Reports back this up — LLM-friendly structure maps improving accuracy and cutting completion time, text+structure hybrids trimming input tokens, &lt;code&gt;AGENTS.md&lt;/code&gt; alone reducing runtime and token spend. The numbers are workload-dependent, so I don't take them literally, but the direction is consistent.&lt;/p&gt;

&lt;p&gt;So the order of operations is the opposite of how the debate usually runs. Pick the repo shape last. Make the structure readable first.&lt;/p&gt;

&lt;h2&gt;
  
  
  The repo is physical. The workspace is operational.
&lt;/h2&gt;

&lt;p&gt;Readable first — then the question becomes how to keep it readable when the code is split across repos. The unit shifts from repo to workspace. Claude Code's &lt;code&gt;--add-dir&lt;/code&gt; pulls sibling repos into one session; Nx points at "synthetic monorepo" infrastructure to view across repos. The idea: &lt;strong&gt;repos can be split, but the agent sees one meaningful workspace.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;My old line — clone several repos locally, open the parent directory, implement across them in one context — fits here. But the parent directory alone is weak. What earns its keep is the thin layer above it:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;What it is&lt;/th&gt;
&lt;th&gt;Why the agent needs it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;AGENTS.md&lt;/code&gt; / &lt;code&gt;CLAUDE.md&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Instructions at root + per directory&lt;/td&gt;
&lt;td&gt;Tells the agent where to look, not just what the project is&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Workspace manifest&lt;/td&gt;
&lt;td&gt;Which repos exist, how they connect&lt;/td&gt;
&lt;td&gt;Turns a folder of clones into one workspace&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cross-repo dependency map&lt;/td&gt;
&lt;td&gt;What depends on what&lt;/td&gt;
&lt;td&gt;Lets the agent trace impact across repos&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unified verify commands&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;test&lt;/code&gt; / &lt;code&gt;lint&lt;/code&gt; / &lt;code&gt;typecheck&lt;/code&gt; / &lt;code&gt;build&lt;/code&gt; / &lt;code&gt;dev&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;One way to check work, everywhere&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Contracts&lt;/td&gt;
&lt;td&gt;API schema, event schema, migrations&lt;/td&gt;
&lt;td&gt;Makes boundaries machine-checkable, not assumed&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;With that layer, multirepo behaves like a synthetic monorepo. Without it, even a clean monorepo wastes context.&lt;/p&gt;

&lt;h2&gt;
  
  
  Objections I'd raise myself
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;"multirepo is just heavier ops."&lt;/strong&gt; Half true — migration fan-out, cross-repo visibility, dependency tracking. My claim is conditional: when the coordination boundary disappears, multirepo + parent workspace beats a team-shaped monorepo &lt;em&gt;more often&lt;/em&gt;. Not always.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"contracts rot."&lt;/strong&gt; The load-bearing objection. The moment a contract drifts from the code, the synthetic workspace is a fiction. So contracts have to be enforced in CI, not kept as a promise humans remember.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"don't generalize solo to teams."&lt;/strong&gt; I don't. Teams need coordination; carving repos to fit it is correct. My one point: &lt;strong&gt;the right shape moves with coordination cost.&lt;/strong&gt; Fewer people, more agents per person → the scale tips toward workspace + contracts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"synthetic monorepo is fragile."&lt;/strong&gt; It is. Clones under a parent directory collapse fast without a manifest and a dependency map. Tooling is still thin here, and I'm feeling my way.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A checklist that earns its place
&lt;/h2&gt;

&lt;p&gt;Not exhaustive — roughly the order I've found things matter.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Area&lt;/th&gt;
&lt;th&gt;Do this&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Structure&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;apps/&lt;/code&gt; &lt;code&gt;packages/&lt;/code&gt; &lt;code&gt;services/&lt;/code&gt; &lt;code&gt;libs/&lt;/code&gt; &lt;code&gt;contracts/&lt;/code&gt; — responsibility legible from the name&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agent instructions&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;AGENTS.md&lt;/code&gt; / &lt;code&gt;CLAUDE.md&lt;/code&gt; at root and per directory&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Contracts&lt;/td&gt;
&lt;td&gt;API / event schema, DB migrations, input/output contracts, made explicit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Verify&lt;/td&gt;
&lt;td&gt;Standard &lt;code&gt;test&lt;/code&gt; / &lt;code&gt;lint&lt;/code&gt; / &lt;code&gt;typecheck&lt;/code&gt; / &lt;code&gt;build&lt;/code&gt; / &lt;code&gt;dev&lt;/code&gt; commands everywhere&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Boundaries&lt;/td&gt;
&lt;td&gt;CODEOWNERS, public API, import boundaries, dependency rules&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Parallelism&lt;/td&gt;
&lt;td&gt;Assume worktree / sandbox / branch-naming from the start&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multirepo&lt;/td&gt;
&lt;td&gt;Parent workspace manifest + cross-repo dependency map&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI readability&lt;/td&gt;
&lt;td&gt;A separate "where to look" doc for agents, apart from the human README&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The shape, in one line
&lt;/h2&gt;

&lt;p&gt;monorepo is still a good way to colocate context — for an AI, concentration is efficiency. But the win isn't the monorepo. On a team, split responsibilities so humans don't block each other. Solo with many agents, clear contracts let a multirepo do the same job. And underneath both, the thing that actually decides whether an agent thrives is the same: is the structure readable?&lt;/p&gt;

&lt;p&gt;The writer moved to the edge, then multiplied. What paces the multiplied writer is no longer where the code lives. It is how far humans and agents can move without waiting on each other — and how quickly an agent can find its way around before it moves at all.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Repo design in the AI era isn't about where you put the code. It's about how readable the structure is, and how little you obstruct the parallelism between humans and agents.&lt;/strong&gt; Held loosely — a view I plan to keep testing as I design against it.&lt;/p&gt;

</description>
      <category>agents</category>
      <category>monorepo</category>
      <category>architecture</category>
      <category>coding</category>
    </item>
    <item>
      <title>Let Aurora Sleep: Multi-Tenant SaaS Cost, Reconsidered with AI IaC</title>
      <dc:creator>Takafumi Endo</dc:creator>
      <pubDate>Tue, 30 Jun 2026 10:21:48 +0000</pubDate>
      <link>https://dev.to/gyu07/let-aurora-sleep-multi-tenant-saas-cost-reconsidered-with-ai-x-iac-3c1o</link>
      <guid>https://dev.to/gyu07/let-aurora-sleep-multi-tenant-saas-cost-reconsidered-with-ai-x-iac-3c1o</guid>
      <description>&lt;p&gt;For most multi-tenant SaaS, the default still rhymes: PostgreSQL on Aurora, behind an API, in a private subnet. Stable, well-understood, the shape you'd draw on a whiteboard.&lt;/p&gt;

&lt;p&gt;But I keep feeling a mismatch between what that shape &lt;em&gt;costs&lt;/em&gt; and what it actually &lt;em&gt;does&lt;/em&gt; while a product is small or bursty. A lot of the time, Vercel + Cloudflare + Supabase or Neon would give me similar real-world performance for less. And yet I get pulled back to AWS and Aurora — not for raw performance, but for enterprise requirements: VPC isolation, audit posture, "it has to live in our AWS org."&lt;/p&gt;

&lt;p&gt;What changed for me is the third option in that fork. It used to be "cheap edge stack &lt;em&gt;or&lt;/em&gt; heavy AWS stack." Now there's a middle path: keep the AWS/Aurora skeleton the enterprise wants, but &lt;strong&gt;redesign it&lt;/strong&gt; so it stops costing like it's always on — and reach that redesign by sparring with an AI against a real dev deployment, instead of needing to already be an infra specialist.&lt;/p&gt;

&lt;p&gt;Here's the whole idea in one picture:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;BEFORE — everything flows through Aurora
  Client ── API ── VPC App ── Aurora (always warm)
                      │
                      └── NAT ── OpenAI        (NAT always provisioned)

AFTER — Aurora only for control/commit
  Client ── CloudFront / S3            viewer &amp;amp; artifacts (no DB)
         └─ Hot Lambda ── DynamoDB     hot path / cache / counters (no DB)

  Control actions ── VPC Lambda ── Aurora        (wakes on purpose)
  LLM calls ──────── non-VPC Lambda ── OpenAI     (no NAT, no DB)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One scoping note up front: this is primarily for dev, internal tools, bursty early-stage SaaS, and low-frequency enterprise environments — not a blanket "let your production database sleep" recommendation for steady, high-traffic workloads. It's an optimization for a specific shape of usage, not a rejection of the proven production default.&lt;/p&gt;

&lt;p&gt;Not a "Postgres is over" piece, and not a best-practice writeup either. This is something I'm still validating — a design space I mostly reached by sparring with Claude Code and Codex while keeping one eye on cost-performance, then deploying to dev to see what actually held. Notes on where my thinking has drifted, written down mostly so I can find out where it's wrong. If anything here is useful, take it as "you can stumble into shapes like this too," not "do it this way."&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Where I've landed for now:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The cost pain usually isn't Aurora. It's making Aurora the center of &lt;em&gt;every&lt;/em&gt; request.&lt;/li&gt;
&lt;li&gt;Demote Aurora to a &lt;strong&gt;thin control plane&lt;/strong&gt; — canonical state, commit, approval, audit — and let it sleep.&lt;/li&gt;
&lt;li&gt;Push viewing, LLM hot paths, and ephemeral state to S3 / CloudFront / DynamoDB / non-VPC Lambda.&lt;/li&gt;
&lt;li&gt;What makes this &lt;em&gt;reachable&lt;/em&gt; for non-specialists is &lt;strong&gt;declarative schema + IaC + AI&lt;/strong&gt;, validated agilely in dev.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The default bills like it's always on
&lt;/h2&gt;

&lt;p&gt;The textbook shape — CloudFront → ALB/API Gateway → app → Aurora, pool model with &lt;code&gt;tenant_id&lt;/code&gt; everywhere and RLS for isolation — is fine. The problem is what accretes around it to make it reliable: always-on Aurora, RDS Proxy, NAT Gateway, readers, VPC endpoints, logs.&lt;/p&gt;

&lt;p&gt;For dev or a 1–30 person workload, the &lt;em&gt;fixed cost of reliability&lt;/em&gt; is wildly out of proportion to the traffic. You're paying ledger-grade rent to store scratch work deleted in an hour.&lt;/p&gt;

&lt;p&gt;The mismatch isn't "Aurora is expensive." It's "Aurora is expensive &lt;strong&gt;when it can never go idle&lt;/strong&gt;."&lt;/p&gt;

&lt;p&gt;Here's the cost &lt;em&gt;shape&lt;/em&gt; the redesign is chasing — not exact dollars, but where the fixed costs go:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;BEFORE                              AFTER
- Aurora always warm               - Aurora wakes only for control/commit
- NAT always provisioned           - No NAT for LLM egress
- RDS Proxy holding connections    - No Proxy in the sleep path
- DB hit on health/viewer/LLM      - Viewer/LLM/health paths stay DB-free
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The reframe: Aurora as a thin control plane
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Don't put Aurora in the path of every request.
Make it a thin control plane: canonical state, approval, audit, commit.
Move viewing, LLM execution, short-lived state, and delivery
to S3 / CloudFront / DynamoDB / non-VPC Lambda.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There's a &lt;strong&gt;system of work&lt;/strong&gt; — high-churn, disposable, fine to lose — and a &lt;strong&gt;system of record&lt;/strong&gt; — money, contracts, audit, singular and strict. The mistake is paying record-grade prices for work-grade state. So split by job:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Job&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;S3&lt;/td&gt;
&lt;td&gt;artifacts, reports, raw payloads (cheap bulk)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CloudFront&lt;/td&gt;
&lt;td&gt;viewer delivery — keeps reads off Aurora&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DynamoDB&lt;/td&gt;
&lt;td&gt;projections, cache, locks, progress, counters (hot path)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Aurora&lt;/td&gt;
&lt;td&gt;tenant, RBAC, manifest, lineage, approval, audit, rollups&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;non-VPC Lambda&lt;/td&gt;
&lt;td&gt;outbound LLM calls — no NAT, never touches the DB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Once viewing and the LLM hot path stop touching Aurora, it stops waking for trivia. That, more than any price knob, moves the bill.&lt;/p&gt;

&lt;p&gt;One discipline keeps DynamoDB from quietly becoming a &lt;em&gt;second&lt;/em&gt; source of truth: anything stored there should be either TTL-bound, recomputable from Aurora/S3, or an explicit live counter with a reconciliation path. If a value is none of those, it probably wants to be in Aurora.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sleep-first only works if the app lets Aurora sleep
&lt;/h2&gt;

&lt;p&gt;Aurora Serverless v2 can scale to zero in supported configurations (&lt;code&gt;min_acu = 0&lt;/code&gt;), and while paused, compute charge goes to zero (storage still bills). The flag is the easy part; the discipline is not poking it awake.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Bad:  set min=0, but login/health/LLM/viewer all read Aurora -&amp;gt; never sleeps
Good: only login, admin, manifest commit, rollups, audit wake it on purpose
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two silent traps: in this sleep-first setup, &lt;strong&gt;RDS Proxy works against you&lt;/strong&gt; — it keeps database connections around, which prevents pause — and any open user-initiated connection does the same. So sleep-first wants no Proxy, a small pool, short idle timeouts. (None of this is a knock on RDS Proxy in general; it's doing exactly its job, which happens to be the opposite of what you want here.)&lt;/p&gt;

&lt;p&gt;The goal isn't to make Aurora cheap by configuration; it's to make the application structurally capable of not needing Aurora most of the time. The &lt;code&gt;min_acu = 0&lt;/code&gt; flag only pays off once the app no longer reaches for the DB on every request.&lt;/p&gt;

&lt;h2&gt;
  
  
  No NAT: separate "can reach data" from "can reach the internet"
&lt;/h2&gt;

&lt;p&gt;Dropping NAT is usually pitched as savings (it bills per hour &lt;em&gt;and&lt;/em&gt; per GB). The better reason is blast radius:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;A Lambda that can touch the data cannot go out.
A Lambda that can go out cannot touch the data.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One function with both powers, if compromised, can read &lt;em&gt;and&lt;/em&gt; exfiltrate. Split it: a VPC control Lambda reaches RDS but can't egress; a non-VPC egress Lambda calls the external LLM but has no line to the DB. Cheaper and smaller blast radius at once. (Caveat: an &lt;code&gt;/ask&lt;/code&gt; flow hands tenant context to the egress function, so minimize the payload, forbid logging it, keep a &lt;code&gt;request_id&lt;/code&gt; audit trail.)&lt;/p&gt;

&lt;p&gt;The point isn't that non-VPC is magically safe. The egress Lambda still touches the OpenAI key and whatever prompt/context you pass it, so it still deserves a narrow IAM role, a single-purpose secret, no broad Secrets Manager or S3 read permissions, and strict logging rules. The win is narrower and more durable: it cannot both read the database &lt;em&gt;and&lt;/em&gt; ship it somewhere.&lt;/p&gt;

&lt;p&gt;And the boundary only holds if the network isn't the only thing keeping the egress Lambda away from the DB. Aurora has to be private, its security group must not allow the egress path, and the egress Lambda must have no Data API access or broad Secrets Manager permissions that would quietly recreate a database path through IAM. "Not in the VPC" is necessary, not sufficient.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part that makes this AI-friendly: declarative schema
&lt;/h2&gt;

&lt;p&gt;This design means making the same placement call constantly: &lt;em&gt;canonical Aurora? DynamoDB cache? S3 artifact? hot path? allowed to wake the DB?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Migration-history schema fights you here — to know what &lt;em&gt;is&lt;/em&gt;, you replay what &lt;em&gt;happened&lt;/em&gt; (&lt;code&gt;001_create… 006_revert…&lt;/code&gt;). Declarative schema flips it: you describe the desired current state and let the tool diff it. Both you and the AI read one artifact that says what &lt;em&gt;should be true now&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;It doesn't abolish prod migrations — the loop is &lt;code&gt;declarative -&amp;gt; plan -&amp;gt; reviewed migration -&amp;gt; apply -&amp;gt; drift check&lt;/code&gt;. &lt;strong&gt;You think in declarative state; you apply via migration.&lt;/strong&gt; That's what fits AI-assisted work: the AI reasons over a stable description, not a changelog.&lt;/p&gt;

&lt;h2&gt;
  
  
  You don't have to be an infra specialist up front
&lt;/h2&gt;

&lt;p&gt;This is the claim I most want to make — and hold most loosely.&lt;/p&gt;

&lt;p&gt;A sleep-first, no-NAT, projection-driven design used to need someone who &lt;em&gt;lived&lt;/em&gt; in AWS networking. The wall wasn't the idea; it was getting VPC routing, IAM boundaries, and serverless wiring all correct. Most of this design, honestly, isn't something I knew up front — it's where I drifted by sparring with Claude Code and Codex against a real dev deployment: "does this Lambda actually have egress?", "what wakes Aurora here?", "where's the tenant boundary?" With IaC describing the whole thing as code, the feedback is a deployed stack you can poke, not a whiteboard argument — and that's what let me reach a shape like this at all.&lt;/p&gt;

&lt;p&gt;It costs more than a managed edge platform and burns dev cycles. But infra design became something you &lt;em&gt;validate agilely&lt;/em&gt; rather than get right up front from expertise alone.&lt;/p&gt;

&lt;p&gt;To be precise about the claim: AI doesn't remove the need for infrastructure expertise. It changes the iteration loop — from "know the answer upfront" to "generate, deploy, inspect, and correct faster." You still need the judgment to know what to inspect; you just acquire it by iterating instead of having to bring all of it to the whiteboard.&lt;/p&gt;

&lt;p&gt;The honest caveat: the AI will confidently produce wiring that's "plausible but subtly wrong" — a security group more open than you think, a function you &lt;em&gt;believe&lt;/em&gt; is sandboxed but isn't. So the loop must include verification you can eyeball: probe the egress, confirm what wakes the DB, read the plan diff. And be clear-eyed that demoting Aurora is really &lt;em&gt;distributed-systems-ification&lt;/em&gt; — you trade fixed cost for consistency, projections, and sync. It's a tradeoff, not a free win.&lt;/p&gt;

&lt;h2&gt;
  
  
  When this stops being the right call
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Aurora awake &amp;lt; ~100h/mo:  min_acu=0 + wake-ahead — keep going
~100–250h/mo:             compare against RDS db.t4g.micro/small
&amp;gt; ~250h/mo:               always-on RDS, or Aurora min_acu=0.5
prod-like / heavy validation: Aurora min_acu=0.5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Judge &lt;strong&gt;total&lt;/strong&gt; cost (ACU + NAT + Proxy + endpoints + DynamoDB + S3 + Lambda + CloudWatch), not Aurora alone. For bursty dev, killing Aurora compute and NAT usually wins; for steady production, often it won't. These aren't universal thresholds — they're decision triggers for my environment, and they move with region, log volume, and DynamoDB/S3 usage.&lt;/p&gt;

&lt;h3&gt;
  
  
  What I'd actually measure
&lt;/h3&gt;

&lt;p&gt;Numbers beat vibes here. If I were deciding whether this is paying off, these are the signals I'd watch:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Aurora &lt;strong&gt;awake hours per month&lt;/strong&gt; (the headline number)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;which endpoints wake Aurora&lt;/strong&gt; — and whether any are supposed to be DB-free&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NAT hourly cost avoided&lt;/strong&gt; by moving egress out of the VPC&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DynamoDB counter / reconciliation lag&lt;/strong&gt; — how stale the cheap layer gets&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;p95 login latency&lt;/strong&gt; right after a cold resume (the resume-tax you're accepting)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;count of unexpected DB calls&lt;/strong&gt; from hot paths (this should trend to zero)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Objections I'd raise myself
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;"You've rebuilt a distributed system to avoid a DB bill."&lt;/strong&gt; Sometimes, yes. The boundary I try to hold is: only move &lt;em&gt;work-grade&lt;/em&gt; state out of Aurora. If a flow needs cross-store transactions, strict read-after-write semantics everywhere, or complex reconciliation, it probably belongs back in the path. The moment you're reaching for distributed consistency to keep the cheap layer correct, you've moved the boundary too far.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Stale auth will bite you."&lt;/strong&gt; The sharp edge. &lt;code&gt;JWT + DynamoDB cache&lt;/code&gt; is fine for low-risk reads, but role/budget changes, manifest commits, approvals, and any tenant-data-injecting &lt;code&gt;/ask&lt;/code&gt; must consult canonical Aurora. "Fast but stale permissions" is how you ship an authz incident.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"The warmup endpoint is a footgun."&lt;/strong&gt; A public &lt;code&gt;/warmup&lt;/code&gt; doing &lt;code&gt;select 1&lt;/code&gt; leaks almost nothing — but it's a &lt;em&gt;button that wakes Aurora&lt;/em&gt;, and a public one can be hammered until sleep-first is meaningless. Behind WAF/rate-limit, single-flight, no-op when recently warm.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A checklist before you try this
&lt;/h2&gt;

&lt;p&gt;If you want to see whether your workload fits, run down these before touching anything:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is your workload &lt;strong&gt;bursty / intermittent&lt;/strong&gt; (not steady high traffic)?&lt;/li&gt;
&lt;li&gt;Can &lt;strong&gt;viewer reads&lt;/strong&gt; be served without touching Aurora (CloudFront/S3/DynamoDB)?&lt;/li&gt;
&lt;li&gt;Can &lt;strong&gt;LLM calls&lt;/strong&gt; run outside the VPC, with no DB access?&lt;/li&gt;
&lt;li&gt;Can the DynamoDB state be &lt;strong&gt;TTL-bound, recomputable, or reconciled&lt;/strong&gt;?&lt;/li&gt;
&lt;li&gt;Can you &lt;strong&gt;tolerate resume latency&lt;/strong&gt; on the paths that do wake Aurora?&lt;/li&gt;
&lt;li&gt;Do you have &lt;strong&gt;metrics for what wakes Aurora&lt;/strong&gt;, so you can catch leaks?&lt;/li&gt;
&lt;li&gt;Is your schema &lt;strong&gt;declarative&lt;/strong&gt;, so you (and the AI) reason over current state, not history?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If most boxes are unchecked, the boring always-on RDS/Aurora is probably still the right call — and that's fine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Still figuring out the edges
&lt;/h2&gt;

&lt;p&gt;The shift isn't any one AWS feature. It's that the boundary between "only an infra specialist could safely build this" and "a product engineer can reach it by sparring with an AI against a dev deployment" has moved a lot.&lt;/p&gt;

&lt;p&gt;Enterprise gravity toward AWS and Aurora is real and isn't leaving. But it no longer forces the always-on, NAT-heavy, everything-through-the-DB default. Keep Aurora as the &lt;strong&gt;vault&lt;/strong&gt; — strict, singular — let it sleep, and run the &lt;strong&gt;workbench&lt;/strong&gt; at the edge, with explicit events carrying workbench changes back into the control plane. Whether this particular shape survives more validation, I don't know yet. What I'm fairly sure of is that the path to &lt;em&gt;trying&lt;/em&gt; designs like it is now something you can iterate into — with AI and IaC, deploying and inspecting — instead of having to know it cold beforehand.&lt;/p&gt;

&lt;p&gt;If you've run something like this in production — or watched it fall apart — I'd like to hear where it broke for you.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>serverless</category>
      <category>architecture</category>
      <category>postgres</category>
    </item>
    <item>
      <title>The Wrapper Got Heavy: Why ChatGPT Clones Are Runtime Problems Now</title>
      <dc:creator>Takafumi Endo</dc:creator>
      <pubDate>Fri, 26 Jun 2026 03:00:39 +0000</pubDate>
      <link>https://dev.to/gyu07/the-wrapper-got-heavy-why-chatgpt-clones-are-runtime-problems-now-19h4</link>
      <guid>https://dev.to/gyu07/the-wrapper-got-heavy-why-chatgpt-clones-are-runtime-problems-now-19h4</guid>
      <description>&lt;p&gt;A year ago, "it's just a ChatGPT wrapper" was a dismissal. You'd hear it about a startup and know what it meant: an &lt;code&gt;LLM API&lt;/code&gt; call, a little RAG, file upload, a chat box on top. Thin. Replaceable. Probably dead the next time the base model shipped a feature.&lt;/p&gt;

&lt;p&gt;I keep coming back to that phrase, because it stopped being true in a way I didn't notice happening. The thing you'd be wrapping is no longer a model with a chat UI. It's a fast, stateful web application with its own agent loop, its own sandbox, its own artifact system. The wrapper didn't get easier to build as the models got better. It got &lt;em&gt;heavier&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The simple interface hides the hard part. A ChatGPT-shaped product is not just an API call with a chat box around it; it's the accumulation of many product and infrastructure decisions that make execution feel safe, stateful, and immediate. The model is the part you can buy. The surrounding runtime is the part people had to design.&lt;/p&gt;

&lt;p&gt;What gets me is the timescale. It's been roughly a year, and the question actually worth arguing about has moved out from under us — from "is this just a wrapper?" to "where does the sandbox even run?" The pace is faster than I can comfortably track. And the part I keep finding fun is that it all bends &lt;em&gt;toward&lt;/em&gt; the practical, not away from it: every one of these shifts makes the tools more usable, more real, closer to something you'd actually ship. Surprising and, honestly, a good time to be building.&lt;/p&gt;

&lt;p&gt;This isn't a "wrappers are over" argument, and it isn't advice. It's me writing down where my thinking has drifted while trying to build these things myself — partly so I can find out where it's wrong. Read it as one person's notes.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "wrapper" used to mean
&lt;/h2&gt;

&lt;p&gt;The old shape was honestly small. Roughly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;prompt → LLM API → (RAG retrieval) → response
        + file parsing on the side
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The whole game was prompt design, a retrieval index, and some glue. You could stand it up in a weekend. The reason "wrapper" was an insult is that the surface area was tiny — the model did the hard part, and you did the part anyone could redo.&lt;/p&gt;

&lt;p&gt;The leverage point was the prompt and the context you stuffed into it. I've &lt;a href="https://dev.to/gyu07/rigor-compresses-why-ai-agents-need-graphs-not-more-context-5404"&gt;written before&lt;/a&gt; that token spend only becomes an asset when you redesign the work around it. Back then, "the work" &lt;em&gt;was&lt;/em&gt; mostly the prompt. There wasn't much else in the box.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two things that are easy to blur
&lt;/h2&gt;

&lt;p&gt;Before going further, it's worth separating two things, because the rest of this post depends on not mixing them up.&lt;/p&gt;

&lt;p&gt;The first is the &lt;strong&gt;consumer surface&lt;/strong&gt;: ChatGPT's Data Analysis, Canvas, and Agent mode; Claude's artifacts and browser agents. These are managed sandboxes the platform owns end to end. You can't bring your own; you can only observe their shape.&lt;/p&gt;

&lt;p&gt;The second is the &lt;strong&gt;developer engine&lt;/strong&gt;: Codex's CLI / app-server / SDK, and Claude Code's headless mode and Agent SDK. These expose the agent loop as something you can &lt;em&gt;drive&lt;/em&gt; from your own code.&lt;/p&gt;

&lt;p&gt;They're the same product family seen from two sides. The lesson you take from each is different:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The consumer products reveal the &lt;strong&gt;shape of the architecture&lt;/strong&gt; — sandboxed analysis, artifact surfaces, browser/terminal agents.&lt;br&gt;
The developer tools reveal the &lt;strong&gt;build strategy&lt;/strong&gt; — don't rebuild the agent loop; drive the existing engine and own the boundary, the policy, and the domain compiler around it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Keep those two lines in mind. The first half of what follows is about the shape. The second half is about the strategy.&lt;/p&gt;

&lt;h2&gt;
  
  
  The sandbox is the actual product surface
&lt;/h2&gt;

&lt;p&gt;The clearest way I've found to see the shape is to stop looking at ChatGPT as "a model" and look at it as &lt;strong&gt;a set of execution environments, each with a network boundary around it.&lt;/strong&gt; Strip the marketing and the consumer surface decomposes into tiers:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Surface&lt;/th&gt;
&lt;th&gt;What it really is&lt;/th&gt;
&lt;th&gt;Network&lt;/th&gt;
&lt;th&gt;Closest analogy&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Plain chat&lt;/td&gt;
&lt;td&gt;inference + retrieval, no shell&lt;/td&gt;
&lt;td&gt;n/a&lt;/td&gt;
&lt;td&gt;the old "wrapper"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data analysis&lt;/td&gt;
&lt;td&gt;stateful Python/Jupyter-like sandbox on uploaded files&lt;/td&gt;
&lt;td&gt;no direct external web/API calls&lt;/td&gt;
&lt;td&gt;short-lived compute sandbox&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Canvas&lt;/td&gt;
&lt;td&gt;code edit + render/preview sandbox&lt;/td&gt;
&lt;td&gt;policy-gated (workspace/admin)&lt;/td&gt;
&lt;td&gt;preview environment&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agent mode&lt;/td&gt;
&lt;td&gt;remote browser + code interpreter + limited terminal + connectors&lt;/td&gt;
&lt;td&gt;restricted + confirmations&lt;/td&gt;
&lt;td&gt;a managed virtual computer&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;What strikes me, laid out like this: &lt;strong&gt;none of these is &lt;em&gt;only&lt;/em&gt; a model feature.&lt;/strong&gt; Each is a product/runtime feature that depends on model behavior but is mostly a managed sandbox with a deliberate trust boundary — what can run, what it can reach, when a human has to confirm. The design center isn't "a better prompt." It's &lt;code&gt;managed sandbox + network boundary + user confirmation + tool isolation&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;And notably, it is &lt;em&gt;not&lt;/em&gt; "trust a user-supplied container as your production sandbox." There's no &lt;code&gt;devcontainer.json&lt;/code&gt; you hand in and have it boot as your runtime. The isolation is the point, and the isolation is theirs, hand-built per surface. That's the part a thin wrapper can't fake — and the part that's now the bulk of the work.&lt;/p&gt;

&lt;p&gt;So the leverage point moved. It used to be the prompt. The model call used to be the product; now it's the commodity-shaped component &lt;em&gt;inside&lt;/em&gt; a much heavier runtime.&lt;/p&gt;

&lt;h2&gt;
  
  
  Runtime choice is really a state-gravity decision
&lt;/h2&gt;

&lt;p&gt;The moment you decide your wrapper needs to &lt;em&gt;run something&lt;/em&gt; — execute the code it wrote, render the component, hold work state between turns — you've inherited an infrastructure decision you can't shortcut. And the heart of it isn't "which platform is fastest." It's &lt;strong&gt;where the state lives.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It helps to notice that "state" here isn't one thing. An agentic product juggles at least four, with different gravity:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. conversation state        — turns, plan, tool-call history
2. workspace filesystem state — the repo/files the agent edits
3. artifact / render state    — the live canvas, the preview
4. connector / auth state     — credentials, permissions, approvals
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The real design question is which of these lives &lt;em&gt;inside&lt;/em&gt; the sandbox, close to the actor, and which gets pushed back to a durable store (a DB, object storage, Git). This is the same &lt;code&gt;state gravity&lt;/code&gt; model I keep &lt;a href="https://dev.to/gyu07/why-ai-agents-make-me-reach-for-sqlite-4dh0"&gt;running into&lt;/a&gt;: high-churn work state wants to be close to whatever reads and writes it most; the durable record stays central. (It's telling that Codex exposes &lt;a href="https://developers.openai.com/codex/config-reference" rel="noopener noreferrer"&gt;SQLite-backed state&lt;/a&gt; for agent jobs and exported results via &lt;code&gt;sqlite_home&lt;/code&gt; — the workbench is local, the ledger is elsewhere. The pattern keeps reappearing.)&lt;/p&gt;

&lt;p&gt;Choosing a platform is choosing that gravity, and none of the options hands it to you for free:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Vercel&lt;/strong&gt; — &lt;a href="https://vercel.com/docs/fluid-compute" rel="noopener noreferrer"&gt;Fluid Compute&lt;/a&gt; killed the "too short-lived for agents" complaint (300s default, up to 800s, 30-min beta), but it still isn't a stateful &lt;em&gt;repo-workspace&lt;/em&gt; sandbox out of the box, so a code-running agent needs extra design on top.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cloudflare&lt;/strong&gt; — Durable Objects are great for per-entity &lt;code&gt;conversation state&lt;/code&gt; (SQLite-backed, one writer each); less of a fit, alone, as an arbitrary-code sandbox or a cloned repo.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS&lt;/strong&gt; — Fargate gives real per-task isolation. Beyond that you're assembling: the pattern I'd reach for is a &lt;em&gt;session-scoped, disposable VM per agent run&lt;/em&gt; — something you build on the primitives rather than buy off the shelf. Maximum control, maximum assembly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Roll your own&lt;/strong&gt; — gVisor / Firecracker, your own warm pools and snapshots. The most control, the deepest hole.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Whichever you pick, you spend real days on cold-start latency, warm pools, and the network policy before a single user-visible feature ships. The wrapper used to be the prompt. Now part of the wrapper is a sandbox you have to operate — and the network boundary is not a checkbox. A good concrete example: Codex cloud runs a &lt;a href="https://developers.openai.com/codex/agent-approvals-security" rel="noopener noreferrer"&gt;two-phase model&lt;/a&gt; — the setup phase has network access to install dependencies, then the agent phase runs offline by default, and secrets are removed before the agent phase even starts. That separation of &lt;code&gt;setup egress&lt;/code&gt; from &lt;code&gt;agent egress&lt;/code&gt;, with secrets scoped to setup, is exactly the kind of boundary you end up rebuilding yourself if you roll your own runtime.&lt;/p&gt;

&lt;h2&gt;
  
  
  Don't rebuild Codex or Claude Code — drive them
&lt;/h2&gt;

&lt;p&gt;Chat-with-a-sandbox is heavy but reachable. The coding &lt;em&gt;agents&lt;/em&gt; — Claude Code, Codex — are a different tier, and this is where the build strategy from earlier pays off. The honest move is to stand on the engines that already exist rather than reimplement the loop.&lt;/p&gt;

&lt;p&gt;Codex is the clear example because the engine is exposed. &lt;a href="https://developers.openai.com/codex/noninteractive" rel="noopener noreferrer"&gt;&lt;code&gt;codex exec&lt;/code&gt;&lt;/a&gt; runs the agent non-interactively — single session to completion, events streamed as JSONL via &lt;code&gt;--json&lt;/code&gt;, a built-in sandbox (read-only by default, &lt;code&gt;workspace-write&lt;/code&gt; when you opt in), approval gating you can set to &lt;code&gt;never&lt;/code&gt; for unattended runs. &lt;code&gt;codex app-server&lt;/code&gt; runs the same core as a server over stdio / WebSocket / Unix socket, which is how IDEs and SDK clients drive it — the &lt;a href="https://developers.openai.com/codex/sdk" rel="noopener noreferrer"&gt;Python SDK&lt;/a&gt;, for instance, controls the local app-server over JSON-RPC, while &lt;code&gt;codex exec --json&lt;/code&gt; is the simpler non-interactive path. Claude Code has its own headless mode (&lt;code&gt;claude -p&lt;/code&gt;) and Agent SDK in the same spirit.&lt;/p&gt;

&lt;p&gt;One caveat I'd flag honestly: you &lt;em&gt;can&lt;/em&gt; speak the &lt;code&gt;app-server&lt;/code&gt; protocol, but treat it as a fast-moving integration surface, not yet a boring, stable production ABI — the docs mark it as primarily for development/debugging, and the WebSocket transport is still hardening. Build on it, but expect it to move.&lt;/p&gt;

&lt;p&gt;The lesson I take from this: the agent loop, the tool orchestration, the apply-patch system, the sandbox enforcement — that's a frontier-grade engine, and the leverage is in &lt;strong&gt;driving&lt;/strong&gt; it, not rebuilding it. Trying to hand-roll the equivalent loop is the new version of trying to hand-roll dominators in a static analyzer: technically possible, almost always the wrong place to spend your rigor.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part worth owning — and how thin the moat really is
&lt;/h2&gt;

&lt;p&gt;So where's the defensible work, if the model is a commodity and the agent loop is borrowed?&lt;/p&gt;

&lt;p&gt;Honestly, I'm not sure "defensible" is the right word, and I want to resist making my own work sound safer than it is. Here's the most honest version I have.&lt;/p&gt;

&lt;p&gt;The durable idea — more durable, I think, than any tooling that implements it — is an &lt;em&gt;epistemology&lt;/em&gt;: typing the facts you hand an agent by &lt;strong&gt;how you know them&lt;/strong&gt;. Is a claim &lt;code&gt;verified&lt;/code&gt; (it follows from a model you built, with evidence) or &lt;code&gt;estimated&lt;/code&gt; (a pattern guessed it)? What's its provenance? I &lt;a href="https://dev.to/gyu07/rigor-compresses-why-ai-agents-need-graphs-not-more-context-5404"&gt;care about this&lt;/a&gt; because an agent handed grounded, labeled facts has to guess less, and a fact that carries its own confidence is worth more than a longer context window.&lt;/p&gt;

&lt;p&gt;The tooling I've built around that — an SDK and compiler aimed at Codex and Claude Code, turning messy domain data into typed, sourced facts — is just my current implementation of the idea, not a moat. And I should take my own earlier objection seriously: if platforms absorb the commodity layer, why wouldn't they absorb this too? They might — a generic facts-grounding layer is exactly the kind of thing that gets commoditized next. There's a second pressure cutting the same way: as models get better at deriving global facts unaided, the need for hand-built &lt;code&gt;verified&lt;/code&gt;/&lt;code&gt;estimated&lt;/code&gt; scaffolding could &lt;em&gt;shrink&lt;/em&gt;, not grow.&lt;/p&gt;

&lt;p&gt;So I won't claim a moat. The narrower thing I will claim — more defensible &lt;em&gt;for&lt;/em&gt; being narrow — is that the facts that matter most are tied to &lt;em&gt;your&lt;/em&gt; domain: your invariants, your private data, the specific thing that's dangerous in your system. That's the part a general platform doesn't have and a smarter model can't infer from nothing. Owning the pipeline that produces those facts buys trustworthy agent output &lt;em&gt;now&lt;/em&gt;, on your actual problem. Whether that's a lasting edge or a temporary one, I don't know. The honest reason to build it isn't defensibility — it's that it makes the agent's answers true today.&lt;/p&gt;

&lt;h2&gt;
  
  
  Objections I'd raise myself
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;"Wrappers are still easy — most apps don't need a sandbox."&lt;/strong&gt; True, and plenty of good products are still a chat box over an API. My claim only covers the &lt;em&gt;frontier-shaped&lt;/em&gt; slice — runs code, renders artifacts, holds work state, feels instant. Outside it, "just wrap it" is still accurate, and I don't want to inflate the rest into something heavier than it is just to dignify the work.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"This is build-vs-buy, nothing new."&lt;/strong&gt; Partly. What's new is that the buy side moved. You used to buy the model and build the app. Now you also buy the &lt;em&gt;agent engine&lt;/em&gt; and the &lt;em&gt;sandbox platform&lt;/em&gt;, and the build is the orchestration, the boundary, and the facts pipeline between them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Won't the platforms just absorb all of this?"&lt;/strong&gt; Probably some of it. Managed agent sandboxes will get boring the way managed Postgres did, and a generic facts-grounding layer is a plausible next thing to be commoditized. I don't have a clean rebuttal — only that the &lt;em&gt;specific&lt;/em&gt; facts tied to your domain and private data are the slowest part to absorb. That's a thinner claim than "I have a moat," and I'd rather state the thin true one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"You're building on a borrowed engine and a rented sandbox."&lt;/strong&gt; Yes, and it makes the strategy less stable than I'd like. The app-server isn't a frozen ABI; the platform sets the gravity, not me. I'm not standing on bedrock — I'm standing on a fast-moving layer and trying to stay close to it. The wager is that staying close beats trying to own the whole stack. It's a wager, not a fortress.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where this is going
&lt;/h2&gt;

&lt;p&gt;The best developers at OpenAI and Anthropic are improving the &lt;em&gt;consumer&lt;/em&gt; product relentlessly, and you can't out-ship them on their own surface — the chat, the sandbox, the artifacts, the latency move every few weeks. Matching that head-on is a losing race.&lt;/p&gt;

&lt;p&gt;So the move is two-handed, and I'll admit it isn't a stable one. One hand keeps up: drive &lt;code&gt;codex exec&lt;/code&gt; and &lt;code&gt;app-server&lt;/code&gt;, Claude Code's headless mode, whichever sandbox platform's gravity fits — ride the improvements instead of fighting them. The other hand grounds the facts your agent runs on. Neither hand is holding bedrock; both sit on layers that move, one rented and one possibly temporary. The wager is only that staying close to the frontier while owning your domain's facts beats the alternatives — not that it's safe.&lt;/p&gt;

&lt;p&gt;The phrase aged in an interesting direction. "Just a wrapper" used to mean &lt;em&gt;thin, replaceable, not really engineering.&lt;/em&gt; For the frontier-shaped slice, it now means a state-gravity decision and an orchestration layer over a borrowed engine. (For everything else, "just wrap it" is still true, and that's fine.) The model call used to be the product. Now it's the commodity inside the product — and the easy part is the part you no longer build.&lt;/p&gt;

&lt;p&gt;That's the pull I keep feeling: stop trying to reproduce the giants, stay close to the layer that keeps moving, and stay honest about how much of the advantage is real versus borrowed.&lt;/p&gt;

</description>
      <category>agents</category>
      <category>sandbox</category>
      <category>architecture</category>
      <category>ai</category>
    </item>
    <item>
      <title>Your Fuzzer Is Only as Smart as Its Oracle</title>
      <dc:creator>Takafumi Endo</dc:creator>
      <pubDate>Wed, 24 Jun 2026 14:07:23 +0000</pubDate>
      <link>https://dev.to/gyu07/your-fuzzer-is-only-as-smart-as-its-oracle-4mbd</link>
      <guid>https://dev.to/gyu07/your-fuzzer-is-only-as-smart-as-its-oracle-4mbd</guid>
      <description>&lt;p&gt;A migration my schema tool generated passed every check I had. The final schema matched the target exactly — &lt;code&gt;convergence&lt;/code&gt;, green. Then I looked at the plan it took to get there: &lt;code&gt;DROP TABLE; CREATE TABLE&lt;/code&gt;. On a table with data. The destination was right; the path would have erased a production database.&lt;/p&gt;

&lt;p&gt;The test was green because I was checking the wrong thing. I had an oracle for &lt;em&gt;where it ended up&lt;/em&gt; and none for &lt;em&gt;how it got there&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;That gap is most of what this post is about. I build developer tools — SDKs, a compiler, a declarative schema-management system — the kind of software where one wrong edge case ships to everyone downstream. And I spent a long time trying to get coding agents to test this stuff for me. Claude Code, Codex — with enough prompting and the right skills you can get something that &lt;em&gt;looks&lt;/em&gt; like a test suite. What I could never get was coverage that followed the real dev flow without quietly skipping the case that mattered. It's convincing right up until you check whether it checked anything.&lt;/p&gt;

&lt;p&gt;What finally worked better was &lt;strong&gt;semantic fuzzing&lt;/strong&gt;: constrained, deterministic random generation, run against &lt;em&gt;properties&lt;/em&gt; instead of a reference output, with the agent writing the generators and domain rules rather than playing tester. It catches a lot. And the more I run the harness, the more it improves.&lt;/p&gt;

&lt;p&gt;But the interesting part isn't the fuzzing. AI made the &lt;em&gt;building&lt;/em&gt; of the harness cheap — generators, adapters, the long tail of domain rules. It did &lt;strong&gt;not&lt;/strong&gt; make the harness run itself correctly, and it did not write the part that decides what "correct" means when you have no second implementation to diff against. That part was always the real work, and it still is.&lt;/p&gt;

&lt;p&gt;So this is a post about oracles wearing a fuzzing costume. As usual: notes on where my thinking has drifted, not advice.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Where I've landed for now (and expect to revise):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Randomness doesn't find bugs. The &lt;strong&gt;oracle&lt;/strong&gt; does; randomness just walks it there.&lt;/li&gt;
&lt;li&gt;So the value isn't the generator. It's the &lt;strong&gt;law&lt;/strong&gt; you check.&lt;/li&gt;
&lt;li&gt;The laws worth having need &lt;strong&gt;no reference implementation&lt;/strong&gt; — they're algebraic relations the tool must satisfy by its own logic.&lt;/li&gt;
&lt;li&gt;AI dropped the cost of the harness. The scarce skill moved from &lt;em&gt;headcount&lt;/em&gt; to &lt;em&gt;seeing what the law is&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  "Semantic" needs pinning down
&lt;/h2&gt;

&lt;p&gt;"I fuzz it" says almost nothing on its own. At one end of the dial is crash fuzzing — uniform noise at a parser, asking only "did it fall over?" At the other is reference-model testing — diffing every output against a full second implementation you now have to build and trust. Semantic fuzzing sits in between, and the whole game is what you do with the middle.&lt;/p&gt;

&lt;p&gt;Three properties make it work. Generation is &lt;strong&gt;distribution-constrained&lt;/strong&gt; (biased toward inputs with real structure — schemas and migration sequences that could plausibly exist — not uniform noise), &lt;strong&gt;deterministic&lt;/strong&gt; (every case carries a seed, so a failure replays exactly), and &lt;strong&gt;shrinkable&lt;/strong&gt; (a failing case minimizes itself to the smallest thing that still breaks).&lt;/p&gt;

&lt;p&gt;That's property-based testing's family tree, and I'd rather say so than pretend it's new. The goal isn't &lt;em&gt;maximum&lt;/em&gt; randomness — it's &lt;em&gt;productive&lt;/em&gt; randomness: reproducible, minimizable, aimed where bugs live. The one thing it refuses to do is what reference-model testing does — build a second implementation to check the answer. It checks &lt;em&gt;relations&lt;/em&gt; the answer must obey instead, which is the move the rest of this post turns on.&lt;/p&gt;

&lt;h2&gt;
  
  
  The oracle is the whole game
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;A fuzzer is never smarter than its oracle.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The generator is just search. It produces inputs; it has no idea what's wrong with any of them. The thing that decides &lt;em&gt;this is a bug&lt;/em&gt; is the oracle — and whatever the oracle can't recognize, the fuzzer cannot find, however many cases you burn.&lt;/p&gt;

&lt;p&gt;Point a billion inputs at a crash oracle and you get crashes. You won't get a migration that converges to the right state via a catastrophic path, or a rollback that silently drops a constraint. Those don't crash. They're wrong &lt;em&gt;semantically&lt;/em&gt;, and a crash oracle is blind to exactly that.&lt;/p&gt;

&lt;p&gt;The trap here is reaching for a reference implementation — "I'll build a correct model and diff against it." But a full reference model of a schema engine &lt;em&gt;is&lt;/em&gt; a second schema engine, with its own bugs to maintain. You doubled the work and got a thing you also can't trust.&lt;/p&gt;

&lt;p&gt;The way out: stop asking for a reference, ask for &lt;strong&gt;laws&lt;/strong&gt; — relations the tool must satisfy regardless of the "right answer."&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Law&lt;/th&gt;
&lt;th&gt;Relation checked&lt;/th&gt;
&lt;th&gt;Reference needed?&lt;/th&gt;
&lt;th&gt;Axis&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;convergence&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;apply(spec)&lt;/code&gt; → introspect → residual drift &lt;code&gt;== 0&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;correctness&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;idempotency&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;apply(spec)&lt;/code&gt; twice → 2nd run is a no-op&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;correctness&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;rollback&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;base → target → base&lt;/code&gt; → back to base, exactly&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;correctness&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;safety&lt;/td&gt;
&lt;td&gt;destructive change → must surface hazard / require approval&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;safety&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;None needs the correct schema in advance — each is true &lt;em&gt;by the meaning of the operation&lt;/em&gt;. &lt;code&gt;convergence&lt;/code&gt; doesn't ask "is this schema right?", it asks "did you reach the state you claimed?" That's the metamorphic move: don't verify the answer, verify a &lt;em&gt;relation&lt;/em&gt; the answer must obey.&lt;/p&gt;

&lt;p&gt;Note &lt;code&gt;safety&lt;/code&gt; as a peer, not a footnote. "Reached the right state" and "took a safe path" are different axes — the &lt;code&gt;DROP TABLE; CREATE TABLE&lt;/code&gt; from the opening satisfies convergence and still erases your data. The oracle must check the path, not just the destination. This is the law that would have caught my green-but-catastrophic migration.&lt;/p&gt;

&lt;h2&gt;
  
  
  A worked example: schemas, extensions, a Docker loop
&lt;/h2&gt;

&lt;p&gt;One generator feeds a pipeline; every stage is a place to hang a law.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; generate schema + mutation sequence
            │
            ▼
   IR / shadow DB                ── convergence, idempotency  (in-process)
            │
            ▼
   diff + online-migration plan  ── safety, plan honesty      (in-process)
            │
            ▼
   apply to Docker Postgres/MySQL── locks, extensions, races  (Docker)
            │
            ▼
   introspect + review           ── drift == 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run that across thousands of seeds and you're not testing "does &lt;code&gt;ADD COLUMN&lt;/code&gt; work." You're hitting combinations a human suite never reaches — especially &lt;strong&gt;extension combinations&lt;/strong&gt;. Base PostgreSQL/MySQL is the easy part; bugs hide where &lt;code&gt;citext&lt;/code&gt; meets a generated column meets a partial index meets an old trigger. Enumerating those by hand used to need a team. Generated, it's a distribution you tune.&lt;/p&gt;

&lt;p&gt;But be honest about &lt;em&gt;which layer answers&lt;/em&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Runtime&lt;/th&gt;
&lt;th&gt;Answers&lt;/th&gt;
&lt;th&gt;Can't answer&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;In-process&lt;/td&gt;
&lt;td&gt;PGlite / &lt;code&gt;node:sqlite&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;convergence, idempotency, real SQL exec&lt;/td&gt;
&lt;td&gt;multi-connection, lock fidelity, full extension catalog&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Docker&lt;/td&gt;
&lt;td&gt;real Postgres / MySQL&lt;/td&gt;
&lt;td&gt;locks, isolation, extensions, version matrix&lt;/td&gt;
&lt;td&gt;provider parity, hosted-backend quirks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Live&lt;/td&gt;
&lt;td&gt;real cloud / hosted&lt;/td&gt;
&lt;td&gt;IAM/KMS enforcement, audit immutability&lt;/td&gt;
&lt;td&gt;(bulk testing — too slow/expensive)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The same generated case &lt;strong&gt;fans out&lt;/strong&gt;: cheap laws run in-process by the thousand; expensive laws that need a real engine run in Docker, sampled. The layer is chosen per &lt;em&gt;property&lt;/em&gt;, not per pipeline. Putting a lock-contention law in the in-process tier wouldn't make it fast — it would make it lie.&lt;/p&gt;

&lt;h2&gt;
  
  
  The flywheel is real, but it has a catch
&lt;/h2&gt;

&lt;p&gt;"Run it more, it gets better" is true — but not because green accumulates. A suite at 100% pass proves nothing about whether it can &lt;em&gt;catch&lt;/em&gt; anything. The flywheel only turns toward value if, on every failure:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The failure minimizes and re-enters as a regression case.&lt;/strong&gt; Every real bug becomes a permanent, tiny, deterministic test. The corpus grows in the shape of your mistakes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You periodically test the oracle itself.&lt;/strong&gt; Inject a known fault — delete a hazard rule, invert a guard, drop a constraint in the introspector — and confirm some law &lt;em&gt;kills&lt;/em&gt; it.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fault injected ──▶ does a law fail?
                     ├─ yes → oracle bites. trust the green.
                     └─ no  → green is decorative. fix the oracle.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That second step is the one I see skipped most, and the one that keeps the whole thing from becoming theater. An oracle you never test is a claim, not a check. Honest flywheel: &lt;em&gt;minimize failures into the corpus, and verify the verifier.&lt;/em&gt; Do that and it genuinely compounds.&lt;/p&gt;

&lt;h2&gt;
  
  
  What got cheap (and what didn't)
&lt;/h2&gt;

&lt;p&gt;A few years ago this pipeline was a staffing decision: generators, shadow DB, migration planner, Docker orchestration, per-dialect adapters — a team and a quarter. The barrier wasn't ideas; it was implementation labor, and that priced most people out of serious simulation testing.&lt;/p&gt;

&lt;p&gt;That wall came down, and I can point to where. A per-dialect adapter — the layer that maps my IR onto one database's quirks — used to be a multi-day slog of reading docs and discovering edge cases by getting burned. The last one I added came out in an afternoon: I described the IR and the target's introspection format, the agent drafted the adapter and a first pass of its quirk-rules, and I spent my time reviewing rather than typing. Parsing is a commodity, local execution is cheap, and the long tail of boilerplate writes itself faster than I can spec it.&lt;/p&gt;

&lt;p&gt;But notice exactly &lt;em&gt;what&lt;/em&gt; got cheap. Building the harness did. Knowing which relations are actually invariant did not. Routing each law to a layer that can honestly answer it did not. Checking that the checker still bites did not. The agent wrote the adapter; it could not tell me whether &lt;code&gt;safety&lt;/code&gt; belonged on the same axis as &lt;code&gt;convergence&lt;/code&gt;, or whether my green meant anything. The scarce skill went from "can you build the harness" to "can you see what the law is." The implementation got democratized. The judgment didn't.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it breaks
&lt;/h2&gt;

&lt;p&gt;Local simulation kills a startling fraction of bugs for almost no money. It does not replace a remote environment:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Provider parity&lt;/strong&gt; — the real cloud API does things no emulator reproduces.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real enforcement&lt;/strong&gt; — IAM denies, KMS policies, VPC isolation, audit-log immutability: only provable against the real thing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hosted-backend quirks&lt;/strong&gt; — managed Postgres, pooler modes, hosted auth schemas. My local Docker Postgres happily ran a migration that deadlocked on a managed instance behind a transaction-mode pooler, because the pooler changes how sessions and locks interleave — behavior the local container simply doesn't have.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Genuine concurrency at scale&lt;/strong&gt; — race probes catch a lot; they don't certify production load.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These live on a rare &lt;strong&gt;live tier&lt;/strong&gt;, used as calibration anchors, not bulk testing. Local says &lt;em&gt;the logic is right&lt;/em&gt;; only live says &lt;em&gt;the platform agrees&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Objections I'd raise myself
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;"This is just property-based testing."&lt;/strong&gt; The generation and shrinking, largely yes. The contribution is the framing: the &lt;em&gt;oracle&lt;/em&gt; is the artifact, the laws need no reference, and safety is a first-class axis.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Why not just have the agent write and run the tests?"&lt;/strong&gt; I tried, hard. The agent will produce a plausible suite, but it tends to test the happy path it just wrote and skip the case that actually bites — and you can't tell from the green. A fuzz harness inverts that: the agent supplies generators and laws (things I can review), and &lt;em&gt;generation&lt;/em&gt; finds the cases neither of us thought of. The agent is good at proposing rules, unreliable at being exhaustive.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"AI-written oracles are subtly unsound."&lt;/strong&gt; The failure mode I fear most — a hole in the oracle reads as &lt;em&gt;safety&lt;/em&gt;. So: borrow battle-tested algorithm cores, keep the bespoke part to domain rules I can eyeball in review, never let the harness downgrade a real bug to "expected." The verifier is the last thing you let the AI write unsupervised.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Green still lies."&lt;/strong&gt; Until you mutation-test the oracle. That's not polish; it's what makes green mean anything.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"This grows into an unmaintainable matrix."&lt;/strong&gt; Only if every law runs on every layer. Counter-discipline: route each property to the cheapest honest layer, retire laws that stop catching anything.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where this is going
&lt;/h2&gt;

&lt;p&gt;I came in through a practical door — semantic fuzzing catches a lot of bugs cheaply — and walked out somewhere narrower: the random part was never the point. The oracle was, and AI made everything &lt;em&gt;except&lt;/em&gt; the oracle cheap enough to finally see that.&lt;/p&gt;

&lt;p&gt;In the AI era, rigor is what pays off: it compresses a vague situation into a few hard facts an agent can act on. Here that takes its sharpest form. Rigor is what turns blind randomness into a bug-finding machine: the law does the finding; the noise just does the walking.&lt;/p&gt;

&lt;p&gt;The implementation barrier is down. Knowing what "correct" even means for the thing you built is the whole job now.&lt;/p&gt;

&lt;p&gt;Which brings me back to that green migration with &lt;code&gt;DROP TABLE&lt;/code&gt; hiding in its path. The fuzzer didn't miss it because the randomness was weak. It missed it because I hadn't yet written the law that calls erasing your data a bug. The day I did, generation found the case in minutes — it had been walking past it the whole time, waiting for me to say it mattered. That's the job now: not running more cases, but learning to name the failures worth catching, one law at a time.&lt;/p&gt;

</description>
      <category>testing</category>
      <category>ai</category>
      <category>database</category>
      <category>fuzzing</category>
    </item>
    <item>
      <title>Every API Will Be Rebuilt for Agents</title>
      <dc:creator>Takafumi Endo</dc:creator>
      <pubDate>Tue, 23 Jun 2026 11:15:18 +0000</pubDate>
      <link>https://dev.to/gyu07/every-api-will-be-rebuilt-for-agents-2hj4</link>
      <guid>https://dev.to/gyu07/every-api-will-be-rebuilt-for-agents-2hj4</guid>
      <description>&lt;p&gt;MCP solves the plug. It doesn't solve the verb gap.&lt;/p&gt;

&lt;p&gt;Hand a coding agent a beautifully documented REST API — versioned, OpenAPI'd, the kind a human developer would happily build against — wrap it in MCP in an afternoon, and watch it still fumble. It picks the wrong endpoint. It calls three when one would do. It does a destructive write it should have asked about. The API isn't bad. It's built for the wrong consumer.&lt;/p&gt;

&lt;p&gt;MCP is winning the part that was always going to be solved — &lt;em&gt;the plug&lt;/em&gt;. And winning is what exposes the uncomfortable part: almost every API we have is built for a consumer that's no longer the only primary consumer. For twenty years that consumer was a human developer who showed up with intent and a mental model. Now it's an agent that has neither and has to reconstruct both from your surface. When the primary consumer of an interface changes that completely, the interface gets rebuilt. It always has.&lt;/p&gt;

&lt;p&gt;So, here's the claim I'll commit to: &lt;strong&gt;the serious product surfaces agents operate against will be rebuilt around agent-native operations.&lt;/strong&gt; Not wrapped. Rebuilt. These are still one person's notes, not advice — and intentionally directional, not a migration plan. The direction isn't what I'm unsure about. Only the timeline is.&lt;/p&gt;

&lt;p&gt;When I say &lt;em&gt;every API&lt;/em&gt;, I don't mean every endpoint gets replaced or every protocol disappears. I mean the product-level operations an agent is trusted to execute move from resource-shaped APIs to intent-shaped contracts — redesigned around goals, state, side-effects, approval, and recovery.&lt;/p&gt;

&lt;h2&gt;
  
  
  MCP standardizes the plug, not the operations
&lt;/h2&gt;

&lt;p&gt;Give MCP its due: it's now a genuine &lt;a href="https://modelcontextprotocol.io/docs/getting-started/intro" rel="noopener noreferrer"&gt;cross-client standard&lt;/a&gt; — Claude, ChatGPT, VS Code, Cursor, Codex, Copilot. Connection, auth, transport, human-in-the-loop, long-running Tasks: all getting standardized, steadily.&lt;/p&gt;

&lt;p&gt;But in the spec, a tool is just a function — a name, a description, a schema. MCP deliberately doesn't decide what state transitions your domain has, what order tools go in, which operations are dangerous, or which need approval. That's the right scope for a protocol. It also means MCP gives you a clean way to expose &lt;em&gt;whatever your API already is&lt;/em&gt; — and what it already is, is a thing designed for a developer who brings the plan.&lt;/p&gt;

&lt;p&gt;Call the failure &lt;strong&gt;the verb gap&lt;/strong&gt;: APIs hand agents nouns and CRUD; agents need verbs that carry intent. No protocol closes that gap, because the gap is in the API, not the wire. MCP solves the plug. It does not solve the verb gap.&lt;/p&gt;

&lt;h2&gt;
  
  
  The wrapper ceiling is already visible
&lt;/h2&gt;

&lt;p&gt;The tell is that the most API-mature companies are walking the wrapper back, in public.&lt;/p&gt;

&lt;p&gt;GitHub's &lt;a href="https://github.com/github/github-mcp-server" rel="noopener noreferrer"&gt;official MCP server&lt;/a&gt; is excellent — and GitHub is &lt;a href="https://github.blog/changelog/2025-10-14-github-mcp-server-now-supports-github-projects-and-more/" rel="noopener noreferrer"&gt;narrowing its default toolset&lt;/a&gt; and consolidating PR tools into fewer, more capable ones, explicitly to cut tool bloat and improve agent reasoning. The implication is hard to miss: even one of the best API companies on the planet is learning that a 1:1 mapping from product API to agent tools isn't the right shape.&lt;/p&gt;

&lt;p&gt;Research points the same way. &lt;a href="https://arxiv.org/abs/2507.16044" rel="noopener noreferrer"&gt;AutoMCP&lt;/a&gt; generated MCP servers from OpenAPI specs: across 50 real APIs and ~5,000 endpoints, tool-call success went from 76.5% to 99.9% — but only after fixing the underlying spec. A &lt;a href="https://arxiv.org/abs/2605.14312" rel="noopener noreferrer"&gt;2026 study&lt;/a&gt; on agent-ready OpenAPI docs found agents failing systematically at planning and tool selection, with defects in essentially every operation. Their line is the one to underline: an API can be structurally valid and still not be semantically ready for an agent.&lt;/p&gt;

&lt;p&gt;Every "just wrap it" path ends at the same wall — the contract is wrong for the new consumer, and you can't wrapper your way to a different contract. Wrapping buys a demo. Rebuilding buys a product.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "rebuilt for agents" looks like
&lt;/h2&gt;

&lt;p&gt;An agent-native API isn't an API an agent &lt;em&gt;can&lt;/em&gt; call. It's an API whose operations carry enough intent, state, and safety for an agent to know what it's doing.&lt;/p&gt;

&lt;p&gt;Not a few coarse endpoints bolted on. A different default shape for every operation — one that answers more than "what do I return." It answers: what's the goal, what state am I in, what are the side-effects, do I need approval, how do I recover.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;inspect_pull_request        → state + what's relevant, not the raw blob
find_merge_blockers         → the global fact, computed, not re-derived
preview_write_action        → dry-run: here's what WOULD happen
commit_approved_action      → the write, gated behind explicit approval
explain_missing_permissions → why it can't, in actionable terms
rollback_last_action        → recovery as a first-class verb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agent-native doesn't just mean coarse-grained. A coarse endpoint can still be unsafe, opaque, and impossible to undo. The difference is that an agent-native operation exposes its preconditions, expected effects, uncertainty, required approvals, and recovery path &lt;em&gt;as part of the contract&lt;/em&gt;. &lt;code&gt;preview&lt;/code&gt; / &lt;code&gt;commit&lt;/code&gt; split intent from effect, so a dangerous write becomes a two-step you can reason about. &lt;code&gt;rollback&lt;/code&gt; makes recovery part of the surface, not an afterthought.&lt;/p&gt;

&lt;p&gt;There's a hint here about &lt;em&gt;how&lt;/em&gt; these get built. Today we usually grow REST outward from the database — tables become resources, columns become fields, CRUD becomes the default. But an operation that knows its preconditions and its legal next moves is really a state machine wearing an API. It's not a stretch to imagine the mainstream flipping: generating the surface from an explicit state model — transitions, guards, and valid next-actions as the source of truth — with storage downstream, instead of the other way around. That's the version of this I'd most want to build.&lt;/p&gt;

&lt;p&gt;And here's why it's one API, not two: developers want all of this too. Preview-before-commit, blast radius, clear permission errors, rollback — that's not an "agent edition," it's just a &lt;em&gt;better&lt;/em&gt; API whose main consumer happens to be an agent. You don't want to maintain a human API plus an agent API forever. Over time, the agent-native design should subsume the human one.&lt;/p&gt;

&lt;p&gt;This connects to &lt;a href="https://dev.to/gyu07/rigor-compresses-why-ai-agents-need-graphs-not-more-context-5404"&gt;the last post&lt;/a&gt;: a good operation collapses a risky multi-step workflow into one confident verb, the way a rigorous fact collapses a hedge into a flat statement. Rigor compresses; so does a good operation.&lt;/p&gt;

&lt;h2&gt;
  
  
  It's not just APIs — CLIs are the same surface
&lt;/h2&gt;

&lt;p&gt;This was never really about REST. It's about any surface an agent operates against, and the CLI is the other big one. A coding agent lives in a terminal — it runs &lt;code&gt;git&lt;/code&gt;, &lt;code&gt;kubectl&lt;/code&gt;, your build and deploy scripts — and hits the same verb gap: tools that assume a human who already knows the flags.&lt;/p&gt;

&lt;p&gt;CLIs add a second problem the API case hides: output. Most CLIs print for a human — colors, progress bars, free-form logs, a summary line you're meant to eyeball. The agent has to scrape that, which is the verb gap again, moved to the return path. So an agent-native CLI prioritizes machine-readable output — &lt;code&gt;--json&lt;/code&gt;, structured logs, stable exit codes, errors that name the next action — over pretty terminal formatting. When you can't have both, the default flips: readable for the agent beats readable for the eye.&lt;/p&gt;

&lt;p&gt;That's the same coin as the verb gap, both sides of it. Calling side: verbs that carry intent. Return side: state the agent can parse, not prose it has to interpret. And the shift is bigger than APIs: every interface we built assuming a human on the other end — APIs, CLIs, logs, output formats — is up for reconsideration once the other end is an agent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Security is what makes it "must," not "nice"
&lt;/h2&gt;

&lt;p&gt;MCP clients put tool name, description, and schema straight into the model's context — so a malicious description can steer the model (tool poisoning, confused-deputy, over-broad scopes). And agents use configured tools &lt;em&gt;autonomously&lt;/em&gt;; GitHub's own docs recommend allowlisting read-only tools for exactly this reason.&lt;/p&gt;

&lt;p&gt;You can't allowlist your way to safe writes on a human-shaped API. You rebuild writes as preview → approve → commit, with permission errors that explain themselves. Safety isn't a wrapper you add; it's a property you design into the operations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three layers now, one contract later
&lt;/h2&gt;

&lt;p&gt;This won't happen overnight. Near term, three things coexist — but only one is the endgame.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;What it is&lt;/th&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Direct API / SDK / CLI&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;local detour — files, shell, git, tests&lt;/td&gt;
&lt;td&gt;persists where proximity beats protocol&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Wrapper MCP over legacy API&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;the human-shaped API, exposed as tools&lt;/td&gt;
&lt;td&gt;a bridge — scaffolding, not a destination&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Agent-native API&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;goal-shaped operations with state, approval, recovery&lt;/td&gt;
&lt;td&gt;where everything converges&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The wrapper is useful &lt;em&gt;as a transition&lt;/em&gt; — it connects what exists today — but it carries the legacy contract underneath, and that contract is the problem. Over time the core gets rebuilt and the wrapper dissolves, the same way mobile BFFs gave way to redesigned APIs instead of living forever as shims.&lt;/p&gt;

&lt;p&gt;Practically: greenfield should start agent-native &lt;em&gt;now&lt;/em&gt; — no reason to ship a human-only contract you'll rewrite. Brownfield wraps as a bridge, then rebuilds the operations that matter most first. Either way the arrow points one direction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Objections I'd raise myself
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;"You can't rewrite every API; legacy is forever."&lt;/strong&gt; Legacy persists — but "persists" isn't "is what you'd build." SOAP still runs; nobody starts there. Human-shaped APIs become the thing you wrap and migrate off, not the thing you design.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"This is just BFFs rebranded."&lt;/strong&gt; The technique isn't new; the consumer is. A BFF serves a fixed UI flow a human designed. An agent-native operation has to surface its own preconditions, danger, and recovery, because the agent is &lt;em&gt;forming&lt;/em&gt; the plan — and it applies to the whole surface, not one frontend.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Models will get good enough to use raw APIs."&lt;/strong&gt; Maybe for reads. But capability makes the security case &lt;em&gt;worse&lt;/em&gt; — a more capable agent doing an unguarded destructive write is the nightmare, not the fix.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Rebuilding every domain doesn't scale."&lt;/strong&gt; The load-bearing worry. The bet is that AI dropped the cost of domain-specific implementation enough that it finally pencils out. If that's wrong, the timeline stretches. The direction doesn't change.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where this is going
&lt;/h2&gt;

&lt;p&gt;The contract at the center of interface design is flipping. Old: &lt;em&gt;the human holds the intent; the API returns materials.&lt;/em&gt; New: &lt;em&gt;the agent holds the goal; the API returns state, constraints, the next safe action, and a way back.&lt;/em&gt; The competitive axis stops being endpoint count and becomes whether an agent can safely accomplish a goal against your surface.&lt;/p&gt;

&lt;p&gt;We probably don't have the right format for this yet. I doubt it's REST or GraphQL; both were shaped for consumers that aren't the agent. For now, OpenAPI and Zod are still the right place to stand: precise schemas reduce mistakes, and AutoMCP's jump from 76.5% to 99.9% is evidence of that. But implementer correctness and agent operability are different axes, and the second one will likely grow its own format. That part I'm looking forward to.&lt;/p&gt;

&lt;p&gt;These days I keep tacking defensive lines onto prompts: "preview before you write," "ask before anything destructive," "explain why you can't." It's not that I distrust the agent — it's that the operations I hand it are the vague part, so reminders are the only patch I have. Rebuild the operations to carry their own preview, approval, and rollback, and those lines stop being necessary.&lt;/p&gt;

&lt;p&gt;If the operation is safe by design, you don't have to ask the agent to be careful with it.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>mcp</category>
      <category>api</category>
      <category>architecture</category>
    </item>
    <item>
      <title>The Repo Is the Context: Why Agents Don’t Need History</title>
      <dc:creator>Takafumi Endo</dc:creator>
      <pubDate>Fri, 19 Jun 2026 10:46:54 +0000</pubDate>
      <link>https://dev.to/gyu07/the-repo-is-the-context-why-agents-dont-need-history-4ien</link>
      <guid>https://dev.to/gyu07/the-repo-is-the-context-why-agents-dont-need-history-4ien</guid>
      <description>&lt;p&gt;History still matters. I just don't want it to be the default context for agents editing the present system.&lt;/p&gt;

&lt;p&gt;A coding agent will read whatever you put in front of it.&lt;/p&gt;

&lt;p&gt;That sounds like a strength. Most of the time it is the problem.&lt;/p&gt;

&lt;p&gt;For a while my answer was "more Markdown" — &lt;code&gt;CLAUDE.md&lt;/code&gt;, &lt;code&gt;AGENTS.md&lt;/code&gt;, specs, ADRs, planning docs. It felt responsible: agents need context, so give them context.&lt;/p&gt;

&lt;p&gt;Then I watched an agent do something confidently wrong because it had faithfully trusted a document that described a system we stopped having months ago. It wasn't a hallucination. It was obedience to history.&lt;/p&gt;

&lt;p&gt;That's when the question shifted for me. Not "what prompt should I write?" but the quieter one underneath it: &lt;em&gt;what should the agent read?&lt;/em&gt; I &lt;a href="https://dev.to/gyu07/rigor-compresses-why-ai-agents-need-graphs-not-more-context-5404"&gt;wrote recently&lt;/a&gt; that token efficiency is really a "what do you hand over" problem — each token should buy a verified fact, not a guess. This post is the same idea, pointed at the repo itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  History is for humans. The present source of truth is for agents.
&lt;/h2&gt;

&lt;p&gt;This is not an argument against keeping history.&lt;/p&gt;

&lt;p&gt;I still think ADRs, migration logs, old specs, and planning docs are valuable. They explain why the system took its current shape — the tradeoffs, the constraints, the abandoned paths, the organizational memory. Humans need that. When you're reviewing a decision, onboarding to a domain, debugging a strange constraint, or trying to understand why an obvious-looking change was deliberately avoided, history is exactly the right thing to read.&lt;/p&gt;

&lt;p&gt;But Claude Code, Codex, and similar coding agents are usually doing something different. They're about to edit the present system.&lt;/p&gt;

&lt;p&gt;For that task, historical prose shouldn't be the primary context. The agent needs the current source of truth: current schema, current module boundaries, current public APIs, current tests, current state machines, current configuration, current dependency rules.&lt;/p&gt;

&lt;p&gt;A human may read migration history to understand how the database evolved. But I don't want Claude Code to learn the current database by replaying every migration — I want it to read the current schema. A human may read ADRs to follow the reasoning behind a decision. But I don't want an editing agent to infer the current architecture by replaying every ADR — I want it to read the current module graph.&lt;/p&gt;

&lt;p&gt;History explains &lt;em&gt;why&lt;/em&gt; the system became this shape. The current source of truth explains &lt;em&gt;what&lt;/em&gt; shape it has now. Both matter. They just serve different readers.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Reader&lt;/th&gt;
&lt;th&gt;What they should read&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Human&lt;/strong&gt; — trying to understand &lt;em&gt;why&lt;/em&gt;
&lt;/td&gt;
&lt;td&gt;ADRs, old specs, migration history, design notes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Coding agent&lt;/strong&gt; — trying to change &lt;em&gt;what exists now&lt;/em&gt;
&lt;/td&gt;
&lt;td&gt;current schema, tests, types, module graph, config, public APIs&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;So the rule was never "delete history." It's:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Keep history for humans.&lt;br&gt;
Give agents the present source of truth.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A well-shaped repository should let an agent understand the current system without replaying its past. And the larger the system gets, the more this matters — not because history becomes useless, but because in prose it gets &lt;em&gt;mixed in&lt;/em&gt; with obsolete truth, and the two become indistinguishable. The danger isn't the old fact. It's the old fact wearing the same clothes as the current one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Drift is the failure mode, and agents are loyal to it
&lt;/h2&gt;

&lt;p&gt;Here's the chain I keep tripping over:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;duplication → drift → context pollution&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The moment a fact lives in two places — once in the code, once in a Markdown description of the code — they begin to diverge. Code changes under pressure; prose changes when someone remembers. So the doc quietly goes stale, and now you have two answers to the same question with no marker for which is current.&lt;/p&gt;

&lt;p&gt;A human reading a slightly-wrong doc squints and cross-checks. An agent doesn't squint. It's &lt;em&gt;very&lt;/em&gt; good at trusting polluted context and acting on it decisively. The thing that makes agents fast — taking the given facts at face value — is exactly what makes drift dangerous.&lt;/p&gt;

&lt;p&gt;So the most useful move isn't writing better docs. It's having fewer places where a fact can rot.&lt;/p&gt;

&lt;h2&gt;
  
  
  A boundary in CLAUDE.md is advice. A boundary in the build is architecture.
&lt;/h2&gt;

&lt;p&gt;This is the line I keep coming back to.&lt;/p&gt;

&lt;p&gt;If &lt;code&gt;CLAUDE.md&lt;/code&gt; says "the billing module must not import from checkout," that's a polite request. Nothing enforces it; the next edit can ignore it and nobody — human or agent — gets stopped.&lt;/p&gt;

&lt;p&gt;If that same boundary lives in ESLint config, in &lt;code&gt;package.json&lt;/code&gt; &lt;code&gt;exports&lt;/code&gt;, or in TypeScript project references, it's no longer advice. It's a wall. The agent doesn't have to &lt;em&gt;remember&lt;/em&gt; the rule, because the rule is a property of the system it's editing.&lt;/p&gt;

&lt;p&gt;That reframes "AI readability" for me. It was never about writing longer instructions. It's about making the repository itself say the true thing — structurally, where it can't drift:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;directory structure&lt;/strong&gt; carries the architecture&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;naming&lt;/strong&gt; carries intent&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;package exports&lt;/strong&gt; carry the public API surface&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;lint rules&lt;/strong&gt; carry dependency boundaries&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;schemas&lt;/strong&gt; carry data contracts&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;state machines&lt;/strong&gt; carry workflows&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;tests&lt;/strong&gt; carry expected behavior&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;config files&lt;/strong&gt; carry the actual stack&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;generated-file markers&lt;/strong&gt; carry "do not touch by hand"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each of these is closer to the system than any sentence about the system can be. That proximity is the whole point — the closer a fact sits to the thing it describes, the less room there is for it to lie.&lt;/p&gt;

&lt;h2&gt;
  
  
  Markdown should be a map, not the territory
&lt;/h2&gt;

&lt;p&gt;I haven't thrown out &lt;code&gt;CLAUDE.md&lt;/code&gt; and &lt;code&gt;AGENTS.md&lt;/code&gt;. I've shrunk them.&lt;/p&gt;

&lt;p&gt;Their job isn't to &lt;em&gt;restate&lt;/em&gt; the architecture. It's to &lt;em&gt;point at&lt;/em&gt; where the current architecture actually lives.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;## Sources of truth&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; Database schema:     packages/database/schema
&lt;span class="p"&gt;-&lt;/span&gt; API contract:        packages/api/openapi.yaml
&lt;span class="p"&gt;-&lt;/span&gt; Checkout workflow:   src/features/checkout/checkout.machine.ts
&lt;span class="p"&gt;-&lt;/span&gt; Public package APIs: package.json "exports"
&lt;span class="p"&gt;-&lt;/span&gt; Module boundaries:   eslint.config.js
&lt;span class="p"&gt;-&lt;/span&gt; Commands:            package.json scripts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's a map. It survives change, because it points at the territory instead of copying it.&lt;/p&gt;

&lt;p&gt;The trap is the opposite: a section in &lt;code&gt;CLAUDE.md&lt;/code&gt; that &lt;em&gt;restates&lt;/em&gt; the behavior — the checkout states, what billing depends on, what the API accepts. The day the code moves, that paragraph becomes a confident lie. And the agent will read it with exactly the same trust it gives the truth.&lt;/p&gt;

&lt;h2&gt;
  
  
  Specs are scaffolding — give them an exit strategy
&lt;/h2&gt;

&lt;p&gt;I still like a short spec at the &lt;em&gt;start&lt;/em&gt; of a feature. It gives Claude Code or Codex a sharper target than vague intent, and it helps me think. That part is genuinely useful.&lt;/p&gt;

&lt;p&gt;The mistake is letting the spec &lt;em&gt;stay&lt;/em&gt; as a second source of truth after the code exists. Once the feature ships, the durable parts of the spec should migrate into the repo's present tense. This is where the repo starts becoming readable without being explained:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;The spec captured…&lt;/th&gt;
&lt;th&gt;…so move it into&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;screen states&lt;/td&gt;
&lt;td&gt;state machine / Storybook stories&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;API behavior&lt;/td&gt;
&lt;td&gt;OpenAPI / GraphQL / typed router&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;validation rules&lt;/td&gt;
&lt;td&gt;Zod / Valibot / JSON Schema&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;domain states&lt;/td&gt;
&lt;td&gt;discriminated unions / domain model&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;permissions&lt;/td&gt;
&lt;td&gt;policy code / authorization tests&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;database rules&lt;/td&gt;
&lt;td&gt;schema / constraints&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;done-ness&lt;/td&gt;
&lt;td&gt;tests&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;infrastructure&lt;/td&gt;
&lt;td&gt;Terraform / OpenTofu / Pulumi&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The spec was scaffolding around the building. You don't leave scaffolding up and then ask people to trust it over the walls.&lt;/p&gt;

&lt;h2&gt;
  
  
  Naming is prompt engineering, run inside the repo
&lt;/h2&gt;

&lt;p&gt;This is the part I find quietly fun. Agents infer intent from names — which means names aren't only for humans anymore.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;createUser&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nf"&gt;updateUser&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nf"&gt;deleteUser&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;tells an agent almost nothing about the domain. This:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;registerMember&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nf"&gt;inviteMember&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nf"&gt;deactivateMember&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nf"&gt;transferWorkspaceOwnership&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;tells it where it is. The same goes for structure — a layout an agent can read as a map of where behavior belongs:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;What lives there&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;domains/billing/commands/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;state-changing operations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;domains/billing/queries/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;reads&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;domains/billing/policies/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;authorization rules&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;domains/billing/repositories/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;data access&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A layout like that answers "where does this change belong?" before the agent has to guess. And it &lt;em&gt;will&lt;/em&gt; guess — if the repo doesn't tell an agent where a thing lives, it'll happily invent a home for it, somewhere plausible and wrong.&lt;/p&gt;

&lt;p&gt;So naming and layout are a kind of standing prompt: written once, into the structure, read on every single run.&lt;/p&gt;

&lt;h2&gt;
  
  
  The same "what do you hand over" question, one layer up
&lt;/h2&gt;

&lt;p&gt;In the graphs post, the facts I wanted to hand an agent were computed — dominance, reachability, ordering — and the discipline was labeling each one &lt;code&gt;verified&lt;/code&gt; or &lt;code&gt;estimated&lt;/code&gt; by where it came from.&lt;/p&gt;

&lt;p&gt;This is the same question, just earlier in the pipeline. A current schema is a &lt;code&gt;verified&lt;/code&gt; fact about the data. A migration log you have to replay is, at best, &lt;code&gt;estimated&lt;/code&gt; truth about the present. A lint-enforced boundary is verified; a sentence in &lt;code&gt;CLAUDE.md&lt;/code&gt; is a hope. The repo, when it's shaped well, is a source of facts that carry their own freshness — because they &lt;em&gt;are&lt;/em&gt; the system, not a description trailing behind it.&lt;/p&gt;

&lt;p&gt;Present schema over migration history. Current module graph over ADRs. Live tests over old acceptance criteria. Real config over README prose. State machines over paragraphs about workflows.&lt;/p&gt;

&lt;p&gt;None of that throws the history away. It just stops asking the editing agent to reconstruct the present from it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Last year I tuned the instructions. This year I shaped the repo.
&lt;/h2&gt;

&lt;p&gt;I want to be honest about where this comes from, because it's experience, not a benchmark.&lt;/p&gt;

&lt;p&gt;A year ago I was fixated on the instruction layer — &lt;code&gt;.claude/&lt;/code&gt;, &lt;code&gt;.codex/&lt;/code&gt;, &lt;code&gt;AGENTS.md&lt;/code&gt;, &lt;code&gt;CLAUDE.md&lt;/code&gt;. I tuned rules, wrote skills, kept the files growing, treating the prompt &lt;em&gt;around&lt;/em&gt; the repo as the thing to perfect.&lt;/p&gt;

&lt;p&gt;Somewhere along the way the obsession moved. Instead of tuning the instructions, I started tuning the repo itself: pulling facts into a single source of truth, refactoring toward structure an agent could read, writing new code the same way from the start.&lt;/p&gt;

&lt;p&gt;What I notice now is that the instruction files matter less than I expected. On a repo in the hundreds of thousands of lines, with a short &lt;code&gt;AGENTS.md&lt;/code&gt; and rules and skills that are nowhere near perfectly tuned, I rarely feel Claude Code drift from what I intended, or stall when it's time to actually implement. The friction I used to paper over with longer instructions mostly just isn't there.&lt;/p&gt;

&lt;p&gt;The honest caveat: the models and the agents themselves got dramatically better over the same stretch, and I can't cleanly separate "my repo got more legible" from "the tools got smarter."&lt;/p&gt;

&lt;p&gt;But the direction of the feeling has been consistent: the more the present lived &lt;em&gt;in the repo&lt;/em&gt; instead of in prose wrapped around it, the less I had to say to get good work out of the agent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this is going
&lt;/h2&gt;

&lt;p&gt;These days I keep adding defensive lines to prompts: "don't trust stale docs," "check the actual schema," "don't guess where this goes."&lt;/p&gt;

&lt;p&gt;The better answer is not to write better warnings.&lt;/p&gt;

&lt;p&gt;It is to shape a repo where those warnings are unnecessary.&lt;/p&gt;

&lt;p&gt;Keep history for people. Shape the present for agents.&lt;/p&gt;

&lt;p&gt;Human-readable history explains why the system became this way.&lt;br&gt;
AI-readable repositories expose what the system is now.&lt;/p&gt;

&lt;p&gt;Don't make agents reconstruct the present from the past.&lt;br&gt;
Make the current source of truth impossible to misunderstand.&lt;/p&gt;

</description>
      <category>agents</category>
      <category>coding</category>
      <category>architecture</category>
      <category>ai</category>
    </item>
    <item>
      <title>Rigor Compresses: Why AI Agents Need Graphs, Not More Context</title>
      <dc:creator>Takafumi Endo</dc:creator>
      <pubDate>Thu, 18 Jun 2026 01:53:21 +0000</pubDate>
      <link>https://dev.to/gyu07/rigor-compresses-why-ai-agents-need-graphs-not-more-context-5404</link>
      <guid>https://dev.to/gyu07/rigor-compresses-why-ai-agents-need-graphs-not-more-context-5404</guid>
      <description>&lt;p&gt;A coding agent can edit one function beautifully.&lt;/p&gt;

&lt;p&gt;But ask it a global question — "what breaks if I change this?", "can this write run without the tenant check?", "is this migration destructive?" — and it often starts guessing.&lt;/p&gt;

&lt;p&gt;For a while, I thought the answer was better token efficiency. Now I think the deeper question is: what facts do you hand the agent in the first place?&lt;/p&gt;

&lt;p&gt;I recently &lt;a href="https://medium.com/@takafumi.endo/token-allocation-the-new-capital-discipline-of-ai-native-companies-15a9183fe083" rel="noopener noreferrer"&gt;wrote on Medium&lt;/a&gt; that token spend only becomes an &lt;em&gt;asset&lt;/em&gt; when you redesign the work around it — verification, context, workflow — rather than just buying more of it.&lt;/p&gt;

&lt;p&gt;This post is the engineering side of that argument. Since then I've been hunting for token-efficient tooling, and writing my own when nothing fits. What follows is the piece I find most leverageable: making each token buy a verified fact instead of a guess. (Just where my own tinkering has led — not advice.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Token efficiency is really a "what do you hand over" problem
&lt;/h2&gt;

&lt;p&gt;"Saving tokens" sounds like a story about compression and caching — or, lately, about giving the agent &lt;em&gt;more&lt;/em&gt; context: bigger windows, more retrieval. But in my own work, what moved the needle most wasn't more context; it was the quality of the few facts I hand the agent.&lt;/p&gt;

&lt;p&gt;Coding agents are strong locally. Within one function, one diff, they write beautifully. But the global facts — what calls this, what breaks if I change it, whether this write can run without the tenant check, whether this migration is destructive — they tend to guess at, fairly confidently, and sometimes get wrong.&lt;/p&gt;

&lt;p&gt;So what helps, I think, isn't a long analysis log or a full-repo grep — it's handing over those global facts short, accurate, and with evidence. The heart of token efficiency is not &lt;em&gt;saving&lt;/em&gt; but &lt;em&gt;maximum signal per token&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The global facts you want are mostly graphs
&lt;/h2&gt;

&lt;p&gt;What's interesting is that most of the global facts agents struggle with look like graph questions.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Impact&lt;/strong&gt; — who depends on this? → reachability over a call/reference graph&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ordering &amp;amp; cycles&lt;/strong&gt; — can these migrations apply in a valid order? → topological sort + cycle detection (DAG)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"X before Y"&lt;/strong&gt; — does the required guard run before the dangerous op? → dominance in a control/call graph&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flow&lt;/strong&gt; — can untrusted input reach this sink? → reachability over a dataflow graph&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;depends-on&lt;/code&gt;, &lt;code&gt;calls&lt;/code&gt;, &lt;code&gt;flows-to&lt;/code&gt;, &lt;code&gt;must-precede&lt;/code&gt; are all relations, and a set of relations is a graph. Facts an agent has trouble deriving on its own often come out cleanly once you turn them into a graph.&lt;/p&gt;

&lt;p&gt;Put differently: every one of these facts can be written in a single language — a dependency graph. Impact, ordering, guard dominance, flow. It's the &lt;strong&gt;common language for handing facts to an agent&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;And the toolkit you need is smaller than you'd think: traversal, topological sort + cycle detection, strongly connected components (SCC), reachability, dominators, occasionally shortest path or max-flow. Past that, an eye for spotting when a problem &lt;em&gt;is&lt;/em&gt; a graph covers most of it, I'd say.&lt;/p&gt;

&lt;p&gt;A fact I want to hand an agent looks less like a paragraph and more like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DB.GUARD_BYPASS  repo.rs:142  severity=high  quality=verified
  claim:   delete reachable without tenant guard
  missing: assert_tenant() does not dominate this sink
  path:    handler → repo.raw_delete
  next:    use repo.delete() (scoped) | read(repo.rs:120)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The hard part isn't formatting the fact. It's producing it cheaply, with enough evidence to trust it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Graph theory has always been practical — but it feels like it's accelerating
&lt;/h2&gt;

&lt;p&gt;Graph theory, DAGs first among them, has long been put to practical use in software. Build systems, dependency resolution, scheduling, compilers, &lt;code&gt;plan&lt;/code&gt;/&lt;code&gt;apply&lt;/code&gt;-style orchestration — open any of them up and it's a graph. None of this is new.&lt;/p&gt;

&lt;p&gt;But it does feel like it's accelerating now, and I think the reason is AI coding. Implementing these algorithms used to sit behind a wall of cost and specialization. That wall has come down, and algorithms feel like they're being democratized in a practical sense.&lt;/p&gt;

&lt;p&gt;The framing I personally find useful is that an algorithm is two layers: a base theory, and on top of it, optimization for a specific domain. The theory is shared; the optimization is domain-specific. What AI dropped most is the latter — the cost of implementing it &lt;em&gt;for your own domain&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  So I've drifted from generic OSS toward small, homegrown detectors
&lt;/h2&gt;

&lt;p&gt;This shift quietly changed how I pick tools. Generic analysis OSS gives you the commodity — algorithms, parsers, a query language — and does it well. But the valuable layer, your own domain, is something you end up filling in yourself. A generic taint engine doesn't carry the assumption that &lt;code&gt;assert_tenant()&lt;/code&gt; is a required guard, or which calls are dangerous sinks, or the invariants of your declarative schema layer. The algorithms are interchangeable; the domain model on top is harder to swap.&lt;/p&gt;

&lt;p&gt;Parsing is a commodity (tree-sitter), the algorithms are a small known core, and an agent can help you write the domain rules quickly. So the bar for "worth building" drops, and a long tail of small, domain-specific detectors — never worth it before — starts to pencil out. That long tail, I suspect, is where a good chunk of the practical value lives. And the agent both builds them and consumes them: a generate ⇄ verify flywheel starts turning.&lt;/p&gt;

&lt;p&gt;The substrate these homegrown detectors build their facts on is two layers: tree-sitter and LSP. tree-sitter is an incremental, error-tolerant parser — it can pull structure cheaply even out of a half-written, broken buffer — but it doesn't resolve names, so I treat facts from it as &lt;code&gt;estimated&lt;/code&gt;. LSP actually resolves types and references, so those can be &lt;code&gt;verified&lt;/code&gt;, at the cost of being stateful, with cold starts and lag. What I find interesting is that the layer you pick maps straight onto the quality label — and that freshness tends to invert: synchronous, local tree-sitter is closest to the dirty buffer, while precise LSP can lag behind as its index catches up. So I've been doing an "until LSP catches up, fall back to tree-sitter and label it estimated" kind of switch. Right now I'm implementing and testing exactly this — driving the LSP client headless (keeping the buffer in sync via &lt;code&gt;didChange&lt;/code&gt;, juggling servers across languages) — and it's the kind of thing that gets more interesting the more you actually build it.&lt;/p&gt;

&lt;p&gt;The division of labor, roughly:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;tree-sitter&lt;/th&gt;
&lt;th&gt;LSP (language server)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Gives you&lt;/td&gt;
&lt;td&gt;syntax / structure (CST)&lt;/td&gt;
&lt;td&gt;resolved types &amp;amp; references&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Resolves names?&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Quality label&lt;/td&gt;
&lt;td&gt;&lt;code&gt;estimated&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;verified&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost / state&lt;/td&gt;
&lt;td&gt;fast, local, synchronous&lt;/td&gt;
&lt;td&gt;stateful, cold starts, lag&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Half-written buffer&lt;/td&gt;
&lt;td&gt;works (error-tolerant)&lt;/td&gt;
&lt;td&gt;often bails&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Freshness vs the edit&lt;/td&gt;
&lt;td&gt;closest to the dirty buffer&lt;/td&gt;
&lt;td&gt;can lag while re-indexing&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;There's one discipline I try to keep: where to draw the trust boundary. Dominator computations and dataflow fixpoints, written with AI help, tend to come out "plausible but subtly unsound" — and in hazard detection, unsoundness means a miss that reads as safety, which is the failure I most want to avoid. So, for myself, here's what I do:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;borrow battle-tested algorithm cores (don't hand-roll dominators)&lt;/li&gt;
&lt;li&gt;keep the bespoke part to the domain rules I can actually eyeball in review&lt;/li&gt;
&lt;li&gt;label every fact &lt;code&gt;verified&lt;/code&gt; (graph-derived) or &lt;code&gt;estimated&lt;/code&gt; (pattern-derived)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Where you put the "verification of the verifier" feels like what decides whether the flywheel turns toward value or toward error.&lt;/p&gt;

&lt;h2&gt;
  
  
  A concrete example: detecting dangerous DB operations
&lt;/h2&gt;

&lt;p&gt;Take the &lt;code&gt;DB.GUARD_BYPASS&lt;/code&gt; fact from earlier — here's how it gets computed. In a declarative schema-management tool, a hazard like "a dangerous write is reachable without passing the guard" turns into a fairly clean graph question: the required guard should dominate the dangerous sink, and if it doesn't, the sink is reachable without the guard. I compute this over read-models I already have (a symbol index, dependency edges), cheaply, and it's close enough to sound that I can emit it as &lt;code&gt;verified&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;By &lt;code&gt;verified&lt;/code&gt; I don't mean globally true in the formal-methods sense — I mean something narrower: the claim follows from the graph model I built, with evidence, rather than from the agent's guess. The model itself can still be incomplete, so the label is about the &lt;em&gt;source&lt;/em&gt; of a fact, not absolute truth.&lt;/p&gt;

&lt;p&gt;What's doing the work here is that &lt;em&gt;rigor produces compression&lt;/em&gt;. A pattern match can only hedge — "possibly an unscoped delete, please verify" — and hedging costs tokens. A dominator result can state one fact flatly: this guard does not dominate this sink. The more rigorous the analysis, the shorter the fact. "Doing it properly" and "handing it over in few tokens" might, it turns out, be the same investment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Personally, the database side is where it gets most interesting
&lt;/h2&gt;

&lt;p&gt;The same dependency question keeps reappearing at larger scales — first inside a function, then across a repository, then across storage systems: what depends on what, what must happen before what, and what changes when this node moves?&lt;/p&gt;

&lt;p&gt;The two-layer structure shows up most sharply, I find, around databases. Even just "storing data," the design changes with PostgreSQL vs SQLite. It changes again depending on whether you ship schema via migrations or express it declaratively. And OLTP vs OLAP are, infrastructurally, almost different worlds.&lt;/p&gt;

&lt;p&gt;But — and this is the part I find interesting — look at them through the lens of "how do you define the dependencies," and things that are scattered at the infrastructure level start to connect into a single graph. Transactions and analytics, writes and aggregations, mutable state and immutable snapshots. Physically separate worlds, yet contiguous on the abstraction of how dependencies are defined. Even an architecture where an event log links a control plane (Postgres) to a data plane (object storage + columnar) ends up looking, to me, like one dependency graph.&lt;/p&gt;

&lt;p&gt;Could you bind even heterogeneous infrastructure together in the &lt;em&gt;same&lt;/em&gt; dependency-graph language you use to hand an agent facts? I feel like there's quite a bit of potential in that view.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this is going
&lt;/h2&gt;

&lt;p&gt;I came in through the practical doorway of token efficiency and ended up at "deterministic facts to hand an agent," the graphs underneath them, the democratization of algorithms that makes them buildable, and the abstraction of dependency. The theory is mature, the implementation barrier has dropped, and per-domain craftsmanship is becoming the center of value, it feels like — and the area still feels wide open.&lt;/p&gt;

&lt;p&gt;I plan to dig into it for a while. As a start, I'll jot down the themes I'm interested in, a bit at a time.&lt;/p&gt;

&lt;p&gt;One last thing — a hope. These days, working with Claude Code or Codex, I keep tacking defensive lines onto my prompts: "watch for false positives," "don't miss false negatives," "don't guess, verify." It isn't that I distrust the agent. It's that the facts I'm handing it are the vague part, so reminders are the only way I can paper over the worry.&lt;/p&gt;

&lt;p&gt;My hope is that, as I keep practicing this graph stuff — as I get better at handing over grounded facts that carry their own &lt;code&gt;verified&lt;/code&gt; / &lt;code&gt;estimated&lt;/code&gt; labels — I'll need to write those "please be careful" lines a little less often.&lt;/p&gt;

&lt;p&gt;If the fact itself is sound, you don't have to ask the agent to be careful about it.&lt;/p&gt;

&lt;p&gt;This is the practical reason I care about the shape of facts, not just the size of context. Instead of telling the agent to be cautious, you bake the caution into the facts.&lt;/p&gt;

&lt;p&gt;That, maybe, is the view I most want to reach at the end of this detour.&lt;/p&gt;

</description>
      <category>agents</category>
      <category>staticanalysis</category>
      <category>lsp</category>
      <category>coding</category>
    </item>
  </channel>
</rss>
