<?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>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>
    <item>
      <title>Why AI Agents Make Me Reach for SQLite</title>
      <dc:creator>Takafumi Endo</dc:creator>
      <pubDate>Wed, 17 Jun 2026 03:34:20 +0000</pubDate>
      <link>https://dev.to/gyu07/why-ai-agents-make-me-reach-for-sqlite-4dh0</link>
      <guid>https://dev.to/gyu07/why-ai-agents-make-me-reach-for-sqlite-4dh0</guid>
      <description>&lt;p&gt;Lately I keep reaching for SQLite where, before, I'd have reached for Postgres without thinking.&lt;/p&gt;

&lt;p&gt;It started with small services, then a bigger question: could a multi-tenant SaaS actually run on SQLite? And for AI agents specifically, isn't a local, embedded database the more natural home for their state? &lt;/p&gt;

&lt;p&gt;Turso is the version of this stack I've found most compelling so far, especially when paired with Cloudflare. I wish D1 would reach embedded-replica parity with Turso, and that AWS offered a managed SQLite-style service the way it offers RDS for Postgres.&lt;/p&gt;

&lt;p&gt;This isn't a "Postgres is over" argument. I still use Postgres more often than SQLite. And it isn't advice. It's just where my thinking has drifted recently — written down mostly so I can find out where it's wrong. Read it as one person's notes, not a recommendation.&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;SQLite isn't replacing Postgres.&lt;/li&gt;
&lt;li&gt;For &lt;strong&gt;work state&lt;/strong&gt;, it's increasingly my first reach, not my last.&lt;/li&gt;
&lt;li&gt;AI agents push this harder: their state is high-churn, local, and mostly private.&lt;/li&gt;
&lt;li&gt;The answer isn't all-local. It's a local &lt;strong&gt;workbench&lt;/strong&gt; plus a central &lt;strong&gt;ledger&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Why the old default existed
&lt;/h2&gt;

&lt;p&gt;For years, "where does the data live?" had one practical answer: a server, behind an API, in a shared Postgres. A lot of that wasn't architecture — it was the cheapest shape available.&lt;/p&gt;

&lt;p&gt;SQLite was already everywhere, but it lacked the operational layer that makes a database viable as SaaS infrastructure: networking, replication, managed backups, and a way to run many small databases without drowning in tooling. So centralizing was the path of least resistance, and a &lt;code&gt;tenant_id&lt;/code&gt; column in shared Postgres became the reflex.&lt;/p&gt;

&lt;p&gt;What changed isn't SQLite. It's that the ecosystem grew the missing parts — and for a growing class of workloads, the thing doing the most frequent writing moved onto my own machine.&lt;/p&gt;

&lt;h2&gt;
  
  
  The constraint that's lifting
&lt;/h2&gt;

&lt;p&gt;SQLite itself is, by design:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Embedded, not networked&lt;/strong&gt; — a library, nothing listens on a port.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Single-file, not replicated&lt;/strong&gt; — no built-in read replicas or failover.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Single-writer&lt;/strong&gt; — WAL gives many readers + &lt;em&gt;one&lt;/em&gt; writer; contend and you hit &lt;code&gt;SQLITE_BUSY&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of this got fixed by changing SQLite. It got fixed &lt;em&gt;around&lt;/em&gt; it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;libSQL&lt;/strong&gt; (Turso's SQLite-compatible fork) — managed server layer, remote access, embedded replicas, encryption, vector search. &lt;em&gt;(The from-scratch Rust successor — "Limbo," now Turso Database — is a separate, less-mature track; don't conflate them.)&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Embedded replicas&lt;/strong&gt; — a full local SQLite copy synced into your process; reads local, writes forwarded to a primary.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cloudflare D1&lt;/strong&gt; — managed serverless SQLite with global read replication and read-your-writes via a Sessions API.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Durable Objects&lt;/strong&gt; — a SQLite database in the same thread as your code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Litestream / LiteFS&lt;/strong&gt; — stream the WAL to object storage for durability, without touching the write path.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The single-writer model hasn't disappeared, and I still design around it. But "SQLite can't be networked, replicated, or run as shared infrastructure" has stopped being quite as true in practice — so some of the reasons I used to centralize by reflex don't hold for me the way they once did.&lt;/p&gt;

&lt;h2&gt;
  
  
  State gravity
&lt;/h2&gt;

&lt;p&gt;A loose model I use to keep the trade-off straight: &lt;strong&gt;state gets pulled toward whatever reads and writes it most.&lt;/strong&gt; Put the writer far from the state and you pay the distance on every op — latency, egress, pools, consistency reconciliation. Put them close and most of that disappears.&lt;/p&gt;

&lt;p&gt;For two decades the writer was a human clicking a web app: small, occasional, round-trip-tolerant. Weak pull, so central state was fine.&lt;/p&gt;

&lt;p&gt;That writer changed. The useful agents run &lt;em&gt;inside&lt;/em&gt; the work environment — file system, terminal, IDE, browser tab, an already-authenticated session. Codex CLI runs in a local terminal against a directory you choose; Claude Code reads the codebase, edits files, and runs commands on your machine. The strength is &lt;strong&gt;proximity&lt;/strong&gt; as much as model quality.&lt;/p&gt;

&lt;p&gt;While it works, an agent holds a lot of state: task plan, file index, staged/unstaged diffs, tool-call history, failures and retries, approval state, a local search index, an embeddings cache, sync cursors, idempotency keys, an undo/redo log.&lt;/p&gt;

&lt;p&gt;It's high-churn, local, and useless to anyone else. Round-tripping all of it to central Postgres is expensive on three fronts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Latency&lt;/strong&gt; — local SQLite reads tend to be one to several orders of magnitude faster than a networked read (workload-dependent): an embedded replica answers from a local file; a remote read pays the network first.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost&lt;/strong&gt; — ledger-grade infrastructure to store scratch work deleted in an hour.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Privacy&lt;/strong&gt; — the moment an agent's index of &lt;em&gt;your&lt;/em&gt; files leaves the machine, you've created a data-handling problem you didn't need.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Writer moves local → work state follows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Local isn't just closer — it's bigger
&lt;/h2&gt;

&lt;p&gt;Proximity is one reason. The one I find most underrated is raw hardware — but I want to be careful about &lt;em&gt;which&lt;/em&gt; case it applies to, because it's easy to overreach here.&lt;/p&gt;

&lt;p&gt;It applies to the &lt;strong&gt;resident agent running on the user's own machine&lt;/strong&gt; — Claude Code, Codex, and the like, running as desktop/CLI apps, a distinctly desktop-shaped workload. Compare what each side gives that single user. A modern laptop, exclusively: several to a dozen-plus CPU cores, tens of GB of RAM, a fast NVMe SSD, the local file system, sometimes a GPU/NPU, and a session already authenticated as them. A cloud multi-tenant runtime would hand that same user a &lt;em&gt;slice&lt;/em&gt; — capped CPU, memory, disk I/O, persistent storage — and to run tools safely server-side you'd also need sandboxes, VMs, network controls, permissioning, audit logging, queueing. So even before network latency enters the picture, the local machine often wins on resources per user; the network just widens the gap. Let the model &lt;em&gt;think&lt;/em&gt; in the cloud — the reading, writing, diffing, indexing, searching, and caching are cheaper where the hardware actually is, and that's the work that generates the local state.&lt;/p&gt;

&lt;p&gt;It does &lt;strong&gt;not&lt;/strong&gt; automatically carry over to a server-side multi-tenant SaaS agent, where the agent runs in your own infrastructure rather than on the user's laptop — there's no laptop in the loop to be "bigger," so this particular argument simply doesn't apply. The case for SQLite &lt;em&gt;there&lt;/em&gt; rests on something different: the per-tenant economics, isolation, and blast-radius properties in the next section — not on "the edge has more compute." Two separate arguments for two separate cases, and I don't want to smuggle one in to prop up the other.&lt;/p&gt;

&lt;h2&gt;
  
  
  database-per-tenant, reconsidered
&lt;/h2&gt;

&lt;p&gt;The pattern I keep arriving at for SaaS: &lt;strong&gt;one SQLite database per tenant&lt;/strong&gt; (or per user, per agent), instead of shared Postgres with &lt;code&gt;tenant_id&lt;/code&gt; everywhere and RLS holding isolation together.&lt;/p&gt;

&lt;p&gt;Because &lt;strong&gt;a database is a file&lt;/strong&gt;, the economics flip:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tenant #10,000 is just another file — no per-database process, no cold start.&lt;/li&gt;
&lt;li&gt;Cost scales with &lt;em&gt;usage&lt;/em&gt;, not database &lt;em&gt;count&lt;/em&gt;. In Turso's model, database count isn't the scarce unit it would be with Postgres; D1's current limits allow up to &lt;strong&gt;50,000 databases/account&lt;/strong&gt;, with billing based on rows + storage rather than a per-database server.&lt;/li&gt;
&lt;li&gt;Isolation is &lt;strong&gt;physical&lt;/strong&gt;, not a &lt;code&gt;WHERE&lt;/code&gt; clause you hope you never forget.&lt;/li&gt;
&lt;li&gt;Backup, PITR, and export become per-file operations. "Delete this customer" gets &lt;em&gt;close&lt;/em&gt; to a per-file action — not literally &lt;code&gt;rm&lt;/code&gt;, since backups, logs, the warehouse, search indexes, and audit trails still matter, but the operational state isn't scattered across shared tables.&lt;/li&gt;
&lt;li&gt;Blast radius is bounded — a bad migration hits one file, not the shared cluster.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The honest costs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cross-tenant queries get harder.&lt;/strong&gt; Not solved by querying 10,000 files at runtime — solved by &lt;code&gt;outbox&lt;/code&gt; per tenant → event stream → central warehouse. That's infra you now own.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Migrations fan out.&lt;/strong&gt; N migrations with partial-failure states; you want tooling that treats "apply to all tenant DBs" as a first-class, resumable op.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Single-writer ceiling is per database.&lt;/strong&gt; Usually fine. A genuinely write-saturating tenant needs a different home — and isolation means &lt;em&gt;only that tenant&lt;/em&gt; does.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;D1 shape limits&lt;/strong&gt; — 10 GB per database, single-threaded query execution per database.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The trade: give up effortless cross-tenant analytics and single-migration simplicity; get physical isolation, flatter cost, simple per-tenant lifecycle ops, bounded blast radius. For a lot of the B2B SaaS I work on, that's felt like a good trade lately — though how it nets out depends heavily on the workload, and I wouldn't generalize it past that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two latency tiers
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  PRIMARY (source of truth)
     │  WAL frames
     ▼
  EDGE REPLICAS  ──── regional, network read
     │
     ▼
  EMBEDDED REPLICA ── in your process, local file read
     │
     └── writes ─────────────▶ forwarded to PRIMARY
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Regional read replicas&lt;/strong&gt; (D1, Turso cloud) put data near the user. D1's read latency can be excellent near the Worker — but it's still a &lt;em&gt;network&lt;/em&gt; read.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Embedded replicas&lt;/strong&gt; (Turso/libSQL) sync a real SQLite file &lt;em&gt;into the process&lt;/em&gt;, so reads never leave the box. This is the tier I want for agents, which read their own state constantly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In both, writes route to a single primary — the single-writer rule reappearing as a &lt;strong&gt;write-latency floor&lt;/strong&gt;. US-West primary + Singapore writer = a Pacific round trip, regardless. You design for read-locality and accept write-centralization, which conveniently matches "local work state, central facts."&lt;/p&gt;

&lt;p&gt;Consistency isn't free: D1's Sessions API gives sequential consistency within a session and read-your-writes via bookmarks. Adopt replicas, adopt their staleness model &lt;em&gt;on purpose&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The agent's database = workbench + memory
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Colocated compute + data.&lt;/strong&gt; A Durable Object is a named unit of compute with its own strongly-consistent, SQLite-backed storage: "this agent owns this workspace" becomes literal — one object, one database, one writer, on demand.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vector search in the same DB.&lt;/strong&gt; Turso ships native vector search alongside relational columns, so for agent memory and RAG, embeddings and records live together — no separate vector DB to sync.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Scratch state in local SQLite, an embedded replica for fast reads of shared state, vector search in-box for memory. Little of that wants to be a network call to central Postgres.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;"SQLite still has one writer."&lt;/strong&gt; True. I'm not claiming high write concurrency — I'm claiming many workloads don't need it &lt;em&gt;inside one database&lt;/em&gt; once you pick the right boundary. It's a reason to choose boundaries well, not to centralize everything.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Postgres does multi-tenant cheaply too."&lt;/strong&gt; It does — Neon, Aurora Serverless, Supabase, RLS. The point isn't "Postgres is bad" — it's that, for me, "shared Postgres + &lt;code&gt;tenant_id&lt;/code&gt;" stopped being the automatic starting point and became one option I weigh among others.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Local-first sync is hard."&lt;/strong&gt; The load-bearing difficulty. Sync is a product surface, not a background job. If two actors edit the same object, you need a conflict model before a database opinion. Per-tenant is easiest when tenants &lt;em&gt;don't&lt;/em&gt; share writable state.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Cross-tenant analytics gets painful."&lt;/strong&gt; Yes — you lose the free &lt;code&gt;JOIN&lt;/code&gt; and buy it back with a warehouse. Analytics-first across all tenants? Shared Postgres may still win. Tenant-isolated with roll-ups? Per-tenant tends to win.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where it's not all-SQLite
&lt;/h2&gt;

&lt;p&gt;Three things keep a strict central store:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Collaboration&lt;/strong&gt; — shared, concurrently-edited objects need sync, conflict resolution, permissions, a converged view. Local SQLite provides none of that.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Accountability&lt;/strong&gt; — who instructed it, which model, what permissions, what changed. That trace can't live only on the laptop it happened on.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Global truth&lt;/strong&gt; — money, inventory, billing, contracts. Spread across edges and the system can disagree with itself about how much money exists. The ledger stays central, strict, singular.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So the shape is a gradient — fast/disposable at the edge, slow/strict at the center:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  SYSTEM OF WORK — the workbench         (local · high churn · disposable)
  ┌──────────────────────────────────────────────┐
  │  Local SQLite          agent / device work     │
  │  Embedded replica      fast local reads         │
  │  Per-tenant SQLite     operational state        │
  └────────────────────────┬───────────────────────┘
                           │
                  ⟨ Event log — sync + audit spine ⟩
                           │
  ┌────────────────────────▼───────────────────────┐
  │  Central ledger (Postgres)  money · billing      │
  │                              inventory · contracts│
  │  Warehouse / search          cross-tenant analytics│
  └──────────────────────────────────────────────┘
  SYSTEM OF RECORD — the vault           (central · slow · strict)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Postgres doesn't go away. It stops being where I &lt;em&gt;start&lt;/em&gt;, and its free cross-tenant analytics moves to the warehouse.&lt;/p&gt;

&lt;h2&gt;
  
  
  A test: trace one change
&lt;/h2&gt;

&lt;p&gt;An agent on a laptop edits a customer-facing record. What did it touch? On whose authority? Against what state? Can the team see it without re-deriving it?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;All-central&lt;/strong&gt; — clean answers, paid for by routing every keystroke (mostly scratch) through the network. Ledger cost for workbench state.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;All-local&lt;/strong&gt; — &lt;em&gt;what&lt;/em&gt; and &lt;em&gt;against what&lt;/em&gt; are fast; &lt;em&gt;authority&lt;/em&gt; and &lt;em&gt;visibility&lt;/em&gt; have no home.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gradient&lt;/strong&gt; — scratch stays in local SQLite; the change that mattered flows through the event log into the ledger, where it's authorized, audited, shared.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What's still missing in 2026
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Turso/libSQL&lt;/strong&gt; — the most complete realization I've used: managed DBs, embedded replicas, vector search, per-tenant economics, open-source core (self-host exit). Build on the production C libSQL; treat the Rust successor as a separate track.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cloudflare D1&lt;/strong&gt; — great &lt;em&gt;if you live on Cloudflare&lt;/em&gt;: zero-ops, built-in read replication, per-tenant at scale, composes with Workers/DO/R2. What I miss is the embedded-replica model — D1 reads stay network reads. &lt;strong&gt;D1 reaching embedded-replica parity is the upgrade I'd most like to see.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Durable Objects&lt;/strong&gt; — the per-entity colocated-SQLite primitive; storage billing for SQLite-backed DOs starting January 2026 reads as a real product line.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS has no first-class managed SQLite/libSQL tier&lt;/strong&gt; — nothing comparable to RDS-for-Postgres or D1/Turso for SQLite workloads. You can assemble it (EFS/S3 + Litestream, SQLite on an ephemeral volume, rqlite on EC2), but there's no managed, replicated, embedded-replica-capable service with RDS-grade polish. For AWS-standardized teams, that's the biggest reason this is harder than it should be.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The pattern feels right to me, libSQL/Turso seem to show it working in production, Cloudflare shows a zero-ops version — and the major clouds, AWS most of all, haven't shipped the managed tier that would make adopting it boring.&lt;/p&gt;

&lt;h2&gt;
  
  
  Design for the gravity
&lt;/h2&gt;

&lt;p&gt;It compounds. As the desktop gets stronger, the browser follows: WASM, OPFS, Web Workers, WebGPU turn the tab from a display surface into a local runtime — which is why a real SQLite file persisting inside a browser tab stopped being exotic. Stronger local execution pulls more state local, and local state pulls SQLite in.&lt;/p&gt;

&lt;p&gt;Desktop agents, the browser becoming a runtime, database-per-tenant, the SQLite revival — to me these don't feel like separate trends so much as one shift seen from different angles: the writer moving back to the edge, the tooling finally giving the edge a real database, and state following. It all feels connected — though that's a hunch I hold loosely, not a thesis I'd defend to the death.&lt;/p&gt;

&lt;p&gt;Postgres can be the &lt;strong&gt;vault&lt;/strong&gt; again. SQLite, per tenant and per agent, becomes the &lt;strong&gt;workbench&lt;/strong&gt;. An event log carries the difference.&lt;/p&gt;

&lt;p&gt;That's the pull I keep feeling — designing for it a bit more, out of habit a bit less, and still figuring out the edges.&lt;/p&gt;

</description>
      <category>sqlite</category>
      <category>database</category>
      <category>ai</category>
      <category>architecture</category>
    </item>
  </channel>
</rss>
