<?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 | ROUTE06</title>
    <description>The latest articles on DEV Community by Takafumi Endo | ROUTE06 (@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 | ROUTE06</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>AIC: Packages Need an Interface for Coding Agents</title>
      <dc:creator>Takafumi Endo | ROUTE06</dc:creator>
      <pubDate>Mon, 10 Aug 2026 06:57:18 +0000</pubDate>
      <link>https://dev.to/gyu07/aic-packages-need-an-interface-for-coding-agents-562o</link>
      <guid>https://dev.to/gyu07/aic-packages-need-an-interface-for-coding-agents-562o</guid>
      <description>&lt;p&gt;I develop several tightly related repositories at the same time.&lt;/p&gt;

&lt;p&gt;Some are reusable SDKs for declarative schemas, infrastructure, stateful workflows, and other domain abstractions. Others are applications that consume several of those SDKs together.&lt;/p&gt;

&lt;p&gt;The development loop constantly crosses package boundaries.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; SDK A ──────┐
             │
 SDK B ──────┼──▶ application
             │        │
 SDK C ──────┘        │
    ▲                 │
    └──── feedback ───┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I've already written about why I don't think this requires a monorepo, and why I prefer the repository itself to carry the current source of truth:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/gyu07/ai-agents-dont-need-a-monorepo-they-need-a-readable-codebase-4c6f"&gt;AI Agents Don't Need a Monorepo. They Need a Readable Codebase&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/gyu07/the-repo-is-the-context-why-agents-dont-need-history-4ien"&gt;The Repo Is the Context: Why Agents Don't Need History&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I won't repeat those arguments here.&lt;/p&gt;

&lt;p&gt;This post starts one layer later.&lt;/p&gt;

&lt;p&gt;As these SDKs became more agent-aware, each package started needing to tell coding agents how it should be used.&lt;/p&gt;

&lt;p&gt;I was already using project-local surfaces such as &lt;code&gt;.claude/&lt;/code&gt;, &lt;code&gt;.codex/&lt;/code&gt;, &lt;code&gt;AGENTS.md&lt;/code&gt;, and package-specific skills. They are useful. Explicit project-local context works.&lt;/p&gt;

&lt;p&gt;The maintenance was the awkward part.&lt;/p&gt;

&lt;p&gt;When an SDK changed, I would tell the agent to update the corresponding instructions, rules, or skills in the consuming repository.&lt;/p&gt;

&lt;p&gt;That worked too.&lt;/p&gt;

&lt;p&gt;But after doing it repeatedly across several packages and repositories, I noticed something:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;My repeated update instructions had quietly become an undocumented protocol.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Which files should change?&lt;/p&gt;

&lt;p&gt;Which source is canonical?&lt;/p&gt;

&lt;p&gt;What should be copied?&lt;/p&gt;

&lt;p&gt;What should only be referenced?&lt;/p&gt;

&lt;p&gt;What belongs to the package, and what belongs to the consuming repository?&lt;/p&gt;

&lt;p&gt;How should different coding-agent harnesses receive the same package knowledge without creating independent copies?&lt;/p&gt;

&lt;p&gt;I initially thought I needed a better synchronizer.&lt;/p&gt;

&lt;p&gt;I now think the problem is one layer higher.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Packages already have an interface for programs. They increasingly need an interface for coding agents.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I've been calling the protocol I'm using for that interface &lt;strong&gt;AIC — Agent Index Convention&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It is still a draft from my own development environment. The exact mechanics will change.&lt;/p&gt;

&lt;p&gt;The boundary it describes feels much more stable.&lt;/p&gt;

&lt;h2&gt;
  
  
  The missing package interface
&lt;/h2&gt;

&lt;p&gt;Packages already know how to introduce themselves to programs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;package name
version
API
types
schemas
config
CLI
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A coding agent needs another set of facts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;where is the current manual?
which rules matter?
which skills are available?
which files are generated?
which commands are safe?
which host-specific configuration applies?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I think of this as the package's &lt;strong&gt;agent-facing interface&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The problem appears when several packages expose that interface inside the same host repository.&lt;/p&gt;

&lt;p&gt;Without a shared convention, each package tends to solve the problem independently.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;package A ──▶ AGENTS.md
package B ──▶ CLAUDE.md
package C ──▶ .claude/skills/
package D ──▶ .codex/...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each integration can be perfectly reasonable in isolation.&lt;/p&gt;

&lt;p&gt;Composition introduces a different set of problems.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concern&lt;/th&gt;
&lt;th&gt;Failure mode&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Ownership&lt;/td&gt;
&lt;td&gt;one package overwrites human or provider-owned context&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Freshness&lt;/td&gt;
&lt;td&gt;copied instructions drift from the installed package&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Discovery&lt;/td&gt;
&lt;td&gt;harnesses load different files and directories&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Composition&lt;/td&gt;
&lt;td&gt;every package assumes it owns the shared surface&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This stopped looking like documentation management.&lt;/p&gt;

&lt;p&gt;It started looking like package composition.&lt;/p&gt;

&lt;h2&gt;
  
  
  The AIC model
&lt;/h2&gt;

&lt;p&gt;AIC separates three actors:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Provider package
      │
      │ declares agent-facing assets
      ▼
Host repository
      │
      │ exposes them through actual harness loading paths
      ▼
Coding agent
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A &lt;strong&gt;Provider&lt;/strong&gt; is a package that supplies agent-facing context.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;Host&lt;/strong&gt; is the repository consuming that package.&lt;/p&gt;

&lt;p&gt;The provider ships its agent-facing source with the package:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;provider-package/
├── agent-index.json
├── AGENTS.md
└── skills/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A minimal manifest might look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"schema"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"agent-index/v1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"package"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@scope/schema-sdk"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0.14.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"summary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Declarative schema toolkit"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"manual"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AGENTS.md"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"skills"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"schema-design"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"src"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"skills/schema-design"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"instanceConfig"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"declarative"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"readFrom"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"schema.config.json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"format"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"fields"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"runtime"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"validation"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;agent-index.json&lt;/code&gt; is not another manual.&lt;/p&gt;

&lt;p&gt;It declares:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;identity
version
canonical manual
discoverable assets
host-resolved facts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The useful change for me is that the maintenance contract becomes declarative.&lt;/p&gt;

&lt;p&gt;Instead of repeatedly telling an agent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Update the Claude and Codex instructions
to match the latest SDK behavior.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;it can inspect a structure closer to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;provider
  ├── canonical manual
  ├── skills
  ├── resolved host config
  └── target loading semantics
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The update no longer depends on how well I happened to describe the maintenance task that day.&lt;/p&gt;

&lt;h2&gt;
  
  
  Inject, reference, or materialize
&lt;/h2&gt;

&lt;p&gt;This distinction has probably been the most useful part of AIC in actual use.&lt;/p&gt;

&lt;p&gt;Not all agent-facing assets should cross the package boundary in the same way.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Class&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;th&gt;Operation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Index&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;identity, version, pointers, resolved config&lt;/td&gt;
&lt;td&gt;inject&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Referenced&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;manuals, detailed rules&lt;/td&gt;
&lt;td&gt;keep with provider&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Materialized&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;harness-discovered skills&lt;/td&gt;
&lt;td&gt;copy deterministically&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                  ┌── Index  ─────▶ inject
Provider package ─┼── Manual ─────▶ reference
                  └── Skill  ─────▶ materialize
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I originally wanted one mechanism for all three.&lt;/p&gt;

&lt;p&gt;Actual loading semantics made that abstraction wrong.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reference what can remain version-bound
&lt;/h3&gt;

&lt;p&gt;Copying a manual into the host creates two independently changing truths.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;installed package   v0.14
copied manual       v0.13
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I've hit enough package version-skew problems elsewhere that I don't want to recreate the same class of bug in the agent-context layer.&lt;/p&gt;

&lt;p&gt;Here it is worse than ordinary stale documentation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A stale instruction consumed by an agent that can edit code and run commands is executable misinformation.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The agent may not be hallucinating at all.&lt;/p&gt;

&lt;p&gt;It may be behaving perfectly according to the wrong version.&lt;/p&gt;

&lt;p&gt;So the canonical manual stays with the installed package.&lt;/p&gt;

&lt;p&gt;The host stores a pointer, not another copy.&lt;/p&gt;

&lt;p&gt;In practice, this has been one of the more durable AIC decisions: upgrading the package does not require another manual copy to somehow remain synchronized.&lt;/p&gt;

&lt;h3&gt;
  
  
  Materialize what discovery requires
&lt;/h3&gt;

&lt;p&gt;Skills are different.&lt;/p&gt;

&lt;p&gt;If a harness discovers a skill only by scanning a particular directory, mentioning its package path is not equivalent to putting it where the harness looks.&lt;/p&gt;

&lt;p&gt;The asset needs physical presence.&lt;/p&gt;

&lt;p&gt;So the rule I use is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Reference when read access is enough. Materialize when discovery requires presence.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is more useful than either "copy everything" or "never copy."&lt;/p&gt;

&lt;p&gt;Context placement should follow actual loading semantics, not an aesthetically uniform abstraction.&lt;/p&gt;

&lt;p&gt;Of the AIC decisions I've been testing, this asymmetry is one of the ones I currently trust most.&lt;/p&gt;

&lt;h2&gt;
  
  
  One host index, many providers
&lt;/h2&gt;

&lt;p&gt;Each provider contributes a small namespaced block to the host index.&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;### Agent index: `@scope/schema-sdk` v0.14.0&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; Manual: read &lt;span class="sb"&gt;`node_modules/@scope/schema-sdk/AGENTS.md`&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Skills: managed under the harness discovery path
&lt;span class="p"&gt;-&lt;/span&gt; Host config: validation=strict
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Several providers can coexist:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AGENTS.md
│
├── human-owned content
├── @scope/package-a
├── @scope/package-b
└── @scope/package-c
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The central invariant is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;A provider owns its namespace, not &lt;code&gt;AGENTS.md&lt;/code&gt;.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Updating package B may update B's block.&lt;/p&gt;

&lt;p&gt;It may not:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rewrite human content
move package A
modify package C
regenerate the whole file
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Materialized assets follow the same rule: each provider gets its own collision-safe namespace.&lt;/p&gt;

&lt;p&gt;This is also something I've been able to test rather than only describe.&lt;/p&gt;

&lt;p&gt;In the implementations I'm using, foreign provider blocks are preserved rather than normalized into the current provider's representation. Repeating a sync with the same input is tested as a no-op. Providers can also share lock state without one provider flattening another provider's private fields.&lt;/p&gt;

&lt;p&gt;Those details are intentionally boring.&lt;/p&gt;

&lt;p&gt;But they are the difference between saying "multiple providers can coexist" and actually letting them coexist.&lt;/p&gt;

&lt;p&gt;The packages do not need pairwise integrations.&lt;/p&gt;

&lt;p&gt;Package A does not need to know package B exists.&lt;/p&gt;

&lt;p&gt;Package B does not need a plugin for package C.&lt;/p&gt;

&lt;p&gt;They compose because they share an ownership rule.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Coordination is expensive. Namespaces are cheap.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is the point where AIC stopped feeling like a synchronizer to me.&lt;/p&gt;

&lt;p&gt;It started feeling like a package protocol.&lt;/p&gt;

&lt;h2&gt;
  
  
  Harness-specific files are derived surfaces
&lt;/h2&gt;

&lt;p&gt;AIC is not an argument against &lt;code&gt;.claude/&lt;/code&gt;, &lt;code&gt;.codex/&lt;/code&gt;, or other harness-specific locations.&lt;/p&gt;

&lt;p&gt;I use them because they are useful.&lt;/p&gt;

&lt;p&gt;The problem was maintaining them independently.&lt;/p&gt;

&lt;p&gt;I want:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                       ┌── AGENTS.md
                       │
provider source ───────┼── Claude adapter
                       ├── Codex adapter
                       ├── skill discovery adapter
                       └── other thin adapters
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;not:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;manual
├── Claude copy
├── Codex copy
├── Cursor copy
└── another copy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Harness-specific files are &lt;strong&gt;derived surfaces&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The canonical package knowledge remains singular.&lt;/p&gt;

&lt;p&gt;This also gives the design an escape hatch.&lt;/p&gt;

&lt;p&gt;If a harness eventually provides a better native mechanism for consuming package-owned instructions or skills, the adapter should disappear.&lt;/p&gt;

&lt;p&gt;The package boundary does not have to.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The adapter layer is expendable. The boundary declaration is not.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That distinction matters because harness behavior will probably change faster than package contracts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sync should be boring
&lt;/h2&gt;

&lt;p&gt;AIC currently has lifecycle operations roughly like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;agent-index &lt;span class="nb"&gt;sync
&lt;/span&gt;agent-index check
agent-index remove
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The most important property is idempotence.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;same provider
same version
same source
same managed host state
       │
       ▼
     no-op
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running &lt;code&gt;sync&lt;/code&gt; twice with identical inputs should not produce a second write, timestamp churn, or Git diff.&lt;/p&gt;

&lt;p&gt;For materialized assets, AIC tracks both sides:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;provider source ── hash ──▶ sourceHash
host copy       ── hash ──▶ destHash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That distinguishes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;provider upgraded
host copy locally edited
nothing changed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Those states should not all result in "copy again."&lt;/p&gt;

&lt;p&gt;The original asset is materialized byte-for-byte. Provenance sits beside it rather than being injected into skill frontmatter, scripts, or templates.&lt;/p&gt;

&lt;p&gt;I don't want sophisticated reconciliation here.&lt;/p&gt;

&lt;p&gt;I want deterministic ownership with boring failure modes.&lt;/p&gt;

&lt;p&gt;There is another reason I care about determinism:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;agents are maintainers too.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A human can often infer that two slightly different layouts represent roughly the same convention.&lt;/p&gt;

&lt;p&gt;An agent benefits more from:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;one format
one ownership rule
one lifecycle
one source of truth
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The easier the maintenance structure is to inspect mechanically, the less the next agent session has to reconstruct from prose.&lt;/p&gt;

&lt;h2&gt;
  
  
  Determinism does not eliminate drift
&lt;/h2&gt;

&lt;p&gt;This is one place where implementation made the tradeoff clearer.&lt;/p&gt;

&lt;p&gt;A protocol can define what should be synchronized without guaranteeing that every synchronization happens immediately.&lt;/p&gt;

&lt;p&gt;I've had cases where a package version and its managed agent state temporarily diverged because a manual step was missed.&lt;/p&gt;

&lt;p&gt;That is exactly the class of problem AIC is intended to make detectable.&lt;/p&gt;

&lt;p&gt;It is also evidence that declaring the protocol does not magically remove its maintenance cost.&lt;/p&gt;

&lt;p&gt;The current split is deliberate:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Operation&lt;/th&gt;
&lt;th&gt;Responsibility&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;preflight&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;cheap version-staleness repair&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;check&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;full version and integrity verification&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sync&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;explicit verification and regeneration&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The common path stays cheap.&lt;/p&gt;

&lt;p&gt;The stronger path stays explicit.&lt;/p&gt;

&lt;p&gt;I prefer that to turning every CLI startup into a full filesystem integrity scan.&lt;/p&gt;

&lt;h2&gt;
  
  
  Generated agent state belongs in Git
&lt;/h2&gt;

&lt;p&gt;I commit the generated index, materialized skills, and lock/provenance state.&lt;/p&gt;

&lt;p&gt;A change like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;- ### Agent index: `@scope/schema-sdk` v0.13.0
&lt;/span&gt;&lt;span class="gi"&gt;+ ### Agent index: `@scope/schema-sdk` v0.14.0
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;changes what the agent can discover.&lt;/p&gt;

&lt;p&gt;A changed materialized skill can change what the agent can do.&lt;/p&gt;

&lt;p&gt;I want those changes visible beside the dependency update that caused them.&lt;/p&gt;

&lt;p&gt;So I think of AIC output more like this:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Artifact&lt;/th&gt;
&lt;th&gt;Reviewable state&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;lockfile&lt;/td&gt;
&lt;td&gt;dependency resolution&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;generated schema&lt;/td&gt;
&lt;td&gt;structural contract&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;migration plan&lt;/td&gt;
&lt;td&gt;intended transition&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;agent index / skill&lt;/td&gt;
&lt;td&gt;agent operating context&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Generation isn't the problem. Invisible generation is.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Preflight repairs; it doesn't claim ownership
&lt;/h2&gt;

&lt;p&gt;Keeping generated state fresh naturally leads to automatic repair.&lt;/p&gt;

&lt;p&gt;This is where I've become deliberately conservative.&lt;/p&gt;

&lt;p&gt;I don't want dependency installation to silently rewrite host-owned files.&lt;/p&gt;

&lt;p&gt;I also don't want the first execution of an SDK CLI to decide by itself that the repository has opted into agent integration.&lt;/p&gt;

&lt;p&gt;So AIC distinguishes adoption from maintenance.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;first encounter
    └──▶ warn / explicit sync

existing provider + stale version
    └──▶ repair own namespace

managed content locally edited
    └──▶ preserve + warn

CI / read-only filesystem
    └──▶ don't mutate

removed or disabled provider
    └──▶ stay removed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The invariant is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Automation may maintain established ownership. It should not invent ownership.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There is a tradeoff.&lt;/p&gt;

&lt;p&gt;Preflight needs an execution opportunity.&lt;/p&gt;

&lt;p&gt;If a dependency is upgraded but the relevant provider CLI has not run yet, materialized state can temporarily remain stale.&lt;/p&gt;

&lt;p&gt;I accept that window today.&lt;/p&gt;

&lt;p&gt;The alternative would be making package installation mutate the host automatically, which I currently consider the worse ownership boundary.&lt;/p&gt;

&lt;p&gt;Teams that need a stricter guarantee can run &lt;code&gt;check&lt;/code&gt; explicitly or in CI.&lt;/p&gt;

&lt;p&gt;So preflight does not eliminate drift.&lt;/p&gt;

&lt;p&gt;It makes the common repair path cheap while leaving full integrity verification explicit.&lt;/p&gt;

&lt;p&gt;This also removes another piece of my old workflow.&lt;/p&gt;

&lt;p&gt;I no longer want this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Please update the agent files after this SDK change.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;to be part of the development procedure.&lt;/p&gt;

&lt;p&gt;If the relationship is structural, the update rule should be structural too.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this actually works today
&lt;/h2&gt;

&lt;p&gt;There is an important qualification.&lt;/p&gt;

&lt;p&gt;The providers I'm using AIC with today are part of the same development environment.&lt;/p&gt;

&lt;p&gt;I control both sides of the protocol.&lt;/p&gt;

&lt;p&gt;That gives me something useful: I can test whether independently versioned packages actually compose through the same rules.&lt;/p&gt;

&lt;p&gt;It does not yet prove that unrelated third-party package authors will adopt them.&lt;/p&gt;

&lt;p&gt;So today I would describe AIC as a &lt;strong&gt;working convention inside one ecosystem&lt;/strong&gt;, not an ecosystem-wide standard.&lt;/p&gt;

&lt;p&gt;The distinction matters because much of the eventual value of a protocol comes from network effects.&lt;/p&gt;

&lt;p&gt;That is the part I have not demonstrated yet.&lt;/p&gt;

&lt;p&gt;There are a few other current assumptions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Referenced assets require an installed package
&lt;/h3&gt;

&lt;p&gt;A manual that stays inside the installed package is only useful once the dependency is actually available.&lt;/p&gt;

&lt;p&gt;On a fresh clone before dependency installation, the pointer can temporarily lead nowhere.&lt;/p&gt;

&lt;p&gt;I currently accept that because the reference is meant to describe the installed package state.&lt;/p&gt;

&lt;p&gt;It does mean AIC is not a substitute for dependency availability.&lt;/p&gt;

&lt;h3&gt;
  
  
  AIC does not invent universal discovery
&lt;/h3&gt;

&lt;p&gt;AIC relies on an entry point the harness already loads — for example &lt;code&gt;AGENTS.md&lt;/code&gt;, an import into it, or a harness-specific discovery directory.&lt;/p&gt;

&lt;p&gt;It does not solve "how does every possible coding agent discover AIC?" from first principles.&lt;/p&gt;

&lt;p&gt;That is intentional.&lt;/p&gt;

&lt;p&gt;I would rather adapt to real loading behavior than introduce another mandatory bootstrap mechanism.&lt;/p&gt;

&lt;h3&gt;
  
  
  Materialized assets expand the trust boundary
&lt;/h3&gt;

&lt;p&gt;A referenced manual is information.&lt;/p&gt;

&lt;p&gt;A materialized skill can influence what an agent actually does.&lt;/p&gt;

&lt;p&gt;That makes third-party providers a different trust problem from packages I control myself.&lt;/p&gt;

&lt;p&gt;Content hashes tell me whether an asset changed.&lt;/p&gt;

&lt;p&gt;They do not answer who should be trusted to supply that asset.&lt;/p&gt;

&lt;p&gt;That is one of the areas I would want to make more explicit before treating &lt;code&gt;agent-index/v1&lt;/code&gt; as a third-party ecosystem contract.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I expect to survive
&lt;/h2&gt;

&lt;p&gt;Agent harnesses are moving quickly.&lt;/p&gt;

&lt;p&gt;Native packaging for instructions, skills, or package-owned agent context may eventually absorb some of what AIC adapters do today.&lt;/p&gt;

&lt;p&gt;If that happens, I don't want to defend the current synchronization machinery for its own sake.&lt;/p&gt;

&lt;p&gt;The parts I expect to survive are smaller:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;declare the agent-facing package boundary

own only your namespace

keep canonical knowledge version-bound

separate reference from discovery-required materialization

make derived state deterministic and reviewable

respect prior host ownership decisions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The adapter layer is expendable.&lt;/p&gt;

&lt;p&gt;The boundary declaration is the interesting part.&lt;/p&gt;

&lt;p&gt;That is also why I don't want to freeze the schema too early.&lt;/p&gt;

&lt;p&gt;Before asking unrelated providers to adopt it, I would rather make the convention boring inside the repositories I already operate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fewer drift incidents
fewer manual repair instructions
fewer conventions that exist only in my head
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If that keeps working, the format has evidence behind it.&lt;/p&gt;

&lt;p&gt;If the ecosystem converges on a better native mechanism, AIC should become a thin adapter to that mechanism rather than compete with it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is still draft
&lt;/h2&gt;

&lt;p&gt;The parts I currently think are structural:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;packages need an explicit agent-facing interface&lt;/li&gt;
&lt;li&gt;multiple providers need namespace ownership&lt;/li&gt;
&lt;li&gt;canonical manuals should remain version-bound to their packages&lt;/li&gt;
&lt;li&gt;referenced and discoverable assets need different persistence semantics&lt;/li&gt;
&lt;li&gt;harness-specific surfaces should be derived rather than independent sources of truth&lt;/li&gt;
&lt;li&gt;synchronization should be deterministic and reviewable&lt;/li&gt;
&lt;li&gt;maintenance structures should be easy for agents themselves to inspect&lt;/li&gt;
&lt;li&gt;automatic repair should respect established ownership&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The parts I expect to change:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the exact &lt;code&gt;agent-index.json&lt;/code&gt; schema&lt;/li&gt;
&lt;li&gt;workspace and nested-repository scoping&lt;/li&gt;
&lt;li&gt;harness adapters&lt;/li&gt;
&lt;li&gt;skill discovery paths&lt;/li&gt;
&lt;li&gt;preflight policy&lt;/li&gt;
&lt;li&gt;integrity and trust rules for third-party providers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a while, I thought I needed a better way to tell coding agents to keep &lt;code&gt;.claude/&lt;/code&gt;, &lt;code&gt;.codex/&lt;/code&gt;, manuals, and skills synchronized.&lt;/p&gt;

&lt;p&gt;Eventually I realized that the repeated instruction was itself the missing specification.&lt;/p&gt;

&lt;p&gt;Packages already know how to introduce themselves to programs.&lt;/p&gt;

&lt;p&gt;AIC is my attempt to give them a small, composable way to introduce themselves to coding agents.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>architecture</category>
      <category>tooling</category>
      <category>agents</category>
    </item>
    <item>
      <title>Installing OpenTelemetry Won't Close Your AI Agent's Feedback Loop</title>
      <dc:creator>Takafumi Endo | ROUTE06</dc:creator>
      <pubDate>Tue, 04 Aug 2026 06:25:36 +0000</pubDate>
      <link>https://dev.to/gyu07/installing-opentelemetry-wont-close-your-ai-agents-feedback-loop-11gf</link>
      <guid>https://dev.to/gyu07/installing-opentelemetry-wont-close-your-ai-agents-feedback-loop-11gf</guid>
      <description>&lt;p&gt;I recently did a lightweight review of how OpenTelemetry (OTel) is being used across three repositories under development at my company:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An AI agent application built primarily with Node.js and AWS Lambda&lt;/li&gt;
&lt;li&gt;An Electron-based desktop application&lt;/li&gt;
&lt;li&gt;A TypeScript SDK and compiler for developers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All three are works in progress. This article is a snapshot of the development environments and codebases at the time of the review, not an assessment of finished products. I used Claude Code to inspect dependencies, configuration, and selected execution paths, so the depth of the review also varied by repository.&lt;/p&gt;

&lt;p&gt;I focused on three questions:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Are we actually using the OpenTelemetry mechanisms that exist in the codebase?&lt;br&gt;
What makes adoption or operation difficult?&lt;br&gt;
Can the resulting telemetry help Claude Code or Codex implement and verify changes?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The background: I have been experimenting with ways to make code, documentation, and runtime behavior easier for coding agents to understand, to get more reliable results from tools such as Claude Code and Codex. I understand OTel's value as a way to give telemetry from different services a shared meaning and a shared causal structure. What I have not yet developed is a confident sense of &lt;em&gt;when&lt;/em&gt; to introduce it and &lt;em&gt;how&lt;/em&gt; to wire it into the everyday development loop.&lt;/p&gt;

&lt;p&gt;My conclusion up front is simple:&lt;/p&gt;

&lt;p&gt;Installing OTel is not the goal. Telemetry becomes part of an AI-assisted development loop only when it is actually generated, accessible to the agent, correlated as one execution, and comparable before and after a change.&lt;/p&gt;

&lt;p&gt;Along the way, I will propose six conditions for agent-usable observability — standardization, propagation, discoverability, controllability, accessibility, and comparability — that I now find more useful than asking "do we have OTel?"&lt;/p&gt;

&lt;h2&gt;
  
  
  "It exists," "it emits," and "the agent can use it" are different states
&lt;/h2&gt;

&lt;p&gt;The three repositories were in very different states:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Repository&lt;/th&gt;
&lt;th&gt;State at the time of review&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;AI agent application&lt;/td&gt;
&lt;td&gt;No direct OTel usage; relies on structured logs, metrics, X-Ray, and audit records&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Desktop application&lt;/td&gt;
&lt;td&gt;A Collector and related environment variables exist, but the application emits little or no telemetry to them&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SDK and compiler&lt;/td&gt;
&lt;td&gt;OTel integration is implemented as an opt-in feature, but internal usage is limited&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The most useful distinction was between three separate states:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;An observability mechanism &lt;strong&gt;exists&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;It actually &lt;strong&gt;produces telemetry&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;An AI agent can &lt;strong&gt;use that telemetry to make a decision&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A running Collector does not help if the application never emits spans. An integration API does not help if nothing calls it.&lt;/p&gt;

&lt;p&gt;One repository showed the shape of the problem clearly. A tracing middleware existed — typed, exported, and documented — but the application's middleware pipeline never included it. Everything about the module looked "done," yet the system produced zero trace data. A coding agent grepping the repo would find a plausible-looking observability layer, and nothing to read at runtime.&lt;/p&gt;

&lt;p&gt;This was not specific to OTel. A custom trace system in another repository had the same gap: trace types and debugging features existed, but the middleware was not connected to the runtime.&lt;/p&gt;

&lt;p&gt;The underlying failure mode: &lt;strong&gt;we treat the implementation of an observability mechanism as completion, without verifying that it produces evidence and that someone — or something — actually consumes it.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  OpenTelemetry's core value
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://opentelemetry.io/docs/" rel="noopener noreferrer"&gt;OpenTelemetry&lt;/a&gt; is a vendor-neutral framework for generating, collecting, and exporting signals such as traces, metrics, and logs. The &lt;a href="https://opentelemetry.io/docs/collector/" rel="noopener noreferrer"&gt;OpenTelemetry Collector&lt;/a&gt; provides a common pipeline for receiving, processing, and forwarding telemetry to one or more backends.&lt;/p&gt;

&lt;p&gt;From the perspective of a coding agent, two parts are especially valuable.&lt;/p&gt;

&lt;h3&gt;
  
  
  Shared meaning
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://opentelemetry.io/docs/specs/semconv/" rel="noopener noreferrer"&gt;Semantic Conventions&lt;/a&gt; standardize not only attribute names, but also their types, meanings, and units across common domains such as HTTP, databases, RPC, and messaging. When the same concept is represented by the same attribute across repositories, Claude Code or Codex has less project-specific naming to infer.&lt;/p&gt;

&lt;h3&gt;
  
  
  Shared causality
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://opentelemetry.io/docs/concepts/context-propagation/" rel="noopener noreferrer"&gt;Context propagation&lt;/a&gt; carries trace context across process and network boundaries so that distributed operations can be connected as one causal chain. Individual logs may be searchable, but if an API request, an async job, a worker execution, and an audit event cannot be associated with the same operation, it is hard to establish what actually caused a failure.&lt;/p&gt;

&lt;p&gt;To me, the core value of OTel is not that it creates more telemetry. It gives telemetry from different execution environments a shared vocabulary and a common correlation key.&lt;/p&gt;

&lt;h2&gt;
  
  
  An agent can read a system without OTel
&lt;/h2&gt;

&lt;p&gt;The first repository did not use OTel at all — yet it was the easiest for the agent to inspect.&lt;/p&gt;

&lt;p&gt;It had structured logs, cloud metrics, X-Ray traces, audit records, and a data pipeline that allowed operational data to be re-queried with SQL. Some of the available datasets and analysis procedures were described as repository-level contracts.&lt;/p&gt;

&lt;p&gt;In that kind of system, a coding agent can inspect runtime behavior just fine.&lt;/p&gt;

&lt;p&gt;So in the repositories I reviewed, OTel was &lt;strong&gt;neither a necessary nor a sufficient condition&lt;/strong&gt; for AI-readable observability. The real requirement was that telemetry be structured, actually generated, reachable by the agent, and joinable as one execution.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI readability depends on where the data appears
&lt;/h2&gt;

&lt;p&gt;The review also revealed a much more basic requirement:&lt;/p&gt;

&lt;p&gt;Can Claude Code or Codex reach the data with the tools already available in the session — Read, Grep, and a shell?&lt;/p&gt;

&lt;p&gt;In one repository, a lightweight trace written into the workspace as JSON and Markdown was the easiest runtime artifact for the agent to use. A richer trace existed in cloud storage, but reaching it required authentication, environment setup, network access, and waiting. That made it far less likely to be used in a loop measured in seconds.&lt;/p&gt;

&lt;p&gt;For the development loops I reviewed, accessibility looked roughly like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Local files and stdout
  &amp;gt; Read-only MCP, CLI, or SQL access
  &amp;gt; An authenticated cloud UI
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is not a universal ranking — cloud telemetry is essential for production incidents. But when an agent edits and validates in a tight loop, immediate local access wins.&lt;/p&gt;

&lt;p&gt;Fortunately, OTel makes the local path cheap. OTel SDKs commonly provide development exporters that write telemetry to the console or stdout — the &lt;a href="https://opentelemetry.io/docs/languages/python/exporters/" rel="noopener noreferrer"&gt;Python SDK&lt;/a&gt;, for example. The Collector's &lt;a href="https://opentelemetry.io/docs/collector/configuration/" rel="noopener noreferrer"&gt;debug exporter&lt;/a&gt; can print telemetry locally, while the Collector Contrib distribution also provides a file exporter. Third-party terminal viewers such as &lt;a href="https://github.com/ymtdzzz/otel-tui" rel="noopener noreferrer"&gt;otel-tui&lt;/a&gt; let you inspect OTLP locally without any backend. When introducing OTel, wiring up a dev-mode path to stdout or local files first — before any remote backend — gives the agent something to read on day one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Six conditions for agent-usable observability
&lt;/h2&gt;

&lt;p&gt;This is the framework I now use instead of "is OTel installed?":&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Condition&lt;/th&gt;
&lt;th&gt;Question&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Standardization&lt;/td&gt;
&lt;td&gt;Are attribute meanings, types, and units defined?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Propagation&lt;/td&gt;
&lt;td&gt;Can the same execution be followed across HTTP and async boundaries?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Discoverability&lt;/td&gt;
&lt;td&gt;Can the available attributes and datasets be enumerated programmatically?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Controllability&lt;/td&gt;
&lt;td&gt;Can time ranges, result limits, and permissions be safely constrained?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Accessibility&lt;/td&gt;
&lt;td&gt;Can the agent read the data within its implementation loop?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Comparability&lt;/td&gt;
&lt;td&gt;Can the state before and after a change be evaluated under the same conditions?&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Different layers of the stack serve different conditions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;OTel is particularly strong at &lt;strong&gt;standardization&lt;/strong&gt; and &lt;strong&gt;propagation&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Collectors and backends support processing, storage, and &lt;strong&gt;controllability&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;MCP servers, CLIs, SQL interfaces, and local files support &lt;strong&gt;discoverability&lt;/strong&gt; and &lt;strong&gt;accessibility&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Queries, tests, and analytical models that evaluate changes support &lt;strong&gt;comparability&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No single layer closes the development loop by itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  OTel does not define domain meaning
&lt;/h2&gt;

&lt;p&gt;Semantic Conventions primarily standardize infrastructure concerns. They do not fully express domain-specific events from an AI agent system, such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A guard rejected an operation&lt;/li&gt;
&lt;li&gt;A hazardous action required approval&lt;/li&gt;
&lt;li&gt;The system returned a correctable error&lt;/li&gt;
&lt;li&gt;A particular next action became available&lt;/li&gt;
&lt;li&gt;A decision was based on a specific piece of evidence&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Adopting OTel does not remove the need for custom attributes and repository-level schema contracts. Work is continuing on GenAI-related conventions in the &lt;a href="https://github.com/open-telemetry/semantic-conventions" rel="noopener noreferrer"&gt;Semantic Conventions repository&lt;/a&gt;, including conventions for GenAI clients and MCP — but that still does not mean every agent- or workflow-specific concept can or should be standardized.&lt;/p&gt;

&lt;p&gt;A native execution trace may also carry richer information than an OTel span: correction codes, permitted next actions, references to evidence, state transitions, replay information. In such systems, OTel should not necessarily replace the native trace. A more natural separation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keep domain-specific execution data in the native trace&lt;/li&gt;
&lt;li&gt;Project shared operational meaning and cross-system causality into OTel&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The OTel representation may be a deliberately &lt;strong&gt;lossy projection&lt;/strong&gt; of the richer native trace.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do not put internal IDs directly into &lt;code&gt;traceparent&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The SDK and compiler repository exposed a concrete integration problem.&lt;/p&gt;

&lt;p&gt;Suppose the system uses human-readable internal identifiers such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;trace_0001
run_abc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Those values cannot be placed directly into the Trace ID or Parent ID fields of &lt;code&gt;traceparent&lt;/code&gt;. Under the &lt;a href="https://www.w3.org/TR/trace-context/" rel="noopener noreferrer"&gt;W3C Trace Context specification&lt;/a&gt;, the Trace ID must be 32 lowercase hex characters and the Parent ID 16, and all-zero values are invalid. A naive conversion produces something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;00-0000000000000000000000trace_0001-run_abc000000000-01
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A compliant receiver must ignore the invalid &lt;code&gt;traceparent&lt;/code&gt;, so the downstream operation may start a new trace or remain uncorrelated — propagation silently fails.&lt;/p&gt;

&lt;p&gt;The cleaner design:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Let the tracer generate valid OTel Trace IDs and Span IDs&lt;/li&gt;
&lt;li&gt;Preserve the internal execution ID as a span attribute&lt;/li&gt;
&lt;li&gt;Make the two identifiers joinable at query time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And note: the existence of a propagation helper does not prove propagation works. Test it across a real service boundary and confirm the resulting spans land in the same trace.&lt;/p&gt;

&lt;h2&gt;
  
  
  OTel and audit evidence serve different purposes
&lt;/h2&gt;

&lt;p&gt;Introducing OTel does not mean replacing existing logs, audit records, or native traces:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Data&lt;/th&gt;
&lt;th&gt;Primary purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Spans&lt;/td&gt;
&lt;td&gt;Latency, errors, and distributed causal analysis&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Metrics&lt;/td&gt;
&lt;td&gt;Aggregation, trends, and alerts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Logs&lt;/td&gt;
&lt;td&gt;Detailed event and exception search&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Audit records&lt;/td&gt;
&lt;td&gt;Accountability, critical actions, and tamper evidence&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Native traces&lt;/td&gt;
&lt;td&gt;State transitions, corrections, and deterministic replay&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Audit records that require tamper detection or deterministic replay should not be moved into sampled OTel spans. OTel is a common observability layer, not an audit ledger.&lt;/p&gt;

&lt;h2&gt;
  
  
  MCP can become the agent-facing query layer
&lt;/h2&gt;

&lt;p&gt;Standardized telemetry is not enough; the agent also needs a safe interface for querying it.&lt;/p&gt;

&lt;p&gt;At the time of writing, Grafana provides an &lt;a href="https://grafana.com/docs/grafana/latest/developer-resources/mcp/" rel="noopener noreferrer"&gt;official MCP server&lt;/a&gt; and documents connecting Claude Code and Codex CLI to observability data. Sentry provides an &lt;a href="https://docs.sentry.io/product/sentry-mcp/" rel="noopener noreferrer"&gt;official MCP integration&lt;/a&gt; for exposing issue and error data to coding agents.&lt;/p&gt;

&lt;p&gt;A useful division of responsibility:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;OTel provides shared meaning and causality&lt;/li&gt;
&lt;li&gt;MCP, CLI, and SQL interfaces provide the agent-facing query surface&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But a query interface is not automatically safe or useful. It still needs read-only access, controllable time ranges and result limits, discoverable attributes, repeatable queries, and before-and-after comparison — the six conditions again, viewed from the interface side.&lt;/p&gt;

&lt;h2&gt;
  
  
  The coding agent is also a telemetry source
&lt;/h2&gt;

&lt;p&gt;One more angle worth noting for readers using Claude Code: the agent is not only a reader of telemetry.&lt;/p&gt;

&lt;p&gt;Claude Code supports OTel export of its own metrics and events — sessions, token usage, tool executions — enabled via environment variables such as &lt;code&gt;CLAUDE_CODE_ENABLE_TELEMETRY=1&lt;/code&gt; and &lt;code&gt;OTEL_METRICS_EXPORTER&lt;/code&gt; / &lt;code&gt;OTEL_LOGS_EXPORTER&lt;/code&gt; (with &lt;code&gt;console&lt;/code&gt; available as an exporter for local debugging). Events emitted while processing a single user prompt share a &lt;code&gt;prompt.id&lt;/code&gt; attribute, so a prompt's API calls and tool executions can be correlated in the backend. See the &lt;a href="https://code.claude.com/docs/en/monitoring-usage" rel="noopener noreferrer"&gt;official monitoring documentation&lt;/a&gt; for details.&lt;/p&gt;

&lt;p&gt;This closes an interesting circle: the same standardization and propagation that make your system readable to the agent can also make the agent's behavior readable to your team.&lt;/p&gt;

&lt;h2&gt;
  
  
  The hard part is often the data boundary, not instrumentation
&lt;/h2&gt;

&lt;p&gt;When introducing OTel, configuring an SDK or Collector may not be the hardest part. The harder question is what should be recorded and where it should be sent.&lt;/p&gt;

&lt;p&gt;URLs, SQL statements, exceptions, prompts, tool arguments, and user data can unintentionally appear in telemetry. The &lt;a href="https://opentelemetry.io/docs/security/handling-sensitive-data/" rel="noopener noreferrer"&gt;OpenTelemetry guidance on handling sensitive data&lt;/a&gt; recommends avoiding collection of sensitive information where possible, and describes removing or transforming data with Collector processors (attribute, filter, redaction, transform).&lt;/p&gt;

&lt;p&gt;However, moving all redaction responsibility into the Collector can make critical policy invisible from the application repository. Collector configuration should be versioned, reviewed, and tested alongside the application — or the application should at least maintain a clear contract describing which attributes may be collected.&lt;/p&gt;

&lt;p&gt;OTLP export is also a new egress path. In environments with restricted networking, connectivity to a Collector or remote backend becomes part of the architecture and security design. Adding observability can mean adding a communication boundary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cloud telemetry introduces a time gap
&lt;/h2&gt;

&lt;p&gt;When an agent uses cloud telemetry to validate a change, there is an unavoidable sequence:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Change
→ Build
→ Deploy
→ Execute
→ Export telemetry
→ Ingest into the backend
→ Query
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Depending on the architecture, stable query results may take tens of seconds to several minutes. A local loop measured in seconds and a cloud loop measured in minutes are different feedback cycles, and a practical setup combines both:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Deterministic local tests&lt;/li&gt;
&lt;li&gt;Local trace files&lt;/li&gt;
&lt;li&gt;Smoke tests in a development environment&lt;/li&gt;
&lt;li&gt;Before-and-after comparisons using cloud telemetry&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Requiring the agent to deploy and wait for ingestion after every change makes the everyday loop unnecessarily heavy.&lt;/p&gt;

&lt;h2&gt;
  
  
  The order I would use today
&lt;/h2&gt;

&lt;p&gt;Based on this review, I would approach agent-readable observability in this order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Connect instrumentation to a real execution path so telemetry is actually generated&lt;/li&gt;
&lt;li&gt;Make development output readable through local files or stdout&lt;/li&gt;
&lt;li&gt;Propagate trace context across HTTP, queues, and workers&lt;/li&gt;
&lt;li&gt;Keep business and execution IDs as separate attributes, not inside standard trace IDs&lt;/li&gt;
&lt;li&gt;Define Semantic Conventions and custom attributes as repository-level contracts&lt;/li&gt;
&lt;li&gt;Establish policies for sensitive data, cardinality, and sampling&lt;/li&gt;
&lt;li&gt;Provide read-only access through MCP, CLI, or SQL&lt;/li&gt;
&lt;li&gt;Make before-and-after comparisons repeatable under the same conditions&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I would not begin with comprehensive auto-instrumentation. It is easier to evaluate OTel by picking one important path and completing the entire chain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Generate → Propagate → Store → Query → Compare
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An AI agent does not need to stare at a large number of spans. It needs to determine what changed because of its own modification, and verify that conclusion again under the same conditions.&lt;/p&gt;

&lt;h2&gt;
  
  
  OTel is a common language, not the finished system
&lt;/h2&gt;

&lt;p&gt;The three repositories in this review are still under development, and their implementations will change. Even so, one distinction became much clearer: having OTel, generating telemetry, and making that telemetry usable by an agent are three different stages — and only the last one closes the loop.&lt;/p&gt;

&lt;p&gt;Turning telemetry into part of an AI-assisted development loop requires more than instrumentation: it also requires access paths, domain schemas, data boundaries, and repeatable before-and-after evaluation. The right starting point is therefore not comprehensive instrumentation, but one minimal path where telemetry is reliably generated, accessible to the agent, and comparable across a change.&lt;/p&gt;

&lt;p&gt;OTel is not the finished form of observability for coding agents.&lt;/p&gt;

&lt;p&gt;It is one of the strongest common languages available for building telemetry that is structured, correlated, and reproducible enough for them to use.&lt;/p&gt;

</description>
      <category>opentelemetry</category>
      <category>observability</category>
      <category>ai</category>
      <category>claudecode</category>
    </item>
    <item>
      <title>Why My Local Coding Agent Could Act but Couldn't Finish</title>
      <dc:creator>Takafumi Endo | ROUTE06</dc:creator>
      <pubDate>Wed, 29 Jul 2026 07:53:24 +0000</pubDate>
      <link>https://dev.to/gyu07/why-my-local-coding-agent-could-act-but-couldnt-finish-81d</link>
      <guid>https://dev.to/gyu07/why-my-local-coding-agent-could-act-but-couldnt-finish-81d</guid>
      <description>&lt;p&gt;There was a month where I blew through my token budget without noticing. Claude Code and Codex, running most of the day, on a codebase I was exploring more than editing. The ceiling I'd set turned out to be a number I'd passed weeks earlier, and I found out by getting the bill.&lt;/p&gt;

&lt;p&gt;So I did the thing I'd been putting off, and ran a coding agent on my own machine to see what happened.&lt;/p&gt;

&lt;p&gt;The short version:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Qwen Code, driving a quantized Qwen against a local llama.cpp server, built the UI fine.&lt;/strong&gt; A video-site clone from scratch: header, sidebar, grid, watch page, all of it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Then it couldn't work out why the thumbnails and video files weren't being served.&lt;/strong&gt; Almost every tool call executed cleanly. The investigation made no progress.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It was also slower than Claude Code or Codex, and my best guess for why is repeated prompt processing rather than generation speed.&lt;/strong&gt; I'll say what would actually settle that when I get there.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;None of this is a benchmark. These are notes on where my thinking has drifted, written down mostly so I can find out where it's wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where my framing was off
&lt;/h2&gt;

&lt;p&gt;The story I'd been telling myself was that token prices are going up. I can't really support that. Unit prices haven't broadly risen; my consumption did, and the shape of agentic coding is why. A turn in these loops can carry the system prompt, the tool definitions, the files already read, and the history along with it, so usage climbs with your setup as much as your effort.&lt;/p&gt;

&lt;p&gt;I &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. My first response to the bill wasn't to switch models. It was to notice that most of the tokens went into &lt;em&gt;finding things&lt;/em&gt;, and to build a terminal emulator with tree-sitter and LSP folded in, so the agent could get deterministic answers to "who calls this." That became &lt;a href="https://dev.to/gyu07/rigor-compresses-why-ai-agents-need-graphs-not-more-context-5404"&gt;the graphs argument&lt;/a&gt;, and it comes back at the end meaning something I hadn't intended.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two reasons to go local, and I'd rather not mix them
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Cost.&lt;/strong&gt; Frontier APIs charge per token; local open-weight inference doesn't. This is what got me looking, and on my hardware it doesn't hold: what I stop paying in dollars comes back as wall-clock and retries, which are scarcer than money.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Locality.&lt;/strong&gt; Some work wants to be near the actor, being private, high-churn, latency-sensitive, or offline-capable, and that holds regardless of what a token costs. This one I believe, and it's the same &lt;a href="https://dev.to/gyu07/why-ai-agents-make-me-reach-for-sqlite-4dh0"&gt;state-gravity&lt;/a&gt; reasoning I keep landing on elsewhere.&lt;/p&gt;

&lt;p&gt;Same hardware, different conclusions. I'd rather not borrow the second to prop up the first.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I had wrong about Qwen
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://qwen.ai/blog?id=qwen3-coder-next" rel="noopener noreferrer"&gt;Qwen3-Coder-Next&lt;/a&gt; is described by its team as being for coding agents and &lt;em&gt;local development&lt;/em&gt;, with emphasis on long-horizon runs, tool use, and recovering from failed execution. That last item is a loop property rather than a generation property, and a telling thing to optimise for. Apache 2.0 throughout, which for anything pointed at a real codebase is a gate that comes before capability.&lt;/p&gt;

&lt;p&gt;I'd also been repeating that &lt;a href="https://github.com/QwenLM/qwen-code" rel="noopener noreferrer"&gt;Qwen Code&lt;/a&gt; is a Gemini CLI fork. It did start as one, and in lineage terms still is, but it stopped syncing upstream early and has grown its own subagents, hooks, and MCP support. What matters here is that it's backend-agnostic: the same harness points either at a local endpoint or a commercial API, which is as close to a controlled comparison as I get on one laptop. Qwen also describes it as reaching feature parity with Claude Code, which is worth holding loosely, since feature parity and outcome parity are separate claims.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;3B active&lt;/code&gt; is a compute number, not a memory number
&lt;/h2&gt;

&lt;p&gt;The correction that most changed how I read model announcements. Activated parameters govern compute per token; total parameters govern the memory to hold the weights. Qwen3-Coder-Next activates around 3B and still has 80B in total, which at 4-bit is on the order of 40 GB for weights alone, before runtime overhead, KV cache, and the context you actually wanted. On a 64 GB machine that makes it a candidate rather than a comfortable one, and the margin thins exactly where I'd want it. It cuts the other way too: I'd filed myself under "small quantized models only," and the quantized 30B-odd class in fact runs with real headroom at this size.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Loading a model and running it comfortably over a long context are two different capabilities, and only the first appears in a requirements table.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Which is why I care about this tier rather than the top of the range. What decides whether local inference becomes &lt;em&gt;normal&lt;/em&gt; is what runs on a machine a working developer already bought.&lt;/p&gt;

&lt;h2&gt;
  
  
  The sessions
&lt;/h2&gt;

&lt;p&gt;Two sittings in April, an evening and then the following afternoon. They weren't the same task: the first picked up a half-finished short-video feed clone and carried on, the second built a video site from scratch. Different tasks, different days, different models, so nothing here is controlled. It's a log rather than an experiment.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Setup&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Machine&lt;/td&gt;
&lt;td&gt;MacBook Pro · M2 Max · 64 GB unified memory · macOS 26&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Runtime&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;llama-server&lt;/code&gt;, llama.cpp b8680, Metal&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agent&lt;/td&gt;
&lt;td&gt;Qwen Code 0.14.5, over the OpenAI-compatible endpoint&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Models&lt;/td&gt;
&lt;td&gt;Qwen3-Coder-Next (80B total, IQ4_XS) · Qwen3.6-35B-A3B (unsloth dynamic Q4_K_M GGUF, ~21 GB)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Context&lt;/td&gt;
&lt;td&gt;131,072, compaction at 50%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Flags&lt;/td&gt;
&lt;td&gt;&lt;code&gt;--jinja -ngl 99 -c 131072 -b 512 -ub 256&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two notes on that. In this setup &lt;code&gt;--jinja&lt;/code&gt; was needed for Qwen Code's tool calls to work at all, and depending on GGUF metadata llama.cpp may also want an explicit tool-aware chat template. The prompt batch is small, 512 with a micro-batch of 256, because anything larger died with a Metal compute error. That one matters later: batching governs how fast context gets ingested, and I had throttled it before I started measuring.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Session&lt;/th&gt;
&lt;th&gt;Task&lt;/th&gt;
&lt;th&gt;wall-clock&lt;/th&gt;
&lt;th&gt;tool calls&lt;/th&gt;
&lt;th&gt;reissued&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Evening, day 1&lt;/td&gt;
&lt;td&gt;feed clone, resuming&lt;/td&gt;
&lt;td&gt;~1h25m&lt;/td&gt;
&lt;td&gt;151&lt;/td&gt;
&lt;td&gt;31&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Day 2, first&lt;/td&gt;
&lt;td&gt;video site, from scratch&lt;/td&gt;
&lt;td&gt;~3h20m&lt;/td&gt;
&lt;td&gt;154&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Day 2, second&lt;/td&gt;
&lt;td&gt;same, continued&lt;/td&gt;
&lt;td&gt;~1h20m&lt;/td&gt;
&lt;td&gt;240&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;"Reissued" just means the agent sent the call again. The logs don't separate malformed arguments from non-zero exits from failing tests, and a command that fails and then gets fixed is arguably the harness working, so the column reads better as friction than as an error rate. Day one is muddier still, with dozens of server-side compute errors that belong to the runtime rather than the model.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part it couldn't finish
&lt;/h2&gt;

&lt;p&gt;The second session is the one I keep thinking about. The UI came out, and came out well: header, sidebar with the full nav, category chips, a video grid with channel names and view counts and duration badges, and a watch page with player, title, subscriber count, like and dislike, share, description. All of it correct.&lt;/p&gt;

&lt;p&gt;What never loaded was the contents of &lt;code&gt;public/&lt;/code&gt;. Every thumbnail in the grid was a broken-image icon with its alt text showing through, and the player sat on a black frame with a spinner reading &lt;code&gt;0:00 / 0:00&lt;/code&gt;. A complete site with nothing in the slots.&lt;/p&gt;

&lt;p&gt;The last hour or so of that session is the agent chasing that one 404, and the shape of it is the part worth looking at:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;observed: thumbnails return 404
  │
  ├─▶ restart the dev server ....... call ok · 404 remains
  ├─▶ try a production build ....... call ok · 404 remains
  ├─▶ flatten the directory layout . call ok · 404 remains
  ├─▶ rewrite the database paths ... call ok · 404 remains
  └─▶ drop the SQL approach ........ call ok · 404 remains
                                     │
                                     └─ each call accepted and run,
                                        none of them establishing
                                        which path was being served
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Much of that final session went into that loop. My last message in the log is a flat "this isn't working."&lt;/p&gt;

&lt;p&gt;So the pair of numbers I keep returning to is this: of 240 tool calls, 232 went through without needing to be reissued, and the bug was still there at the end. That's a per-call rate, not a measure of how finished anything was. It had the competence to reproduce a large, fiddly interface from scratch, then couldn't work out why a static file wasn't being served, a smaller and much more boring problem than the one it had just solved. It kept generating plausible fixes for the wrong layer.&lt;/p&gt;

&lt;p&gt;I'd call that a &lt;strong&gt;diagnosis&lt;/strong&gt; problem rather than a generation one: forming and discarding hypotheses about a system it built itself, with no cheap way to check any of them. Which is the honest answer to whether a quantized local coder can ship a non-trivial front end. It can produce a lot of good code and still not close the last inch.&lt;/p&gt;

&lt;p&gt;My prior experience makes me suspect Claude Code would have got out of that hole faster, and this run offers no evidence for that comparison: no matched run, nothing held constant, no recording, so there's no row for it in the table. It's a narrow suspicion anyway. Not about code quality, but about how quickly the loop notices it's fixing the wrong thing. I mention it because leaving it out would flatter the local run.&lt;/p&gt;

&lt;h2&gt;
  
  
  As for the time
&lt;/h2&gt;

&lt;p&gt;"It's slower" turned out to be doing no work at all. Any of these could be the culprit:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;time to first response&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;prompt processing throughput&lt;/strong&gt;, meaning how fast it ingests context&lt;/li&gt;
&lt;li&gt;output token generation speed&lt;/li&gt;
&lt;li&gt;number of tool calls and reissues&lt;/li&gt;
&lt;li&gt;context length and compaction behaviour&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Published write-ups on agentic coding point at the same place. In a loop like this, each turn can require the server to process the system prompt, the tool definitions, the retained file context, and the conversation so far all over again, so the same material gets worked through dozens of times in one task, and prompt processing can eventually dominate generation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;call 1   [ system · tools · files read · history ] ──▶ one short action
call 2   [ system · tools · files read · history ] ──▶ one short action
call 3   [ system · tools · files read · history ] ──▶ one short action
  ⋮               re-read in full, longer each time
×240

   writing   a few hundred tokens per call
   reading   the entire transcript, per call
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
 Meanwhile decode speed on my hardware class keeps improving, going by &lt;a href="https://ollama.com/blog/mlx-performance" rel="noopener noreferrer"&gt;the numbers runtimes are publishing for Apple Silicon&lt;/a&gt;. The axis being optimised hardest is the one I'd already stopped waiting on.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The bottleneck may be less about the model writing than the model re-reading.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I want to be exact about that sentence's status. All I have is wall-clock, call counts, and reissues. What would settle it is cumulative prompt time against decode time, prompt tokens against tokens generated, and how much of each prompt came from cache, all of which &lt;code&gt;llama-server&lt;/code&gt; reports and none of which I collected. So: leading suspect, consistent with how these harnesses behave, not demonstrated here. If prompt time turns out to be a minority of the total, the explanation doesn't hold.&lt;/p&gt;

&lt;p&gt;If it does hold, the tooling detour means something I hadn't intended. Vague context is charged twice locally, once in tokens and once in wall-clock, every loop. Handing an agent short deterministic facts instead of files to re-read is a cost optimisation against a metered API; on hardware I own it becomes a latency optimisation, and I'm the one sitting there for it. The way that second session ended is the same argument from the other side: an agent that could cheaply ask which file was being served wouldn't have spent an hour fixing one that wasn't.&lt;/p&gt;

&lt;h2&gt;
  
  
  I wanted to blame quantization
&lt;/h2&gt;

&lt;p&gt;Behaviour was less stable than I'm used to, with tool calls going sideways and loops not recovering cleanly, and my instinct was to file it under quantization. That instinct was mostly a wish for a tidy story.&lt;/p&gt;

&lt;p&gt;Quantization does affect quality, and published comparisons show that two schemes at the same bit width can behave measurably differently, which is why I named both of mine. But the candidates I can't separate go well past it. The base models' tool-use ability, chat-template fit, sampling settings, context bloat over a long run, the runtime, the throttled batch size, the quantization recipe. That's a list of things to check rather than a cause, and most of it I can change without swapping models.&lt;/p&gt;

&lt;p&gt;The same caution applies across the two sessions. Qwen3-Coder-Next is the model my own arithmetic said would fit but not comfortably, and it reissued roughly one call in five while throwing repeated Metal errors, where the smaller one ran much more cleanly. Memory pressure is my leading explanation, but it was a different model on a different task with a different 4-bit recipe, so I'd read it as suggestive. What survives is that bit width alone explains nothing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Swappability is an investigation virtue, not a production one
&lt;/h2&gt;

&lt;p&gt;Fixing a local OpenAI-compatible server as the execution layer is what made this tractable: the runtime stays put and I recombine around it. It may also be why it broke.&lt;/p&gt;

&lt;p&gt;Look at those candidates again and notice how many are interface problems rather than capability problems. Template fit, tool-call formatting, sampling defaults, context compaction. Those are seams. A stack where one organisation post-trains the model and ships the agent doesn't have seams there, because the model was trained against that harness's tool loop, on its own tool schema, with its own recovery behaviour. The interface has been tested as one product.&lt;/p&gt;

&lt;p&gt;So the view I've drifted to, an impression from use rather than something measured, is that model and harness are better evaluated as a single unit, and that the OpenAI × Codex and Anthropic × Claude Code advantage may come not only from model quality but from having no seam to misfit.&lt;/p&gt;

&lt;p&gt;Cursor is the interesting exception, having stayed model-flexible while also training its own model against its own editor and tools. Notably it built that on someone else's open checkpoint, which suggests the load-bearing part is the co-design rather than owning the pretraining.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;"Your bottleneck is the runtime, not the model."&lt;/strong&gt; Probably partly true, and I handed you the evidence: I was running a throttled prompt batch to stop Metal crashing, which sits directly in the path of the thing I'm complaining about. Whether it's the cause, I can't say without the prefill numbers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Your test flatters the local model."&lt;/strong&gt; Agreed. A front-end build is generous work, being well-specified and verifiable, and it still didn't finish. A long refactor across an unfamiliar repo is where this would compound worst.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Just rent a cloud GPU."&lt;/strong&gt; The option I'm least able to assess. It removes the memory ceiling and hands you ops, utilisation risk, and cost modelling in return. I notice I keep not choosing it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"The gap is closing, so this post expires."&lt;/strong&gt; Probably. That's rather the point of re-running the test.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Either the open tier keeps compressing until a long local loop is comfortable, or the coupled stacks keep pulling ahead in ways a swappable arrangement can't track. I'd prefer the first, and I'm not confident enough to plan around it. Meanwhile the on-device direction is already being built into Apple's and Google's frameworks as a split rather than a migration, with private, low-latency work on the device and the heavy inference elsewhere, which makes the question &lt;em&gt;which parts route where&lt;/em&gt; rather than &lt;em&gt;local or hosted&lt;/em&gt;. Doing all of this by hand reads like the phase before that.&lt;/p&gt;

&lt;p&gt;So I keep reinstalling. Every few months, same test, same laptop, and I write down where it broke. It hasn't changed what I open on a Tuesday. It has changed what I think I'm buying when I do, which is less about tokens and more about something that can work out which file is actually being served. Next time I'll log the prompt timings, and then I'll know whether any of this is true.&lt;/p&gt;

</description>
      <category>llm</category>
      <category>localllm</category>
      <category>qwen</category>
      <category>agents</category>
    </item>
    <item>
      <title>Why Legacy Rewrites Need a Judge, Not a Better Prompt</title>
      <dc:creator>Takafumi Endo | ROUTE06</dc:creator>
      <pubDate>Tue, 28 Jul 2026 07:59:03 +0000</pubDate>
      <link>https://dev.to/gyu07/why-legacy-rewrites-need-a-judge-not-a-better-prompt-50ea</link>
      <guid>https://dev.to/gyu07/why-legacy-rewrites-need-a-judge-not-a-better-prompt-50ea</guid>
      <description>&lt;p&gt;Lately I keep getting asked about legacy replacements.&lt;/p&gt;

&lt;p&gt;It's become one of the more common conversations I have, and the framing is almost always the same: &lt;em&gt;AI can rewrite this now, can't it?&lt;/em&gt; The demand is clearly rising, and I don't think the premise is wrong. A coding agent can rewrite a legacy application startlingly fast. Producing the new code has stopped being the bottleneck.&lt;/p&gt;

&lt;p&gt;What hasn't moved is the ability to say &lt;em&gt;the new system behaves like the old one&lt;/em&gt;. That turns out to be a claim nobody has the evidence to make — about either system.&lt;/p&gt;

&lt;p&gt;So when one of these comes up now, analyzing the old code is no longer where I'd start. That's not the obvious move: the default is to understand the old system first, then rebuild from that understanding. It's the order every plan I've seen begins in, and it's the order we tried, with a coding agent doing the analysis. The honest summary is that it didn't converge.&lt;/p&gt;

&lt;p&gt;The approach I've drifted into instead:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Record the old system's observable behavior as an executable suite, accept that suite as the specification for the migration scope, and let coding agents implement against it until it's green.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Test-driven modernization, with the legacy system acting as the &lt;strong&gt;oracle&lt;/strong&gt; — not supplying the tests. The scenarios, the observation points, and the normalization rules are all still mine to design. What the old system supplies is the expected values.&lt;/p&gt;

&lt;p&gt;This isn't advice, and it isn't a claim that any of this is solved. It's the most practical hypothesis I've got after watching the obvious approach fail to converge.&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;Source analysis produces &lt;strong&gt;interpretations&lt;/strong&gt;. Execution produces &lt;strong&gt;comparable evidence&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;For a declared scope, the coverage contract plus the recorded results plus the tests &lt;em&gt;are&lt;/em&gt; the specification — not because they're complete, but because they were accepted.&lt;/li&gt;
&lt;li&gt;The coding agent is the replaceable part. The &lt;strong&gt;harness&lt;/strong&gt; that judges it is the asset.&lt;/li&gt;
&lt;li&gt;The deliverable that matters isn't the new code. It's the evidence someone can sign a cutover against.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  First I tried just pointing the agent at it
&lt;/h2&gt;

&lt;p&gt;The version I built before this one had no harness in it at all.&lt;/p&gt;

&lt;p&gt;Claude Code, pointed straight at the legacy codebase. I put real effort into the scaffolding — skills for the recurring analysis moves, prompt structures that held up across sessions, work plans that decomposed the system into ordered units, and a loop: analyze a region of the old code, write the equivalent in the new stack, review, move on. Not a naive setup, and I still think it was the right thing to try first.&lt;/p&gt;

&lt;p&gt;It worked, partially. That's the awkward part — it produces something. You get a new implementation that is recognizably the same application, and on a good day a reviewer nods at it.&lt;/p&gt;

&lt;p&gt;What it didn't produce was a reason to believe it. Three things kept going wrong, and they turned out to be the same thing wearing different clothes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The token cost per unit of confirmed behavior was miserable.&lt;/strong&gt; The loop spent most of its budget re-reading code it had already read, re-deriving conclusions it had already reached, and writing long analysis prose so the &lt;em&gt;next&lt;/em&gt; step could consume it. Enormous input, and the output was a paragraph of interpretation — expensive tokens buying a plausible reading of a stored procedure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fidelity was something we checked by eye.&lt;/strong&gt; Two screens side by side, two sets of records lined up, a squint. "Looks right." There is no number in that sentence. Nobody could say which side effects were verified and which weren't, or whether yesterday's slice was still passing. And volume defeats attention long before it defeats the agent: nobody eyeballs the four hundredth order-status transition carefully.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Nothing ratcheted.&lt;/strong&gt; A slice that had been reviewed and accepted could quietly regress while the next one was in progress, and we'd find out at integration, or later. Manual review doesn't leave anything behind that can re-run itself.&lt;/p&gt;

&lt;p&gt;The diagnosis I eventually landed on: I was trying to solve a &lt;em&gt;measurement&lt;/em&gt; problem with better prompts. Every iteration of skills and plans was an attempt to make the agent's interpretation more convincing. What I needed wasn't a more convincing interpretation. It was a mechanism that could disagree with the agent without a human squinting at two screens.&lt;/p&gt;

&lt;p&gt;So I stopped improving the analysis and started building the measurement. What follows isn't a design reasoned out in advance — it's what the failure of the first approach pointed at.&lt;/p&gt;

&lt;h2&gt;
  
  
  Behavior was never in one place
&lt;/h2&gt;

&lt;p&gt;The analysis-first approach doesn't converge, and it isn't because the agent reads badly. It's that the behavior isn't gathered anywhere.&lt;/p&gt;

&lt;p&gt;Some is in application code. The rest is in stored procedures, triggers, nightly batch jobs, a permissions table that three screens read differently, a scheduler entry nobody owns, an external service returning a shape the docs don't mention, and operational rules that live in a person. A &lt;code&gt;Java / JSP + Oracle + nightly batch + mail&lt;/code&gt; system doesn't have a specification; it has an accumulated set of effects. You can read all of it and still not know the ordering, or which branch fires in production, or that invoice rounding differs by one path.&lt;/p&gt;

&lt;p&gt;But you can &lt;em&gt;run&lt;/em&gt; it — drive a business scenario against the running system and record what comes out the other side. Recording alone doesn't make that authoritative, though. It needs provenance (which build, which data set, which run), stable semantics across repeated runs, and an accepted policy that lets that run serve as the baseline. Human judgment belongs at the scope, policy, and cutover boundaries, not on every field of every run — putting a person in the inner loop is how this stops scaling at exactly the volume where it starts to matter.&lt;/p&gt;

&lt;p&gt;A green suite also proves only the scenarios it contains, which is the obvious objection and deserves answering here rather than at the end. So the loop needs a second gate: evidence that the surfaces, journeys, data effects, error paths, and boundaries the scope requires are actually represented. Coverage here isn't a percentage, it's a contract about what had to be present. Which makes "the suite is the specification" a slogan; the accurate version is that for a declared scope, the coverage contract, the tests, and the attested legacy results together form the executable specification.&lt;/p&gt;

&lt;h2&gt;
  
  
  The comparator may normalize representation. It may not forgive behavior.
&lt;/h2&gt;

&lt;p&gt;The harness runs each scenario against both systems and compares. Which sounds obvious right up until you try to compare two systems that were &lt;em&gt;supposed&lt;/em&gt; to differ. IDs are new. Timestamps are new. Row ordering is unspecified. The new UI's implementation and DOM are deliberately different, even where the user-visible behavior has to stay equivalent.&lt;/p&gt;

&lt;p&gt;So a comparator that demands equality is useless. But the fix is not a comparator that forgives things — this is what I got wrong at first, and it's the distinction the whole apparatus rests on.&lt;/p&gt;

&lt;p&gt;Representation is generated IDs, request timestamps, encodings, the order of unordered collections, and metadata &lt;em&gt;proven&lt;/em&gt; to be non-semantic. Note the proven: "this field looked incidental" is how a business rule gets discarded. What the comparator cannot do is decide that a behavior may change or disappear. That decision exists and is frequent, but it's a migration-policy decision that moves part of the scope outside strict translation, recorded with what was decided and by whom.&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's allowed to decide&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Normalization&lt;/td&gt;
&lt;td&gt;representation only — the same behavior, differently expressed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Migration policy&lt;/td&gt;
&lt;td&gt;intentional change or removal, outside strict translation mode&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two rows, deliberately. A permissive mode can carry a narrowly scoped exception — but never as a normal path to green. The moment "ignore this difference" becomes routine vocabulary, a green result turns into something you can negotiate your way into, and the apparatus is just an expensive way to feel reassured.&lt;/p&gt;

&lt;h2&gt;
  
  
  One scenario, end to end
&lt;/h2&gt;

&lt;p&gt;Abstractly this all sounds tidy, so here's a single slice as it actually moves. Order creation, standard path, item in stock.&lt;/p&gt;

&lt;p&gt;The legacy run, recorded:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;scenario  order.create / standard / in-stock
legacy    build=legacy-2019.11  data=masked-set-3  run=8f2c

  screen      list refreshed, new row at top, status "pending approval"
  api         201  {order_no: "O-2024-000123", total: 150000}
  db          orders +1, order_lines +1, inventory_reservations +1 (qty 10)
  permission  approver check evaluated → allow
  events      OrderCreated
  batch       nightly invoice job picks it up (t+1)
  mail        order-received → customer address
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The new implementation, first pass:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PASS  screen, api status, orders, order_lines, permission, batch, mail
DIFF  api.order_no              expected O-2024-000123   actual O-2026-000001
DIFF  inventory_reservations    expected 1 (qty 10)      actual 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two diffs, and they are not the same kind of thing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;order_no   generated id       → normalized (shape asserted: O-YYYY-NNNNNNN)
qty on     inventory reserve  → semantic; NOT normalizable
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first resolves. The second is behavior, so it becomes a task rather than a rule:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Expected:  inventory reservation created: 1 (qty 10)
Actual:    inventory reservation created: 0
Repair:    Add inventory reservation to order creation.
           Preserve idempotency.
           Do not modify billing or shipping modules.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agent implements, harness re-runs, and the slice closes with something a human can sign:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;order.create / standard / in-stock          PRESERVED
  evidence     legacy run 8f2c, new run c41a, 7 observed effects, 0 unresolved
  normalized   order_no, created_at, DOM structure
  policy       none applied
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That last block is the actual product of the exercise. Not the code — the row that says which run, how many effects, what was normalized away, and whether anyone changed the rules to get here.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rigor compresses, again
&lt;/h2&gt;

&lt;p&gt;Note what the agent received: not a description of a system, but a diff and a scope constraint. In the first approach the equivalent input was paragraphs about how the legacy inventory module &lt;em&gt;probably&lt;/em&gt; works — long, hedged, expensively derived, partly wrong. Here it's two numbers: shorter, and grounded in an observed run.&lt;/p&gt;

&lt;p&gt;Same aphorism as &lt;a href="https://dev.to/gyu07/rigor-compresses-why-ai-agents-need-graphs-not-more-context-5404"&gt;the graph post&lt;/a&gt;, arriving from another direction. The more precisely the behavior was captured, the fewer tokens it takes to say what's broken. Capture costs up front and then stops costing on every iteration — the opposite of re-reading the codebase each round.&lt;/p&gt;

&lt;p&gt;Which leaves the loop itself unremarkable, and that's the point: red-green-refactor, except the green came from a system that already exists.&lt;/p&gt;

&lt;h2&gt;
  
  
  The agent writes the code; the harness decides if it's right
&lt;/h2&gt;

&lt;p&gt;The discipline is a trust-boundary question: the agent must not be the thing that judges its own work.&lt;/p&gt;

&lt;p&gt;Claude Code, Codex, whatever comes next — interchangeable. The stable asset is everything around it: scenario definitions, execution adapters per stack, both runners, result normalization, behavior comparison, repair-task generation, evidence storage. Agents are strikingly good at producing a plausible "all tests pass," so the comparator, the normalization rules, and the evidence store are the pieces I want deliberately authored, small enough to review, and boring. The bespoke, agent-assisted part stays on the side where a mistake surfaces as a failing scenario rather than a silent pass — otherwise you've built an unsoundness that reads as safety.&lt;/p&gt;

&lt;p&gt;The capture layers are mostly plumbing: browser driving for screen behavior (Playwright), boundary capture for APIs, before/after snapshots for the database, tracing for execution paths and side effects, stubbed sinks for batch and mail and events, &lt;code&gt;JSONL&lt;/code&gt; and &lt;code&gt;Parquet&lt;/code&gt; for evidence, &lt;code&gt;SQLite&lt;/code&gt; and &lt;code&gt;DuckDB&lt;/code&gt; for working state and diff analysis. Swap any of it for your equivalent; the tools matter less than they look like they do. What matters is that the output is a normalized record that can be replayed and compared — and that, rather than the language or the stack, is what decides whether a system can be migrated this way.&lt;/p&gt;

&lt;p&gt;Two honest caveats there. A stubbed sink proves the system issued the right request at the boundary; it does not prove the mail arrived or the settlement cleared. And traces are not proofs of equivalence — internal routes &lt;em&gt;should&lt;/em&gt; change in a rewrite. What traces buy is explanation: why a diff happened, which observations were missing, which side effects nobody thought to record.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "complete" has to mean
&lt;/h2&gt;

&lt;p&gt;No suite proves everything a system can do, so "complete" isn't available in the honest sense. What's available is an operational definition: for &lt;em&gt;this&lt;/em&gt; scope, these surfaces and these effects had to be present, and the coverage contract says so explicitly. Everything else gets a bucket rather than silence:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PRESERVED     verified equivalent
INTENTIONAL   changed by policy, recorded and attributed
REMOVED       withdrawn by policy, recorded and attributed
UNVERIFIED    known unknown, still open
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The middle two are outcomes of policy decisions, not comparator settings — the same separation as before, arriving at the sign-off document. And &lt;code&gt;UNVERIFIED&lt;/code&gt; is the bucket that earns the whole scheme: a migration always has behavior nobody captured, and the difference between a good and a bad project is whether that list exists and gets looked at. It's also where faithfully-reproduced legacy bugs go to be argued about. You &lt;em&gt;will&lt;/em&gt; reproduce them; the answer is to move them into &lt;code&gt;INTENTIONAL&lt;/code&gt; deliberately rather than discover them later.&lt;/p&gt;

&lt;p&gt;This is also why the unit of work is a slice — customer registration, order creation, invoice generation — rather than the system. Two hundred red scenarios at once tell you nothing, and progress becomes a count of slices with evidence attached, which is a harder number to fool yourself with than percent-complete.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd defend
&lt;/h2&gt;

&lt;p&gt;AI made implementation cheap. It did not make correctness cheap, and it made &lt;em&gt;proving&lt;/em&gt; nothing easier at all. So the artifact I care about on these projects isn't the generated code. It's the executable specification that can reject the generated code — and the useful property is that nobody in the loop is judging their own output.&lt;/p&gt;

&lt;p&gt;I'd hold the specifics loosely. The first approach didn't fail because it was stupid; it was reasonable given what the tools did at the time, and the tools moved. That'll keep happening. If agents get materially better at holding a whole system's behavior in view, or capturing behavior gets cheaper than I think it can, parts of the structure above stop earning their keep and I'll be building something else. What I'd defend is narrower than the design: the correctness of a rewrite has to be decided by something other than the thing that wrote it.&lt;/p&gt;

&lt;p&gt;What I want is for a cutover to stop being a judgment call made on confidence and become something the evidence says — what was preserved, what changed by decision, and what nobody has checked yet.&lt;/p&gt;

&lt;p&gt;If the behavior is captured properly, you don't have to ask anyone to be brave about the release.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>testing</category>
      <category>architecture</category>
      <category>legacy</category>
    </item>
    <item>
      <title>Why Athena/Iceberg Tends to Make Code the Spec</title>
      <dc:creator>Takafumi Endo | ROUTE06</dc:creator>
      <pubDate>Tue, 21 Jul 2026 03:45:35 +0000</pubDate>
      <link>https://dev.to/gyu07/why-athenaiceberg-tends-to-make-code-the-spec-oon</link>
      <guid>https://dev.to/gyu07/why-athenaiceberg-tends-to-make-code-the-spec-oon</guid>
      <description>&lt;p&gt;Every time this comes up, someone credits the same setup: Athena on Iceberg is where "the code is the spec" — where you open Git and read the whole system, catalog to transforms to schema, without logging into anything. In my experience they're not wrong.&lt;/p&gt;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

&lt;/div&gt;



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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

&lt;/div&gt;



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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

&lt;/div&gt;



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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

&lt;/div&gt;



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

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

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

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

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

&lt;/div&gt;



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

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

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

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

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

&lt;/div&gt;



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

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

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

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

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

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

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

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

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

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

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

&lt;/div&gt;



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

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

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

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

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

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

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

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

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

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

</description>
      <category>ai</category>
      <category>database</category>
      <category>dataengineering</category>
      <category>dbt</category>
    </item>
    <item>
      <title>AWS Is Not Simpler. Agents Just Got Better at Reading It.</title>
      <dc:creator>Takafumi Endo | ROUTE06</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 | ROUTE06</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 | ROUTE06</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 | ROUTE06</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 | ROUTE06</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>
  </channel>
</rss>
