<?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>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>
    <item>
      <title>Self-Hosting OpenClaw AI Assistant on a VPS with Tailscale VPN (Zero Public Ports)</title>
      <dc:creator>Nunc</dc:creator>
      <pubDate>Wed, 04 Feb 2026 11:38:31 +0000</pubDate>
      <link>https://dev.to/nunc/self-hosting-openclaw-ai-assistant-on-a-vps-with-tailscale-vpn-zero-public-ports-35fn</link>
      <guid>https://dev.to/nunc/self-hosting-openclaw-ai-assistant-on-a-vps-with-tailscale-vpn-zero-public-ports-35fn</guid>
      <description>&lt;p&gt;I wanted my own AI assistant — one that runs 24/7 on my server, remembers everything, and doesn't expose a single port to the internet. Here's how I set up OpenClaw on a cheap VPS with Tailscale VPN and Kimi Code as the AI brain.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Kimi Code Instead of Claude?
&lt;/h2&gt;

&lt;p&gt;If you've been following the AI tool scene, you probably know that OpenClaw was originally built around Anthropic's Claude models. So why not just use a Claude subscription?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Because Anthropic will ban you for it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In January 2026, Anthropic &lt;a href="https://venturebeat.com/technology/anthropic-cracks-down-on-unauthorized-claude-usage-by-third-party-harnesses" rel="noopener noreferrer"&gt;started enforcing their TOS&lt;/a&gt; against using Claude Pro/Max subscriptions through third-party tools. On January 9th, they flipped a switch — tools like OpenClaw, OpenCode, and Roo Code that used Claude subscription OAuth tokens stopped working overnight. Users got hit with: &lt;em&gt;"This credential is only authorized for use with Claude Code."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The crackdown targeted any tool "spoofing" the Claude Code client. DHH &lt;a href="https://news.ycombinator.com/item?id=46549823" rel="noopener noreferrer"&gt;called it&lt;/a&gt; "very customer hostile." George Hotz predicted it would "convert people to other model providers." And that's exactly what happened.&lt;/p&gt;

&lt;p&gt;You &lt;em&gt;can&lt;/em&gt; still use Claude via a proper API key (that's allowed under the TOS), but API pricing adds up fast — Claude Sonnet 4 runs $3/$15 per million tokens in/out. Heavy usage can easily cost $1,000+/month.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Kimi Code is the opposite approach.&lt;/strong&gt; Moonshot AI &lt;a href="https://www.kimi.com/code/docs/en/benefits.html" rel="noopener noreferrer"&gt;explicitly permits&lt;/a&gt; personal use of their API key in compatible third-party agents. Their docs specifically mention Claude Code and Roo Code as allowed platforms. For ~$19/month you get a subscription with a rolling weekly quota, the K2.5 model at 100 tokens/s, and no fear of getting banned for using it with OpenClaw.&lt;/p&gt;

&lt;h2&gt;
  
  
  What We're Building
&lt;/h2&gt;

&lt;p&gt;By the end of this guide you'll have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;OpenClaw&lt;/strong&gt; — an open-source personal AI assistant with a web dashboard, CLI, and optional messaging integrations (WhatsApp, Telegram, Discord, etc.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tailscale VPN&lt;/strong&gt; — zero-config WireGuard mesh that makes your VPS invisible to the internet&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kimi Code&lt;/strong&gt; — a subscription-based AI coding service from Moonshot AI with the powerful K2.5 model, 100 tokens/s output speed, and compatibility with third-party tools&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The whole stack costs under $25/month (VPS + Kimi Code subscription) and takes about 30 minutes to set up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A VPS (I used Hetzner CX22 — 4GB RAM, Ubuntu 24.04, ~€4/month)&lt;/li&gt;
&lt;li&gt;A local machine (Linux, macOS, or Windows with WSL)&lt;/li&gt;
&lt;li&gt;A Tailscale account (free at &lt;a href="https://tailscale.com" rel="noopener noreferrer"&gt;tailscale.com&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 1: Secure the VPS with Tailscale
&lt;/h2&gt;

&lt;p&gt;The goal is simple: &lt;strong&gt;no public ports, no attack surface&lt;/strong&gt;. Your VPS will only be reachable through your private Tailscale network.&lt;/p&gt;

&lt;h3&gt;
  
  
  Install Tailscale on the VPS
&lt;/h3&gt;

&lt;p&gt;SSH into your fresh VPS (this is the last time you'll use the public IP):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh root@YOUR_VPS_PUBLIC_IP
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Install Tailscale and authenticate:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://tailscale.com/install.sh | sh
tailscale up &lt;span class="nt"&gt;--ssh&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The &lt;code&gt;--ssh&lt;/code&gt; flag enables Tailscale SSH — a built-in SSH server that authenticates via your Tailscale identity. No keys, no passwords, no exposed port 22.&lt;/p&gt;
&lt;h3&gt;
  
  
  Create a non-root user
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;adduser admin
usermod &lt;span class="nt"&gt;-aG&lt;/span&gt; &lt;span class="nb"&gt;sudo &lt;/span&gt;admin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Lock down the firewall
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install UFW&lt;/span&gt;
apt &lt;span class="nb"&gt;install &lt;/span&gt;ufw &lt;span class="nt"&gt;-y&lt;/span&gt;

&lt;span class="c"&gt;# Default: deny everything&lt;/span&gt;
ufw default deny incoming
ufw default allow outgoing

&lt;span class="c"&gt;# Allow only Tailscale subnet&lt;/span&gt;
ufw allow &lt;span class="k"&gt;in &lt;/span&gt;on tailscale0
ufw allow &lt;span class="k"&gt;in &lt;/span&gt;from 100.64.0.0/10

&lt;span class="c"&gt;# Enable firewall&lt;/span&gt;
ufw &lt;span class="nb"&gt;enable&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Disable traditional SSH
&lt;/h3&gt;

&lt;p&gt;Since Tailscale SSH handles authentication, disable the regular SSH daemon:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;systemctl disable &lt;span class="nt"&gt;--now&lt;/span&gt; ssh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Verify the lockdown
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ufw status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;You should see only Tailscale traffic allowed. Your VPS now has &lt;strong&gt;zero public ports&lt;/strong&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Install Tailscale on your local machine
&lt;/h3&gt;

&lt;p&gt;On your local machine (Linux/WSL):&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://tailscale.com/install.sh | sh
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl start tailscaled
&lt;span class="nb"&gt;sudo &lt;/span&gt;tailscale up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Now connect to your VPS — no public IP needed:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tailscale ssh admin@YOUR_VPS_TAILSCALE_IP
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That's it. You're in via an encrypted WireGuard tunnel, and nobody on the internet can even see your server exists.&lt;/p&gt;
&lt;h3&gt;
  
  
  WSL Users: Fix the MTU Issue
&lt;/h3&gt;

&lt;p&gt;If you're running Tailscale in WSL and SSH connections hang (connect but never complete the handshake), you've hit a known MTU bug. The Tailscale interface defaults to MTU 1280, but WSL's network stack can't handle packets that large on the WireGuard tunnel.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Symptoms:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;tailscale ping&lt;/code&gt; works fine&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;tailscale ssh&lt;/code&gt; hangs indefinitely&lt;/li&gt;
&lt;li&gt;Verbose SSH (&lt;code&gt;ssh -vvv&lt;/code&gt;) stalls at &lt;code&gt;expecting SSH2_MSG_KEX_ECDH_REPLY&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt;&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;ip &lt;span class="nb"&gt;link set &lt;/span&gt;dev tailscale0 mtu 1200
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This is a temporary fix that resets on WSL restart. To make it permanent, add it to your shell profile:&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;echo&lt;/span&gt; &lt;span class="s1"&gt;'sudo ip link set dev tailscale0 mtu 1200 2&amp;gt;/dev/null'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; ~/.bashrc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Or add a sudoers rule so it doesn't prompt for a password:&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;# /etc/sudoers.d/tailscale-mtu&lt;/span&gt;
your_username &lt;span class="nv"&gt;ALL&lt;/span&gt;&lt;span class="o"&gt;=(&lt;/span&gt;ALL&lt;span class="o"&gt;)&lt;/span&gt; NOPASSWD: /usr/sbin/ip &lt;span class="nb"&gt;link set &lt;/span&gt;dev tailscale0 mtu 1200
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Step 2: Install OpenClaw
&lt;/h2&gt;

&lt;p&gt;OpenClaw (formerly Clawdbot, then Moltbot) is an open-source personal AI assistant created by Peter Steinberger. It has 68k+ stars on GitHub and supports dozens of messaging channels, tools, cron jobs, webhooks, and more.&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/openclaw" rel="noopener noreferrer"&gt;
        openclaw
      &lt;/a&gt; / &lt;a href="https://github.com/openclaw/openclaw" rel="noopener noreferrer"&gt;
        openclaw
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Your own personal AI assistant. Any OS. Any Platform. The lobster way. 🦞 
    &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;OpenClaw 🦞 — Your assistant, on your devices, in your chats&lt;/h1&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%2Fraw.githubusercontent.com%2Fopenclaw%2Fopenclaw%2Fmain%2Fdocs%2Fassets%2Fopenclaw-banner-dark.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%2Fraw.githubusercontent.com%2Fopenclaw%2Fopenclaw%2Fmain%2Fdocs%2Fassets%2Fopenclaw-banner-dark.png" alt="OpenClaw — EXFOLIATE! EXFOLIATE! Your personal AI assistant, running on your own devices."&gt;&lt;/a&gt;
  
&lt;/p&gt;

&lt;p&gt;
  &lt;a href="https://github.com/openclaw/openclaw/actions/workflows/ci.yml" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/f13833506d22ab17e357a456aa6b529c8c93e272cfd4db544334c963d795f62a/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f6f70656e636c61772f6f70656e636c61772f63692e796d6c3f6272616e63683d6d61696e267374796c653d666c61742d737175617265266c6162656c3d6369" alt="CI status"&gt;&lt;/a&gt;
  &lt;a href="https://www.npmjs.com/package/openclaw" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/05d5966b56a176864edeec1fee8b2e8e4738418723910c89ea5f92bc561b966a/68747470733a2f2f696d672e736869656c64732e696f2f6e706d2f762f6f70656e636c61773f7374796c653d666c61742d737175617265266c6162656c3d6e706d" alt="npm version"&gt;&lt;/a&gt;
  &lt;a href="https://nodejs.org" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/297d602fa7c5b1e8d793d8d73d54e1205d2e3361cd3ada13a43cba61d7765e04/68747470733a2f2f696d672e736869656c64732e696f2f6e6f64652f762f6f70656e636c61773f7374796c653d666c61742d737175617265" alt="Node.js version"&gt;&lt;/a&gt;
  &lt;a href="https://github.com/openclaw/openclaw/LICENSE" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/422db9fd40f5831c765cf6530b6750c081b696bd18d904cf89554df98c676277/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d677265656e3f7374796c653d666c61742d737175617265" alt="License: MIT"&gt;&lt;/a&gt;
  &lt;a href="https://discord.gg/clawd" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/4a7cf39d8e7bc01c86a2f81ae413f325b81c2aeeaf56418c18cd9e8020125204/68747470733a2f2f696d672e736869656c64732e696f2f646973636f72642f313435363335303036343036353930343836373f6c6162656c3d646973636f7264266c6f676f3d646973636f7264266c6f676f436f6c6f723d776869746526636f6c6f723d353836354632267374796c653d666c61742d737175617265" alt="Discord"&gt;&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;OpenClaw is a personal AI assistant that runs on your devices and meets you in the channels you already use. It is designed for a single operator and connects models, tools, messaging channels, and optional companion apps through one Gateway.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://openclaw.ai" rel="nofollow noopener noreferrer"&gt;Website&lt;/a&gt; · &lt;a href="https://docs.openclaw.ai" rel="nofollow noopener noreferrer"&gt;Docs&lt;/a&gt; · &lt;a href="https://docs.openclaw.ai/start/getting-started" rel="nofollow noopener noreferrer"&gt;Getting started&lt;/a&gt; · &lt;a href="https://docs.openclaw.ai/start/showcase" rel="nofollow noopener noreferrer"&gt;Showcase&lt;/a&gt; · &lt;a href="https://docs.openclaw.ai/help/faq" rel="nofollow noopener noreferrer"&gt;FAQ&lt;/a&gt; · &lt;a href="https://github.com/openclaw/openclaw/VISION.md" rel="noopener noreferrer"&gt;Vision&lt;/a&gt; · &lt;a href="https://deepwiki.com/openclaw/openclaw" rel="nofollow noopener noreferrer"&gt;DeepWiki&lt;/a&gt;&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Install&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;The installer supports macOS, Linux, and Windows. It provisions a supported Node.js runtime when needed.&lt;/p&gt;

&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; macOS / Linux / WSL2&lt;/span&gt;
curl -fsSL https://openclaw.ai/install.sh &lt;span class="pl-k"&gt;|&lt;/span&gt; bash&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="highlight highlight-source-powershell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; Windows PowerShell&lt;/span&gt;
iwr &lt;span class="pl-k"&gt;-&lt;/span&gt;useb https:&lt;span class="pl-k"&gt;//&lt;/span&gt;openclaw.ai&lt;span class="pl-k"&gt;/&lt;/span&gt;install.ps1 &lt;span class="pl-k"&gt;|&lt;/span&gt; iex&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Already manage Node.js? Install the published package instead (Node 22.22.3+, 24.15+, or 25.9+):&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;npm install -g openclaw@latest&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;See the &lt;a href="https://docs.openclaw.ai/install" rel="nofollow noopener noreferrer"&gt;installation guide&lt;/a&gt; for npm 12 lifecycle-script requirements, Docker, Nix, and other deployment paths.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Quick start&lt;/h2&gt;
&lt;/div&gt;

&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;openclaw onboard --install-daemon
openclaw gateway status
openclaw dashboard&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Onboarding verifies model…&lt;/p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/openclaw/openclaw" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;



&lt;h3&gt;
  
  
  Install Node.js 22+
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://deb.nodesource.com/setup_22.x | &lt;span class="nb"&gt;sudo&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; bash -
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; nodejs
node &lt;span class="nt"&gt;--version&lt;/span&gt;  &lt;span class="c"&gt;# Should be v22+&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Install pnpm
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://get.pnpm.io/install.sh | sh -
&lt;span class="nb"&gt;source&lt;/span&gt; ~/.bashrc
pnpm &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Clone and build
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; ~/GIT &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd&lt;/span&gt; ~/GIT
git clone https://github.com/openclaw/openclaw.git
&lt;span class="nb"&gt;cd &lt;/span&gt;openclaw

&lt;span class="c"&gt;# Install dependencies&lt;/span&gt;
pnpm &lt;span class="nb"&gt;install&lt;/span&gt;

&lt;span class="c"&gt;# Build the UI and TypeScript&lt;/span&gt;
pnpm ui:build
pnpm build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Run the onboard wizard
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm openclaw onboard &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--non-interactive&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--accept-risk&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--flow&lt;/span&gt; quickstart &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--mode&lt;/span&gt; &lt;span class="nb"&gt;local&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--skip-channels&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--skip-skills&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--install-daemon&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;~/.openclaw/openclaw.json&lt;/code&gt; — main configuration&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;~/.openclaw/workspace/&lt;/code&gt; — agent workspace&lt;/li&gt;
&lt;li&gt;A systemd user service for the gateway&lt;/li&gt;
&lt;li&gt;Gateway listening on &lt;code&gt;127.0.0.1:18789&lt;/code&gt; (loopback only — not exposed!)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Verify it's running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm openclaw gateway status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Gateway: bind=loopback (127.0.0.1), port=18789
Runtime: running (pid XXXXX)
RPC probe: ok
Dashboard: http://127.0.0.1:18789/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 3: Subscribe to Kimi Code and Get an API Key
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.kimi.com/code/en" rel="noopener noreferrer"&gt;Kimi Code&lt;/a&gt; is a subscription-based AI coding service by Moonshot AI. It powers the K2.5 model at up to 100 tokens/s and works with third-party tools like OpenClaw, Claude Code, and Roo Code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Subscribe
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;a href="https://www.kimi.com/code/en" rel="noopener noreferrer"&gt;kimi.com/code&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Log in or create an account&lt;/li&gt;
&lt;li&gt;Select a &lt;strong&gt;Coding Plan&lt;/strong&gt; (starts at ~$19/month for the Andante tier)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Your quota refreshes on a 7-day rolling cycle. Unused quota does not carry over.&lt;/p&gt;

&lt;h3&gt;
  
  
  Generate an API key
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Go to the &lt;a href="https://www.kimi.com/code/console" rel="noopener noreferrer"&gt;Kimi Code Console&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Navigate to &lt;strong&gt;API Keys&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Create New Key&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Copy the key immediately&lt;/strong&gt; — it's only shown once!&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Security note:&lt;/strong&gt; Treat your API key like a password. Never commit it to Git or expose it in client-side code. Kimi Code allows personal use in compatible third-party agents, but requests consume your subscription quota.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Step 4: Configure OpenClaw with Kimi Code
&lt;/h2&gt;

&lt;p&gt;Run the interactive config:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm openclaw config
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Select:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Model&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Moonshot AI&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Kimi Coding API key&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Paste your API key from the Kimi Code Console&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Or do it in one shot:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm openclaw onboard &lt;span class="nt"&gt;--kimi-code-api-key&lt;/span&gt; &lt;span class="s2"&gt;"YOUR_API_KEY"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Restart the gateway to apply:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm openclaw gateway restart
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Test it
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm openclaw agent &lt;span class="nt"&gt;--message&lt;/span&gt; &lt;span class="s2"&gt;"Hello! What model are you?"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should get a response from Kimi K2.5.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Access the Dashboard
&lt;/h2&gt;

&lt;p&gt;The dashboard runs on &lt;code&gt;localhost:18789&lt;/code&gt; on the VPS. Since we locked down all ports, we access it through an SSH tunnel.&lt;/p&gt;

&lt;p&gt;From your local machine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh &lt;span class="nt"&gt;-L&lt;/span&gt; 18789:localhost:18789 admin@YOUR_VPS_TAILSCALE_IP
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or with Tailscale SSH:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tailscale ssh &lt;span class="nt"&gt;-L&lt;/span&gt; 18789:localhost:18789 admin@YOUR_VPS_TAILSCALE_IP
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now open your browser:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://localhost:18789/?token=YOUR_GATEWAY_TOKEN
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll find the gateway token in &lt;code&gt;~/.openclaw/openclaw.json&lt;/code&gt; under &lt;code&gt;gateway.auth.token&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The dashboard gives you a web UI to chat with your AI, manage sessions, configure channels, and more.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6: Keep It Running
&lt;/h2&gt;

&lt;p&gt;The onboard wizard already installed a systemd user service. Make sure it survives reboots:&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;# Enable lingering so user services run without login&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;loginctl enable-linger admin

&lt;span class="c"&gt;# Check status&lt;/span&gt;
systemctl &lt;span class="nt"&gt;--user&lt;/span&gt; status openclaw-gateway
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Updating OpenClaw
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; ~/GIT/openclaw
git pull &lt;span class="nt"&gt;--rebase&lt;/span&gt; origin main
pnpm &lt;span class="nb"&gt;install
&lt;/span&gt;pnpm ui:build
pnpm build
pnpm openclaw gateway restart
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Architecture Overview
&lt;/h2&gt;

&lt;p&gt;Here's what the final setup looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────────┐        ┌──────────────────────────┐
│   Your Machine          │        │   VPS (Ubuntu 24.04)     │
│                         │        │                          │
│  Browser ──SSH tunnel──────────────→ OpenClaw Gateway :18789│
│                         │        │   └── Kimi K2.5 API      │
│  Terminal               │        │   └── Workspace           │
│   └── tailscale ssh ──────────────→ Shell access             │
│                         │        │                          │
│  Tailscale ◄──WireGuard──────────►  Tailscale               │
└─────────────────────────┘        └──────────────────────────┘
                                   Firewall: ALL public ports CLOSED
                                   Only Tailscale (100.64.0.0/10) allowed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No public IP exposure. No open ports. No password authentication. Just a WireGuard tunnel and your AI assistant waiting on the other side.&lt;/p&gt;

&lt;h2&gt;
  
  
  Security Checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[x] UFW firewall: deny all incoming except Tailscale&lt;/li&gt;
&lt;li&gt;[x] SSH: disabled (using Tailscale SSH instead)&lt;/li&gt;
&lt;li&gt;[x] Root login: disabled&lt;/li&gt;
&lt;li&gt;[x] Password auth: disabled&lt;/li&gt;
&lt;li&gt;[x] OpenClaw gateway: bound to loopback only&lt;/li&gt;
&lt;li&gt;[x] Dashboard access: via SSH tunnel only&lt;/li&gt;
&lt;li&gt;[x] API key: stored in server config, never exposed&lt;/li&gt;
&lt;li&gt;[x] Auto-updates: enabled on VPS&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Useful Commands
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Gateway management&lt;/span&gt;
pnpm openclaw gateway status
pnpm openclaw gateway restart
pnpm openclaw logs

&lt;span class="c"&gt;# Health check&lt;/span&gt;
pnpm openclaw doctor

&lt;span class="c"&gt;# Send a message&lt;/span&gt;
pnpm openclaw agent &lt;span class="nt"&gt;--message&lt;/span&gt; &lt;span class="s2"&gt;"Your message here"&lt;/span&gt;

&lt;span class="c"&gt;# List available models&lt;/span&gt;
pnpm openclaw models list

&lt;span class="c"&gt;# Reconfigure&lt;/span&gt;
pnpm openclaw config
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;For under $25/month (VPS + Kimi Code) you get a private AI assistant that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Runs 24/7 on your own hardware&lt;/li&gt;
&lt;li&gt;Is completely invisible to the internet&lt;/li&gt;
&lt;li&gt;Supports WhatsApp, Telegram, Slack, and dozens more channels&lt;/li&gt;
&lt;li&gt;Uses a powerful AI model (Kimi K2.5 via Kimi Code subscription)&lt;/li&gt;
&lt;li&gt;Remembers your conversations and preferences&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The hardest part was honestly debugging the WSL Tailscale MTU issue. Everything else was surprisingly smooth.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Resources:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/openclaw/openclaw" rel="noopener noreferrer"&gt;OpenClaw GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.openclaw.ai" rel="noopener noreferrer"&gt;OpenClaw Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tailscale.com" rel="noopener noreferrer"&gt;Tailscale&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.kimi.com/code/en" rel="noopener noreferrer"&gt;Kimi Code&lt;/a&gt; — subscribe and get your API key&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.kimi.com/code/console" rel="noopener noreferrer"&gt;Kimi Code Console&lt;/a&gt; — manage API keys and devices&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.kimi.com/code/docs/en/" rel="noopener noreferrer"&gt;Kimi Code Docs&lt;/a&gt; — membership benefits and usage details&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;Have you self-hosted an AI assistant? What's your setup? Let me know in the comments!&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>selfhosted</category>
      <category>security</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How to Install Moltbot with Docker and Gemini on WSL</title>
      <dc:creator>Nunc</dc:creator>
      <pubDate>Wed, 28 Jan 2026 11:37:09 +0000</pubDate>
      <link>https://dev.to/nunc/how-to-install-moltbot-with-docker-and-gemini-on-wsl-1aod</link>
      <guid>https://dev.to/nunc/how-to-install-moltbot-with-docker-and-gemini-on-wsl-1aod</guid>
      <description>&lt;p&gt;Ever wanted your own AI assistant that you can chat with via WhatsApp? Meet Moltbot - an open-source AI gateway that just went through a rebrand with an interesting backstory.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Name Change Story
&lt;/h2&gt;

&lt;p&gt;You might have heard of "Clawdbot" - a popular AI assistant project with a space lobster mascot. On January 27, 2026, Anthropic sent a trademark request because "Clawd" was too similar to their "Claude" trademark.&lt;/p&gt;

&lt;p&gt;Creator Peter Steinberger (@steipete) took it in stride:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Anthropic asked us to change our name (trademark stuff), and honestly? 'Molt' fits perfectly - it's what lobsters do to grow."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The mascot is now "Molty" and the project is "Moltbot". Sometimes forced changes lead to better names.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Gemini Instead of Claude?
&lt;/h2&gt;

&lt;p&gt;You might wonder: "I have a Claude Code subscription, why not use that?"&lt;/p&gt;

&lt;p&gt;Here's the problem: &lt;strong&gt;Anthropic actively blocks third-party tools from using Claude Code subscriptions&lt;/strong&gt;, and doing so violates their Terms of Service.&lt;/p&gt;

&lt;p&gt;In early January 2026, Anthropic &lt;a href="https://piunikaweb.com/2026/01/09/anthropic-reportedly-blocking-non-official-claude-api-connections/" rel="noopener noreferrer"&gt;cracked down on "harnesses"&lt;/a&gt; - third-party tools that use Claude Code OAuth to access consumer subscriptions. An Anthropic employee explained:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Restrictions target third-party harnesses spoofing the official client, which violate ToS by creating unusual traffic patterns without telemetry — complicating debugging, rate limits, and support."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The risks of using Claude Code subscription with Moltbot:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Risk&lt;/th&gt;
&lt;th&gt;Details&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Account ban&lt;/td&gt;
&lt;td&gt;Anthropic has banned users for TOS violations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sudden disconnection&lt;/td&gt;
&lt;td&gt;Your bot stops working without warning&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gray area legally&lt;/td&gt;
&lt;td&gt;Operating outside official support&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The error you'll see: &lt;em&gt;"This credential is only authorized for use with Claude Code and cannot be used for other API requests."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The safe alternatives:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Gemini CLI&lt;/strong&gt; (this guide) - Free tier, legitimate OAuth&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Anthropic API key&lt;/strong&gt; - Pay-as-you-go, officially supported&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Google Antigravity&lt;/strong&gt; - Access Claude via Google's infrastructure&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This guide uses Gemini because it's free, has generous limits, and Google actively supports third-party integrations.&lt;/p&gt;

&lt;h2&gt;
  
  
  What You'll Build
&lt;/h2&gt;

&lt;p&gt;By the end of this guide, you'll have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Moltbot running in Docker on WSL&lt;/li&gt;
&lt;li&gt;Google Gemini as your AI backend (free tier available)&lt;/li&gt;
&lt;li&gt;Optional WhatsApp integration to chat with your bot&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Windows with WSL 2 (Ubuntu 22.04)&lt;/li&gt;
&lt;li&gt;An internet connection&lt;/li&gt;
&lt;li&gt;About 30 minutes&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 1: Install Docker in WSL
&lt;/h2&gt;

&lt;p&gt;Open your WSL terminal and run:&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;# Update and install dependencies&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get update
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; ca-certificates curl gnupg

&lt;span class="c"&gt;# Add Docker's GPG key&lt;/span&gt;
&lt;span class="nb"&gt;sudo install&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt; 0755 &lt;span class="nt"&gt;-d&lt;/span&gt; /etc/apt/keyrings
curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://download.docker.com/linux/ubuntu/gpg | &lt;span class="nb"&gt;sudo &lt;/span&gt;gpg &lt;span class="nt"&gt;--dearmor&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /etc/apt/keyrings/docker.gpg
&lt;span class="nb"&gt;sudo chmod &lt;/span&gt;a+r /etc/apt/keyrings/docker.gpg

&lt;span class="c"&gt;# Add Docker repository&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"deb [arch=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;dpkg &lt;span class="nt"&gt;--print-architecture&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt; signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt; /etc/os-release &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$VERSION_CODENAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt; stable"&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; /etc/apt/sources.list.d/docker.list &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /dev/null

&lt;span class="c"&gt;# Install Docker Engine&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get update
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

&lt;span class="c"&gt;# Add yourself to docker group&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;usermod &lt;span class="nt"&gt;-aG&lt;/span&gt; docker &lt;span class="nv"&gt;$USER&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;service docker start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; Log out of WSL and back in for the group change to work:&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;exit&lt;/span&gt;
&lt;span class="c"&gt;# Reopen WSL, then verify:&lt;/span&gt;
docker run hello-world
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: Clone and Build Moltbot
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; ~/GIT/moltbot-project
&lt;span class="nb"&gt;cd&lt;/span&gt; ~/GIT/moltbot-project
git clone https://github.com/moltbot/moltbot.git
&lt;span class="nb"&gt;cd &lt;/span&gt;moltbot
./docker-setup.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;During setup, use these quick settings:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Prompt&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Security warning&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Onboarding mode&lt;/td&gt;
&lt;td&gt;QuickStart&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Model/auth provider&lt;/td&gt;
&lt;td&gt;Skip for now&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gateway bind&lt;/td&gt;
&lt;td&gt;lan&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gateway auth&lt;/td&gt;
&lt;td&gt;token&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Step 3: Add Gemini Support
&lt;/h2&gt;

&lt;p&gt;Create a custom Dockerfile that includes Gemini CLI:&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;cd&lt;/span&gt; ~/GIT/moltbot-project

&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; Dockerfile.custom &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
FROM moltbot:local
USER root
RUN npm install -g @google/gemini-cli
USER node
VOLUME /home/node/.gemini
&lt;/span&gt;&lt;span class="no"&gt;EOF

&lt;/span&gt;docker build &lt;span class="nt"&gt;-t&lt;/span&gt; moltbot-with-gemini &lt;span class="nt"&gt;-f&lt;/span&gt; Dockerfile.custom &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 4: Authenticate with Gemini
&lt;/h2&gt;

&lt;p&gt;Install and authenticate Gemini CLI in WSL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; @google/gemini-cli
gemini
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Follow the OAuth flow in your browser. This creates credentials in &lt;code&gt;~/.gemini/&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Wire It All Together
&lt;/h2&gt;

&lt;p&gt;Create a Docker Compose override to mount your Gemini credentials:&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;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; ~/GIT/moltbot-project/moltbot/docker-compose.override.yml &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
services:
  moltbot-gateway:
    image: moltbot-with-gemini
    environment:
      HOME: /home/node
    volumes:
      - ~/.gemini:/home/node/.gemini
      - ~/.clawdbot:/home/node/.moltbot
  moltbot-cli:
    image: moltbot-with-gemini
    environment:
      HOME: /home/node
    volumes:
      - ~/.gemini:/home/node/.gemini
      - ~/.clawdbot:/home/node/.moltbot
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Enable the Gemini plugin:&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;cd&lt;/span&gt; ~/GIT/moltbot-project/moltbot

&lt;span class="c"&gt;# Enable plugin&lt;/span&gt;
docker compose &lt;span class="nt"&gt;-f&lt;/span&gt; docker-compose.yml &lt;span class="nt"&gt;-f&lt;/span&gt; docker-compose.override.yml run &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="nt"&gt;--rm&lt;/span&gt; moltbot-cli plugins &lt;span class="nb"&gt;enable &lt;/span&gt;google-gemini-cli-auth

&lt;span class="c"&gt;# Authenticate inside container&lt;/span&gt;
docker compose &lt;span class="nt"&gt;-f&lt;/span&gt; docker-compose.yml &lt;span class="nt"&gt;-f&lt;/span&gt; docker-compose.override.yml run &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="nt"&gt;--rm&lt;/span&gt; moltbot-cli auth login &lt;span class="nt"&gt;--provider&lt;/span&gt; google-gemini-cli

&lt;span class="c"&gt;# Configure models&lt;/span&gt;
docker compose &lt;span class="nt"&gt;-f&lt;/span&gt; docker-compose.yml &lt;span class="nt"&gt;-f&lt;/span&gt; docker-compose.override.yml run &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="nt"&gt;--rm&lt;/span&gt; moltbot-cli configure
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 6: Start and Test
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Start the gateway&lt;/span&gt;
docker compose &lt;span class="nt"&gt;-f&lt;/span&gt; docker-compose.yml &lt;span class="nt"&gt;-f&lt;/span&gt; docker-compose.override.yml up &lt;span class="nt"&gt;-d&lt;/span&gt; moltbot-gateway

&lt;span class="c"&gt;# Test it&lt;/span&gt;
docker compose &lt;span class="nt"&gt;-f&lt;/span&gt; docker-compose.yml &lt;span class="nt"&gt;-f&lt;/span&gt; docker-compose.override.yml run &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="nt"&gt;--rm&lt;/span&gt; moltbot-cli agent &lt;span class="nt"&gt;--local&lt;/span&gt; &lt;span class="nt"&gt;--session-id&lt;/span&gt; &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Hello!"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should get a response from your AI.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bonus: Add WhatsApp
&lt;/h2&gt;

&lt;p&gt;WhatsApp is surprisingly easy - it uses QR code login like WhatsApp Web.&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;# Configure WhatsApp channel&lt;/span&gt;
docker compose &lt;span class="nt"&gt;-f&lt;/span&gt; docker-compose.yml &lt;span class="nt"&gt;-f&lt;/span&gt; docker-compose.override.yml run &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="nt"&gt;--rm&lt;/span&gt; moltbot-cli configure
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Select &lt;strong&gt;Channels&lt;/strong&gt; -&amp;gt; &lt;strong&gt;WhatsApp&lt;/strong&gt; and set:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Personal phone mode: Yes&lt;/li&gt;
&lt;li&gt;dmPolicy: allowlist&lt;/li&gt;
&lt;li&gt;allowFrom: Your phone number (e.g., +15551234567)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then link your phone:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose &lt;span class="nt"&gt;-f&lt;/span&gt; docker-compose.yml &lt;span class="nt"&gt;-f&lt;/span&gt; docker-compose.override.yml run &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="nt"&gt;--rm&lt;/span&gt; moltbot-cli channels login
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Scan the QR code with WhatsApp (Settings -&amp;gt; Linked Devices -&amp;gt; Link a Device). Now you can chat with your AI via WhatsApp self-messages.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick Reference Commands
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; ~/GIT/moltbot-project/moltbot

&lt;span class="c"&gt;# Start&lt;/span&gt;
docker compose &lt;span class="nt"&gt;-f&lt;/span&gt; docker-compose.yml &lt;span class="nt"&gt;-f&lt;/span&gt; docker-compose.override.yml up &lt;span class="nt"&gt;-d&lt;/span&gt; moltbot-gateway

&lt;span class="c"&gt;# Stop&lt;/span&gt;
docker compose &lt;span class="nt"&gt;-f&lt;/span&gt; docker-compose.yml &lt;span class="nt"&gt;-f&lt;/span&gt; docker-compose.override.yml down

&lt;span class="c"&gt;# View logs&lt;/span&gt;
docker compose &lt;span class="nt"&gt;-f&lt;/span&gt; docker-compose.yml &lt;span class="nt"&gt;-f&lt;/span&gt; docker-compose.override.yml logs &lt;span class="nt"&gt;-f&lt;/span&gt; moltbot-gateway

&lt;span class="c"&gt;# Open dashboard&lt;/span&gt;
docker compose &lt;span class="nt"&gt;-f&lt;/span&gt; docker-compose.yml &lt;span class="nt"&gt;-f&lt;/span&gt; docker-compose.override.yml run &lt;span class="nt"&gt;--rm&lt;/span&gt; moltbot-cli dashboard
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Troubleshooting
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Docker permission denied?&lt;/strong&gt;&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;usermod &lt;span class="nt"&gt;-aG&lt;/span&gt; docker &lt;span class="nv"&gt;$USER&lt;/span&gt;
&lt;span class="c"&gt;# Then log out and back in&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Gemini rate limits (429)?&lt;/strong&gt;&lt;br&gt;
The free tier has limits. Wait a bit or add an API key fallback from &lt;a href="https://aistudio.google.com/" rel="noopener noreferrer"&gt;Google AI Studio&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Config files owned by root?&lt;/strong&gt;&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 chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; &lt;span class="nv"&gt;$USER&lt;/span&gt;:&lt;span class="nv"&gt;$USER&lt;/span&gt; ~/.clawdbot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;Moltbot gives you a self-hosted AI gateway that can connect to various backends (Gemini, Claude via Antigravity, etc.) and channels (WhatsApp, Discord, Telegram). The Docker setup keeps everything contained and reproducible.&lt;/p&gt;

&lt;p&gt;The rebrand from Clawdbot to Moltbot is a good reminder that sometimes external pressure leads to better outcomes - "Molt" really does fit the lobster theme better.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Have you set up Moltbot or a similar AI assistant? Share your experience in the comments!&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>docker</category>
      <category>wsl</category>
      <category>ai</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How to Run Clawdbot on WSL with Your Claude Code Subscription</title>
      <dc:creator>Nunc</dc:creator>
      <pubDate>Mon, 26 Jan 2026 13:33:45 +0000</pubDate>
      <link>https://dev.to/nunc/how-to-run-clawdbot-on-wsl-with-your-claude-code-subscription-13i4</link>
      <guid>https://dev.to/nunc/how-to-run-clawdbot-on-wsl-with-your-claude-code-subscription-13i4</guid>
      <description>&lt;p&gt;I wanted to run Clawdbot—an AI chatbot framework—on Windows WSL, but there was a catch: I needed to use my existing Claude Code subscription instead of paying for separate API access. Here's how I got it working.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Authentication Challenge
&lt;/h2&gt;

&lt;p&gt;Clawdbot supports multiple authentication methods, but most require paid API access. If you have a Claude Code subscription, you can leverage its OAuth mechanism instead. The trick? Claude Code CLI must be &lt;strong&gt;inside&lt;/strong&gt; the Docker container.&lt;/p&gt;

&lt;p&gt;Here's why this setup is required:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Requirement&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Claude Code CLI in container&lt;/td&gt;
&lt;td&gt;Clawdbot uses Claude Code's OAuth mechanism&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mount &lt;code&gt;~/.claude&lt;/code&gt; credentials&lt;/td&gt;
&lt;td&gt;Where Claude Code stores authentication&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Set &lt;code&gt;HOME=/home/node&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;So Claude Code finds the credentials&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Other auth methods won't work with your subscription:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Anthropic API key requires paid API access&lt;/li&gt;
&lt;li&gt;Setup-token paste has incompatible format&lt;/li&gt;
&lt;li&gt;Claude Code CLI OAuth uses your subscription&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before starting, make sure you have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Windows with WSL 2 (Ubuntu 22.04)&lt;/li&gt;
&lt;li&gt;Claude Code CLI installed and authenticated in WSL&lt;/li&gt;
&lt;li&gt;Internet connection&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you haven't installed Claude Code yet:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; @anthropic-ai/claude-code &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; claude
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Step 1: Install Docker Engine in WSL
&lt;/h2&gt;

&lt;p&gt;First, set up Docker in your WSL environment:&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;# Update package index&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get update

&lt;span class="c"&gt;# Install required packages&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; ca-certificates curl gnupg

&lt;span class="c"&gt;# Add Docker's official GPG key&lt;/span&gt;
&lt;span class="nb"&gt;sudo install&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt; 0755 &lt;span class="nt"&gt;-d&lt;/span&gt; /etc/apt/keyrings
curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://download.docker.com/linux/ubuntu/gpg | &lt;span class="nb"&gt;sudo &lt;/span&gt;gpg &lt;span class="nt"&gt;--dearmor&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /etc/apt/keyrings/docker.gpg
&lt;span class="nb"&gt;sudo chmod &lt;/span&gt;a+r /etc/apt/keyrings/docker.gpg

&lt;span class="c"&gt;# Add Docker repository&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"deb [arch=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;dpkg &lt;span class="nt"&gt;--print-architecture&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt; signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt; /etc/os-release &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$VERSION_CODENAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt; stable"&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; /etc/apt/sources.list.d/docker.list &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /dev/null

&lt;span class="c"&gt;# Install Docker Engine&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get update
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

&lt;span class="c"&gt;# Add your user to docker group&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;usermod &lt;span class="nt"&gt;-aG&lt;/span&gt; docker &lt;span class="nv"&gt;$USER&lt;/span&gt;

&lt;span class="c"&gt;# Start Docker service&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;service docker start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Log out and back into WSL for the group change to take effect, then verify:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker &lt;span class="nt"&gt;--version&lt;/span&gt;
docker compose version
docker run hello-world
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Step 2: Clone and Build Clawdbot
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; ~/GIT/clawdbot-project
&lt;span class="nb"&gt;cd&lt;/span&gt; ~/GIT/clawdbot-project
git clone https://github.com/clawdbot/clawdbot.git
&lt;span class="nb"&gt;cd &lt;/span&gt;clawdbot
./docker-setup.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;During the onboarding prompts, use these settings:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Prompt&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Security warning&lt;/td&gt;
&lt;td&gt;Yes (continue)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Onboarding mode&lt;/td&gt;
&lt;td&gt;QuickStart&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Model/auth provider&lt;/td&gt;
&lt;td&gt;Skip for now&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gateway bind&lt;/td&gt;
&lt;td&gt;lan&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gateway auth&lt;/td&gt;
&lt;td&gt;token&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Skills&lt;/td&gt;
&lt;td&gt;Skip for now&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hooks&lt;/td&gt;
&lt;td&gt;Skip for now&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h2&gt;
  
  
  Step 3: Build Custom Image with Claude Code
&lt;/h2&gt;

&lt;p&gt;Here's the key part. Create a custom Dockerfile that includes Claude Code CLI:&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;cd&lt;/span&gt; ~/GIT/clawdbot-project

&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; Dockerfile.custom &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
FROM clawdbot:local

# Install Claude Code CLI
RUN npm install -g @anthropic-ai/claude-code

# Mount point for Claude credentials
VOLUME /home/node/.claude
&lt;/span&gt;&lt;span class="no"&gt;EOF

&lt;/span&gt;docker build &lt;span class="nt"&gt;-t&lt;/span&gt; clawdbot-with-claude &lt;span class="nt"&gt;-f&lt;/span&gt; Dockerfile.custom &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Step 4: Create Docker Compose Override
&lt;/h2&gt;

&lt;p&gt;Create an override file to use the custom image and mount your Claude credentials:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# ~/GIT/clawdbot-project/clawdbot/docker-compose.override.yml&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;clawdbot-gateway&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;clawdbot-with-claude&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;HOME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/home/node&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;~/.claude:/home/node/.claude&lt;/span&gt;
  &lt;span class="na"&gt;clawdbot-cli&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;clawdbot-with-claude&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;HOME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/home/node&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;~/.claude:/home/node/.claude&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Save this to &lt;code&gt;~/GIT/clawdbot-project/clawdbot/docker-compose.override.yml&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 5: Configure Dashboard Access
&lt;/h2&gt;

&lt;p&gt;Enable insecure auth for local dashboard access by editing &lt;code&gt;~/.clawdbot/clawdbot.json&lt;/code&gt;. Add this inside the &lt;code&gt;"gateway"&lt;/code&gt; section:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"controlUi"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"allowInsecureAuth"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Also change &lt;code&gt;"bind": "loopback"&lt;/code&gt; to &lt;code&gt;"bind": "lan"&lt;/code&gt; if you want to access it from your network.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 6: Start and Test
&lt;/h2&gt;

&lt;p&gt;Start 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="nb"&gt;cd&lt;/span&gt; ~/GIT/clawdbot-project/clawdbot
docker compose &lt;span class="nt"&gt;-f&lt;/span&gt; docker-compose.yml &lt;span class="nt"&gt;-f&lt;/span&gt; docker-compose.override.yml up &lt;span class="nt"&gt;-d&lt;/span&gt; clawdbot-gateway
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Test the agent:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose &lt;span class="nt"&gt;-f&lt;/span&gt; docker-compose.yml &lt;span class="nt"&gt;-f&lt;/span&gt; docker-compose.override.yml run &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="nt"&gt;--rm&lt;/span&gt; clawdbot-cli agent &lt;span class="nt"&gt;--local&lt;/span&gt; &lt;span class="nt"&gt;--session-id&lt;/span&gt; &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Hello!"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;You should get a response from Claude.&lt;/p&gt;
&lt;h2&gt;
  
  
  Accessing the Dashboard
&lt;/h2&gt;

&lt;p&gt;Get your gateway token:&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;grep &lt;/span&gt;CLAWDBOT_GATEWAY_TOKEN ~/GIT/clawdbot-project/clawdbot/.env
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Then open &lt;code&gt;http://127.0.0.1:18789/?token=YOUR_TOKEN_HERE&lt;/code&gt; in your browser.&lt;/p&gt;
&lt;h2&gt;
  
  
  Bonus: Adding WhatsApp
&lt;/h2&gt;

&lt;p&gt;WhatsApp is surprisingly easy to set up—it uses QR code login like WhatsApp Web.&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;cd&lt;/span&gt; ~/GIT/clawdbot-project/clawdbot
docker compose &lt;span class="nt"&gt;-f&lt;/span&gt; docker-compose.yml &lt;span class="nt"&gt;-f&lt;/span&gt; docker-compose.override.yml run &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="nt"&gt;--rm&lt;/span&gt; clawdbot-cli configure
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Select &lt;strong&gt;Channels&lt;/strong&gt; → &lt;strong&gt;WhatsApp&lt;/strong&gt; and configure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Personal phone mode: Yes&lt;/li&gt;
&lt;li&gt;dmPolicy: allowlist&lt;/li&gt;
&lt;li&gt;allowFrom: Your phone number (E.164 format like +1555123456)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then link your phone:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose &lt;span class="nt"&gt;-f&lt;/span&gt; docker-compose.yml &lt;span class="nt"&gt;-f&lt;/span&gt; docker-compose.override.yml run &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="nt"&gt;--rm&lt;/span&gt; clawdbot-cli channels login
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Scan the QR code with WhatsApp → Settings → Linked Devices → Link a Device.&lt;/p&gt;
&lt;h2&gt;
  
  
  Quick Reference Commands
&lt;/h2&gt;

&lt;p&gt;&lt;/p&gt;
  Click to expand common commands
  &lt;br&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Start gateway&lt;/span&gt;
docker compose &lt;span class="nt"&gt;-f&lt;/span&gt; docker-compose.yml &lt;span class="nt"&gt;-f&lt;/span&gt; docker-compose.override.yml up &lt;span class="nt"&gt;-d&lt;/span&gt; clawdbot-gateway

&lt;span class="c"&gt;# Stop gateway&lt;/span&gt;
docker compose &lt;span class="nt"&gt;-f&lt;/span&gt; docker-compose.yml &lt;span class="nt"&gt;-f&lt;/span&gt; docker-compose.override.yml down

&lt;span class="c"&gt;# View logs&lt;/span&gt;
docker compose &lt;span class="nt"&gt;-f&lt;/span&gt; docker-compose.yml &lt;span class="nt"&gt;-f&lt;/span&gt; docker-compose.override.yml logs &lt;span class="nt"&gt;-f&lt;/span&gt; clawdbot-gateway

&lt;span class="c"&gt;# Test agent&lt;/span&gt;
docker compose &lt;span class="nt"&gt;-f&lt;/span&gt; docker-compose.yml &lt;span class="nt"&gt;-f&lt;/span&gt; docker-compose.override.yml run &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="nt"&gt;--rm&lt;/span&gt; clawdbot-cli agent &lt;span class="nt"&gt;--local&lt;/span&gt; &lt;span class="nt"&gt;--session-id&lt;/span&gt; &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Hello"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




&lt;p&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Troubleshooting
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Docker permission denied:&lt;/strong&gt; Run &lt;code&gt;sudo usermod -aG docker $USER&lt;/code&gt; and log out/in.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Claude credentials not working:&lt;/strong&gt; Re-authenticate with &lt;code&gt;claude /login&lt;/code&gt; in WSL.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dashboard shows "pairing required":&lt;/strong&gt; Ensure &lt;code&gt;controlUi.allowInsecureAuth: true&lt;/code&gt; is in your config.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No response from agent:&lt;/strong&gt; Check that &lt;code&gt;HOME=/home/node&lt;/code&gt; is set and &lt;code&gt;~/.claude&lt;/code&gt; is mounted in your override file.&lt;/p&gt;
&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;The key insight here is that Claude Code's OAuth tokens can be shared with containerized applications by mounting the credentials directory. This pattern works for any Docker-based tool that supports Claude Code authentication.&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/openclaw" rel="noopener noreferrer"&gt;
        openclaw
      &lt;/a&gt; / &lt;a href="https://github.com/openclaw/openclaw" rel="noopener noreferrer"&gt;
        openclaw
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Your own personal AI assistant. Any OS. Any Platform. The lobster way. 🦞 
    &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;OpenClaw 🦞 — Your assistant, on your devices, in your chats&lt;/h1&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%2Fraw.githubusercontent.com%2Fopenclaw%2Fopenclaw%2Fmain%2Fdocs%2Fassets%2Fopenclaw-banner-dark.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%2Fraw.githubusercontent.com%2Fopenclaw%2Fopenclaw%2Fmain%2Fdocs%2Fassets%2Fopenclaw-banner-dark.png" alt="OpenClaw — EXFOLIATE! EXFOLIATE! Your personal AI assistant, running on your own devices."&gt;&lt;/a&gt;
  
&lt;/p&gt;

&lt;p&gt;
  &lt;a href="https://github.com/openclaw/openclaw/actions/workflows/ci.yml" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/f13833506d22ab17e357a456aa6b529c8c93e272cfd4db544334c963d795f62a/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f6f70656e636c61772f6f70656e636c61772f63692e796d6c3f6272616e63683d6d61696e267374796c653d666c61742d737175617265266c6162656c3d6369" alt="CI status"&gt;&lt;/a&gt;
  &lt;a href="https://www.npmjs.com/package/openclaw" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/05d5966b56a176864edeec1fee8b2e8e4738418723910c89ea5f92bc561b966a/68747470733a2f2f696d672e736869656c64732e696f2f6e706d2f762f6f70656e636c61773f7374796c653d666c61742d737175617265266c6162656c3d6e706d" alt="npm version"&gt;&lt;/a&gt;
  &lt;a href="https://nodejs.org" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/297d602fa7c5b1e8d793d8d73d54e1205d2e3361cd3ada13a43cba61d7765e04/68747470733a2f2f696d672e736869656c64732e696f2f6e6f64652f762f6f70656e636c61773f7374796c653d666c61742d737175617265" alt="Node.js version"&gt;&lt;/a&gt;
  &lt;a href="https://github.com/openclaw/openclaw/LICENSE" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/422db9fd40f5831c765cf6530b6750c081b696bd18d904cf89554df98c676277/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d677265656e3f7374796c653d666c61742d737175617265" alt="License: MIT"&gt;&lt;/a&gt;
  &lt;a href="https://discord.gg/clawd" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/4a7cf39d8e7bc01c86a2f81ae413f325b81c2aeeaf56418c18cd9e8020125204/68747470733a2f2f696d672e736869656c64732e696f2f646973636f72642f313435363335303036343036353930343836373f6c6162656c3d646973636f7264266c6f676f3d646973636f7264266c6f676f436f6c6f723d776869746526636f6c6f723d353836354632267374796c653d666c61742d737175617265" alt="Discord"&gt;&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;OpenClaw is a personal AI assistant that runs on your devices and meets you in the channels you already use. It is designed for a single operator and connects models, tools, messaging channels, and optional companion apps through one Gateway.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://openclaw.ai" rel="nofollow noopener noreferrer"&gt;Website&lt;/a&gt; · &lt;a href="https://docs.openclaw.ai" rel="nofollow noopener noreferrer"&gt;Docs&lt;/a&gt; · &lt;a href="https://docs.openclaw.ai/start/getting-started" rel="nofollow noopener noreferrer"&gt;Getting started&lt;/a&gt; · &lt;a href="https://docs.openclaw.ai/start/showcase" rel="nofollow noopener noreferrer"&gt;Showcase&lt;/a&gt; · &lt;a href="https://docs.openclaw.ai/help/faq" rel="nofollow noopener noreferrer"&gt;FAQ&lt;/a&gt; · &lt;a href="https://github.com/openclaw/openclaw/VISION.md" rel="noopener noreferrer"&gt;Vision&lt;/a&gt; · &lt;a href="https://deepwiki.com/openclaw/openclaw" rel="nofollow noopener noreferrer"&gt;DeepWiki&lt;/a&gt;&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Install&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;The installer supports macOS, Linux, and Windows. It provisions a supported Node.js runtime when needed.&lt;/p&gt;

&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; macOS / Linux / WSL2&lt;/span&gt;
curl -fsSL https://openclaw.ai/install.sh &lt;span class="pl-k"&gt;|&lt;/span&gt; bash&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="highlight highlight-source-powershell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; Windows PowerShell&lt;/span&gt;
iwr &lt;span class="pl-k"&gt;-&lt;/span&gt;useb https:&lt;span class="pl-k"&gt;//&lt;/span&gt;openclaw.ai&lt;span class="pl-k"&gt;/&lt;/span&gt;install.ps1 &lt;span class="pl-k"&gt;|&lt;/span&gt; iex&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Already manage Node.js? Install the published package instead (Node 22.22.3+, 24.15+, or 25.9+):&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;npm install -g openclaw@latest&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;See the &lt;a href="https://docs.openclaw.ai/install" rel="nofollow noopener noreferrer"&gt;installation guide&lt;/a&gt; for npm 12 lifecycle-script requirements, Docker, Nix, and other deployment paths.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Quick start&lt;/h2&gt;
&lt;/div&gt;

&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;openclaw onboard --install-daemon
openclaw gateway status
openclaw dashboard&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Onboarding verifies model…&lt;/p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/openclaw/openclaw" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;






&lt;p&gt;&lt;strong&gt;Have you tried running AI assistants in Docker? What authentication challenges did you face? Share in the comments!&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>docker</category>
      <category>wsl</category>
      <category>ai</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
