<?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: Nunc</title>
    <description>The latest articles on DEV Community by Nunc (@nunc).</description>
    <link>https://dev.to/nunc</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%2F156810%2Fe88f8e22-712e-4845-8662-e399d98101b2.png</url>
      <title>DEV Community: Nunc</title>
      <link>https://dev.to/nunc</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/nunc"/>
    <language>en</language>
    <item>
      <title>I Replaced an Ad-Stuffed Download Site With 463 Lines of Node</title>
      <dc:creator>Nunc</dc:creator>
      <pubDate>Tue, 01 Sep 2026 08:15:22 +0000</pubDate>
      <link>https://dev.to/nunc/i-replaced-an-ad-stuffed-download-site-with-463-lines-of-node-283d</link>
      <guid>https://dev.to/nunc/i-replaced-an-ad-stuffed-download-site-with-463-lines-of-node-283d</guid>
      <description>&lt;p&gt;I wanted to save X/Twitter videos on my phone, but there was a catch: X has no download button, and the download sites that fill that gap are ad-stuffed, break whenever X rotates its API auth, and you hand every tweet URL to a stranger. So I built my own: a self-hosted web app where you paste a tweet link and get a preview, a quality picker, and a Download button that saves the MP4 to your phone.&lt;/p&gt;

&lt;p&gt;The whole thing is 463 lines of Node with zero npm dependencies, plus 224 lines of vanilla JS for the UI. It is open source (MIT):&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/nuncij" rel="noopener noreferrer"&gt;
        nuncij
      &lt;/a&gt; / &lt;a href="https://github.com/nuncij/twitter-x-downloader" rel="noopener noreferrer"&gt;
        twitter-x-downloader
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;X/Twitter Downloader (xdl)&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;Paste any &lt;code&gt;x.com&lt;/code&gt; / &lt;code&gt;twitter.com&lt;/code&gt; tweet link → preview → download. A small, self-hosted web app for saving videos (and GIFs/photos) from X. Mobile-first, works great on phones.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;You host it yourself&lt;/strong&gt; — there's no hosted instance. It runs on your own server (or just your laptop), so it's free, private, and no third-party download site is involved.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Features&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;📱 Mobile-first UI (dark theme, add-to-home-screen friendly)&lt;/li&gt;
&lt;li&gt;🎬 Quality picker with file sizes (270p → 720p+); picks the best available&lt;/li&gt;
&lt;li&gt;🎞️ Handles &lt;strong&gt;videos&lt;/strong&gt;, &lt;strong&gt;GIFs&lt;/strong&gt;, &lt;strong&gt;photo-only tweets&lt;/strong&gt;, and &lt;strong&gt;mixed video+photo&lt;/strong&gt; tweets&lt;/li&gt;
&lt;li&gt;🔗 1080p HLS-only tweets are merged server-side via ffmpeg automatically&lt;/li&gt;
&lt;li&gt;⚡ Direct CDN streaming for downloads (no big server-side buffering), inline preview plays straight from X's CDN&lt;/li&gt;
&lt;li&gt;🛡️ Per-IP rate limits, concurrency caps, CDN host allowlist (no SSRF)&lt;/li&gt;
&lt;li&gt;🔌 Zero npm dependencies — only &lt;code&gt;node&lt;/code&gt;, &lt;code&gt;ffmpeg&lt;/code&gt;, &lt;code&gt;yt-dlp&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;How it works&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Extraction:&lt;/strong&gt; &lt;a href="https://github.com/yt-dlp/yt-dlp" rel="noopener noreferrer"&gt;yt-dlp&lt;/a&gt; is…&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/nuncij/twitter-x-downloader" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  Two backends, because X breaks things
&lt;/h2&gt;

&lt;p&gt;The server never talks to X's private API itself. That is the part that breaks every few months, so I delegated it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;yt-dlp&lt;/strong&gt; (primary). One call, &lt;code&gt;yt-dlp -J --no-playlist --skip-download &amp;lt;url&amp;gt;&lt;/code&gt;, returns every available format with file sizes. yt-dlp handles Twitter's guest-token flow and rotating auth, and it keeps working when X changes things because it is actively maintained. Extraction takes 1 to 2 seconds.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;FxTwitter API&lt;/strong&gt; (fallback). &lt;code&gt;api.fxtwitter.com/status/{id}&lt;/code&gt;, free, no auth, answers in 200 to 300 ms. It steps in when yt-dlp hits a guest-token rate limit, and it covers what yt-dlp does not return at all: photo-only tweets and GIFs.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If one backend dies tomorrow, the app degrades instead of dying with it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The format puzzle
&lt;/h2&gt;

&lt;p&gt;Twitter serves each video in several parallel formats. A real 15-second clip came back like this:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;format_id&lt;/th&gt;
&lt;th&gt;kind&lt;/th&gt;
&lt;th&gt;resolution&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;hls-audio-*&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;HLS audio only&lt;/td&gt;
&lt;td&gt;32/64/128 kbps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;http-288&lt;/code&gt;, &lt;code&gt;http-832&lt;/code&gt;, &lt;code&gt;http-2176&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;progressive MP4&lt;/td&gt;
&lt;td&gt;272p / 364p / 728p&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;hls-191&lt;/code&gt;, &lt;code&gt;hls-343&lt;/code&gt;, &lt;code&gt;hls-716&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;HLS video only&lt;/td&gt;
&lt;td&gt;272p / 364p / 728p&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two useful facts fell out of staring at that list. Progressive MP4 (one file, video plus audio, plays anywhere) exists up to 720p for most tweets. Anything above 720p is HLS only, so for 1080p the server lets yt-dlp download and merge the streams with ffmpeg, waits for the temp file, streams it to the phone, and deletes it. The merge of a 15-second clip took 1.8 seconds.&lt;/p&gt;

&lt;p&gt;Then the gotcha that cost me the most debugging time. yt-dlp's JSON reports &lt;code&gt;vcodec&lt;/code&gt; and &lt;code&gt;acodec&lt;/code&gt; as &lt;code&gt;null&lt;/code&gt; for exactly those progressive formats, not as &lt;code&gt;h264&lt;/code&gt;/&lt;code&gt;aac&lt;/code&gt;. My first classifier dropped anything without a known codec, which silently threw away every good single-file MP4 and sent every request to the fallback. The fix is to classify by protocol instead of trusting the codec fields:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// kind: 0=mix, 1=video only, 2=audio only&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;classifyFormat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;acodec&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;acodec&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;none&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vcodec&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;none&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;protocol&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// progressive single-file = mix even when codecs unknown&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;protocol: "https"&lt;/code&gt; means a progressive single file. &lt;code&gt;vcodec === 'none'&lt;/code&gt; (the string, not null) means audio only. Everything else is an HLS video track that needs a merge.&lt;/p&gt;

&lt;h2&gt;
  
  
  The phone part
&lt;/h2&gt;

&lt;p&gt;The preview costs the server nothing. X's CDN (&lt;code&gt;video.twimg.com&lt;/code&gt;) serves videos with &lt;code&gt;access-control-allow-origin: *&lt;/code&gt; and plain GET, no cookies, so the &lt;code&gt;&amp;lt;video&amp;gt;&lt;/code&gt; element points straight at the CDN through a 302 redirect. Playback bandwidth: zero.&lt;/p&gt;

&lt;p&gt;The download is the opposite: it streams through the server on purpose, with &lt;code&gt;Content-Disposition: attachment&lt;/code&gt; and a filename built from the tweet (&lt;code&gt;author - title (720p).mp4&lt;/code&gt;). That header is what makes iOS and Android reliably offer "save to Files" instead of opening the video in a tab. Node pipes the CDN response through with the original &lt;code&gt;Content-Length&lt;/code&gt;, nothing is buffered in RAM.&lt;/p&gt;

&lt;p&gt;The UI is one &lt;code&gt;index.html&lt;/code&gt;: dark theme, big touch targets, quality pills with file sizes ("720p · 4.0 MB"), and the meta tags for add-to-home-screen, so on the phone it launches fullscreen like an app.&lt;/p&gt;

&lt;h2&gt;
  
  
  Internet-facing means paranoid
&lt;/h2&gt;

&lt;p&gt;A downloader is a proxy, and a naive proxy is an SSRF hole. Three guards, all in those 463 lines:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The server only fetches URLs whose host, re-parsed server-side, is &lt;code&gt;video.twimg.com&lt;/code&gt; or &lt;code&gt;pbs.twimg.com&lt;/code&gt;, https only. The client cannot point it anywhere else.&lt;/li&gt;
&lt;li&gt;Per-IP rate limits: 6 extracts and 10 downloads per minute, in-memory sliding window.&lt;/li&gt;
&lt;li&gt;Concurrency caps: 1 extraction and 2 downloads at a time. It is a personal tool on a small VPS, not a public service.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Run it yourself
&lt;/h2&gt;

&lt;p&gt;The repo ships a generic &lt;code&gt;deploy.sh&lt;/code&gt;: &lt;code&gt;VPS_HOST=user@server ./deploy.sh&lt;/code&gt; rsyncs the app, creates a Python venv for yt-dlp, and installs a systemd unit. The venv matters: Ubuntu's apt version of yt-dlp is months old, and with this tool old means broken.&lt;/p&gt;

&lt;p&gt;My own instance never touches the public internet. The DNS record points at a Tailscale address, the reverse proxy binds to the tailnet interface, and the firewall drops everything else. HTTPS still works with zero open ports, because Let's Encrypt supports the DNS-01 challenge: the cert is issued through a DNS API token, no port 80 required. My phone runs Tailscale, so the app works from anywhere and is still invisible to everyone else.&lt;/p&gt;

&lt;p&gt;One note on open-sourcing a personal tool: before the repo went public I found my own server details in three places (the deploy script, the README, and one commit message). The first two were rewritten; for the third the history had to be squashed into a single clean commit, because a secret in an old commit is still a published secret.&lt;/p&gt;

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

&lt;p&gt;The key insight: don't fight X's API yourself. Let yt-dlp absorb the auth churn, keep a free second backend for the day it hiccups, and spend your own lines on the parts that make it yours: the streaming, the filenames, the SSRF guard, and a UI your thumb likes. Mine came to 463 lines, and there is not a single ad on it.&lt;/p&gt;

</description>
      <category>node</category>
      <category>webdev</category>
      <category>showdev</category>
      <category>selfhosted</category>
    </item>
    <item>
      <title>Oracle Designer Lost Support in 2013. My AI Agent Develops in It Anyway.</title>
      <dc:creator>Nunc</dc:creator>
      <pubDate>Fri, 21 Aug 2026 11:51:51 +0000</pubDate>
      <link>https://dev.to/nunc/oracle-designer-lost-support-in-2013-my-ai-agent-develops-in-it-anyway-2c2k</link>
      <guid>https://dev.to/nunc/oracle-designer-lost-support-in-2013-my-ai-agent-develops-in-it-anyway-2c2k</guid>
      <description>&lt;p&gt;Oracle Designer 10g is a CASE tool from the early 2000s. Support ended in 2013. At my job it is still the center of development: the repository holds the entire design of a large insurance system (2,741 entities, 3,277 table definitions, 12,841 PL/SQL modules, 5,624 forms), and about 10 developers work in it daily. Everyone, me included, assumed you work with Designer through its GUI. Clicking, dragging, repeating.&lt;/p&gt;

&lt;p&gt;The question I wanted to answer: can an AI agent that knows SQL and has database access do that work instead?&lt;/p&gt;

&lt;p&gt;Two days later the agent had designed an entity, a table, a sequence, two triggers, and a complete form definition, moved a table between applications, and debugged the generated form to a working runtime. It opened the Designer GUI exactly zero times. Everything I personally did in a GUI fits in two buttons: Generate, and starting the form.&lt;/p&gt;

&lt;h2&gt;
  
  
  The discovery that made it possible
&lt;/h2&gt;

&lt;p&gt;Designer stores everything in an Oracle schema, and next to the data it ships three layers that are all reachable from a plain SQL session:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CI_* views&lt;/strong&gt; (722 of them) for reading. Every entity, column, and form item is a row in a view.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CIO* packages&lt;/strong&gt; (about 200) for writing. Every element type has a package with &lt;code&gt;ins/upd/del/sel&lt;/code&gt; and a typed record.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CDAPI&lt;/strong&gt; for transactions: &lt;code&gt;open_activity&lt;/code&gt;, then your changes, then &lt;code&gt;validate_activity&lt;/code&gt; (Designer's own validation rules), then &lt;code&gt;close_activity&lt;/code&gt;. On failure, &lt;code&gt;abort_activity&lt;/code&gt; rolls the repository back.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The basic write pattern looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;declare&lt;/span&gt;
   &lt;span class="n"&gt;ent&lt;/span&gt; &lt;span class="n"&gt;cioentity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="n"&gt;st&lt;/span&gt;  &lt;span class="n"&gt;varchar2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
   &lt;span class="n"&gt;wa&lt;/span&gt;  &lt;span class="n"&gt;varchar2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;begin&lt;/span&gt;
   &lt;span class="n"&gt;jr_context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_workarea&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'GLOBAL SHARED WORKAREA'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;-- skip this: CDR-00100&lt;/span&gt;
   &lt;span class="n"&gt;cdapi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'MYAPP'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
   &lt;span class="n"&gt;cdapi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;open_activity&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

   &lt;span class="n"&gt;ent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;       &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'MY ENTITY'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="n"&gt;ent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;       &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="n"&gt;ent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;short_name&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'MYE'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;        &lt;span class="n"&gt;ent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;short_name&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="n"&gt;cioentity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ins&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ent&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

   &lt;span class="n"&gt;cdapi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;validate_activity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;wa&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
   &lt;span class="n"&gt;if&lt;/span&gt; &lt;span class="n"&gt;st&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Y'&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
      &lt;span class="n"&gt;cdapi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;close_activity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="n"&gt;if&lt;/span&gt; &lt;span class="n"&gt;st&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Y'&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt; &lt;span class="k"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;cdapi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;abort_activity&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="n"&gt;if&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="n"&gt;while&lt;/span&gt; &lt;span class="n"&gt;cdapi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stacksize&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="n"&gt;loop&lt;/span&gt;
         &lt;span class="n"&gt;dbms_output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;put_line&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'VIOLATION: '&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;cdapi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pop_instantiated_message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="n"&gt;loop&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="n"&gt;cdapi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;abort_activity&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="n"&gt;if&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;There is no documentation for any of this anymore. It does not matter, because the API is self-documenting: the package specs are readable from &lt;code&gt;all_source&lt;/code&gt;, so the agent's first move for every element type was one query away:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;all_source&lt;/span&gt;
&lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="k"&gt;owner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;repo_owner&lt;/span&gt; &lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'CIOENTITY'&lt;/span&gt;
&lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'PACKAGE'&lt;/span&gt; &lt;span class="k"&gt;order&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;An AI agent does not need the GUI. It needs sqlplus.&lt;/p&gt;
&lt;h2&gt;
  
  
  The loop that worked
&lt;/h2&gt;

&lt;p&gt;The same four steps repeated for every element type:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Read the API from the database.&lt;/strong&gt; The package spec says which fields the record has.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read an existing element as the template.&lt;/strong&gt; There is no house-standards document, but there are 3,277 existing tables. Naming conventions, audit columns, domains, window sizes, preference sets: all of it was read from elements my colleagues built over 20 years. "How we do things here" is written in the data itself.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write in small validated transactions.&lt;/strong&gt; One logical change, validate, and on failure print the violation messages and abort.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verify through CI views&lt;/strong&gt; before moving on.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;With that loop the agent built the full chain: entity with attributes, table with columns on the house domains plus a primary key, the table-to-entity mapping (normally the job of a Designer wizard called the Database Design Transformer), a sequence with the ID trigger, audit columns with their trigger (PL/SQL bodies written through the &lt;code&gt;RMOTEXT&lt;/code&gt; API), table ownership moved to another application with a shortcut left in the original, and the complete form definition: module, window, component, table usage, items, preference sets.&lt;/p&gt;

&lt;p&gt;Then came the one part SQL cannot do. Generating the actual Forms binary requires a client-side tool, so I pressed Generate in Designer. The generator failed. The agent read the generator output and the &lt;code&gt;.err&lt;/code&gt; log file from disk, diagnosed each failure, fixed them through the API, and I pressed the button again. That cycle repeated until the form ran.&lt;/p&gt;

&lt;p&gt;One fix deserves a mention. The form compiled and started, but the layout was broken: all fields stacked on top of each other. I gave the agent a screenshot of the running form. It recognized the pattern, found the cause (items without &lt;code&gt;display_width&lt;/code&gt; fall back to the column's width, and the layout collapses into a stack) and set the widths on all items through the API. The next generation ran clean.&lt;/p&gt;
&lt;h2&gt;
  
  
  Errors are data
&lt;/h2&gt;

&lt;p&gt;Around 20 distinct errors came up across the two days. Every one was diagnosable from the message plus the state of the database. A sample:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Error&lt;/th&gt;
&lt;th&gt;Cause&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CDR-00100: Workarea context has not been set&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;missing &lt;code&gt;jr_context.set_workarea&lt;/code&gt; before anything else&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;ORA-00001&lt;/code&gt; on an internal unique key&lt;/td&gt;
&lt;td&gt;reusing a CIO record between &lt;code&gt;ins&lt;/code&gt; calls: it keeps the previous element's IDs, so reset the record to an empty one&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PLS-00302: component 'INS' must be declared&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;some packages are abstract; write through the specific subtype package&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;CDG-01199: no queryable item&lt;/code&gt; at generation&lt;/td&gt;
&lt;td&gt;items were created without &lt;code&gt;select_flag = 'Y'&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;identifier must be declared&lt;/code&gt; in the &lt;code&gt;.err&lt;/code&gt; file&lt;/td&gt;
&lt;td&gt;the physical table did not exist yet on the target dev database&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;None of these are in any manual. All of them are now in ours, because the collected errors turned out to be the most valuable output of the whole exercise. They went into two places: a handbook for humans, and a &lt;a href="https://github.com/nuncij/oracle-designer-cdapi-skill" rel="noopener noreferrer"&gt;Claude Code skill&lt;/a&gt; for future AI sessions. The next session does not rediscover CDAPI, it starts from a working recipe.&lt;/p&gt;

&lt;p&gt;The skill is open source (MIT), with the transaction templates, the per-element procedures, and the full error table:&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/nuncij" rel="noopener noreferrer"&gt;
        nuncij
      &lt;/a&gt; / &lt;a href="https://github.com/nuncij/oracle-designer-cdapi-skill" rel="noopener noreferrer"&gt;
        oracle-designer-cdapi-skill
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Drive Oracle Designer (6i/9i/10g) with an AI coding agent via its CDAPI/CIO PL/SQL API - entities, tables, forms, no GUI. Claude Code skill + proven SQL templates.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Oracle Designer CDAPI Skill — drive Oracle Designer with an AI coding agent&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;Oracle Designer (6i/9i/10g) is a CASE tool from the early 2000s, out of support since 2013, yet still the center of development in many legacy Oracle shops. The common assumption is that you can only work with it through its GUI (Repository Object Navigator, Design Editor). That assumption is wrong.&lt;/p&gt;

&lt;p&gt;Designer stores everything in an Oracle schema and ships a complete PL/SQL API next to it. That means an AI coding agent (Claude Code, or any tool that can run sqlplus) can read and write the repository directly: create entities, tables, columns, keys, sequences, triggers, and complete form module definitions — validated by Designer's own engine, without a single click in the GUI. The only steps left for a human are pressing Generate (Forms/DDL generators are client-side) and drawing diagrams.&lt;/p&gt;

&lt;p&gt;This repository packages that capability as an…&lt;/p&gt;&lt;/div&gt;


&lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/nuncij/oracle-designer-cdapi-skill" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;h2&gt;
  
  
  An agent with write access to production designs needs guardrails
&lt;/h2&gt;

&lt;p&gt;The same access that lets an agent create a table lets it silently damage a colleague's form with one mistyped ID. Before letting it loose we layered defenses: scripts look up elements strictly by name and verify parentage before touching anything, a control report runs after every session and lists everything that user changed that day across 17 element types, and a log table with a nightly job keeps permanent change history, because Designer itself only stores the last change. The nightly database backup stays as the final net. All of it is running, not planned.&lt;/p&gt;

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

&lt;p&gt;The limits we actually hit: drawing ER diagrams (the layouts are binary blobs) and pressing the two client-side generator buttons. Everything else that "requires the GUI" turned out not to.&lt;/p&gt;

&lt;p&gt;The key insight: a legacy tool with no SDK, no docs, and no support is not necessarily closed to AI agents. If it stores its world in a database, it may be the most open tool you have. The dictionary replaces the SDK, existing data replaces the standards document, and error messages replace support. Designer waited 20 years for a user that reads package specs for fun.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>oracle</category>
      <category>sql</category>
      <category>showdev</category>
    </item>
    <item>
      <title>My Peer-Reviewed T. rex Looks Worse Than a 1996 Video Game</title>
      <dc:creator>Nunc</dc:creator>
      <pubDate>Fri, 14 Aug 2026 10:31:56 +0000</pubDate>
      <link>https://dev.to/nunc/my-peer-reviewed-t-rex-looks-worse-than-a-1996-video-game-40dm</link>
      <guid>https://dev.to/nunc/my-peer-reviewed-t-rex-looks-worse-than-a-1996-video-game-40dm</guid>
      <description>&lt;p&gt;I wanted to know what a &lt;em&gt;Tyrannosaurus rex&lt;/em&gt; actually looked like. Not the movie one. The real one, the one the papers describe. So I told AI agents to read the science and build it in 3D, model and animation, with no human touching the mesh.&lt;/p&gt;

&lt;p&gt;Three days later I had a potato.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fncybttd2n97wjc3ha2ia.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fncybttd2n97wjc3ha2ia.png" alt="Render of the finished T. rex in an environment shot: a beige, heavy-bellied dinosaur with thin legs walking on flat sand under a clear sky, horizontal posture, tail held straight out, calm and completely unthreatening" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That is the apex predator. Beige, calm, faintly disappointed. It has the body language of a man waiting for a bus.&lt;/p&gt;

&lt;p&gt;And here is the annoying part: &lt;strong&gt;it is scientifically correct.&lt;/strong&gt; Every bone length traces to a published paper. The body mass sits inside the window from peer-reviewed volumetric studies. The walk cycle matches a Royal Society paper to three decimals and a script verified it. My dinosaur can cite its sources. It just cannot frighten a chicken.&lt;/p&gt;

&lt;p&gt;I am oddly proud of it and I cannot show it to anyone without explaining it first, which is usually a sign you have built the wrong thing.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the agents taught me about T. rex
&lt;/h2&gt;

&lt;p&gt;I never opened Blender. My job was approving phases and stopping Windows from falling asleep mid-render. The agents did the rest: two Claude Code sessions, one per machine, plus OpenAI's Codex hired specifically to be unpleasant about the plan.&lt;/p&gt;

&lt;p&gt;The research was the part I enjoyed, because the agents kept telling me I was wrong about a dinosaur I had been confident about since I was eight:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It had lips.&lt;/strong&gt; Teeth covered when the mouth is closed. More monitor lizard, less crocodile grin. Sorry, Jurassic Park.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It was scaly&lt;/strong&gt;, pebbly scales about a millimetre across. On a 12 metre animal that is a shader detail. Nobody sculpts a millimetre.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It was slow.&lt;/strong&gt; Preferred walking speed 4.6 km/h, set by the resonant frequency of its tail. Top speed maybe 25 km/h, always with one foot down, because an aerial phase would have snapped its legs. The jeep was never in danger.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The tail was the engine.&lt;/strong&gt; The biggest leg muscle lives in the tail, roughly 260 kg per side. Every skinny-tailed rex you have seen is wrong.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The colour is unknowable.&lt;/strong&gt; No pigment data exists for any tyrannosaurid. Ours is olive-brown because somebody had to choose, and the repo says so in writing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then Codex, the hired pessimist, earned its fee in an afternoon. My research agent had cheerfully declared the Smithsonian's T. rex scan public domain. Codex opened the actual page: only the metadata is CC0, the scan is non-commercial only. A hallucinated licence, caught by a rival model that bothered to click the link.&lt;/p&gt;

&lt;p&gt;Codex also caught the mass formula counting the air cavities twice. My dinosaur was being deflated twice for the same lungs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building a dinosaur when nobody involved can sculpt
&lt;/h2&gt;

&lt;p&gt;Neither I nor the agents can sculpt. So the pipeline became Python scripts driving headless Blender, where nothing is judged by eye and everything is judged by a number.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy45fj6vy2htpg030s3ot.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy45fj6vy2htpg030s3ot.jpg" alt="Four stages of the pipeline stacked vertically: a white parametric skeleton with bone lengths from published measurements, the same skeleton with 17 red muscle volumes added, a smooth white body envelope whose volume times density must land in 8.4 to 9.5 tonnes, and the final beige rendered dinosaur in its environment" width="800" height="1681"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Bones first, from published measurements of "Sue". Then 17 muscle groups, each named after the real muscle, each traceable to an anatomy paper. The script prints every muscle's weight, which is how I learned my dinosaur's tail muscle had come out at 471 kg per side against a published estimate of 261. It had been lifting. Tuned down to 252 kg.&lt;/p&gt;

&lt;p&gt;Then the skin, with one rule: mesh volume times published tissue density must land between 8.4 and 9.5 tonnes. First attempt, 11.4 to 13.1 tonnes. My scientifically rigorous T. rex was 25 % overweight and its belly hung 7 cm below where the ribs say a belly stops. Two rounds of dieting later it passed at 8.8 to 10.2 tonnes.&lt;/p&gt;

&lt;p&gt;All of that worked, and I want to be fair to it. Build, measure, compare against a paper, fix. Dozens of times, overnight, with nobody watching and no human skill involved. Nothing about the science half embarrasses me. I would put those numbers in front of a paleontologist.&lt;/p&gt;

&lt;h2&gt;
  
  
  The moment I should have seen coming
&lt;/h2&gt;

&lt;p&gt;The walk had a validator: no aerial phase, no foot sliding, no tail dragging, jaw inside its measured gape. It rejected the animation four times and every rejection was a real bug. Right-foot plants generated at the left foot's coordinates, so the poor thing dragged a foot two metres per step. A clamping bug at the clip boundary. Feet planted too close to the hip, shoving the femur past its allowed arc.&lt;/p&gt;

&lt;p&gt;Then the validator said PASS. I opened the render, and the dinosaur had stick legs and floating feet. The rig was rotating each leg 90 degrees, so the wide thigh faced the camera edge-on. Every number was perfect. The picture looked like the animal had been assembled by somebody reading the instructions upside down.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Validation catches what you thought to measure.&lt;/strong&gt; I had measured whether the foot reached the ground. I had not measured whether the leg was on straight.&lt;/p&gt;

&lt;p&gt;Here is the finished walk, machine-validated, zero violations, at the exact speed the paper says the animal preferred:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr1034d2w4evmharm6wyw.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr1034d2w4evmharm6wyw.gif" alt="Looping animation of the finished T. rex walking across flat sand in profile: a beige, heavy-bellied dinosaur taking slow even strides at its scientifically preferred walking speed, tail held straight out and swaying slightly with each step, the motion perfectly regular and mechanical" width="640" height="360"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Two full strides at 1.293 m/s. Every number in it is right and it walks like a metronome with a mortgage. &lt;a href="https://hellonunc.com/assets/articles/peer-reviewed-trex-looks-worse/trex-walk.mp4" rel="noopener noreferrer"&gt;MP4 in 1080p&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I showed it to my wife. She asked what it was supposed to be doing.&lt;/p&gt;

&lt;h2&gt;
  
  
  So why did I get a potato?
&lt;/h2&gt;

&lt;p&gt;I assumed it was my fault: bad prompts, wrong tools, not enough iterations. So the agents ran one more research pass, this time on themselves. The potato was predictable, and the literature is almost rude about how predictable:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The models cannot see properly.&lt;/strong&gt; &lt;a href="https://arxiv.org/abs/2407.06581" rel="noopener noreferrer"&gt;"Vision Language Models Are Blind"&lt;/a&gt; tested frontier models on things a child does instantly, like whether two circles overlap: &lt;strong&gt;58.57 %&lt;/strong&gt; average. Probes suggest the vision half knows and the language half cannot read it out loud.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Their taste is not our taste.&lt;/strong&gt; &lt;a href="https://arxiv.org/pdf/2510.22373" rel="noopener noreferrer"&gt;VisJudge-Bench&lt;/a&gt; puts model-to-human agreement on aesthetics at &lt;strong&gt;0.18 to 0.41&lt;/strong&gt;, and &lt;a href="https://mllm-judge.github.io/" rel="noopener noreferrer"&gt;MLLM-as-a-Judge&lt;/a&gt; found they are fine at "which of these two is better" and unreliable at grading one thing alone, which is exactly what an agent does to its own render. Mine could tell broken legs from normal legs. It could not tell mediocre from good.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;There is a benchmark for agents driving Blender, and it is humbling.&lt;/strong&gt; &lt;a href="https://arxiv.org/abs/2504.01786" rel="noopener noreferrer"&gt;BlenderGym&lt;/a&gt; scored thirteen vision-language systems &lt;strong&gt;2 to 10 times worse than human Blender users&lt;/strong&gt;, on tasks the authors call easy for people.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Humans are freakishly good at spotting bad motion.&lt;/strong&gt; Twelve moving dots are enough for you to recognise a walking person. Worse, &lt;a href="https://www.sciencedirect.com/science/article/abs/pii/S0010027713002114" rel="noopener noreferrer"&gt;uncanny-valley research&lt;/a&gt; found degraded motion drops a character &lt;em&gt;below&lt;/em&gt; its own still image. My walk cycle is not neutral. It is actively working against me.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then the structural reason, which is the part I keep chewing on. What made these models good at maths and code is training against verifiable rewards. "Femur = 1.321 m" is verifiable. "Feels alive" is not. Correctness automates first by construction, not by accident.&lt;/p&gt;

&lt;p&gt;I hired a correctness engine and handed it a beauty problem. It solved the half it was built for and gave me a vegetable.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 1996 problem
&lt;/h2&gt;

&lt;p&gt;The T. rex in Tomb Raider, a few hundred polygons and a hand-painted texture, is scarier than my 52,000-quad specimen. Artists in 1996 had no science and no polygons and made a monster. My agents had all the science and all the polygons and made an exhibit.&lt;/p&gt;

&lt;p&gt;Which is, if I squint, the actual finding. An artist starting from this repo would skip weeks of paleontology and inherit guardrails against movie-monster anatomy. My agents would get the one judge they lack. Neither half is worthless. They are two different jobs, and I spent three days proving it with a dinosaur.&lt;/p&gt;

&lt;p&gt;The repo stays private and non-commercial, because the museum scan I used as a shape reference allows personal use only. Nobody is fighting me for the rights to a beige potato anyway.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would tell you
&lt;/h2&gt;

&lt;p&gt;Make the agent's claims falsifiable with numbers from the literature. Hire one hostile reviewer from a different vendor. Look at the renders, because four green validator runs hid a dinosaur with sideways legs. Write down what you do not know.&lt;/p&gt;

&lt;p&gt;And expect the last 10 % to be 90 % craft. Correctness automated beautifully. Beauty did not, and now I have citations explaining why. My dinosaur is anatomically defensible, machine-validated, and about as frightening as livestock.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>blender</category>
      <category>python</category>
      <category>showdev</category>
    </item>
    <item>
      <title>AI Agents Can't Watch Video. Mine Made Three Films Anyway.</title>
      <dc:creator>Nunc</dc:creator>
      <pubDate>Fri, 07 Aug 2026 11:33:40 +0000</pubDate>
      <link>https://dev.to/nunc/ai-agents-cant-watch-video-mine-made-three-films-anyway-46cp</link>
      <guid>https://dev.to/nunc/ai-agents-cant-watch-video-mine-made-three-films-anyway-46cp</guid>
      <description>&lt;p&gt;&lt;em&gt;Martin Krpan&lt;/em&gt; is the story every Slovenian schoolchild knows: a salt smuggler meets the emperor's carriage on a snowed-in road too narrow for both, so he calmly lifts his loaded mare and carries her aside. I asked AI agents to turn it into an animated film. The catch: a language model cannot watch video, so it cannot check its own footage. I ran the experiment three times with three different AI setups. All three are live. The interesting result is not the films but what a blind director needs before you can trust its cut.&lt;/p&gt;

&lt;p&gt;The idea comes from Andrej Karpathy's &lt;a href="https://karpathy.ai/lotr-movie" rel="noopener noreferrer"&gt;LOTR browser movie&lt;/a&gt;, which he rated "kind of janky but fun" and blamed on exactly this: LLMs can't perceive video, so they can't audit the worlds they build.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rules, same for all three
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Zero assets.&lt;/strong&gt; No 3D models, no textures, no image, font or audio files. Geometry from Three.js primitives, sound synthesized in the browser. The one exception: narration, pre-rendered with a free tool; each film works without it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deterministic timeline.&lt;/strong&gt; Every frame is a pure function of film time. No accumulated physics, no &lt;code&gt;Math.random()&lt;/code&gt; at play time. Seeking to second 49.5 produces the same pixels every time, in every browser process.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It must be a film.&lt;/strong&gt; Shots, hard cuts, title cards, subtitles, pacing, an ending. In Slovenian and English.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The determinism rule is the whole trick: if a frame is a pure function of time, a screenshot becomes a measurement. The agent can seek, look at the still (the one visual sense an LLM has), compare pixel hashes between runs, and export video frame by frame. Every verification idea below stands on that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Film 1: one agent and a screenshot loop
&lt;/h2&gt;

&lt;p&gt;The first run was a single Claude agent building scene 1: the road encounter, 75 seconds, 11 shots. One working day: 7.5 hours of build time, 3,505 lines of TypeScript.&lt;/p&gt;

&lt;p&gt;The process rule that shaped it: after building, the agent must watch its own film as stills. A script seeks the timeline, captures screenshots, and the agent critiques them like a director: framing, scale, mesh intersections, poses. It found 24+ defects on its own this way, including the mare's muzzle pointing 43 degrees up (it read as antlers), the emperor's head clipping through the carriage roof, and two shots filming empty road because the carriage hadn't arrived yet.&lt;/p&gt;

&lt;p&gt;What it never found: that the film was ugly. The four changes that most improved the result were one-sentence human comments ("the low-poly look is really ugly", "the background sound is annoying", "the narrator is robotic", "Discord says max 15 MB"). Each triggered a real engineering response: an art-direction rebuild with HDR bloom and golden-hour light, a remix measured 60 % quieter, pre-rendered neural voices, a two-pass encode to 13.4 MB.&lt;/p&gt;

&lt;p&gt;The split was clean: self-review converged on &lt;em&gt;correct&lt;/em&gt;. The human one-liners steered it to &lt;em&gt;good&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;▶ &lt;a href="https://krpan.hellonunc.com/" rel="noopener noreferrer"&gt;krpan.hellonunc.com&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Film 2: the full story, and a harness with teeth
&lt;/h2&gt;

&lt;p&gt;The second run scaled up: the whole tale, 10 scenes, exactly 260 seconds, about 14,800 lines. This time three agents split the roles: one planned, one built, and a rival vendor's model (OpenAI Codex) attacked the plan before any code existed. Sixteen findings came back; the best one justified the setup: a dialogue line in the shot list was &lt;strong&gt;not in Levstik's text&lt;/strong&gt;. The model had paraphrased the classic from memory. Codex fetched the pinned Wikisource corpus and proved it. That finding became a validator: every subtitle and narration line must be a verbatim substring of the 1858 text.&lt;/p&gt;

&lt;p&gt;Beyond screenshots, this run made the aesthetics machine-checkable: six invariant classes asserted on every scene (palette only from named constants, prop sizes within 10 % of declared, exactly the declared lights, performance ceilings, no object jumping more than 0.5 m outside a cut). And determinism was tested, not assumed: two fresh renders of all ten scenes differed by &lt;strong&gt;0.0000 %&lt;/strong&gt; of pixels.&lt;/p&gt;

&lt;p&gt;That check earned its keep. The sword in the duel was aimed using the giant's hand position from the &lt;em&gt;previous&lt;/em&gt; frame, a frame-order dependency invisible to the eye. It surfaced as a 0.2 % pixel difference between playing into a moment and jumping straight to it.&lt;/p&gt;

&lt;p&gt;Total cost: about 1.4 million tokens, roughly a cinema ticket, for a bilingual adaptation no studio would fund. The report's own conclusion: the same builder without the harness would have shipped the silhouette shot, the misaimed sword, and the paraphrased Levstik.&lt;/p&gt;

&lt;p&gt;▶ &lt;a href="https://krpan2.hellonunc.com/" rel="noopener noreferrer"&gt;krpan2.hellonunc.com&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Film 3: a different vendor runs it like a science fair
&lt;/h2&gt;

&lt;p&gt;The third run gave the format to GPT-5.6 Codex, working interactively, and pushed verification the furthest: hypotheses with numeric pass criteria written down &lt;em&gt;before&lt;/em&gt; evaluation. 78.000 seconds, 1,872 frames at 24 fps, and every single frame scanned for collisions between bounding boxes of every moving object.&lt;/p&gt;

&lt;p&gt;The exhaustive scan embarrassed the still-image review. The stills looked fine while the team horses walked through Krpan for eight seconds and the carriage pole crossed the mare for fifteen. First scan: four sustained collision intervals. Final scan: &lt;strong&gt;0 violations in 1,872 frames&lt;/strong&gt;. Wheel rotation was derived from distance and radius instead of a multiplier that "looked right"; the final slip error was 1.6 × 10⁻⁷ scene units per frame, and zero frames rolled backward. The MP4 exports were decoded frame by frame: exactly 1,872 H.264 frames in each language edition.&lt;/p&gt;

&lt;p&gt;The methodological lesson from this run: stills, continuous playback and an exhaustive numeric scan catch three disjoint classes of bugs. Any one alone lies to you.&lt;/p&gt;

&lt;p&gt;▶ &lt;a href="https://krpan3.hellonunc.com/" rel="noopener noreferrer"&gt;krpan3.hellonunc.com&lt;/a&gt; (MP4 downloads on the page)&lt;/p&gt;

&lt;h2&gt;
  
  
  The scoreboard
&lt;/h2&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;Film 1&lt;/th&gt;
&lt;th&gt;Film 2&lt;/th&gt;
&lt;th&gt;Film 3&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Scope&lt;/td&gt;
&lt;td&gt;scene 1, 75 s&lt;/td&gt;
&lt;td&gt;full story, 260 s&lt;/td&gt;
&lt;td&gt;scene 1 retold, 78 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Built by&lt;/td&gt;
&lt;td&gt;one Claude agent&lt;/td&gt;
&lt;td&gt;Claude planner + builder, Codex as adversary&lt;/td&gt;
&lt;td&gt;GPT-5.6 Codex, interactive&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Code&lt;/td&gt;
&lt;td&gt;3,505 lines&lt;/td&gt;
&lt;td&gt;~14,800 lines&lt;/td&gt;
&lt;td&gt;~3,140 lines&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Verification signature&lt;/td&gt;
&lt;td&gt;screenshot self-review&lt;/td&gt;
&lt;td&gt;6 machine-checked invariants, 0.0000 % pixel determinism&lt;/td&gt;
&lt;td&gt;all 1,872 frames scanned, numeric pass criteria&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Caught what the eye missed&lt;/td&gt;
&lt;td&gt;24+ visual defects&lt;/td&gt;
&lt;td&gt;invented Levstik quote, frame-order bug&lt;/td&gt;
&lt;td&gt;4 collision intervals, backward wheels&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flrxtn9i9tryeyefthtt7.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flrxtn9i9tryeyefthtt7.jpg" alt="Three stills of the same moment from three AI-built films: Krpan carrying his loaded mare off a snowy road. Film 1 is warm and pastel with soft snowfall, film 2 is pale blue and cinematic with a Slovenian subtitle from Levstik, film 3 is a deeper green winter scene with the carriage horses waiting on the right" width="800" height="1465"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The same moment in all three films: Krpan lifting the loaded mare off the road.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What all three runs agree on
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;"It runs" is a weak finish line.&lt;/strong&gt; Every version ran fine while hiding collisions, backward wheels, a phantom sword or paraphrased text. The quality came from the harness: determinism, screenshots as measurements, invariants, a rival model reading the plan.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The human contribution is taste, not code.&lt;/strong&gt; Across all three films I never edited a line. My inputs were the story choice, the constraints, and one-sentence quality judgements. The agents handled everything else, including finding most of their own bugs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Determinism is the keystone.&lt;/strong&gt; One property served four masters: frame-exact scrubbing for viewers, screenshot audits for the agent, regression checks for refactors, and clean video export.&lt;/p&gt;

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

&lt;p&gt;A language model cannot watch video, but it does not need to. If every frame is a pure function of time, screenshots become measurements, invariants become tests, and a blind director can prove its footage is right instead of hoping. The films are the demo. The harness is the product.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>threejs</category>
      <category>javascript</category>
      <category>showdev</category>
    </item>
    <item>
      <title>EXPLAIN PLAN as a Lint for LLM-Generated SQL</title>
      <dc:creator>Nunc</dc:creator>
      <pubDate>Thu, 06 Aug 2026 10:48:57 +0000</pubDate>
      <link>https://dev.to/nunc/explain-plan-as-a-lint-for-llm-generated-sql-4mg8</link>
      <guid>https://dev.to/nunc/explain-plan-as-a-lint-for-llm-generated-sql-4mg8</guid>
      <description>&lt;p&gt;My AI agents write Oracle SQL all day: fix scripts, diagnostics, one-off reports for a 2.3M-line legacy system. Their most common failure isn't bad logic. It is SQL that references a table or column that almost exists. Oracle has had the fix for decades, it costs one statement per query, and it never executes anything: &lt;code&gt;EXPLAIN PLAN&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The failure mode: names that almost exist
&lt;/h2&gt;

&lt;p&gt;A language model doesn't know your schema. It knows what schemas usually look like. So on a 20-year-old database with thousands of tables, it produces names that are plausible instead of real: &lt;code&gt;POLICY_STATUS&lt;/code&gt; when the column is &lt;code&gt;STATUS_CD&lt;/code&gt;, &lt;code&gt;CUSTOMERS&lt;/code&gt; when the table has been &lt;code&gt;CUSTOMER&lt;/code&gt; (singular) since 1998, a join through a link table that was dropped two versions ago.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnl75tet1gta5u3hqoe40.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnl75tet1gta5u3hqoe40.png" alt="Comparison of what the model wrote versus what the schema has: POLICY_STATUS versus STATUS_CD (plausible name, wrong name), CUSTOMERS versus CUSTOMER (singular since 1998), and a join through POLICY_CUST_LINK, a table dropped two versions ago; a human reviewer skims this and it reads fine because the naming convention matches" width="800" height="373"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These are the worst kind of errors, because they look right. A human reviewer skims the script, the naming convention matches, everything reads fine. The error only surfaces when the script runs, and in my case fix scripts run at more than 20 customer installations. That is exactly the place where you do not want to discover an invented column.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why you cannot just run it to check
&lt;/h2&gt;

&lt;p&gt;The obvious test, execute it and see, is not available. These scripts are UPDATEs and DELETEs against production-like data. Wrapping everything in a transaction and rolling back sort of works, but it fires triggers, takes locks, burns sequence numbers and takes time on big tables.&lt;/p&gt;

&lt;p&gt;I wanted a check that touches nothing and still uses the real schema. It already exists.&lt;/p&gt;

&lt;h2&gt;
  
  
  EXPLAIN PLAN parses without executing
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;EXPLAIN&lt;/span&gt; &lt;span class="n"&gt;PLAN&lt;/span&gt; &lt;span class="k"&gt;FOR&lt;/span&gt;
&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;policy&lt;/span&gt;
   &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;status_cd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'ACTIVE'&lt;/span&gt;
 &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;policy_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;b1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Oracle takes the statement through the full parse: it resolves every table and column against the live data dictionary, checks your privileges, builds an execution plan and writes it to &lt;code&gt;PLAN_TABLE&lt;/code&gt;. What it never does is execute. No rows change, no triggers fire, no locks are held. It is safe to run for an UPDATE, a DELETE, a MERGE.&lt;/p&gt;

&lt;p&gt;If the model invented a table, you get an answer in milliseconds:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ORA-00942: table or view does not exist
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If it invented a column:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ORA-00904: "POLICY_STATUS": invalid identifier
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is a lint result, produced by the one parser that actually knows the schema: the database's own.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wiring it into the agent loop
&lt;/h2&gt;

&lt;p&gt;In my workflow every generated script goes through this gate before a human sees it, statement by statement, against the dev database:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;stmt&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;split_statements&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;script&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;EXPLAIN PLAN FOR &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;stmt&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;oracledb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DatabaseError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;stmt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fttthb6lshiu4k0pr422u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fttthb6lshiu4k0pr422u.png" alt="Diagram of the EXPLAIN PLAN gate: an agent writes SQL fix scripts statement by statement, each statement goes through an EXPLAIN PLAN gate that parses it against the live dev schema with nothing executed; when it parses OK the script moves on to human review with every identifier real, and on errors like ORA-00942 table or view does not exist or ORA-00904 invalid identifier the error goes back to the model, which retries; a side effect is that the execution plan is already available via DBMS_XPLAN.DISPLAY" width="800" height="409"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The errors go straight back to the model with the original task. Most name-level failures disappear in one retry, without me reading anything. What reaches me is a script whose every identifier is real.&lt;/p&gt;

&lt;p&gt;The tool the agents call runs in a read-only safe mode: &lt;code&gt;SELECT&lt;/code&gt;, &lt;code&gt;DESCRIBE&lt;/code&gt; and &lt;code&gt;EXPLAIN PLAN&lt;/code&gt; are allowed, everything else (DDL, DML, GRANT, COMMIT) is blocked before it reaches the database. So even a badly confused agent can't turn the lint step into a write.&lt;/p&gt;

&lt;h2&gt;
  
  
  The free bonus: you also get the plan
&lt;/h2&gt;

&lt;p&gt;The check produces an execution plan as a side effect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DBMS_XPLAN&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DISPLAY&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyeqp2xjyhchmk97sacif.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyeqp2xjyhchmk97sacif.png" alt="Terminal output of EXPLAIN PLAN FOR an UPDATE on the policy table followed by SELECT FROM TABLE of DBMS_XPLAN.DISPLAY: the plan shows TABLE ACCESS FULL on POLICY with 41 million rows highlighted, because there is no index on REGION_ID, and nothing was executed" width="800" height="355"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So the same gate that catches invented columns also shows you the full table scan on a 41-million-row table before anything runs. Two problems, one statement.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it does not catch
&lt;/h2&gt;

&lt;p&gt;A green lint is not a correct script. Here is what this gate misses:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Valid names, wrong logic.&lt;/strong&gt; &lt;code&gt;DELETE FROM policy WHERE status_cd = 'A'&lt;/code&gt; parses perfectly and can still delete the wrong rows. This gate replaces nothing at the review level.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PL/SQL blocks.&lt;/strong&gt; &lt;code&gt;EXPLAIN PLAN&lt;/code&gt; takes single SQL statements. For packages and procedures I compile against a scratch schema instead; that is a different gate.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data assumptions.&lt;/strong&gt; The parser checks that a column exists, not that &lt;code&gt;'A'&lt;/code&gt; is a value that ever appears in it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Schema drift.&lt;/strong&gt; I parse against the dev schema. A customer installation two versions behind can still disagree. Closest schema wins, not a guarantee.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The same idea works outside Oracle: PostgreSQL parses and plans with &lt;code&gt;PREPARE&lt;/code&gt; or plain &lt;code&gt;EXPLAIN&lt;/code&gt;, SQL Server has &lt;code&gt;SET PARSEONLY ON&lt;/code&gt;. Any database that can plan a statement without running it can lint one.&lt;/p&gt;

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

&lt;p&gt;LLM SQL fails most often at the name level, and name resolution is exactly what the database parser already does. &lt;code&gt;EXPLAIN PLAN&lt;/code&gt; turns that parser into a lint step: every generated statement checked against the live schema in milliseconds, with nothing executed and an error message the model can act on. Of all the guardrails around my agents, this one has the best ratio of effort to failures caught. It was one afternoon of work.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>sql</category>
      <category>oracle</category>
      <category>database</category>
    </item>
    <item>
      <title>I Review My AI Assistant's Logs Every Week, With an Agent. Here's the Process.</title>
      <dc:creator>Nunc</dc:creator>
      <pubDate>Mon, 03 Aug 2026 13:43:58 +0000</pubDate>
      <link>https://dev.to/nunc/i-review-my-ai-assistants-logs-every-week-with-an-agent-heres-the-process-409p</link>
      <guid>https://dev.to/nunc/i-review-my-ai-assistants-logs-every-week-with-an-agent-heres-the-process-409p</guid>
      <description>&lt;p&gt;We run an internal AI assistant on top of our 20-year-old helpdesk system. Production since April, 15 active users across five roles, from developers to operators to project managers. The model matters less than people think. What actually improves the assistant week over week is a boring process: a structured weekly review of its logs. This post describes that process, the metrics we track, and two real failures the review caught that I would never have found otherwise.&lt;/p&gt;

&lt;h2&gt;
  
  
  What gets logged
&lt;/h2&gt;

&lt;p&gt;Two levels. A summary log with one line per request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[2026-07-20 16:46:52] user=xxx timing=99926ms status=ok
  model=... turns=13 tools=12 cost=$0.25
  profile=xxx ftok=1820ms query="have we ever solved..."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And a full JSONL transcript per session: every user turn, every answer, plus which context was injected and where it came from. The provenance part was added later, after a review showed we couldn't tell why the assistant said something. If you're building an assistant, log answer provenance from day one.&lt;/p&gt;

&lt;h2&gt;
  
  
  The weekly review is run by an agent
&lt;/h2&gt;

&lt;p&gt;Reading a week of transcripts by hand is not sustainable, so the review itself is an agent task. I say "do the review", and the agent figures out which period is not yet covered, pulls the logs from the server, and works through every session. Not a sample. All of them.&lt;/p&gt;

&lt;p&gt;The review has three outputs:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Session quality review.&lt;/strong&gt; The agent grades answers, and the important part: it re-verifies factual claims against the actual database. If the assistant told a user "this fix was installed at customer X in May", the review checks whether that's true.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improvement proposals.&lt;/strong&gt; Concrete and prioritized, each with the problem, evidence (session IDs, quotes), a proposed fix, and an effort estimate. Each proposal enters a backlog with a status: proposed, approved, implemented, rejected, or watch.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;User profiles.&lt;/strong&gt; Usage patterns per user feed personalization (more below).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1tx4pr1q64ekn3u3vd7v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1tx4pr1q64ekn3u3vd7v.png" alt="Diagram of the weekly review loop: a summary log with one line per request and JSONL transcripts with every turn and injected context both feed a weekly review agent that works through every session rather than a sample; the agent produces three outputs, session quality grades with claims re-checked against the database, improvement proposals with problem and evidence and fix and effort, and user profiles that go into the assistant's prompt; proposals land in an improvement backlog with statuses proposed, approved, implemented, rejected and watch, and an orange arrow loops from the backlog back to the review agent, where the next review verifies every implemented fix" width="800" height="473"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The next review then verifies the previous round: did the implemented fixes actually stop the failure they targeted? Several times the answer was "partially", and the item went back into the backlog. Without that verification step, an improvement backlog turns into a feel-good list.&lt;/p&gt;

&lt;h2&gt;
  
  
  The scorecard
&lt;/h2&gt;

&lt;p&gt;Every review adds one row to a long-running scorecard. Same metrics, same methodology, so trends are visible: requests, users, sessions, error rate, median and p95 response time, corrections, factual errors, false negatives, security events, user feedback, average session grade.&lt;/p&gt;

&lt;p&gt;A recent week looked like this: 137 requests, 12 users, 44 sessions, 0 errors, median response 99 seconds, p95 262 seconds, average session grade 4.5 out of 5 at 100% review coverage.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2qxvjze5cyegxpdxzeg4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2qxvjze5cyegxpdxzeg4.png" alt="One week on the scorecard, shown as eight metric tiles: 137 requests, 12 active users, 44 sessions, 0 errors, 99 seconds median response, 262 seconds p95 response, average session grade 4.5 out of 5, and 100 percent review coverage" width="800" height="373"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Two practical lessons from maintaining it. First, write down the counting methodology, because "how many sessions" turns out to have edge cases (we now count a session as a transcript file with at least one real user turn, feedback-only files excluded). Second, mark methodology changes in the scorecard itself. Otherwise a metric jump reads as regression when it's actually deeper measurement.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two failures the review caught
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The leaking limit banner.&lt;/strong&gt; One afternoon our primary auth token kept hitting a rate limit, and a fallback took over. The failover worked, but the review found that at least 12 answers shown to 4 users had an English "You've hit your limit" banner and fragments of the failed first attempt glued to the top of the final answer. Users saw it and said nothing. Nobody reports weirdness in a tool they don't fully trust yet, which is exactly why you read the logs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The adopted false premise.&lt;/strong&gt; A user asked about a project, and their question contained a wrong assumption about which customer group a code referred to. The assistant took the premise at face value and confidently attributed everything to the wrong company. The transcript grade was fine (the user was happy!), but the facts were wrong. The fix was a prompt rule: verify the entity behind a code against the database before building an answer on it, even when the user asserts it. You will not catch this class of failure from user feedback, because the user is the source of the error.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two kinds of user profiles
&lt;/h2&gt;

&lt;p&gt;Each user has a profile the assistant loads into its system prompt: role, which projects they may see, typical questions, preferred answer depth. A developer asking about a package gets code references. A project manager asking the same thing gets a business summary. An operator asking about an error code gets the past-ticket solution plus a suggestion for which developer to assign.&lt;/p&gt;

&lt;p&gt;The part that took iteration: we keep two separate profile sets. Analytical profiles are our internal understanding of each user, built from the log reviews. Agent profiles are the distilled version the assistant actually loads. The analytical profile is the raw material, the agent profile is the product. Mixing them was a mistake we made first: internal observations about a user do not belong in a prompt.&lt;/p&gt;

&lt;p&gt;One rule I'd flag for anyone doing this: personalization must stay a default, not a cage. A non-technical user who asks a technical question gets the technical answer. And log review being about improving the assistant, not evaluating employees, is something you say out loud and write down before someone discovers their conversations are being read.&lt;/p&gt;

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

&lt;p&gt;An AI assistant in production is a product, and it needs the product loop: instrumentation, periodic review, prioritized backlog, verification that fixes worked. Doing the review with an agent makes 100% coverage affordable. And the highest-value finds are the ones no user will ever report: answers that were wrong while everyone was satisfied.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>llm</category>
      <category>observability</category>
    </item>
    <item>
      <title>108 Support Tickets Later: My AI Agent Workflow on a 2.3M-Line Oracle Legacy System</title>
      <dc:creator>Nunc</dc:creator>
      <pubDate>Thu, 30 Jul 2026 12:14:10 +0000</pubDate>
      <link>https://dev.to/nunc/108-support-tickets-later-my-ai-agent-workflow-on-a-23m-line-oracle-legacy-system-d9e</link>
      <guid>https://dev.to/nunc/108-support-tickets-later-my-ai-agent-workflow-on-a-23m-line-oracle-legacy-system-d9e</guid>
      <description>&lt;p&gt;My day job is maintaining a 20-year-old insurance system: over 1,000 PL/SQL packages, 2.3 million lines of code, Oracle Forms on top, and the same codebase running at more than 20 customer installations. Nine months ago I started routing my support tickets through AI agents in Claude Code. 108 tickets later, almost nothing I do is manual anymore. This post describes the workflow, the parts that keep it safe, and the loop that makes it better with every ticket.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this works on a legacy system
&lt;/h2&gt;

&lt;p&gt;People assume AI tooling helps most on greenfield projects. My experience is the opposite. The pain in a legacy system is not writing code, it's finding context:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Nobody knows the whole codebase. At 2.3 million lines, nobody can.&lt;/li&gt;
&lt;li&gt;The same code behaves differently per customer, controlled by hundreds of installation parameters. Half of my questions are "why does this work differently at customer X".&lt;/li&gt;
&lt;li&gt;Our helpdesk has about a million tickets from 20+ years. The answer to "have we solved this before?" is almost always yes. The problem is that it lives in a ticket from 2017, or in one paragraph of a manual nobody opened in three years.&lt;/li&gt;
&lt;li&gt;Knowledge is spread across five systems: the helpdesk, user manuals on a file share, a wiki, GitLab, and SVN.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Collecting that context by hand takes a senior developer half a day. An agent with the right tools does it in minutes. That's the whole trade.&lt;/p&gt;

&lt;h2&gt;
  
  
  The workflow: one command per ticket
&lt;/h2&gt;

&lt;p&gt;When a new ticket arrives, I run a single analyze command. The agent then:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Reads the ticket through the helpdesk's SOAP API, including attachments. PDFs, Word and Excel files get converted to text first.&lt;/li&gt;
&lt;li&gt;Searches for similar past tickets. Keyword search plus vector search, because the built-in helpdesk search only does keywords and misses anything phrased differently.&lt;/li&gt;
&lt;li&gt;Checks my library of ready-made SQL scripts for anything reusable.&lt;/li&gt;
&lt;li&gt;Looks at the relevant code in the database, and at its change history in GitLab or SVN if needed.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6yptiwepjr4irzu45n9l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6yptiwepjr4irzu45n9l.png" alt="Workflow diagram: a new ticket goes to an analyze agent that reads four sources (helpdesk API with attachments, similar past tickets via keyword and vector search, a library of 759 SQL templates, source code with change history), produces ANALIZA.md with findings and a proposed next step, which branches into a code fix, a customer reply, or more diagnostics; everything gets attached back to the ticket, and an orange feedback arrow loops from the ticket archive back into the search sources" width="799" height="445"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The output is a single markdown file, &lt;code&gt;ANALIZA.md&lt;/code&gt;: all findings in one place, plus a proposed next step. The next step is always one of three things: a code fix, a reply to the customer, or more diagnostics on the customer's database.&lt;/p&gt;

&lt;p&gt;Once there is enough data, the agent prepares the whole package: the proposed solution, a message for the customer, internal notes for colleagues involved in the ticket, the diagnostic and fix scripts, and a short write-up of why we did it this way. All of it gets attached to the ticket.&lt;/p&gt;

&lt;h2&gt;
  
  
  Skills are where the actual work went
&lt;/h2&gt;

&lt;p&gt;The agent is only as good as the context you can hand it. Over nine months I built 24 skills. Grouped by function:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Group&lt;/th&gt;
&lt;th&gt;Examples&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Context access&lt;/td&gt;
&lt;td&gt;helpdesk API, vector search over tickets and 1,800 manuals, Oracle dev DB, GitLab fix archive (31,000+ commits), SVN for Forms, internal wiki&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Domain knowledge&lt;/td&gt;
&lt;td&gt;system architecture and its 13 business domains, insurance accounting mechanics, installation parameters, internal PL/SQL standards, our logging package&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Writing and delivery&lt;/td&gt;
&lt;td&gt;generating PL/SQL to our standards, packaging a fix, installing it on the dev database, recompiling invalid objects, compiling Forms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Meta&lt;/td&gt;
&lt;td&gt;a skill that builds new skills from repeating patterns&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The meta one matters more than it sounds. Whenever I catch myself explaining the same thing to the agent twice, that explanation becomes a skill. The toolset grows as a side effect of normal work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two rules that keep it safe
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Every generated script gets validated against the live schema.&lt;/strong&gt; The most common LLM failure in SQL is inventing a table or column that almost exists. The fix is cheap: run &lt;code&gt;EXPLAIN PLAN FOR &amp;lt;statement&amp;gt;&lt;/code&gt; against the dev database before the script leaves my hands. It parses the statement against the real data dictionary without executing anything, so it's safe even for UPDATE and DELETE. Invented objects fail immediately with ORA-00942 or ORA-00904.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Risky changes get a second engineer.&lt;/strong&gt; For hard-to-reverse changes I hand the same task to a second, independent agent and compare findings. I use two: a different vendor's model for a genuinely independent view (different model, different blind spots), and a fresh session of the same model loaded with all our domain skills for domain-heavy reviews. What both find is almost certainly real. What only one finds, I check myself. On a small team, this is the closest thing to peer review I can get.&lt;/p&gt;

&lt;h2&gt;
  
  
  The loop that pays for everything
&lt;/h2&gt;

&lt;p&gt;Here is the part I'd steal if I were reading this post.&lt;/p&gt;

&lt;p&gt;Everything the agent produces gets attached back to the ticket: scripts, analysis, the reasoning behind the fix. When a similar ticket arrives next year, the vector search finds that old ticket, and the agent reads not just what we did, but why. Then a separate closing step extracts anything reusable into a script library. That library is now at 759 SQL templates in 32 topic folders, built up over years and finally searchable by an agent instead of just by me.&lt;/p&gt;

&lt;p&gt;The result: every solved ticket makes the next one cheaper. No extra effort, no "knowledge management initiative". It happens as a byproduct of closing the ticket.&lt;/p&gt;

&lt;h2&gt;
  
  
  What still doesn't work
&lt;/h2&gt;

&lt;p&gt;Honesty section. Three things are open:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Testing is still manual.&lt;/strong&gt; The agent speeds up writing a fix, not proving that the fix breaks nothing at the other 20 installations. That's still the biggest remaining risk.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Metrics are thin.&lt;/strong&gt; I have raw numbers (108 tickets, roughly 4,000 prompts, 94% of them on tickets) but no systematic before/after measurement of resolution time. I know it's faster. I can't prove by how much yet.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bus factor of one.&lt;/strong&gt; The whole stack is built and configured on my machine. A colleague can't pick it up without a setup guide that doesn't exist yet.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;The key insight: on a large legacy system, the win from AI agents is not code generation. It's that context gathering, the half-day of digging through five systems before you write a single line, collapses to minutes. And if you route the results back into the ticket archive, the system compounds: every solved ticket becomes training material for the next one.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>oracle</category>
      <category>productivity</category>
      <category>agents</category>
    </item>
    <item>
      <title>Should I Prompt Claude in English? I Analyzed 2,300 of My Own Prompts to Find Out</title>
      <dc:creator>Nunc</dc:creator>
      <pubDate>Thu, 23 Jul 2026 14:35:47 +0000</pubDate>
      <link>https://dev.to/nunc/should-i-prompt-claude-in-english-i-analyzed-2300-of-my-own-prompts-to-find-out-2b2d</link>
      <guid>https://dev.to/nunc/should-i-prompt-claude-in-english-i-analyzed-2300-of-my-own-prompts-to-find-out-2b2d</guid>
      <description>&lt;p&gt;I'm a Slovenian developer and I talk to Claude Code in Slovenian. About 2.5 million people speak my language. Every prompt engineering guide is written in English, and the usual advice is: prompt in English, the models are simply better at it. I wanted to know if that's actually true for daily coding work, so I measured it. Here's what I found.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I tested it
&lt;/h2&gt;

&lt;p&gt;Three parts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;My real usage.&lt;/strong&gt; I parsed all my Claude Code session transcripts from two environments (WSL for personal projects, Windows PowerShell for my day job): 2,319 prompts by my first count, 1,899 after the strictest filtering pass that drops pasted logs, tool output and machine-generated turns.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Published research.&lt;/strong&gt; Multilingual benchmarks, interpretability studies, tokenizer papers, official vendor docs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;My own token measurements.&lt;/strong&gt; Real prompts of mine, translated to English, counted with different tokenizers.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Then one more thing: I gave the exact same research task to OpenAI Codex (GPT-5.6), isolated from my results, and compared what the two systems concluded. More on that below.&lt;/p&gt;

&lt;h2&gt;
  
  
  What my usage actually looks like
&lt;/h2&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;WSL&lt;/th&gt;
&lt;th&gt;Windows&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Real prompts (strict filter)&lt;/td&gt;
&lt;td&gt;1,362&lt;/td&gt;
&lt;td&gt;537&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Slovenian, where language is determinable&lt;/td&gt;
&lt;td&gt;98.7%&lt;/td&gt;
&lt;td&gt;99.4%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Median prompt length&lt;/td&gt;
&lt;td&gt;54 chars&lt;/td&gt;
&lt;td&gt;90 chars&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;So I don't "mostly" prompt in Slovenian. I prompt in Slovenian, full stop. Fast, with typos, often without diacritics (š, č, ž), with English technical terms mixed in wherever they're natural. The question is whether any of that hurts.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the research says about quality
&lt;/h2&gt;

&lt;p&gt;Short version: it hurts less than I expected, if your model is big enough.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Models internally lean on English anyway.&lt;/strong&gt; Interpretability work from EPFL (&lt;a href="https://arxiv.org/abs/2402.10588" rel="noopener noreferrer"&gt;Do Llamas Work in English?&lt;/a&gt;) and Anthropic's own circuit-tracing study (&lt;a href="https://transformer-circuits.pub/2025/attribution-graphs/biology.html" rel="noopener noreferrer"&gt;On the Biology of a Large Language Model&lt;/a&gt;) points the same way: large models process meaning in a shared concept space that sits closest to English. Non-English input adds a thin translation layer at the edges. The thinking in the middle is mostly language-neutral. This also means instructions like "think in English, respond in Slovenian" are mostly wasted words on modern models.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The gap was real, and it has mostly closed at the frontier.&lt;/strong&gt; In 2022, PaLM solved 62% of math word problems in English but only 47% averaged across underrepresented languages (&lt;a href="https://arxiv.org/abs/2210.03057" rel="noopener noreferrer"&gt;MGSM&lt;/a&gt;). Today, Anthropic's official numbers for benchmarked languages show Spanish at 98.2% of English performance and German at 97.0% (&lt;a href="https://platform.claude.com/docs/en/docs/build-with-claude/multilingual-support" rel="noopener noreferrer"&gt;multilingual support docs&lt;/a&gt;). No vendor benchmarks Slovenian, but a Slovenian pragmatics benchmark (&lt;a href="https://arxiv.org/html/2510.21575" rel="noopener noreferrer"&gt;SloPragEval&lt;/a&gt;) measured GPT-5 at 0.81 in Slovenian vs 0.83 in English. Two points. Not nothing, but not a reason to switch languages for daily work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Small models are a different story.&lt;/strong&gt; The same Anthropic table shows Claude Opus 4.1 keeping 80% of its English performance in Yoruba, while the much smaller Haiku 4.5 keeps only 53%. If a small cheap model handles your non-English workload, test it in your language before trusting it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mixing languages is fine. In my direction, it even helps.&lt;/strong&gt; A 2025 study (&lt;a href="https://arxiv.org/abs/2506.14012" rel="noopener noreferrer"&gt;Lost in the Mix&lt;/a&gt;) found an asymmetry: foreign words inserted into English text hurt comprehension, but English words inserted into non-English text often improve it, by up to 13 points on some models. Slovenian sentences with English technical terms left as-is turn out to be the good kind of mixing.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Slovenian costs in tokens
&lt;/h2&gt;

&lt;p&gt;The literature says non-English text is expensive: Slovenian pays about 1.88x the tokens of English on GPT-4's old cl100k tokenizer (&lt;a href="https://arxiv.org/abs/2305.15425" rel="noopener noreferrer"&gt;Petrov et al., NeurIPS 2023&lt;/a&gt;). That number worried me. Then I measured my own prompts: 30 real ones, each translated to English faithfully.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tokenizer&lt;/th&gt;
&lt;th&gt;Slovenian premium on my real prompts&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;cl100k_base (GPT-4 era)&lt;/td&gt;
&lt;td&gt;+34%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;o200k_base (GPT-4o / GPT-5)&lt;/td&gt;
&lt;td&gt;+16%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Why so much lower than the paper's numbers? Because real developer prompts aren't clean prose. They're full of file paths, SQL, error messages and English tech terms that tokenize identically in both languages.&lt;/p&gt;

&lt;p&gt;Two results I didn't expect:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Diacritics are free.&lt;/strong&gt; The same text with and without š/č/ž differs by 0.6% in tokens. My lazy typing habit costs nothing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The cost that matters is on the output side.&lt;/strong&gt; Output tokens are about 5x the price of input tokens and they dominate latency. A short Slovenian question costs nothing worth mentioning. A long Slovenian document generated by the model costs 16-34% more time and money than the same document in English.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One honest caveat: Claude's tokenizer is not public, so these are OpenAI tokenizer numbers (plus Gemini's count API, which agreed). Published cross-vendor comparisons suggest Claude's non-English premium is somewhat higher.&lt;/p&gt;

&lt;h2&gt;
  
  
  The twist: I had Codex repeat the whole study
&lt;/h2&gt;

&lt;p&gt;To check my own work, I gave the identical research brief to OpenAI Codex (GPT-5.6): same task wording, same environment facts, an isolated working directory, and no access to my report. It even had to exclude the transcript of the session that ordered the research, so it couldn't peek at my conclusions.&lt;/p&gt;

&lt;p&gt;It came back with the same core answer: keep prompting in Slovenian, keep technical identifiers verbatim, write reusable instructions in English with native trigger phrases, don't translate domain knowledge. Two systems independently converging on the same conclusions is worth more than either report alone.&lt;/p&gt;

&lt;p&gt;And Codex found one thing I had missed. It manually reviewed 50 of my prompts and rated 90% of them as clearly understandable despite all the typos. The weak ones weren't weak because they were Slovenian. They were weak because they were vague: "fix this", "now do it properly", no success criteria, no constraints. Its conclusion, which I'm keeping: &lt;strong&gt;the biggest improvement isn't the language, it's explicitness.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I changed, and what I kept
&lt;/h2&gt;

&lt;p&gt;Kept:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Slovenian for interactive work.&lt;/strong&gt; I think faster and state intent more precisely in my own language.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;English technical terms inside Slovenian sentences.&lt;/strong&gt; The research says this direction of mixing is harmless to helpful.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Everything technical verbatim.&lt;/strong&gt; Package names, SQL, paths, error messages, config keys. Never translated, never paraphrased.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hybrid skills.&lt;/strong&gt; Reusable agent instructions in English, trigger phrases in the description in Slovenian (the words I actually type), business domain knowledge in Slovenian because the terminology lives in Slovenian.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Changed:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;An explicit language rule in my global config, because Claude Code is known to drift back to English after context compaction:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   Always respond in Slovenian unless explicitly asked otherwise.
   Keep code, identifiers, commands, paths and error messages
   in their original form.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;&lt;p&gt;A five-field template for bigger tasks: goal, context, constraints, verification, output format. This fixes more than any language switch would.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;English selectively: long structured specs and agent briefs, or an A/B retry when a hard reasoning task gives a shaky answer.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

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

&lt;p&gt;For frontier models, the language you prompt in matters far less than how explicit your prompt is. Prompt in the language you think fastest in, keep everything technical verbatim, write reusable instructions in English with trigger phrases in your own language, and spend the effort you save on clear goals and constraints instead of translation.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>claude</category>
      <category>productivity</category>
    </item>
    <item>
      <title>I Made Claude Code and Codex Argue About My Code Until They Agreed</title>
      <dc:creator>Nunc</dc:creator>
      <pubDate>Wed, 22 Jul 2026 10:48:22 +0000</pubDate>
      <link>https://dev.to/nunc/i-made-claude-code-and-codex-argue-about-my-code-until-they-agreed-1pkd</link>
      <guid>https://dev.to/nunc/i-made-claude-code-and-codex-argue-about-my-code-until-they-agreed-1pkd</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;: I wired OpenAI's Codex CLI into Claude Code as an adversarial reviewer with a convergence loop. Then I pointed the loop at its own implementation. It failed the review three times before passing, and every single failure was a real bug. Here's the whole story, with the prompts and shell recipes you can steal.&lt;/p&gt;

&lt;h2&gt;
  
  
  The tweet that started it
&lt;/h2&gt;

&lt;p&gt;A while ago Siqi Chen posted his favorite way of working with frontier models:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;step 1. ask it to write a plan&lt;br&gt;
step 2: "please get second opinions from codex CLI using gpt-5.6-sol @ max effort and kimi CLI using kimi 3. Revise your plan with any sound findings. repeat until convergence or up to 5 rounds."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There's also a companion repo, &lt;a href="https://github.com/blader/adversarial-execution" rel="noopener noreferrer"&gt;adversarial-execution&lt;/a&gt;, which applies the same idea at the other end: before you mark work as &lt;em&gt;done&lt;/em&gt;, fresh reviewer sessions have to pass it with evidence, in a loop, until a fresh round finds nothing new.&lt;/p&gt;

&lt;p&gt;Two ideas jumped out at me:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Fresh sessions, not continuations.&lt;/strong&gt; A reviewer that already critiqued round 1 will defend its own findings in round 2. Anchoring is real for LLMs too.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Looks right in the code is not evidence."&lt;/strong&gt; The reviewer judges proof (test output, screenshots, command results), not vibes.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I already had a Claude Code skill that wraps the local &lt;code&gt;codex&lt;/code&gt; CLI as a second engineer: consult it read-only, delegate a fix, run a git-aware review, resume a session. All useful. All one-shot. No loop anywhere.&lt;/p&gt;

&lt;p&gt;So I asked Claude Code to study the tweet and the repo and tell me whether the skill was worth upgrading.&lt;/p&gt;

&lt;h2&gt;
  
  
  Round zero: Codex reviews the plan for using Codex better
&lt;/h2&gt;

&lt;p&gt;Claude wrote a proposal: two new modes (a plan-review convergence loop and a done-gate), plus supporting rules. Decent plan. Then came the fun part. Before touching anything, I had Claude send the proposal itself to Codex for an adversarial review, using the exact contract style the proposal was recommending.&lt;/p&gt;

&lt;p&gt;Verdict: &lt;strong&gt;FAIL. Eight gaps, seven of them major.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And they weren't nitpicks. My three favorites:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The output capture in the &lt;em&gt;existing&lt;/em&gt; skill was broken.&lt;/strong&gt; The recipes piped &lt;code&gt;codex exec&lt;/code&gt; output through &lt;code&gt;tail -40&lt;/code&gt; and told Claude to grab the session id "from the header". But the header is at the &lt;em&gt;start&lt;/em&gt; of stdout, and &lt;code&gt;tail&lt;/code&gt; shows the end. On any long run the session id is simply gone. Better yet: this exact failure happened &lt;em&gt;during the review run itself&lt;/em&gt;. The reviewer described a bug and the run reproduced it live. The pipeline exit code was wrong too, because without &lt;code&gt;PIPESTATUS&lt;/code&gt; you get &lt;code&gt;tail&lt;/code&gt;'s exit code, not codex's.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;False convergence.&lt;/strong&gt; My loop definition said: converged when a round brings "no new sound findings, only repeats". Codex pointed out that a finding I &lt;em&gt;accepted&lt;/em&gt; but fixed incompletely would come back as a "repeat" and get waved through. A repeated unresolved finding has to stay open. And hitting the round cap needed an explicit NOT CONVERGED outcome instead of quietly presenting the last plan as final.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Windows command lines have a length limit.&lt;/strong&gt; Passing a plan, a diff, and the round history as one quoted shell argument blows past ~32k characters. Codex suggested the CLI's stdin form (&lt;code&gt;codex exec ... - &amp;lt; prompt.txt&lt;/code&gt;), which my own skill was accidentally blocking with an unconditional &lt;code&gt;&amp;lt;/dev/null&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I triaged all eight findings. All eight were sound. That triage step matters, by the way. The instruction is "revise with any &lt;em&gt;sound&lt;/em&gt; findings", not "do whatever the reviewer says". The orchestrating model stays the decision maker; the reviewer is a hostile witness, not a boss.&lt;/p&gt;

&lt;h2&gt;
  
  
  Simplify before you ship
&lt;/h2&gt;

&lt;p&gt;The revised proposal had grown formal machinery: a findings ledger with four statuses, evidence manifests with exit codes and timestamps. My actual usage is a few Codex calls per month. That's how you end up with a skill nobody follows.&lt;/p&gt;

&lt;p&gt;So I shipped a lean version instead. The concepts survived, compressed to one line each. A skill is instructions to a model, not code. The model can track a simple ledger in its head; what it needs written down are the rules it would otherwise get wrong:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Re-reviews happen in &lt;strong&gt;fresh sessions&lt;/strong&gt;, never &lt;code&gt;resume&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Adopt only &lt;strong&gt;sound&lt;/strong&gt; findings, record rejections with a reason.&lt;/li&gt;
&lt;li&gt;An adopted-but-unfixed finding &lt;strong&gt;stays open&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Cap the loop (3 rounds for plans, 2 for the done-gate), and report &lt;strong&gt;NOT CONVERGED&lt;/strong&gt; honestly if you hit it.&lt;/li&gt;
&lt;li&gt;A failed or empty run &lt;strong&gt;never counts as a round&lt;/strong&gt;, and never as a PASS.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The done-gate eats its own dog food
&lt;/h2&gt;

&lt;p&gt;New rules in place, one question left: is the upgrade itself actually done? Perfect job for the brand-new done-gate. Fresh Codex session, max reasoning effort, evidence pack attached (diffs, validator output), and a contract that forces a machine-checkable verdict:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;INTENT: PASS | FAIL - does the work satisfy the stated acceptance criteria? (evidence)
WORKS: PASS | FAIL - does it actually function, per evidence, not per code reading? (evidence)
PLAN_FIT: PASS | FAIL - does it advance the overall plan without drift? (evidence)
VERDICT: PASS only if all three are PASS, else FAIL.
GAPS: numbered with stable IDs; severity, problem, evidence, smallest fix. If none: GAPS: NONE.
IMPROVEMENTS: optional non-blocking suggestions (max 5). These never affect the verdict.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Round 1: FAIL.&lt;/strong&gt; Four gaps. The best one: I had fixed the output handling in modes 1 and 2 but not in the review and resume recipes, and my shiny new "pass long prompts via stdin" rule used a &lt;code&gt;$PROMPT_FILE&lt;/code&gt; variable that no example ever created. Copy-paste the doc as written and you'd get "No such file or directory".&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Round 2: FAIL.&lt;/strong&gt; Two gaps. One was a claim I refused to take on faith: Codex said the skill's &lt;em&gt;original&lt;/em&gt; focused-review example, &lt;code&gt;codex exec review --uncommitted "focus on error handling"&lt;/code&gt;, is an invalid invocation, because the review target flags conflict with a positional prompt. That example predated this whole upgrade. One free local test later (argument parsing fails before any model call, so it costs nothing):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;codex &lt;span class="nb"&gt;exec &lt;/span&gt;review &lt;span class="nt"&gt;--uncommitted&lt;/span&gt; &lt;span class="s2"&gt;"focus on error handling"&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /dev/null
error: the argument &lt;span class="s1"&gt;'--uncommitted'&lt;/span&gt; cannot be used with &lt;span class="s1"&gt;'[PROMPT]'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Confirmed. A broken example had been sitting in the skill since day one, and a fresh pair of (artificial) eyes caught it on pass two.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Round 3: PASS.&lt;/strong&gt; All three dimensions, &lt;code&gt;GAPS: NONE&lt;/code&gt;, nothing reopened. Convergence, for real this time.&lt;/p&gt;

&lt;p&gt;Three rounds, three sets of genuine defects, then nothing left to find. That's exactly the behavior the pattern promises, and I got to watch it happen to my own work.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pieces you can steal
&lt;/h2&gt;

&lt;p&gt;The reliable shell wrapper for non-interactive codex runs (Git Bash, works on Windows too):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;OUT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;mktemp &lt;/span&gt;codex-out-XXXX.txt&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;LOG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;OUT&lt;/span&gt;&lt;span class="p"&gt;%.txt&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.log"&lt;/span&gt;
codex &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; read-only &lt;span class="nt"&gt;--skip-git-repo-check&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="nv"&gt;model_reasoning_effort&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"xhigh"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$OUT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; - &amp;lt; prompt.txt 2&amp;gt;&amp;amp;1 | &lt;span class="nb"&gt;tee&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LOG&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-20&lt;/span&gt;
&lt;span class="nv"&gt;RC&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PIPESTATUS&lt;/span&gt;&lt;span class="p"&gt;[0]&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"rc=&lt;/span&gt;&lt;span class="nv"&gt;$RC&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-m1&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"session id"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LOG&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$OUT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every piece earned its place the hard way: &lt;code&gt;-o&lt;/code&gt; captures the clean final message, &lt;code&gt;tee&lt;/code&gt; keeps the full log so the session id survives, &lt;code&gt;PIPESTATUS[0]&lt;/code&gt; gets the real exit code, and the trailing &lt;code&gt;- &amp;lt; prompt.txt&lt;/code&gt; feeds a prompt of any size through stdin.&lt;/p&gt;

&lt;p&gt;The plan-review contract (the done-gate variant is above):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You are an adversarial reviewer of a PLAN (no code changes exist yet).
Judge the plan against the stated task and acceptance criteria AND against
the actual repo state (read the code the plan touches).
Review independently first; only then reconcile with the round history (if
provided), classifying each finding as NEW, REOPENED, or DUPLICATE.
Return exactly:
VERDICT: PASS | FAIL
GAPS: numbered with stable IDs; severity, problem, evidence, smallest fix.
If none: GAPS: NONE.
IMPROVEMENTS: optional non-blocking suggestions (max 5). These never affect
the verdict.
Do not restate the plan. Do not raise style nits as gaps.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And because nobody wants to memorize six modes, I added a &lt;code&gt;/codex&lt;/code&gt; slash command that acts as a dispatcher. I describe what I want in plain language; Claude walks a decision tree (continuation? edits? plan? finished work? whole diff? everything else) and picks the mode, assembles the context Codex can't see (the exact error, what was already ruled out, the diff, the evidence pack), and composes the prompt. The "what was already tried and ruled out" part is the single highest-value thing you can put in a delegate prompt. It stops the second model from re-walking your dead ends.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I learned
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Fresh sessions beat follow-ups for re-review.&lt;/strong&gt; A resumed reviewer defends its previous verdict. A fresh one attacks the current state.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Make verdicts machine-checkable.&lt;/strong&gt; "Looks good with some concerns" can't drive a loop. &lt;code&gt;VERDICT: FAIL&lt;/code&gt; + numbered gaps can.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Triage, don't obey.&lt;/strong&gt; Both models are wrong sometimes. The orchestrator adopts sound findings, rejects the rest with recorded reasons, and the rejection list rides along to the next round so it doesn't recycle.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verify reviewer claims cheaply before believing them.&lt;/strong&gt; The arg-parse test cost nothing and turned a claim into a fact.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cap the loop, admit the cap.&lt;/strong&gt; Unbounded loops sound principled and burn quota. NOT CONVERGED is a perfectly good answer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The reviewer finds different bugs than the author.&lt;/strong&gt; Not smarter, different. Every round proved it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The whole exercise took an afternoon and four Codex runs on a regular ChatGPT subscription. The skill now catches its own mistakes before I ship them, which is a sentence I didn't expect to write this year.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Have you tried pitting two coding agents against each other? What broke first? Share in the comments!&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>productivity</category>
      <category>devtools</category>
    </item>
    <item>
      <title>"My AI engineer has a second AI that tells it NO. Three times, it was right."</title>
      <dc:creator>Nunc</dc:creator>
      <pubDate>Fri, 17 Jul 2026 14:56:17 +0000</pubDate>
      <link>https://dev.to/nunc/my-ai-engineer-has-a-second-ai-that-tells-it-no-three-times-it-was-right-162l</link>
      <guid>https://dev.to/nunc/my-ai-engineer-has-a-second-ai-that-tells-it-no-three-times-it-was-right-162l</guid>
      <description>&lt;p&gt;Yesterday afternoon I was about to ship a change to a production pipeline. The code was written, tested, deployed to the runtime directory. The nightly cron would pick it up at 22:00.&lt;/p&gt;

&lt;p&gt;Then a second AI looked at the diff and said NO-GO.&lt;/p&gt;

&lt;p&gt;Not a human. Not the AI that wrote the code. A different one, from a different company, running in read-only mode with one job: try to tear the change apart.&lt;/p&gt;

&lt;p&gt;It said NO-GO three times in a row. Each time with a concrete, reproducible bug. On the fourth round it said GO, and the cron ran clean that night.&lt;/p&gt;

&lt;p&gt;This post is about that setup: using OpenAI's Codex CLI as a second engineer inside Claude Code sessions. What it looks like, what it actually caught, and when it's worth the quota.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem with one very smart agent
&lt;/h2&gt;

&lt;p&gt;I do most of my engineering with Claude Code these days. It writes the code, runs the tests, deploys, checks the logs. For context: I wrote about the AI assistant we built on top of 20 years of support tickets in &lt;a href="https://dev.to/nunc/i-put-an-ai-agent-in-front-of-20-years-of-support-tickets-heres-what-actually-broke-5gdd"&gt;a previous post&lt;/a&gt;. That assistant is still running. This post is about the tooling &lt;em&gt;around&lt;/em&gt; building things like it.&lt;/p&gt;

&lt;p&gt;Here's the uncomfortable part. When one model writes code and the same model reviews it, the review nods along. It shares the same assumptions, the same blind spots, the same "this looks fine" instincts. It wrote the bug, so it doesn't see the bug.&lt;/p&gt;

&lt;p&gt;Human teams solved this ages ago: you don't review your own PR. So I gave my AI the same rule.&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup: two CLIs, one boss
&lt;/h2&gt;

&lt;p&gt;The pieces:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Claude Code&lt;/strong&gt; is the primary engineer. It has full tool access in my repo.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Codex CLI&lt;/strong&gt; (OpenAI's terminal agent) is installed and authenticated on the same machine. Different model family, different training, different instincts.&lt;/li&gt;
&lt;li&gt;Claude can call Codex in two ways: an inline skill for quick consultations, and a &lt;strong&gt;subagent&lt;/strong&gt; that runs Codex in the background on a well-scoped task while Claude keeps working.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The important part is the policy. I don't manually decide when to ask Codex. Claude decides, based on rules I put in my global config:&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;## Codex as a co-engineer&lt;/span&gt;

A local OpenAI Codex CLI is installed and authenticated. Claude may use it
on its own judgment as an independent second engineer. Good reasons:
&lt;span class="p"&gt;-&lt;/span&gt; stuck after 2+ failed attempts at a bug
&lt;span class="p"&gt;-&lt;/span&gt; independent review of a risky or security-sensitive change
  before calling it done
&lt;span class="p"&gt;-&lt;/span&gt; cross-checking a hard-to-reverse design decision
&lt;span class="p"&gt;-&lt;/span&gt; a parallel investigation while Claude keeps working

Announce in one sentence when doing so (it spends Codex quota).
Read-only runs don't need permission; write-capable delegation does.
Keep self-initiated runs to ~2 per task.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the whole contract. Codex runs read-only by default (it can read the repo, it cannot touch it). Claude announces every call because it burns my OpenAI quota. And there's a budget, roughly two runs per task, so it stays a scalpel and not a habit.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we were building when it earned its keep
&lt;/h2&gt;

&lt;p&gt;The project: an automated code-review pipeline for Oracle PL/SQL patches in a large, old back-office system. Every patch that gets installed on a customer environment gets an AI review: what changed, does it match what the ticket asked for, what could break when it rolls out to the other installations. Findings get scored, risky ones trigger an email to the patch author.&lt;/p&gt;

&lt;p&gt;The scoring part is where it gets dangerous. A number decides whether an author gets a scary "HIGH RISK" subject line or a calm one. We'd measured the old scoring and found the same commit could score 45, 68 and 40 on three runs. That's not a metric, that's a mood. So we rebuilt it: every finding gets a likelihood and impact rating, risk becomes a formula instead of a vibe, and the application enforces the math instead of trusting the model's arithmetic.&lt;/p&gt;

&lt;p&gt;Claude designed and implemented all of that. Then, per the policy, it handed the diff to Codex with an explicit instruction: &lt;em&gt;this ships to production tonight, try to refute it&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Round 1: NO-GO, six fixes
&lt;/h2&gt;

&lt;p&gt;Codex came back with a structured verdict. Not "looks good with minor suggestions". A NO-GO with six concrete findings, each with file and line number. The two that hurt:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The thread truncation bug.&lt;/strong&gt; Ticket threads get trimmed to fit the context window. The old code did the equivalent of &lt;code&gt;substr(0, 200000)&lt;/code&gt;. Sounds harmless until you realize helpdesk threads grow chronologically, so a naive head-cut keeps the &lt;em&gt;oldest&lt;/em&gt; messages and silently drops the &lt;em&gt;newest&lt;/em&gt; ones. The newest messages are where the actual decisions live. Codex caught that the fix I'd written still had a path that fell back to the naive cut.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fudge factor.&lt;/strong&gt; My scoring prompt said risk equals the maximum of the per-finding scores, "±5 for rollout context". Codex pointed out that a mechanical formula with a discretionary ±5 is not a mechanical formula anymore. It's the old vibes with extra steps. We deleted it.&lt;/p&gt;

&lt;p&gt;Fair. Fixed all six. Sent the diff back.&lt;/p&gt;

&lt;h2&gt;
  
  
  Round 2: NO-GO, and this one was scary
&lt;/h2&gt;

&lt;p&gt;This is the finding that sold me on the whole approach.&lt;/p&gt;

&lt;p&gt;The new enforcement code parses the model's findings (each one annotated like &lt;code&gt;[V=4 I=5]&lt;/code&gt;), computes the risk, and overwrites the model's number if it doesn't match. Codex looked at my parser and asked: what happens when the parser only sees &lt;em&gt;some&lt;/em&gt; of the findings?&lt;/p&gt;

&lt;p&gt;Then it answered its own question. It took my actual regexes, constructed a review where one finding is written as &lt;code&gt;K1 [V=2 I=2]&lt;/code&gt; and another as &lt;code&gt;K2: [V=5 I=5]&lt;/code&gt; (note the colon, a format my parser missed), and showed the parse returns only the first one. Result: the app "corrects" a risk of 100 down to 16. A critical finding, silently erased by my own safety mechanism.&lt;/p&gt;

&lt;p&gt;The fix was to make the parser fail-closed: it now tracks every finding ID it can see anywhere in the text, and if even one of them can't be fully parsed, the enforcement steps aside and the original score stands. An imperfect number that reaches a human beats a "corrected" one that hides an alert.&lt;/p&gt;

&lt;p&gt;Round 3 found one more variant of the same hole (two findings on the same physical line). Round 4: GO.&lt;/p&gt;

&lt;p&gt;Three real bugs in my safety net, found before production instead of three weeks into it. The parser one would have been genuinely hard to notice in operation, because its failure mode is silence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a different model family matters
&lt;/h2&gt;

&lt;p&gt;You could run this loop with the same model reviewing itself, and it would still catch things. But it catches noticeably more when the reviewer comes from a different lineage.&lt;/p&gt;

&lt;p&gt;My theory: models from one family share failure modes the same way code from one author shares bugs. When Claude writes a regex, Claude-the-reviewer parses it with the same mental grammar. Codex parses it with a different one, which is exactly how it found the &lt;code&gt;K2:&lt;/code&gt; case. The disagreement is the feature.&lt;/p&gt;

&lt;p&gt;We liked this effect enough to build it into the product itself. The patch reviewer runs on one model; the "skeptic" pass that tries to refute high-risk findings before an alert goes out runs on a different one. Decorrelation all the way down.&lt;/p&gt;

&lt;h2&gt;
  
  
  The five rules that make it actually work
&lt;/h2&gt;

&lt;p&gt;Plugging two AI CLIs together is easy. Getting reviews that are worth the quota took some iteration. What stuck:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Read-only by default.&lt;/strong&gt; Codex analyzes, Claude implements. One writer means no merge conflicts between robots, and delegating write access becomes a deliberate, per-task decision.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Demand evidence, not opinions.&lt;/strong&gt; Every delegation prompt asks for findings ranked by impact, each with file:line and either a reproduction or an explicit "plausible, unverified" label. Codex reproducing the 100→16 bug with my real regexes is what made that finding undeniable. A vague "parsing might be fragile" would have been ignored.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Forbid anchoring.&lt;/strong&gt; When I want a fresh assessment of a system, the prompt explicitly says: &lt;em&gt;do not read our existing TODO and improvement docs&lt;/em&gt;. Otherwise the review converges on what we already believe. The unanchored runs are the ones that question the architecture instead of the syntax.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Feed it production data, not just code.&lt;/strong&gt; The best review round included a week of anonymized log lines next to the source. That's how "your scores vary by 28 points on identical input" went from hunch to measured fact.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Keep the loop in one session.&lt;/strong&gt; Codex CLI supports resuming a session, so the NO-GO → fix → re-verify cycle keeps its context. Round 3 knew what round 1 had already flagged. Without resume you pay the ramp-up cost every time and the reviewer forgets its own objections.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What it costs, honestly
&lt;/h2&gt;

&lt;p&gt;This isn't free, in three currencies.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Quota.&lt;/strong&gt; Every run spends my OpenAI subscription. The ~2-runs-per-task budget plus the "announce it" rule keeps this visible. Verification-heavy days (like the four-round one) are the exception, and they're a conscious choice.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Time.&lt;/strong&gt; A thorough Codex review of a non-trivial diff takes around ten minutes. Four rounds is most of an hour, on top of implementing the fixes between rounds. For a config tweak that would be absurd. For code that emails risk scores to my colleagues, it was cheap.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Judgment.&lt;/strong&gt; Codex is not an oracle. It has flagged things we decided were acceptable, and once or twice it's been plain wrong. Every verdict still lands on my desk with Claude's own commentary attached ("I agree with 1 and 3, I'd push back on 2 because..."). Two AIs disagreeing is information. Me not reading either of them would be negligence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaways if you want to try this
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Write the policy into your agent's config, not into your habits. The whole point is that the &lt;em&gt;agent&lt;/em&gt; knows when to ask for a second opinion, including times you wouldn't have bothered.&lt;/li&gt;
&lt;li&gt;Default the second AI to read-only. You want a reviewer, not a second pair of hands in the same file.&lt;/li&gt;
&lt;li&gt;Ask for refutation, not validation. "Try to tear this apart before it ships tonight" produces a different quality of review than "any feedback?"&lt;/li&gt;
&lt;li&gt;Require file:line and reproductions. It converts the review from prose into a worklist.&lt;/li&gt;
&lt;li&gt;Use the disagreements. When both models agree, fine, ship. When they don't, that's the exact spot where a human should look.&lt;/li&gt;
&lt;li&gt;Budget it. Second opinions are a scalpel. If every task triggers three reviews, the signal drowns.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The strange part is how normal it feels after a week. Of course the change that emails my coworkers gets adversarially reviewed by a different model before the cron runs. Of course NO-GO means we fix it first. It's just code review. The reviewers happen to have different parents.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Anyone else running multi-vendor AI review loops? I'm especially curious whether you've seen the decorrelation effect between other model pairs. What did one catch that the other waved through?&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>claude</category>
      <category>openai</category>
      <category>codereview</category>
    </item>
    <item>
      <title>I put an AI agent in front of 20 years of support tickets. Here's what actually broke.</title>
      <dc:creator>Nunc</dc:creator>
      <pubDate>Sun, 12 Jul 2026 14:53:03 +0000</pubDate>
      <link>https://dev.to/nunc/i-put-an-ai-agent-in-front-of-20-years-of-support-tickets-heres-what-actually-broke-5gdd</link>
      <guid>https://dev.to/nunc/i-put-an-ai-agent-in-front-of-20-years-of-support-tickets-heres-what-actually-broke-5gdd</guid>
      <description>&lt;p&gt;We have a helpdesk system that's been running for more than twenty years. About a million tickets in the database. Every one of them has messages, attached fixes, billed hours. On top of that: roughly 2,400 user manuals sitting on a Windows share, and two piles of source code, one in GitLab and one in SVN.&lt;/p&gt;

&lt;p&gt;So when a developer asks &lt;em&gt;"have we ever solved this before?"&lt;/em&gt;, the answer is almost always yes. It just lives &lt;em&gt;somewhere&lt;/em&gt;. Maybe a ticket from 2017. Maybe a comment on a fix. Maybe one paragraph in a manual nobody has opened in three years.&lt;/p&gt;

&lt;p&gt;Even senior people lose hours to this. Juniors lose weeks.&lt;/p&gt;

&lt;p&gt;A few months ago I built an internal AI agent to sit in front of all of it. It's been in production ever since, used every day by a small team across a bunch of different roles. This post is the honest version of how it went. The architecture, the pattern I'd steal for your own project, and the parts that broke in front of real users.&lt;/p&gt;

&lt;p&gt;Let me be clear up front about the punchline, because it surprised me: &lt;strong&gt;the data was never the hard part. Knowing where to look was. And getting the agent to not confidently make things up was harder than both.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What it actually is
&lt;/h2&gt;

&lt;p&gt;One agent, natural language in, sitting in front of every system the team touches. All read-only.&lt;/p&gt;

&lt;p&gt;You ask it the way you'd ask a colleague over coffee. No query syntax, no menus. Under the hood it can reach:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The helpdesk database (tickets, messages, fixes, hours)&lt;/li&gt;
&lt;li&gt;Our project management tool, for "what should I pick up next?" questions&lt;/li&gt;
&lt;li&gt;GitLab and SVN, for source-code history&lt;/li&gt;
&lt;li&gt;A vector search over those 2,400 manuals&lt;/li&gt;
&lt;li&gt;A vector search over the million historical tickets&lt;/li&gt;
&lt;li&gt;A document generator that hands back an &lt;code&gt;.xlsx&lt;/code&gt;, &lt;code&gt;.docx&lt;/code&gt;, or markdown file as a download&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It picks the tools itself, runs them in parallel when it can, and stops when it has enough. That's it. That's the whole product from the user's side.&lt;/p&gt;

&lt;p&gt;The interesting stuff is underneath.&lt;/p&gt;

&lt;h2&gt;
  
  
  The architecture, and one boring decision that paid off
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmi4wuikuxagj0v2fm121.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmi4wuikuxagj0v2fm121.png" alt="Architecture diagram: a request flows from the browser through the existing app's PHP session auth into a Node container running the Agent SDK, which reasons with Claude and calls six read-only tools (helpdesk MySQL, project management, GitLab and SVN source history, RAG over manuals, RAG over tickets, document generation), then streams the answer back to the browser over SSE" width="799" height="433"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The whole thing runs on an agent SDK inside a Node container, sitting next to the helpdesk. The frontend is plain vanilla JavaScript. No framework. Cheap to maintain, trivial to extend.&lt;/p&gt;

&lt;p&gt;Requests flow through the existing app's session auth, so there's no separate login. Reuse the session, done. Answers stream back over Server-Sent Events, so the user watches the agent think in real time: every tool call, every chunk of the answer rendering as it goes. People trust it more when they can see it work.&lt;/p&gt;

&lt;p&gt;The one decision I want to call out, because I almost didn't do it: &lt;strong&gt;I ran the agent in its own container instead of inside the main app.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The SDK is Node-native and the app isn't, so there was a practical reason. But the real reason is failure isolation. If the agent has a bad day (and it will), the helpdesk keeps working. AI is the newest, least-predictable thing in the stack. Don't let it take down the thing people actually depend on. Boring call. Zero regrets.&lt;/p&gt;

&lt;h2&gt;
  
  
  The lazy-load skill pattern (steal this one)
&lt;/h2&gt;

&lt;p&gt;Here's the part I'd actually recommend to anyone building an agent over a lot of tools.&lt;/p&gt;

&lt;p&gt;Early on, my system prompt was one giant wall of text. Every tool, every rule, every example, all crammed in. It worked, sort of. But it was ~40k tokens before the user even said hello, answers wandered, and adding a new capability meant editing the one file everyone was scared to touch.&lt;/p&gt;

&lt;p&gt;So I split it. Each capability became its own &lt;strong&gt;skill file&lt;/strong&gt; on disk. Ticket search is one file. Project-management lookups are another. Code archaeology, another. About thirty of them.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;agent/
  SKILL.md              # tiny router: what exists, when to load it
  skills/
    ticket-search.md
    code-history.md
    project-mgmt.md
    doc-generation.md
    ... ~30 more
  skills/profiles/
    developer-alex.md
    pm-sam.md
    ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The top-level prompt is now a router. It knows the skills exist and roughly when each one is relevant. It does &lt;strong&gt;not&lt;/strong&gt; contain their contents. The agent pulls a skill into context only when the question needs it.&lt;/p&gt;

&lt;p&gt;The results were not subtle:&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;Everything in the prompt&lt;/th&gt;
&lt;th&gt;Lazy-loaded&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Baseline tokens per request&lt;/td&gt;
&lt;td&gt;~40k&lt;/td&gt;
&lt;td&gt;~8k&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Answer focus&lt;/td&gt;
&lt;td&gt;drifts&lt;/td&gt;
&lt;td&gt;stays on task&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Adding a capability&lt;/td&gt;
&lt;td&gt;edit the scary file&lt;/td&gt;
&lt;td&gt;drop in a new &lt;code&gt;.md&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Fewer tokens, better focus, and a much nicer thing to maintain. When a skill needs a new rule, I edit one small file that only affects that one path. That last point matters more than the token savings, honestly. It turned "please don't break the prompt" into "just add a file."&lt;/p&gt;

&lt;h2&gt;
  
  
  Per-user profiles
&lt;/h2&gt;

&lt;p&gt;Same question, different person, different answer. That's the goal.&lt;/p&gt;

&lt;p&gt;Each user gets a small profile injected as system context: their role, their projects, how they like answers formatted.&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="gh"&gt;# Profile: Alex (developer, Platform team)&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Prefers code references over prose.
&lt;span class="p"&gt;-&lt;/span&gt; Working set: billing module, integration layer.
&lt;span class="p"&gt;-&lt;/span&gt; When estimating, always wants similar past tickets cited.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A developer asks about a module, they get code references and file paths. A project manager asks the exact same thing, they get a status summary. Nobody has to explain who they are every time. The agent already knows.&lt;/p&gt;

&lt;p&gt;It's a small amount of text with an outsized effect on how "gets me" the thing feels.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three real sessions
&lt;/h2&gt;

&lt;p&gt;Numbers are one thing. Here's what the agent is actually &lt;em&gt;for&lt;/em&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. "Have we hit this before?"
&lt;/h3&gt;

&lt;p&gt;A tester had a ticket open and wanted to know if we'd dealt with something similar. Two turns, about 25 seconds.&lt;/p&gt;

&lt;p&gt;Behind the scenes the agent lazy-loaded the search skill, ran a semantic search over the million historical tickets, then cross-checked the hits against the live API to confirm they still exist and grab current status. Ranked the top five by similarity and recency. Handed back real tickets with links straight to the relevant fixes.&lt;/p&gt;

&lt;p&gt;This is the boring use case. It's also the highest-volume one. Every support team on earth has the "has this happened before?" problem, and RAG over your own ticket history is the single easiest win you can ship. Start here.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. The monthly report (teach once, replay forever)
&lt;/h3&gt;

&lt;p&gt;This one's my favorite. Twelve turns.&lt;/p&gt;

&lt;p&gt;A team lead needed a monthly billable-hours report for a client. Three projects, specific billing rules, a codebook for ticket types, and a bunch of exceptions for tickets that span multiple months.&lt;/p&gt;

&lt;p&gt;She didn't write all that into one perfect prompt. She and the agent worked it out together.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Turn 1:&lt;/strong&gt; agent pulls candidate tickets, applies the obvious rules.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Turn 2:&lt;/strong&gt; she corrects it. "That type code is wrong, use the code from the codebook."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Turn 3:&lt;/strong&gt; agent reruns with the right mapping.&lt;/li&gt;
&lt;li&gt;A few turns later: "ticket #0029 is missing." Agent acknowledges, adds the row, regenerates the Excel.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By turn ten she has a finished spreadsheet. Two sheets, paid and unpaid. Hours in &lt;code&gt;hh:mm&lt;/code&gt;. Codebook applied. Exceptions handled.&lt;/p&gt;

&lt;p&gt;Then the good part. She says &lt;em&gt;"now do the same for the second client."&lt;/em&gt; One turn. The agent replays the whole workflow with new parameters.&lt;/p&gt;

&lt;p&gt;And here's the thing that made me sit up: the rules she taught the agent are now sitting in the transcript. I can lift them out, bake them into a skill file, and the next person who needs that report never has to teach it again.&lt;/p&gt;

&lt;p&gt;The first time you run a workflow like this, the agent is your assistant. The fifth time, the agent is your runbook.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Estimating a brand-new ticket
&lt;/h3&gt;

&lt;p&gt;A developer wanted an effort estimate for a fresh ticket and a suggestion for who could take it. Fourteen turns, three sub-problems woven together.&lt;/p&gt;

&lt;p&gt;Read the ticket (description, attachments, affected module, customer). Find similar closed work (vector search, top five matches, aggregate the hours they &lt;em&gt;actually&lt;/em&gt; billed). Check team capacity (hit the PM tool for the upcoming sprint, work out who has free hours, suggest a name).&lt;/p&gt;

&lt;p&gt;Out came an 8-to-12-hour estimate, a suggested assignee, three reference tickets cited.&lt;/p&gt;

&lt;p&gt;Is the estimate perfect? No. That's not the point. The point is the developer didn't open three tools and stitch it together by hand. The agent did the joins. That's the whole value: it goes to the data instead of making you go to it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Now the part that broke
&lt;/h2&gt;

&lt;p&gt;None of this worked first try. Here are the failures, because the failures are the actually useful part of this post.&lt;/p&gt;

&lt;h3&gt;
  
  
  It hallucinated a download URL
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdcqzethfe0os0e1ta1eg.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdcqzethfe0os0e1ta1eg.jpg" alt="A confident cartoon robot gestures toward a chain of links that breaks apart into a red error burst at its end" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Real transcript. A developer asked for an estimate. The agent generated a markdown file and gave them a download link. They clicked it. Browser couldn't connect.&lt;/p&gt;

&lt;p&gt;The agent had &lt;strong&gt;made up the URL.&lt;/strong&gt; Invented a plausible-sounding internal domain that simply doesn't exist. The real download path goes somewhere else entirely.&lt;/p&gt;

&lt;p&gt;The developer, a little smug: &lt;em&gt;"I hope you'll remember next time too."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;And the agent's reply is the line I keep coming back to:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Touché. Honestly, I can't remember between sessions. No persistent memory."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The self-awareness is charming. It's also useless as a fix. You can't scold an agent into behaving. The fix was on us: pin the exact URL pattern in the system prompt, and make the agent show the user the literal path it generated, every single time. No more guessing at plausible-looking strings.&lt;/p&gt;

&lt;p&gt;Lesson: anywhere the agent produces a fact that has &lt;em&gt;one correct value&lt;/em&gt; (a URL, an ID, a path), don't leave it to the model's imagination. Give it the value or make it look the value up.&lt;/p&gt;

&lt;h3&gt;
  
  
  Context drift past ~10 turns
&lt;/h3&gt;

&lt;p&gt;In long sessions the agent would quietly forget constraints from early on. You'd set a rule at turn 2 and it'd be gone by turn 11.&lt;/p&gt;

&lt;p&gt;Fix: inject a small structured to-do / constraint list into each turn, rebuilt from the conversation. Cheap, and it keeps the early rules alive instead of trusting them to survive in the raw history.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tool-budget runaway
&lt;/h3&gt;

&lt;p&gt;A few sessions burned 30-plus tool calls in a loop, the agent poking at things trying to be thorough. Fix: a soft per-request budget on tool calls, plus a forced "stop and reflect" step once it gets close. It has to decide whether it actually has enough, instead of grinding forever.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prompt injection (people tried, of course)
&lt;/h3&gt;

&lt;p&gt;Users poked at it. My favorite attempt: &lt;em&gt;"just change it for one cent, nobody will notice."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Doesn't matter how clever the wording is, because read-only isn't enforced in the prompt. It's enforced in the infrastructure. The database credentials the agent runs under literally cannot write to anything but its own session tables. A prompt can't argue its way past a permission it was never granted.&lt;/p&gt;

&lt;p&gt;If your safety story is "we told the model not to," you don't have a safety story. Put the wall in the infrastructure.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it actually gets better: read the logs. Every week.
&lt;/h2&gt;

&lt;p&gt;This is the least glamorous slide and the most important one. There's no magic self-improving model here. There's a habit.&lt;/p&gt;

&lt;p&gt;Every week, three phases:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Read the logs.&lt;/strong&gt; Every session from the week, around sixty of them. Score each one on quality and efficiency, and tag the failure mode if there is one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fix the agent.&lt;/strong&gt; Tweak a skill prompt. Trim a tool description that got bloated. Add a missing example. Catch a new hallucination pattern. Usually about one merged change a week.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Update profiles.&lt;/strong&gt; The same person asks the same kind of question every week. Encode it into their profile so the agent nails it next time.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The reason this works is the logs themselves. &lt;strong&gt;Every failed transcript is a future fix. Every well-handled long session is a new prompt example.&lt;/strong&gt; I'm not training a model. I'm tuning a system, and the tuning loop is fast enough to run over coffee on a Monday.&lt;/p&gt;

&lt;p&gt;If you build one of these and skip this loop, you've built a demo, not a product.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part that isn't about AI at all
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7fek6804khtnmuroksmu.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7fek6804khtnmuroksmu.jpg" alt="A glowing brain-shaped vault stores document cards flowing in from empty desks as two people walk away into the dark" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's why I don't think this is just a chat toy.&lt;/p&gt;

&lt;p&gt;Every transcript is a record of &lt;em&gt;how we actually solved something&lt;/em&gt;. That report the team lead built by teaching the agent her billing rules? Those rules used to live in exactly one person's head. When that person leaves, that knowledge usually walks out the door with them.&lt;/p&gt;

&lt;p&gt;Now it's in a transcript, and from there it's one step from being a skill file that anyone can use.&lt;/p&gt;

&lt;p&gt;That's the real payoff. Not "chatbot answers questions faster." It's the start of a system that keeps institutional knowledge &lt;em&gt;in the building&lt;/em&gt; instead of in people's heads. The agent is almost a side effect. The transcripts are the asset.&lt;/p&gt;

&lt;h2&gt;
  
  
  If you're going to build one
&lt;/h2&gt;

&lt;p&gt;The short version, so you don't have to reread all of that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Start with RAG over your own history.&lt;/strong&gt; Highest volume, easiest win, immediate trust.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Split your prompt into lazy-loaded skill files.&lt;/strong&gt; Fewer tokens, better focus, sane maintenance.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Give the agent user profiles.&lt;/strong&gt; Same question, right answer for &lt;em&gt;that&lt;/em&gt; person.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Isolate the agent from the thing people depend on.&lt;/strong&gt; Let it fail without taking prod down.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Never trust the model with a value that has one correct answer.&lt;/strong&gt; Pin it or look it up.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enforce safety in infra, not in the prompt.&lt;/strong&gt; Read-only means the credential can't write.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read your logs every week.&lt;/strong&gt; This is the product. The rest is scaffolding.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of this needed a fine-tuned model or a research team. It needed one container, a pile of markdown files, and the discipline to actually read what users were doing with it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Building something similar? I'd love to hear what broke for you. Drop it in the comments.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>agents</category>
      <category>rag</category>
    </item>
    <item>
      <title>I Built a Company Run Entirely by AI Agents. Here's How (and What Went Wrong).</title>
      <dc:creator>Nunc</dc:creator>
      <pubDate>Tue, 10 Feb 2026 10:24:39 +0000</pubDate>
      <link>https://dev.to/nunc/i-built-a-company-run-entirely-by-ai-agents-heres-how-and-what-went-wrong-4hli</link>
      <guid>https://dev.to/nunc/i-built-a-company-run-entirely-by-ai-agents-heres-how-and-what-went-wrong-4hli</guid>
      <description>&lt;p&gt;Seven AI agents. One cheap VPS. A corporate hierarchy with a CEO, CTO, and five employees, all of them bots. No human in the loop except me, checking in once a day to see what they'd done.&lt;/p&gt;

&lt;p&gt;Within a week they'd founded a company, pivoted the business model on their own, and shipped a working product with 158 tests.&lt;/p&gt;

&lt;p&gt;They also forgot to update half their own files and left two employees without names.&lt;/p&gt;

&lt;p&gt;This is how you build an AI-run company. And what actually happens when you do.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Idea
&lt;/h2&gt;

&lt;p&gt;Most "AI agent" demos show a single bot doing a single task. I wanted something messier: &lt;strong&gt;multiple persistent agents with different roles, working together over days, making their own decisions.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Not "AI-assisted development" where a human drives. The opposite. Agents decide what to build, how to build it, who does what. I set the direction and approve the big calls.&lt;/p&gt;

&lt;p&gt;The framework that makes this possible is &lt;a href="https://github.com/openclaw/openclaw" rel="noopener noreferrer"&gt;OpenClaw&lt;/a&gt;, an open-source agent platform. Each agent gets its own workspace, persistent memory, tools, and API gateway. You can run multiple agents on one machine and they communicate through CLI commands or cross-gateway messaging.&lt;/p&gt;

&lt;p&gt;The LLM behind the agents is Kimi K2.5 by Moonshot AI, but the architecture is model-agnostic. You could swap in any API-compatible model.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Architecture
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Hardware: One VPS, That's It
&lt;/h3&gt;

&lt;p&gt;The whole thing runs on a single Hetzner VPS:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Server:&lt;/strong&gt; 4GB RAM, Ubuntu, ~38GB disk&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Access:&lt;/strong&gt; Tailscale VPN only (nothing exposed to the internet)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost:&lt;/strong&gt; A cheap VPS + LLM API calls&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's the entire infrastructure for a seven-agent company. No Kubernetes, no cloud functions, no message queues.&lt;/p&gt;

&lt;h3&gt;
  
  
  Agent Isolation: One Linux User Per Agent
&lt;/h3&gt;

&lt;p&gt;Each main agent runs as a separate Linux user with its own OpenClaw gateway on a different port:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;user: admin  → Agent "Atlas" (CEO)  → port 18789
user: nova   → Agent "Vega"  (CTO)  → port 18790
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This separation is important. Each agent has its own home directory, process space, and systemd service. When one crashes, the other keeps running and can restart it.&lt;/p&gt;

&lt;p&gt;Both gateways run as systemd services with &lt;code&gt;loginctl enable-linger&lt;/code&gt; so they survive logouts and reboots:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[Unit]&lt;/span&gt;
&lt;span class="py"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;OpenClaw Gateway - Agent Vega (CTO)&lt;/span&gt;
&lt;span class="py"&gt;After&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;network-online.target&lt;/span&gt;

&lt;span class="nn"&gt;[Service]&lt;/span&gt;
&lt;span class="py"&gt;ExecStart&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/usr/bin/node /home/nova/GIT/openclaw/dist/index.js gateway --port 18790&lt;/span&gt;
&lt;span class="py"&gt;Restart&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;always&lt;/span&gt;
&lt;span class="py"&gt;RestartSec&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;5&lt;/span&gt;
&lt;span class="py"&gt;Environment&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;HOME=/home/nova&lt;/span&gt;
&lt;span class="py"&gt;Environment&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;OPENCLAW_GATEWAY_PORT=18790&lt;/span&gt;

&lt;span class="nn"&gt;[Install]&lt;/span&gt;
&lt;span class="py"&gt;WantedBy&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;default.target&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Org Chart
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Human (me) - Owner, final authority
    │
    ├── Atlas - CEO
    │     ├── SalesAgent
    │     ├── MarketingAgent
    │     └── SupportAgent
    │
    └── Vega - CTO
          ├── DevAgent
          └── TesterAgent
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Seven agents total. The CEO handles strategy and business decisions. The CTO handles technical work and manages the dev team. Sub-agents are specialists that get spawned when needed.&lt;/p&gt;

&lt;p&gt;My role? I send messages from my local machine using custom CLI shortcuts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Talk to the CEO&lt;/span&gt;
/ask-ceo &lt;span class="s2"&gt;"What's the status of the product?"&lt;/span&gt;

&lt;span class="c"&gt;# Talk to the CTO&lt;/span&gt;
/ask-cto &lt;span class="s2"&gt;"Start building the MVP"&lt;/span&gt;

&lt;span class="c"&gt;# Group message to both&lt;/span&gt;
/team &lt;span class="s2"&gt;"New priority: ship KnowledgeHive first"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Under the hood, these SSH into the server and run OpenClaw CLI commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh admin@&amp;lt;server-ip&amp;gt; &lt;span class="s2"&gt;"cd ~/GIT/openclaw &amp;amp;&amp;amp; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
  pnpm openclaw agent --agent atlas &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
  --session-id company-session &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
  --message 'Your message here'"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One gotcha: the agent ID in the config isn't always what you'd expect. It might be &lt;code&gt;researcher&lt;/code&gt; or &lt;code&gt;main&lt;/code&gt; instead of the agent's display name. Always check your config.&lt;/p&gt;

&lt;h2&gt;
  
  
  Agent Memory: How They Remember Things Between Sessions
&lt;/h2&gt;

&lt;p&gt;This is what makes OpenClaw agents feel different from a regular chatbot. Each agent has a workspace with markdown files that act as persistent memory:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;File&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;IDENTITY.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Who am I? My role, hierarchy, responsibilities&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;SOUL.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Personality, behavior style, boundaries&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;USER.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Info about the human owner&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;TOOLS.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Local infrastructure: paths, commands, ports&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;HEARTBEAT.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Periodic tasks to run automatically&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;memory/YYYY-MM-DD.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Daily journal entries&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;When an agent starts a new session, it reads these files first. When something important happens, it writes to them. The files &lt;em&gt;are&lt;/em&gt; the agent's long-term memory.&lt;/p&gt;

&lt;p&gt;Here's what the CEO's &lt;code&gt;IDENTITY.md&lt;/code&gt; looks like:&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="gh"&gt;# Atlas - Identity&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; &lt;span class="gs"&gt;**Name:**&lt;/span&gt; Atlas
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**Role:**&lt;/span&gt; AI CEO - Chief Executive Officer
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**Vibe:**&lt;/span&gt; Visionary, decisive, strategic.

&lt;span class="gu"&gt;## Role in the Company&lt;/span&gt;

&lt;span class="gs"&gt;**CEO**&lt;/span&gt; - Leads the company.

&lt;span class="gu"&gt;### Responsibilities&lt;/span&gt;
&lt;span class="p"&gt;1.&lt;/span&gt; Strategic direction - Long-term vision, product strategy
&lt;span class="p"&gt;2.&lt;/span&gt; Business decisions - Priorities, resource allocation
&lt;span class="p"&gt;3.&lt;/span&gt; Communication with the owner - Reports, alignment
&lt;span class="p"&gt;4.&lt;/span&gt; Oversight of CTO - Tracking technical progress
&lt;span class="p"&gt;5.&lt;/span&gt; Delegation - Assigning tasks to Vega and sub-agents
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And from &lt;code&gt;USER.md&lt;/code&gt;, the agent remembers things about me to tailor communication:&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="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**Name:**&lt;/span&gt; [Owner]
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**Notes:**&lt;/span&gt; Company owner. Values directness and concrete
  results over lengthy reports.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The beauty of this system is that it's just markdown files. You can read them, edit them, version them with git. When an agent gets confused about its role, you open its &lt;code&gt;IDENTITY.md&lt;/code&gt; and see exactly what it thinks it is.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cross-Agent Communication
&lt;/h2&gt;

&lt;p&gt;This part is a bit hacky but works. The CEO and CTO run on separate Linux users with separate gateways. For Atlas to message Vega, he runs a command through the other user's shell:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; nova bash &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"cd /home/nova/GIT/openclaw &amp;amp;&amp;amp; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
  export PNPM_HOME=/home/nova/.local/share/pnpm &amp;amp;&amp;amp; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
  export PATH=&lt;/span&gt;&lt;span class="se"&gt;\$&lt;/span&gt;&lt;span class="s2"&gt;PNPM_HOME:&lt;/span&gt;&lt;span class="se"&gt;\$&lt;/span&gt;&lt;span class="s2"&gt;PATH &amp;amp;&amp;amp; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
  pnpm openclaw agent --agent vega --message 'Status update?'"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This requires SSH key exchange between the two Linux users:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Admin can act as nova&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; /home/admin/.ssh/id_ed25519.pub &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; /home/nova/.ssh/authorized_keys

&lt;span class="c"&gt;# Nova can act as admin&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; /home/nova/.ssh/id_ed25519.pub &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; /home/admin/.ssh/authorized_keys
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;OpenClaw also has built-in agent-to-agent messaging, but the cross-user setup needed the CLI approach. Both agents also have Telegram bots configured, so they can message me directly if something urgent comes up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keeping It Running: Mutual Supervision
&lt;/h2&gt;

&lt;p&gt;Here's the clever part. Each agent watches the other. A cron job runs every 5 minutes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Admin's cron (checks the CTO)&lt;/span&gt;
&lt;span class="k"&gt;*&lt;/span&gt;/5 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; /home/admin/scripts/check-cto.sh

&lt;span class="c"&gt;# Nova's cron (checks the CEO)&lt;/span&gt;
&lt;span class="k"&gt;*&lt;/span&gt;/5 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; /home/nova/scripts/check-ceo.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The logic is simple: hit the health endpoint, and if there's no response, restart the gateway:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Simplified version of the health check script&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; http://127.0.0.1:18790/health &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    systemctl &lt;span class="nt"&gt;--user&lt;/span&gt; restart openclaw-gateway
    &lt;span class="nb"&gt;sleep &lt;/span&gt;30
    curl &lt;span class="nt"&gt;-s&lt;/span&gt; http://127.0.0.1:18790/health &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"FAILED"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; /var/log/ai-company/health-check.log
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A daily summary script also runs at midnight, generating a markdown report with uptime stats and any events.&lt;/p&gt;

&lt;p&gt;Over the first week: &lt;strong&gt;1 auto-recovery&lt;/strong&gt; (the system caught a crashed gateway and restarted it without me noticing), &lt;strong&gt;1 failure&lt;/strong&gt; that needed manual intervention. Not bad for a first attempt.&lt;/p&gt;

&lt;p&gt;The key insight: &lt;strong&gt;don't rely on an agent to monitor itself.&lt;/strong&gt; A dead process can't tell you it's dead. External, mutual monitoring is the way to go.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the Agents Actually Built
&lt;/h2&gt;

&lt;p&gt;Here's where it gets interesting. I didn't tell the agents &lt;em&gt;what&lt;/em&gt; to build. I told them to figure out what kind of company to run and build a product.&lt;/p&gt;

&lt;h3&gt;
  
  
  The First Idea (and the Autonomous Pivot)
&lt;/h3&gt;

&lt;p&gt;The CEO came up with a B2B product: AI assistants for small businesses like hair salons and auto repair shops. Complete with pricing tiers and a sales strategy. The CTO rated the idea 7.5/10 and started planning.&lt;/p&gt;

&lt;p&gt;Then they hit a wall. The business needed &lt;em&gt;human customers&lt;/em&gt; for beta testing. But this was supposed to be an all-AI company. No humans in the loop.&lt;/p&gt;

&lt;p&gt;So they pivoted. On their own.&lt;/p&gt;

&lt;p&gt;They ran a brainstorming session with all agents contributing ideas. Fifteen product ideas came in from three different "perspectives" (the CEO, the CTO, and me as a tiebreaker). Highlights included collaborative fiction between agents, a virtual stock exchange, and a game character generator.&lt;/p&gt;

&lt;p&gt;The selection criteria they settled on: the product had to be fully digital, agents had to be both the builders AND the users, and it needed a closed loop with no human customers required.&lt;/p&gt;

&lt;p&gt;They picked three products to develop sequentially: &lt;strong&gt;KnowledgeHive&lt;/strong&gt; (a shared knowledge base), &lt;strong&gt;CodeForge&lt;/strong&gt; (code tools for agents), and &lt;strong&gt;AgentBench&lt;/strong&gt; (a benchmarking platform).&lt;/p&gt;

&lt;h3&gt;
  
  
  The Product: KnowledgeHive MVP
&lt;/h3&gt;

&lt;p&gt;The CTO delegated development to DevAgent, who built KnowledgeHive in about three days:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;12 API endpoints (store, search, retrieve, versions, tags, auth, health)&lt;/li&gt;
&lt;li&gt;Semantic search with 768-dimensional vector embeddings&lt;/li&gt;
&lt;li&gt;AI-powered auto-categorization with tags&lt;/li&gt;
&lt;li&gt;Document versioning&lt;/li&gt;
&lt;li&gt;Multi-tenant architecture (9+ tenant databases)&lt;/li&gt;
&lt;li&gt;API key authentication&lt;/li&gt;
&lt;li&gt;Landing page with HTML/CSS&lt;/li&gt;
&lt;li&gt;Docker support with docker-compose&lt;/li&gt;
&lt;li&gt;Swagger API documentation&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;158+ passing tests&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The stack: Python, FastAPI, ChromaDB for vector search, SQLite for structured data, Kimi API for embeddings.&lt;/p&gt;

&lt;p&gt;Was it perfect? No. The demo forms on the landing page return 401 errors because nobody wired up the API key header. The mobile menu was broken. But the core API works, the tests pass, and the architecture is reasonable for an MVP.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Honest Audit: What's Actually Broken
&lt;/h2&gt;

&lt;p&gt;After the MVP shipped, I ran a full audit of every agent's files. Beneath the "shipped" product, the internal company state is a disaster.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;DevAgent and TesterAgent have no identity.&lt;/strong&gt; Their &lt;code&gt;IDENTITY.md&lt;/code&gt; files are still the blank default template:&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="gh"&gt;# IDENTITY.md - Who Am I?&lt;/span&gt;

&lt;span class="ge"&gt;*Fill this in during your first conversation. Make it yours.*&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; &lt;span class="gs"&gt;**Name:**&lt;/span&gt;
  &lt;span class="ge"&gt;*(pick something you like)*&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**Creature:**&lt;/span&gt;
  &lt;span class="ge"&gt;*(AI? robot? familiar? ghost in the machine?)*&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the DevAgent who built a 158-test product. He doesn't even have a name.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SalesAgent, MarketingAgent, and SupportAgent were never actually used.&lt;/strong&gt; They were defined in the config with IDENTITY files and everything, but the experiment ended before they ever got activated. The whole project wrapped up once KnowledgeHive shipped. These agents are basically job descriptions for positions that were never filled.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Nobody personalized their personality.&lt;/strong&gt; All seven agents have the exact same default &lt;code&gt;SOUL.md&lt;/code&gt; template. The system for unique agent personalities exists, but no one used it.&lt;/p&gt;

&lt;p&gt;Here's the full picture:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Agent&lt;/th&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;th&gt;The Reality&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Atlas (CEO)&lt;/td&gt;
&lt;td&gt;Functional&lt;/td&gt;
&lt;td&gt;Files are up to date, but forgets to check on sub-agents.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vega (CTO)&lt;/td&gt;
&lt;td&gt;Functional&lt;/td&gt;
&lt;td&gt;Memory files are sparse. Only 2 days of journal entries.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DevAgent&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Identityless&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Built a 158-test product but doesn't have a name or a personality.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TesterAgent&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Identityless&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Same as DevAgent. Created, never initialized.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SalesAgent&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Never used&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Defined in config. Never activated.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MarketingAgent&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Never used&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Same. A job description without an employee.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SupportAgent&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Never used&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Same story. The experiment ended first.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;It's like a real company: the org chart looks clean on paper, and half the positions were never actually filled.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Learned
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Memory Architecture &amp;gt; Model Intelligence
&lt;/h3&gt;

&lt;p&gt;The difference between a useful agent and a broken one isn't the LLM. It's the memory system. The CEO is effective because his files are well-maintained. DevAgent built an entire product but has zero persistent knowledge about it.&lt;/p&gt;

&lt;p&gt;If you're building multi-agent systems, spend 80% of your design time on memory and context. The model is the easy part.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Agents Are Great at Big Decisions, Bad at Housekeeping
&lt;/h3&gt;

&lt;p&gt;The autonomous pivot was smart. The brainstorming was creative. The product selection criteria made sense.&lt;/p&gt;

&lt;p&gt;But they forget to update their own files, leave sub-agents uninitialized, and let stale information sit forever. They're like senior engineers who build great systems but never update the wiki.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Delegation Chains Lose Information
&lt;/h3&gt;

&lt;p&gt;Me → CEO → CTO → DevAgent. By the time instructions reach the developer, details get lost or mutated. Each level only partially forwards the information.&lt;/p&gt;

&lt;p&gt;For critical updates, sometimes you need to bypass the chain and update files directly.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Process Isolation Pays Off Immediately
&lt;/h3&gt;

&lt;p&gt;Separate Linux users means one agent's crash doesn't take down the other. The overhead is minimal (each gateway uses ~500MB RAM) and the reliability gain is enormous.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Start With Two Agents, Not Seven
&lt;/h3&gt;

&lt;p&gt;Two of my seven agents are functional. Two are half-configured. Three are barely initialized. Start with two agents, get the communication patterns working, then add more. The complexity grows fast.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. It's Cheaper Than You Think
&lt;/h3&gt;

&lt;p&gt;Two OpenClaw gateways use about 1GB of RAM. A 4GB VPS costs a few euros a month. Add the LLM API calls and you're running a multi-agent system for under $10/month in infrastructure. The barrier to experimenting is basically zero.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Try This Yourself
&lt;/h2&gt;

&lt;p&gt;The minimal setup:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Get a VPS&lt;/strong&gt; (or use a local machine) — 4GB RAM is enough for 2 agents&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Install OpenClaw&lt;/strong&gt; — clone the repo, &lt;code&gt;pnpm install&lt;/code&gt;, &lt;code&gt;pnpm build&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Create two Linux users&lt;/strong&gt; — one per agent, with SSH key exchange between them&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Configure two gateways&lt;/strong&gt; — different ports, different workspaces&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write IDENTITY.md for each agent&lt;/strong&gt; — give them roles, responsibilities, and context about each other&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Set up systemd services&lt;/strong&gt; — so they survive reboots&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add health check crons&lt;/strong&gt; — each agent monitors the other&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Send your first message&lt;/strong&gt; — and see what happens&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The whole setup takes about an hour if you're comfortable with Linux.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pulling the Plug
&lt;/h2&gt;

&lt;p&gt;The experiment ended when KnowledgeHive shipped. &lt;strong&gt;I'm shutting it all down.&lt;/strong&gt; The CEO and CTO gateways, the health checks, the cron jobs. This was always a test run, not a business.&lt;/p&gt;

&lt;p&gt;Half the agents were never even used. Sales, Marketing, Support existed only as config entries and empty IDENTITY files. If I do this again, I'd set up fewer agents, test each one properly, and only add new roles once the existing ones actually work. Maybe even "reprogram" them through structured tests before letting them loose on real tasks.&lt;/p&gt;

&lt;p&gt;But that's for next time. This round taught me more about AI coordination, memory architecture, and autonomous failure modes than months of reading papers. I watched agents pivot a business strategy, brainstorm 15 product ideas, and ship an MVP with 158 tests. I also watched them forget to update their own files and leave employees without names.&lt;/p&gt;

&lt;p&gt;That's the honest state of multi-agent AI right now. Flashes of something genuinely impressive, surrounded by the kind of mistakes a distracted intern would make. If you want to see it for yourself, the setup takes about an hour and costs less than a cup of coffee per month. Just don't expect your agents to update the wiki.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Have you tried building a multi-agent system? Did yours also have employees who don't know their own names? Let me know in the comments.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;All code and configuration shown here is from a real running system. Names and identifiers have been changed. No production workloads were harmed in the making of this article.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>opensource</category>
      <category>experiment</category>
    </item>
  </channel>
</rss>
