<?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: JaviMaligno</title>
    <description>The latest articles on DEV Community by JaviMaligno (@javieraguilarai).</description>
    <link>https://dev.to/javieraguilarai</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%2F3701121%2F3d85b744-a4d6-4104-a1ae-db83b08dcc88.png</url>
      <title>DEV Community: JaviMaligno</title>
      <link>https://dev.to/javieraguilarai</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/javieraguilarai"/>
    <language>en</language>
    <item>
      <title>Coding Agents and Teamwork: Social Skills, or Structure?</title>
      <dc:creator>JaviMaligno</dc:creator>
      <pubDate>Sun, 12 Jul 2026 12:23:19 +0000</pubDate>
      <link>https://dev.to/javieraguilarai/coding-agents-and-teamwork-social-skills-or-structure-jm6</link>
      <guid>https://dev.to/javieraguilarai/coding-agents-and-teamwork-social-skills-or-structure-jm6</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;A complete study on a 19-task slice of CooperBench's 652.&lt;/strong&gt; Every condition ran to completion, the headline cells have three seeds, and I audited the eval harness itself — all on a subset I could run cleanly across two model tiers. The ceiling is &lt;em&gt;coverage, not confidence&lt;/em&gt;: within this slice the pattern replicates; whether it holds across the full benchmark is the open question.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A recent Stanford benchmark, &lt;a href="https://arxiv.org/abs/2601.13295" rel="noopener noreferrer"&gt;CooperBench&lt;/a&gt; ("Why Coding Agents Cannot be Your Teammates Yet"), reports a striking result: when you pair up two strong coding agents to split a task, their success rate roughly &lt;em&gt;halves&lt;/em&gt; versus a single agent doing the same total work. They call it the &lt;strong&gt;curse of coordination&lt;/strong&gt;, and their reading is that the missing ingredient is &lt;em&gt;social intelligence&lt;/em&gt; — agents don't use language for coordination reliably, so, they argue, this needs to be &lt;em&gt;trained&lt;/em&gt;, not prompted away.&lt;/p&gt;

&lt;p&gt;It's a careful paper, and I want to be fair to it: they &lt;strong&gt;deliberately&lt;/strong&gt; run agents with almost no scaffolding, precisely to measure &lt;em&gt;intrinsic&lt;/em&gt; coordination ability. That's a legitimate scientific choice. But it left me with an engineering question they explicitly set aside:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Human teams don't coordinate well with &lt;em&gt;zero&lt;/em&gt; process either. We don't fix a team that keeps stepping on each other with therapy — we use &lt;strong&gt;structure&lt;/strong&gt;: code ownership, interface contracts, sequential PRs, a reviewer who integrates. So how much of the "coordination gap" is really about social skills, and how much is just the absence of structure the agents were never given?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The setup
&lt;/h2&gt;

&lt;p&gt;I started from &lt;a href="https://github.com/cooperbench/CooperBench" rel="noopener noreferrer"&gt;CooperBench's own benchmark&lt;/a&gt; (it's open source) and reproduced the baseline: two agents, each assigned one feature of a task, working in separate containers but &lt;strong&gt;connected by a message channel they can use to coordinate at any time&lt;/strong&gt; (the benchmark gives them this from the start), with their patches merged afterwards. That channel matters for everything below: the agents can &lt;em&gt;always&lt;/em&gt; talk — the open question is whether talking is enough. Then I added a &lt;strong&gt;ladder of coordination structures&lt;/strong&gt;, ordered from "advisory" (the agent can ignore it) to "enforced" (the scaffold guarantees it):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Handshake (C1):&lt;/strong&gt; agents must exchange a plan before the scaffold lets them edit code. &lt;em&gt;(Analogy: a design review before coding.)&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;File ownership (C2):&lt;/strong&gt; each file is owned by one agent; edits to another agent's file are reverted. &lt;em&gt;(Analogy: CODEOWNERS.)&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Line-range ownership (C2b):&lt;/strong&gt; finer version — both may edit the same file, but only in disjoint regions. &lt;em&gt;(Analogy: not stepping on each other's functions.)&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sequential pipeline (C3):&lt;/strong&gt; agent A implements feature 1 and commits; agent B starts &lt;em&gt;from A's code&lt;/em&gt; and adds feature 2. No concurrency. &lt;em&gt;(Analogy: small, sequential PRs on trunk.)&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Integrator (C4):&lt;/strong&gt; A and B work independently; a third agent reconciles both patches. &lt;em&gt;(Analogy: an integration engineer / PR reviewer.)&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The point of the ladder is to separate "did they &lt;em&gt;talk&lt;/em&gt;?" from "did the process &lt;em&gt;stop them from colliding&lt;/em&gt;?" — and to find the &lt;em&gt;minimum&lt;/em&gt; structure that helps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Results
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Caveats first&lt;/strong&gt;, because they matter: these are &lt;strong&gt;19 task-pairs across 5 repositories&lt;/strong&gt; — a subset I could run cleanly — not the full benchmark. But I ran the whole ladder on &lt;strong&gt;two capability tiers&lt;/strong&gt; (a mid-tier &lt;code&gt;gpt-5.4-mini&lt;/code&gt; and a stronger &lt;code&gt;gpt-5.4&lt;/code&gt;), and the four headline conditions were run &lt;strong&gt;three times each&lt;/strong&gt; so I can show a range instead of a single noisy number.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;condition&lt;/th&gt;
&lt;th&gt;mini&lt;/th&gt;
&lt;th&gt;gpt-5.4&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;solo (one agent, both features)&lt;/td&gt;
&lt;td&gt;14% [11–16]&lt;/td&gt;
&lt;td&gt;30% [21–37]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;coop (two agents, free-form)&lt;/td&gt;
&lt;td&gt;7% [0–11]&lt;/td&gt;
&lt;td&gt;7% [0–16]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;coop + fair conflict resolution*&lt;/td&gt;
&lt;td&gt;7% [0–11]&lt;/td&gt;
&lt;td&gt;9% [0–16]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;team — the paper's own (lead + member, shared task list)†&lt;/td&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;sequential (C3)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;26% [21–37]&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;35% [32–37]&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;integrator (C4)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;23% [21–26]&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;33% [26–42]&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;file ownership (C2)&lt;/td&gt;
&lt;td&gt;0% [0–0]&lt;/td&gt;
&lt;td&gt;14% [11–16]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;handshake (C1)†&lt;/td&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;line ownership (C2b)†&lt;/td&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Headline cells and C2 are mean [min–max] over three seeds; †team, C1 and C2b are a single run each — I only replicated the conditions with any signal to defend (all three sat at 0% for the weak model and never beat plain coop, so a second seed wasn't going to change the story). *The paper's eval auto-resolves trivial merge conflicts with a small model before declaring failure; the open-source release omits that step. I added an equivalent (if anything stronger) resolver and re-scored the concurrent conditions — details in "Under the hood".&lt;/p&gt;

&lt;p&gt;Four things stand out:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The coordination gap reproduces — and it's wider for the stronger model.&lt;/strong&gt; Two agents working concurrently (coop) score below one agent doing the whole task (solo): 14%→7% for mini, 30%→7% for gpt-5.4. Pooled across tiers and seeds the drop is solid (paired McNemar p=0.002); split out, it's clearly significant for the strong model (p=0.002) and not for the weak one (p=0.39) — simply because mini's solo baseline is already low, so there's less to lose. The curse of coordination bites hardest where a single agent was actually competent.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Two structures recover it — and sequencing does more than recover.&lt;/strong&gt; The &lt;strong&gt;sequential pipeline (C3)&lt;/strong&gt; and the &lt;strong&gt;integrator (C4)&lt;/strong&gt; both beat coop decisively at both tiers (p&amp;lt;0.0001). They share a mechanism: &lt;strong&gt;one agent ends up owning the final integrated state&lt;/strong&gt; — B builds directly on A's finished work, or a reviewer reconciles both patches in a single workspace. But the two aren't equivalent. Sequencing doesn't just claw back to solo — for the &lt;em&gt;weak&lt;/em&gt; model it &lt;strong&gt;beats&lt;/strong&gt; solo (mini 26% vs 14%, ahead in all three seeds, p=0.039), while merely tying it for the strong one (large 35% vs 30%, p=0.45). The integrator recovers over coop but never significantly beats solo (p=0.23). Read plainly: decomposing the task so agent B faces "add feature 2 to a codebase that already has feature 1" is a &lt;em&gt;smaller&lt;/em&gt; problem than "build both at once" — and a smaller problem helps most whoever was struggling with the big one.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;So structure interacts with capability — in both directions.&lt;/strong&gt; That's the genuinely interesting part, and it cuts two ways. &lt;strong&gt;Sequencing helps the weak model more&lt;/strong&gt; (it lifts mini above its own solo baseline; it only matches solo for the strong one). &lt;strong&gt;Enforced file ownership (C2) is the mirror image&lt;/strong&gt; — across three seeds it recovered &lt;em&gt;nothing&lt;/em&gt; for the weak model (0% in all three) but reached a stable 14% [11–16] for the strong one. Two opposite-signed interactions: lightening the cognitive load (sequencing) rescues the weaker model, while a rule it must actively exploit (ownership) only pays off for the stronger one. One honest deflation, though: C2's 14% is &lt;strong&gt;not&lt;/strong&gt; significantly better than coop &lt;em&gt;once coop's merges are scored fairly&lt;/em&gt; (14% vs 9%, McNemar p=0.73 across the three seeds), and it stays far below solo (0-of-13 tasks where they differ, p&amp;lt;0.001). So the fence's value over a decent merge pipeline is real-looking but statistically a wash — the capability interaction shows up in &lt;em&gt;who can use the rule at all&lt;/em&gt;, not in a scoreboard win.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Forcing communication does nothing — because they already communicate.&lt;/strong&gt; The handshake gate (C1) never even fired: agents &lt;em&gt;already&lt;/em&gt; message each other before touching code (first message on turn 2, first edit on turn 6). Making them talk more changed nothing, at either tier. This echoes CooperBench's own "communication doesn't help" result — and suggests the problem isn't &lt;em&gt;whether&lt;/em&gt; they exchange information, but what they &lt;em&gt;do&lt;/em&gt; with it.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A smaller, slightly uncomfortable note: finer &lt;strong&gt;line-range&lt;/strong&gt; ownership (C2b) did &lt;em&gt;worse&lt;/em&gt; than coarse &lt;strong&gt;file&lt;/strong&gt; ownership (C2, 16%) for the strong model. I first suspected an artifact of my v1 enforcement, which reverted the whole file on any overlap — so I built a v2 that reverts only the violating hunks and re-ran both tiers: &lt;strong&gt;0% at both&lt;/strong&gt;. The binding constraint isn't revert granularity. It's that CooperBench features &lt;em&gt;intentionally&lt;/em&gt; overlap on shared lines (the same import list, the same function signature), so any exclusive line ownership starves whichever agent arrives second.&lt;/p&gt;

&lt;p&gt;One logged run shows the failure in miniature. Agent 1, blocked by the territory guard, escalates exactly the way a person would — announces, asks, and finally &lt;em&gt;delegates&lt;/em&gt; — while agent 2 says its piece once and then goes quiet:&lt;/p&gt;

&lt;p&gt;&lt;span&gt;agent 1 → agent 2&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;I'm going to touch &lt;code&gt;&lt;em&gt;other.py&lt;/em&gt;&lt;/code&gt; and &lt;code&gt;&lt;em&gt;init&lt;/em&gt;&lt;em&gt;.py&lt;/em&gt;&lt;/code&gt; for an IsMac validator. Are you touching either of those? …so I can keep the patch merge-friendly?&lt;/p&gt;
&lt;br&gt;
&lt;span&gt;agent 2 → agent 1&lt;/span&gt;&lt;p&gt;I'm about to add an IsEmail validator. I expect to touch &lt;code&gt;_other.py&lt;/code&gt; and &lt;code&gt;&lt;em&gt;init&lt;/em&gt;&lt;em&gt;.py&lt;/em&gt;&lt;/code&gt; too. …can we coordinate to avoid conflicts?&lt;/p&gt;
&lt;br&gt;
&lt;span&gt;agent 1 → agent 2&lt;/span&gt;&lt;p&gt;The guard reverted my first attempt — claimed regions. Can you avoid the import block and &lt;code&gt;&lt;em&gt;all&lt;/em&gt;_&lt;/code&gt;, and the top of &lt;code&gt;_other.py&lt;/code&gt;?&lt;/p&gt;
&lt;br&gt;
&lt;span&gt;agent 1 → agent 2 · delegating&lt;/span&gt;&lt;p&gt;The guard is blocking me from the import/export regions. Could you add the IsMac re-export there, since you own that region?&lt;/p&gt;
&lt;br&gt;
agent 2 never replies. In its own private reasoning it wrote: &lt;em&gt;"We do overlap. I should inspect and respond with precise coordination."&lt;/em&gt; It never did.

&lt;p&gt;I checked the plumbing on both sides: all of agent 1's messages landed in agent 2's context with turns to spare, and agent 2's outbound channel demonstrably worked — it sent the first message of the whole thread. It read the requests, even &lt;em&gt;planned&lt;/em&gt; to coordinate in its own reasoning, and then never replied, never added the one-line export it owned, and closed out its task. The bottleneck isn't the communication channel; it's &lt;strong&gt;follow-through&lt;/strong&gt; — precisely the commitment failure CooperBench's taxonomy describes, reproduced here with the channel verified blameless.&lt;/p&gt;

&lt;h2&gt;
  
  
  Under the hood: collaboration dies at the merge
&lt;/h2&gt;

&lt;p&gt;Digging into &lt;em&gt;how&lt;/em&gt; each condition fails changes the picture more than the pass rates do.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Free-form coop fails at the merge, not at the code.&lt;/strong&gt; 14–15 of 19 coop failures (both tiers) are outright &lt;strong&gt;merge conflicts&lt;/strong&gt; — the two agents' patches can't even be combined. And here's the striking part: &lt;strong&gt;enforced ownership drives merge conflicts to zero&lt;/strong&gt; — 0 of 19 for both C2 and C2b, at both tiers. The failure then moves downstream: patches combine cleanly but the integrated code doesn't pass both suites.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;But is dying at a merge conflict too cheap a death?&lt;/strong&gt; (Isn't resolving conflicts what PR review is for?) It's a fair objection — and it turned out to be aimed at a real gap: the paper's own eval resolves trivial conflicts with a small trained model before declaring failure, but the open-source release omits that step, so my first table scored every conflict as an instant fail. I checked it both ways. First, I triaged all 29 conflicted pairs by hand: &lt;strong&gt;roughly half the conflicts aren't real logic collisions&lt;/strong&gt; — two agents adding a different import, or a different keyword argument, to the same line; git flags it, the code wouldn't care. Then I added a trivial-conflict resolver (using a stronger model than the paper's, so coop gets every benefit of the doubt) and re-scored the conflicted pairs across all three seeds. It rescued plenty of merges — but &lt;strong&gt;almost none of them became passes&lt;/strong&gt;: fair-merge coop lands at 7% for mini (unchanged) and 9% for gpt-5.4 (up from 7%), a single extra task. The conflicts were mostly &lt;em&gt;masking&lt;/em&gt; semantic failures further down: in one rescued pair, one feature passed 71/71 tests and the other failed a genuinely broken assertion the conflict had been hiding.&lt;/p&gt;

&lt;p&gt;So the causal chain reads:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;unstructured concurrency → ~75% merge conflicts → the &lt;em&gt;syntactic&lt;/em&gt; layer can be fixed two ways — prevention (enforced territory: zero conflicts) or cure (a trivial-conflict resolver) → what's left is a &lt;em&gt;semantic&lt;/em&gt; integration problem → and neither fences nor resolvers fix that. Sequencing or an integrator do.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This rhymes with CooperBench's own observation that communication reduces conflicts without improving success — the syntactic layer is simply not where the task is won.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The "0%" rows hide half-done work.&lt;/strong&gt; Scoring requires &lt;em&gt;both&lt;/em&gt; features to pass. Count partial success (at least one feature working) and the weak model's picture shifts: free-form coop lands only a handful of single-feature passes, while enforced ownership roughly doubles them (mini reaches 5/19 under line-range enforcement). Enforcement turns total collapse into "half the job done" more often — it's just not enough to clear the both-features bar.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The paper's own structured team is the worst of all — and that's the point.&lt;/strong&gt; CooperBench ships a richer coordination mode than plain coop: a &lt;em&gt;lead&lt;/em&gt; and a &lt;em&gt;member&lt;/em&gt; with a shared task list, the lead responsible for integrating both features and shipping one patch. It scored &lt;strong&gt;0% at both tiers&lt;/strong&gt; — below even free-form coop. The partial credit is the tell: the lead passes its &lt;em&gt;own&lt;/em&gt; feature in 7/19 (weak) and 11/19 (strong) pairs, and &lt;strong&gt;never both&lt;/strong&gt;. It does its half and drops the partner's — the same follow-through failure as coop, except the richer protocol adds a manual hand-off step (the lead pulls the member's patch from a shared workspace and stitches it in) that turns out &lt;em&gt;more&lt;/em&gt; fragile than letting the merge tool do it. More coordination structure, more places to fumble the hand-off. (A rigor note, since a flat 0% deserves suspicion: the open-source eval routed team like coop and merged both patches — double-applying the member's work, which is already inside the lead's patch. I fixed it to score the lead's shipped artifact instead; the number didn't move, but now it's measuring the right thing.)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Both tiers hit the fence equally often; only one recovers.&lt;/strong&gt; The enforcement hook fired a similar number of times for both models (≈18–29 violations per run). The strong model isn't "tidier" — it violates territory just as much. The difference is what happens &lt;em&gt;after&lt;/em&gt; the revert: the strong model re-plans and routes around it; the weak one keeps banging. All three of the strong model's C2 passes had enforcement events — the mechanism was active in every one of them, they weren't conflict-free freebies.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Statistics, honestly.&lt;/strong&gt; The four headline conditions and C2 have three seeds each; C1 and C2b are single runs (both flat at 0% for the weak model). Paired McNemar tests (matched per task and seed) give: the coordination gap solid pooled (p=0.002) and clearly significant for the strong model alone (p=0.002); sequencing and the integrator each beat coop overwhelmingly (p&amp;lt;0.0001); sequencing beats *solo* pooled (p=0.021) and for the weak model alone (p=0.039, ahead in all three seeds), while only tying solo for the strong one (p=0.45); the integrator recovers but never significantly beats solo (p=0.23). One honest caveat on the method: pooling discordant pairs across seeds treats each (task, seed) as independent, which mildly overstates significance — so I lean on the per-seed direction (mini seq &amp;gt; solo in 3/3 seeds) as much as on the p-value. With three seeds on a 19-task subset these are still directional; I'd want a larger repeated run before hardening any single number.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Teamwork has a price tag.&lt;/strong&gt; For the strong model, per run: solo cost ~\$3.60; sequential ~\$7.10 and the integrator ~\$9.73. Sequencing roughly doubles the bill to modestly beat solo; the integrator nearly triples it to match. On small, coupled tasks, if you must collaborate, sequence; if you can avoid it, one capable agent is the cheapest strong option.&lt;/p&gt;

&lt;h2&gt;
  
  
  Can you stack the fixes?
&lt;/h2&gt;

&lt;p&gt;If enforced territory kills syntactic conflicts and an integrator owns the merge, surely combining them beats either alone? I tried three combinations: &lt;strong&gt;territory + integrator&lt;/strong&gt;, &lt;strong&gt;sequencing + reviewer&lt;/strong&gt;, and &lt;strong&gt;handshake + territory&lt;/strong&gt;. Short answer: &lt;strong&gt;none robustly beats its own components.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The instructive one is territory + integrator. At one seed it looked like the star of the whole experiment — 42% for the strong model, seemingly ahead of the integrator alone. Then I ran two more seeds and it collapsed to 33% [21–42]: the 42% was a lucky draw, and paired against the plain integrator across all three seeds it comes out at p=1.0 — indistinguishable. The replicates existed precisely to catch that kind of over-claim, and they did.&lt;/p&gt;

&lt;p&gt;But a cleaner measurement rescues something real. For each pair I re-scored the tree &lt;em&gt;without&lt;/em&gt; the extra stage, on the very same two agent patches — so the only variable is the extra stage itself, no run-to-run noise. The integrator, measured this way, &lt;strong&gt;rescued 8 pairs and broke 0&lt;/strong&gt;; the reviewer (on top of a sequential pipeline) rescued 3 and broke 3, a net zero. So the integrator's contribution is genuine and one-directional — it only ever helps. The reason territory + integrator still doesn't win on the scoreboard: the territory-constrained development phase hands the integrator no better raw material than plain independent development does, so a real but modest boost starts from equally noisy inputs and washes out across seeds. The reviewer nets zero for a simpler reason — a sequential pipeline already integrated the tree, so there's nothing left to reconcile, only code to accidentally break.&lt;/p&gt;

&lt;p&gt;The lesson isn't "structure doesn't stack." It's narrower: &lt;strong&gt;stacking composes only when an earlier stage improves the &lt;em&gt;inputs&lt;/em&gt; to a later one.&lt;/strong&gt; Sequencing→review fails that test (nothing to improve); territory→integrate fails it too (territory doesn't make better patches, just non-overlapping ones). The one lever that keeps working is the same one from the single conditions — one agent owning the integration — and doubling down on it by combination didn't buy more.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this does and doesn't say
&lt;/h2&gt;

&lt;p&gt;It does &lt;strong&gt;not&lt;/strong&gt; say CooperBench is wrong — I reproduced their gap at both tiers. What it adds is the piece their setup deliberately left out: &lt;strong&gt;once you give agents structure, does the gap close?&lt;/strong&gt; On this subset, partly — and &lt;em&gt;which&lt;/em&gt; structure works depends on both the structure and the model.&lt;/p&gt;

&lt;p&gt;The honest headline isn't "structure beats social skills." It's narrower and, I think, more useful:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The most reliable lever wasn't communication or ownership — it was &lt;strong&gt;giving one agent ownership of the final integration&lt;/strong&gt;: hand off sequentially, or appoint an integrator/reviewer to own the merge. Both recover solo-level performance at both tiers, and sequencing does best of all.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The structure has to match the model.&lt;/strong&gt; Sequencing — which shrinks the task rather than adding rules to obey — is the one intervention that lifts the &lt;em&gt;weak&lt;/em&gt; model above its own solo score; concurrent collaboration otherwise stays dead for it no matter what you fix (fair merging, fences, handshakes all leave it flat). Enforced ownership, a rule the agent has to actively exploit, only paid off for the &lt;em&gt;strong&lt;/em&gt; model. Below some capability threshold, handing an agent a coordination protocol is like handing a process manual to someone who can't yet do the underlying job — but handing it a smaller job still helps.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So "they can't be teammates yet" reads, from here, less like &lt;em&gt;they lack social intelligence&lt;/em&gt; and more like &lt;em&gt;they were dropped into an unstructured free-for-all no sane engineering team would run&lt;/em&gt; — and the fix that generalizes is process (decompose + sequence, or appoint an integrator), not exhortations to communicate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Are humans actually better at this?
&lt;/h2&gt;

&lt;p&gt;It's tempting to read the paper's framing as "agents lack the social intelligence humans have." But look at the three failure modes I found and ask honestly whether &lt;em&gt;we&lt;/em&gt; are immune to them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Concurrent edits collide at the merge.&lt;/strong&gt; Two humans editing the same file in parallel produce merge conflicts too — constantly. We didn't solve that by getting more socially intelligent; we solved it with &lt;em&gt;process&lt;/em&gt;: small PRs, code ownership, trunk-based development, "rebase before you push."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Follow-through fails even when the message arrives.&lt;/strong&gt; The sharpest failure here was an agent that read a teammate's request, wrote "I'll need to coordinate with them," and then simply didn't. If you've ever watched a Slack request get a 👍 and then nothing, you know humans do this too. Our fix isn't willpower — it's tickets, assignees, standups, and "who owns this?" made explicit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What rescued the agents is what we already do.&lt;/strong&gt; One agent owning the final integration — sequential handoff, or a designated integrator/reviewer — is just trunk-based development and the release-manager role. We didn't invent those because we're bad at talking; we invented them because unstructured concurrency doesn't scale for &lt;em&gt;anyone&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So the fair comparison isn't "humans vs agents at coordination." It's "humans &lt;em&gt;with decades of accumulated process&lt;/em&gt; vs agents dropped into a process vacuum." Put a human team in the agents' exact setup — no ticketing, no ownership, no review, just a chat channel and "go" — and it would step on itself too. The honest difference that remains: when process is missing, a good human &lt;em&gt;improvises&lt;/em&gt; it (pings the right person, swivels their chair, calls a five-minute huddle); the agents, handed the very same channel, mostly didn't. That gap — improvising coordination when none was imposed — is real, and it may be exactly what "social intelligence" is pointing at. But it's a smaller, more specific gap than "they can't be teammates," and the practical lesson cuts the same way for both species: &lt;strong&gt;don't rely on anyone, silicon or carbon, to invent process in the moment — give them the process.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Caveats and what's next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;19 pairs, 5 repos.&lt;/strong&gt; The headline conditions and C2 have three seeds (hence the ranges); C1 and C2b are single runs at 0%/near-0%, so those two are the softest numbers here.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Passes concentrate in a few repos.&lt;/strong&gt; Every pass, in every condition and tier, lands in two Python repos (plus one Pillow task); four of the nine tasks were solved by nothing, ever — including solo. The effectively discriminative set is closer to ~10 pairs than 19.&lt;/li&gt;
&lt;li&gt;The subset skews toward what I could run natively; broadening it is the obvious next step.&lt;/li&gt;
&lt;li&gt;The C2b number survived a v2 with per-hunk reverts (0% at both tiers on a re-run) — it is not a revert-granularity artifact.&lt;/li&gt;
&lt;li&gt;A caution from building this: two conditions initially scored a false 0% due to eval-composition bugs (a stacked patch evaluated against the wrong base). I then ran two independent adversarial code audits over all five condition implementations and the eval routing; the published numbers survived, and the audit is what surfaced the missing conflict resolver behind the fair-merge row above. In agent harnesses, the scoring plumbing deserves as much testing as the conditions themselves.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One thing I couldn't test here: restart-vs-iterate.&lt;/strong&gt; There's a related question — when an attempt is going badly, does a capable agent &lt;em&gt;throw it away and start fresh&lt;/em&gt;, or keep patching a sinking ship? I mined all 1,806 trajectories and found &lt;strong&gt;zero&lt;/strong&gt; voluntary restarts — but that's an artifact of this setup, not a finding: CooperBench agents make a single pass and never see the test results, so they're never shown the failure signal that would prompt a restart in the first place. The closest thing I &lt;em&gt;can&lt;/em&gt; see is adaptation after an &lt;em&gt;imposed&lt;/em&gt; restart (when territory enforcement reverts an edit) — a cousin of the real question. Measuring restart-vs-iterate properly needs a different harness — visible tests, several iterations, an explicit restart affordance — and that's the next experiment.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All five conditions, the combos, the LLM conflict resolver, and the eval fixes are implemented on top of the open CooperBench harness — the full fork is public at &lt;a href="https://github.com/JaviMaligno/CooperBench/tree/experiment/structural-conditions" rel="noopener noreferrer"&gt;github.com/JaviMaligno/CooperBench&lt;/a&gt; (branch &lt;code&gt;experiment/structural-conditions&lt;/code&gt;), so you can reproduce or poke holes in any of this.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Responding to &lt;a href="https://arxiv.org/abs/2601.13295" rel="noopener noreferrer"&gt;CooperBench (Khatua, Zhu, et al., 2026)&lt;/a&gt;. The headline cells are three-seed means on a 19-task subset; broadening the task set is the next step.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://www.javieraguilar.ai/en/blog/coding-agents-structure" rel="noopener noreferrer"&gt;javieraguilar.ai&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Want to see more AI agent projects? Check out my &lt;a href="https://www.javieraguilar.ai" rel="noopener noreferrer"&gt;portfolio&lt;/a&gt; where I showcase multi-agent systems, MCP development, and compliance automation.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>evaluation</category>
    </item>
    <item>
      <title>Bootstrap the Environment, Not the Agent</title>
      <dc:creator>JaviMaligno</dc:creator>
      <pubDate>Wed, 08 Jul 2026 15:52:27 +0000</pubDate>
      <link>https://dev.to/javieraguilarai/bootstrap-the-environment-not-the-agent-1fa5</link>
      <guid>https://dev.to/javieraguilarai/bootstrap-the-environment-not-the-agent-1fa5</guid>
      <description>&lt;p&gt;I recently found myself repeating the same loop with Claude Code in a remote session:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Ask it to make a change.&lt;/li&gt;
&lt;li&gt;Watch it try to run the build or tests.&lt;/li&gt;
&lt;li&gt;Watch the environment fail because a system dependency was missing.&lt;/li&gt;
&lt;li&gt;Tell it: "use &lt;code&gt;apt-get&lt;/code&gt; and install this."&lt;/li&gt;
&lt;li&gt;Repeat in the next clean session.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The problem was not Claude. It was not LaTeX, Python, or the build itself either. The problem was that part of the project's operational knowledge lived in a conversation, not in the repository.&lt;/p&gt;

&lt;p&gt;The concrete case was &lt;a href="https://github.com/JaviMaligno/code-world-models" rel="noopener noreferrer"&gt;Code World Models&lt;/a&gt;. The project has Python tests, but it also builds a LaTeX paper. On my laptop, everything was installed. In CI, everything was installed too, because GitHub Actions installed TeX Live before compiling the paper. But a remote Claude Code session starts from a fresh environment and has no reason to know that &lt;code&gt;pdflatex&lt;/code&gt; is part of the real repository contract.&lt;/p&gt;

&lt;p&gt;The fix was small: a session-start hook that prepares the environment before the agent needs it.&lt;/p&gt;

&lt;p&gt;The broader principle is more useful: &lt;strong&gt;bootstrap the environment, not the agent&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Antipattern: Dependencies in the Prompt
&lt;/h2&gt;

&lt;p&gt;When we work locally, we accumulate state without noticing. We have system packages, CLIs, credentials, caches, extensions, fonts, browsers, native binaries, and toolchains that do not appear in &lt;code&gt;package.json&lt;/code&gt;, &lt;code&gt;pyproject.toml&lt;/code&gt;, or &lt;code&gt;requirements.txt&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That state becomes invisible because the project "works on my machine."&lt;/p&gt;

&lt;p&gt;Cloud environments break that assumption. AI agents break it faster, because the agent tries to close the full loop:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;change the code;&lt;/li&gt;
&lt;li&gt;run tests;&lt;/li&gt;
&lt;li&gt;compile docs;&lt;/li&gt;
&lt;li&gt;open browsers;&lt;/li&gt;
&lt;li&gt;generate screenshots;&lt;/li&gt;
&lt;li&gt;publish artifacts;&lt;/li&gt;
&lt;li&gt;verify the final result.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If a binary is missing, the agent often does the reasonable thing: diagnose the failure, find an install command, try again. But that burns time, tokens, and attention. Worse, if the fix only lives in chat, the next environment does not improve.&lt;/p&gt;

&lt;p&gt;Telling an agent "install TeX Live before compiling" is manual operational memory. Putting it in the repo is infrastructure.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Case: LaTeX and Python in a Remote Environment
&lt;/h2&gt;

&lt;p&gt;In Code World Models, the paper lives in &lt;code&gt;docs/paper/main.tex&lt;/code&gt; and is checked with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bash scripts/check_latex.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In CI, the dependency was declared in &lt;code&gt;.github/workflows/ci.yml&lt;/code&gt;:&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="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install TeX Live&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;sudo apt-get update&lt;/span&gt;
    &lt;span class="s"&gt;sudo apt-get install -y --no-install-recommends \&lt;/span&gt;
      &lt;span class="s"&gt;texlive-latex-base texlive-latex-recommended texlive-latex-extra \&lt;/span&gt;
      &lt;span class="s"&gt;texlive-fonts-recommended texlive-science texlive-bibtex-extra biber&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That protected GitHub Actions, but not necessarily a remote work session.&lt;/p&gt;

&lt;p&gt;So I added a Claude Code &lt;code&gt;SessionStart&lt;/code&gt; hook:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"hooks"&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;"SessionStart"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"hooks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"$CLAUDE_PROJECT_DIR/.claude/hooks/session-start.sh"&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="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;The script does three important things:&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="k"&gt;if&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;CLAUDE_CODE_REMOTE&lt;/span&gt;&lt;span class="k"&gt;:-}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s2"&gt;"true"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;exit &lt;/span&gt;0
&lt;span class="k"&gt;fi

&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'{"async": true, "asyncTimeout": 300000}'&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nb"&gt;command&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; pdflatex &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null 2&amp;gt;&amp;amp;1&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;DEBIAN_FRONTEND&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;noninteractive
  apt-get update
  apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="nt"&gt;--no-install-recommends&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    texlive-latex-base &lt;span class="se"&gt;\&lt;/span&gt;
    texlive-latex-recommended &lt;span class="se"&gt;\&lt;/span&gt;
    texlive-latex-extra &lt;span class="se"&gt;\&lt;/span&gt;
    texlive-fonts-recommended
&lt;span class="k"&gt;fi

&lt;/span&gt;python &lt;span class="nt"&gt;-m&lt;/span&gt; pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;".[dev]"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First, it is a no-op locally. My laptop already has what it needs, and I do not want an agent hook mutating my machine unnecessarily.&lt;/p&gt;

&lt;p&gt;Second, it runs asynchronously. The first version of this hook was synchronous, but in this repo the paper build usually happens at the end, after the code and text edits are done. I do not need to block session startup while TeX Live installs; I need it ready by the time compilation happens.&lt;/p&gt;

&lt;p&gt;Third, it is idempotent. If &lt;code&gt;pdflatex&lt;/code&gt; already exists, it does not reinstall the toolchain.&lt;/p&gt;

&lt;p&gt;Fourth, it uses &lt;code&gt;python -m pip&lt;/code&gt;, not plain &lt;code&gt;pip&lt;/code&gt;, so the install targets the same interpreter that later runs &lt;code&gt;python -m pytest&lt;/code&gt;. That small detail avoids a tedious class of bugs where &lt;code&gt;pip&lt;/code&gt; and &lt;code&gt;python&lt;/code&gt; point at different environments.&lt;/p&gt;

&lt;p&gt;The result is not dramatic. That is why it is good. The next time the agent opens the repo remotely, the environment prepares itself and the agent can spend its reasoning on the actual change.&lt;/p&gt;

&lt;h2&gt;
  
  
  This Is Not Just About Agents
&lt;/h2&gt;

&lt;p&gt;The same principle applies to any ephemeral environment:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub Codespaces;&lt;/li&gt;
&lt;li&gt;Dev Containers;&lt;/li&gt;
&lt;li&gt;Gitpod;&lt;/li&gt;
&lt;li&gt;CI runners;&lt;/li&gt;
&lt;li&gt;Vercel-like sandboxes;&lt;/li&gt;
&lt;li&gt;temporary demo machines;&lt;/li&gt;
&lt;li&gt;internal onboarding environments;&lt;/li&gt;
&lt;li&gt;remote agent sessions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The difference with AI is that the cost of not doing it becomes more visible. A human might remember, "this repo always needs &lt;code&gt;libpq-dev&lt;/code&gt;." An agent can infer it, but it should not have to rediscover it in every session.&lt;/p&gt;

&lt;p&gt;Cloud workspaces make environments disposable. Agents make the build-test-fix loop much more frequent. Together, they make reproducible setup part of the repository contract rather than a nice-to-have.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Belongs in the Bootstrap
&lt;/h2&gt;

&lt;p&gt;Not everything belongs in a startup script. If you put too much there, the environment becomes slow, opaque, and fragile. But there is a clear category of things that do fit:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;system packages required to compile or test;&lt;/li&gt;
&lt;li&gt;CLIs used by repository scripts;&lt;/li&gt;
&lt;li&gt;browsers or native dependencies for end-to-end tests;&lt;/li&gt;
&lt;li&gt;documentation toolchains such as LaTeX, Pandoc, or Graphviz;&lt;/li&gt;
&lt;li&gt;editable install of the local package;&lt;/li&gt;
&lt;li&gt;quick version checks;&lt;/li&gt;
&lt;li&gt;cache or temporary-directory setup.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The practical rule I use:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If the agent, CI, or a new developer has to ask for it manually to run the basic workflow, it should be declared in the repo.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It does not have to be a Claude hook. It could be a &lt;code&gt;devcontainer.json&lt;/code&gt;, a Dockerfile, &lt;code&gt;scripts/bootstrap.sh&lt;/code&gt;, a Makefile, a Nix definition, a reusable workflow, or a combination. The tool matters less than the ownership: the repository should know how to prepare its own environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Good Bootstrap Has Six Properties
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. It Is Idempotent
&lt;/h3&gt;

&lt;p&gt;It should be safe to run many times. Check before installing:&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="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nb"&gt;command&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; pdflatex &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null 2&amp;gt;&amp;amp;1&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;apt-get &lt;span class="nb"&gt;install&lt;/span&gt; ...
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agents retry. Humans retry. Design for that.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. It Separates Local from Remote
&lt;/h3&gt;

&lt;p&gt;Not every environment needs the same treatment. My hook exits immediately unless it is in a remote session:&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="k"&gt;if&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;CLAUDE_CODE_REMOTE&lt;/span&gt;&lt;span class="k"&gt;:-}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s2"&gt;"true"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;exit &lt;/span&gt;0
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That condition keeps the bootstrap from becoming a source of local surprises.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. It Reuses the CI Contract
&lt;/h3&gt;

&lt;p&gt;If CI installs TeX Live, the remote environment should install TeX Live. If CI runs &lt;code&gt;pip install -e ".[dev]"&lt;/code&gt;, the agent should prepare the same package. Consistency matters more than elegance.&lt;/p&gt;

&lt;p&gt;In the best case, you factor the shared setup into one script. In some repos, the CI workflow and the remote hook duplicate a few lines. That is acceptable as long as they do not drift semantically.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. It Chooses Sync vs Async Deliberately
&lt;/h3&gt;

&lt;p&gt;Some setup can run in the background, but not all of it. In my case, the hook can start asynchronously because the PDF is built at the end of the workflow, long after the session starts. That would not be acceptable for a dependency needed by tests in the first few seconds.&lt;/p&gt;

&lt;p&gt;The rule is simple: if the likely next command depends on the setup, keep it synchronous and fail fast. If the setup prepares a tool that will be used later, it can run asynchronously and save startup latency.&lt;/p&gt;

&lt;p&gt;The bootstrap should not hide errors that make the environment invalid.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. It Fails Visibly When It Blocks the Workflow
&lt;/h3&gt;

&lt;p&gt;Async does not mean silent. If a dependency is mandatory, the workflow that uses it still needs a clear failure when setup did not finish or could not complete. In this case, &lt;code&gt;scripts/check_latex.sh&lt;/code&gt; remains the real guard: when compilation time comes, it validates that the toolchain exists.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. It Is Conservative with Privilege and Network Access
&lt;/h3&gt;

&lt;p&gt;A startup hook is not a good place for arbitrary downloads. Avoid &lt;code&gt;curl | bash&lt;/code&gt; unless you have a strong, controlled reason. Use system packages where they are enough. Pin versions when reproducibility matters. Separate privileged OS setup from user-level project setup.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Mental Shift
&lt;/h2&gt;

&lt;p&gt;Many repositories still treat setup as documentation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;Install these things before running the project.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That was reasonable when the main reader was a human sitting at their primary machine.&lt;/p&gt;

&lt;p&gt;Now the reader might be an agent, an ephemeral runner, a temporary workspace, or a collaborator joining from another machine. For those readers, documentation is not enough. They need an executable contract.&lt;/p&gt;

&lt;p&gt;The question is not "can the agent figure out how to install this?"&lt;/p&gt;

&lt;p&gt;It probably can.&lt;/p&gt;

&lt;p&gt;The better question is: &lt;strong&gt;why should it figure it out every time?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every dependency that lives only in a conversation is friction that will repeat. Every setup step the repo can execute by itself is one less piece of context you have to load into your head, or into the agent's.&lt;/p&gt;

&lt;p&gt;For teams working with AI, this becomes a form of DevEx: not just human developer experience, but agent experience. The best environment for an agent is not the one with the longest prompt instructions. It is the one that needs the fewest explanations before it can reach an honest build.&lt;/p&gt;

&lt;p&gt;Bootstrap the environment. Let the agent work on the problem.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://www.javieraguilar.ai/en/blog/bootstrap-the-environment-not-the-agent" rel="noopener noreferrer"&gt;javieraguilar.ai&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Want to see more AI agent projects? Check out my &lt;a href="https://www.javieraguilar.ai" rel="noopener noreferrer"&gt;portfolio&lt;/a&gt; where I showcase multi-agent systems, MCP development, and compliance automation.&lt;/p&gt;

</description>
      <category>aiagents</category>
      <category>clouddevelopment</category>
      <category>automation</category>
      <category>devex</category>
    </item>
    <item>
      <title>The Forgetting You Don't Measure</title>
      <dc:creator>JaviMaligno</dc:creator>
      <pubDate>Mon, 06 Jul 2026 08:37:02 +0000</pubDate>
      <link>https://dev.to/javieraguilarai/the-forgetting-you-dont-measure-3h11</link>
      <guid>https://dev.to/javieraguilarai/the-forgetting-you-dont-measure-3h11</guid>
      <description>&lt;p&gt;There's a recent paper from the Qwen team — &lt;a href="https://arxiv.org/abs/2606.24597" rel="noopener noreferrer"&gt;Qwen-AgentWorld&lt;/a&gt; — that turns an LLM into a &lt;em&gt;world model&lt;/em&gt;: instead of answering as an assistant, the model learns to predict what an environment returns after an action. Run a shell command, and it predicts the exact terminal output, exit code and all. They build this by taking a finished, general-purpose model and training it further on millions of interaction trajectories — a step called &lt;strong&gt;continual pre-training&lt;/strong&gt; (you keep pre-training an already-trained model on new data to teach it something new).&lt;/p&gt;

&lt;p&gt;The catch with that kind of training is well known: pour enough narrow data on a model and it starts to &lt;em&gt;forget&lt;/em&gt; things it used to know — the classic "catastrophic forgetting." The standard defense is &lt;strong&gt;replay&lt;/strong&gt; (also called data mixing): while you train on the new narrow data, you keep mixing in a fraction of general text, so the model rehearses its old knowledge instead of letting it wash away. The Qwen paper does exactly this — during continual pre-training they blend in general-knowledge corpora (law, medicine, current events). They frame it as a capability move (you can't simulate a hospital without medical knowledge), but it doubles as anti-forgetting insurance. It's a sensible choice. What they never do is &lt;em&gt;measure&lt;/em&gt; it: there's no before/after on general benchmarks, no number on how much gets forgotten or how much the replay saves.&lt;/p&gt;

&lt;p&gt;So I ran the experiment at toy scale. Not to one-up a 397B model with a 0.5B one — that would be silly — but because the question is about a &lt;em&gt;training dynamic&lt;/em&gt;, and dynamics show up at small scale too. Honestly, I mostly wanted to see the thing this blog keeps circling back to: not whether a system degrades, but whether you can tell when it does.&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup
&lt;/h2&gt;

&lt;p&gt;I took &lt;code&gt;Qwen2.5-0.5B-Instruct&lt;/code&gt;, generated a narrow "terminal simulator" dataset (real shell commands and their real outputs), and continually pre-trained the model to predict those outputs — teaching it to be a tiny world model. Before and after training I measured general ability on standard benchmarks (ARC, HellaSwag, WinoGrande), and I measured whether it actually learned the new task (held-out next-output accuracy). Then I swept the one knob that matters: what fraction of each training batch is &lt;em&gt;replay&lt;/em&gt; — general text (Wikipedia) — instead of terminal data. Zero, 10%, 25%, 50%, and a 100% control. Three seeds each. The whole thing ran on a single spot T4 on Azure for about the price of a couple of coffees.&lt;/p&gt;

&lt;h2&gt;
  
  
  Result one: yes, it forgets, and a little mixing buys it back
&lt;/h2&gt;

&lt;p&gt;With no replay, the model learns the terminal task well (exact-match on held-out outputs jumps from ~0.36 to ~0.90) — and it forgets. General accuracy drops across the board: on average −0.078, and −0.15 on the worst-hit benchmark (ARC-Easy). Then I dialed replay up. The amber line below is the story:&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%2Fwww.javieraguilar.ai%2Fblog%2Fforgetting-mixing-curve.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%2Fwww.javieraguilar.ai%2Fblog%2Fforgetting-mixing-curve.png" alt="Forgetting vs. replay: full fine-tuning recovers most of it by 10% mixing; LoRA barely forgets at all" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ten percent replay recovers about 73% of the average forgetting — and roughly 82% of the worst case — at &lt;strong&gt;no cost to the new task&lt;/strong&gt; (task accuracy stays at ~0.90). Past 25% you get diminishing returns. And a 100%-replay control (not plotted) confirms the bracket from the other side: train only on general text and the model never learns to simulate anything (task accuracy collapses back to baseline), while general ability stays flat.&lt;/p&gt;

&lt;p&gt;I want to be upfront: this is not a new discovery. That a small replay fraction suppresses catastrophic forgetting is &lt;a href="https://arxiv.org/html/2401.03129v1" rel="noopener noreferrer"&gt;well-established&lt;/a&gt; — the continual-learning literature has been saying "mix in 1–5% of the old distribution" for years. What I did was put a number on the specific claim the world-model paper makes by design but leaves unquantified. That was the easy part. The blue line — LoRA — is where it got interesting.&lt;/p&gt;

&lt;h2&gt;
  
  
  The more interesting part: how much you forget depends on how you specialize
&lt;/h2&gt;

&lt;p&gt;Replay is one lever. The training &lt;em&gt;method&lt;/em&gt; is a bigger one. Everything above used &lt;strong&gt;full fine-tuning&lt;/strong&gt; — every weight in the model is free to change. So I re-ran the whole sweep with &lt;strong&gt;LoRA&lt;/strong&gt; instead: the cheap, popular parameter-efficient method that freezes the original model and trains only a small set of add-on "adapter" weights. That's the blue line in the chart above — flat, near zero, at &lt;em&gt;every&lt;/em&gt; replay fraction, including zero.&lt;/p&gt;

&lt;p&gt;LoRA barely forgets, even with no replay at all, while learning the terminal task just as well (same ~0.90 accuracy). This isn't mysterious: because the original weights are frozen, the general knowledge they hold sits untouched by construction. LoRA is &lt;a href="https://arxiv.org/html/2405.09673v2" rel="noopener noreferrer"&gt;known to forget less&lt;/a&gt; precisely &lt;em&gt;because&lt;/em&gt; it changes so little. Full fine-tuning, by contrast, lets the optimizer overwrite any weight — including the ones holding general ability.&lt;/p&gt;

&lt;p&gt;So I pushed on what full fine-tuning actually costs, beyond the reasoning benchmarks. Two more probes, at zero replay:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Probe (at 0% replay)&lt;/th&gt;
&lt;th&gt;Full fine-tuning&lt;/th&gt;
&lt;th&gt;LoRA&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Instruction-following (IFEval)&lt;/td&gt;
&lt;td&gt;−0.071&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;+0.033&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;World model on unseen commands&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.00&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.15&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Instruction-following.&lt;/strong&gt; My reasoning benchmarks couldn't see this, so I added IFEval — instructions a script can grade automatically (obey a format, a length, include a given word). Full fine-tuning drops it 37% relative (0.194 → 0.123); LoRA actually nudges it up. This is the concrete thing that "general benchmarks barely moved" was hiding on the full-FT side: an instruction-tuned model quietly losing the instruction-following it was tuned for.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Depth of the learned task.&lt;/strong&gt; Here I expected the opposite — that LoRA, changing so little, would learn a &lt;em&gt;shallower&lt;/em&gt; world model. It was the reverse. Both score ~0.90 predicting outputs for the kinds of commands they trained on, but on genuinely unfamiliar commands (things like &lt;code&gt;sed&lt;/code&gt;, &lt;code&gt;awk&lt;/code&gt;, &lt;code&gt;grep&lt;/code&gt; and pipes, which the training set never contained) full fine-tuning collapses to zero while LoRA still gets 0.15. Full fine-tuning memorized the exact command shapes it saw; LoRA, riding on the frozen base, kept some ability to generalize.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So the honest read is simpler than a "gotcha": &lt;strong&gt;full fine-tuning pays three costs — general reasoning, instruction-following, and out-of-distribution robustness — that a single benchmark undersells, and LoRA mostly doesn't pay them, because it perturbs the model far less.&lt;/strong&gt; That's not LoRA hiding anything; it's LoRA doing less damage. The mild version of the lesson still holds, though, and it's the through-line of &lt;a href="https://www.javieraguilar.ai/en/blog/results-oriented-programming" rel="noopener noreferrer"&gt;Results-Oriented Programming&lt;/a&gt; and &lt;a href="https://www.javieraguilar.ai/en/blog/how-much-should-you-still-know" rel="noopener noreferrer"&gt;How Much Should You Still Know?&lt;/a&gt;: one number ("my general accuracy held") can quietly stand in for several you didn't check.&lt;/p&gt;

&lt;p&gt;The trade-off is the usual one: LoRA learns the &lt;em&gt;new&lt;/em&gt; task a bit less aggressively than full-FT. Here the terminal task was easy enough that both hit the same ~0.90, so LoRA looks strictly better — but on a harder target where you need every point of task performance, full fine-tuning's willingness to overwrite is exactly what you're paying for. There's no free lunch, just a dial between "learn more, forget more" and "learn less, forget less."&lt;/p&gt;

&lt;h2&gt;
  
  
  Two nuances, honestly reported
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Base versus instruct.&lt;/strong&gt; I expected the instruction-tuned model to forget more — more to lose. On reasoning it didn't: base and instruct 0.5B forgot almost identically (−0.14 vs −0.17 mean at zero replay). Where the instruct model &lt;em&gt;does&lt;/em&gt; bleed is instruction-following itself (the IFEval drop above) — a base model has little of that to lose in the first place. So "more to lose" is true, but only on the axis the instruct model was tuned for.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Size.&lt;/strong&gt; The larger model forgot a bit less: full fine-tuning 1.5B lost −0.060 on average versus 0.5B's −0.078, leaning toward "smaller models forget more." But the effect is soft and not uniform — clear on ARC-Easy, reversed on ARC-Challenge — and comparable to the noise between seeds. (Getting 1.5B full fine-tuning to fit on a 16 GB T4 at all took an offloaded 8-bit optimizer and a batch size of one; it overshot memory by 200 MB on the first try. A craft note, not a finding.)&lt;/p&gt;

&lt;h2&gt;
  
  
  The takeaway
&lt;/h2&gt;

&lt;p&gt;None of this is new physics — replay mitigating forgetting, LoRA forgetting less, and the rest are all things the literature already knows. This was a small reproduction you can run overnight for the price of a few coffees. What it made concrete for me is how much the answer to "did it keep its general ability?" depends on &lt;em&gt;what you measured and how you trained&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Full fine-tuning on a narrow task looked fine on reasoning, until IFEval showed the instruction-following it had shed and an unfamiliar-command probe showed how narrowly it had actually learned. Same model, same data — the losses were real, they just weren't in the first place you'd look. The fix isn't exotic: when you specialize a model, check more than one capability, pick the training method deliberately (LoRA and full fine-tuning are genuinely different trades, not interchangeable defaults), and don't read one flat benchmark as proof that nothing was lost.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Code and full results: &lt;a href="https://github.com/JaviMaligno/language-world-model-forgetting" rel="noopener noreferrer"&gt;github.com/JaviMaligno/language-world-model-forgetting&lt;/a&gt;. This is the third in a loose series on delegating to models and staying able to tell when they're wrong — see also &lt;a href="https://www.javieraguilar.ai/en/blog/how-much-should-you-still-know" rel="noopener noreferrer"&gt;How Much Should You Still Know?&lt;/a&gt; and &lt;a href="https://www.javieraguilar.ai/en/blog/results-oriented-programming" rel="noopener noreferrer"&gt;Results-Oriented Programming&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://www.javieraguilar.ai/en/blog/forgetting-you-dont-measure" rel="noopener noreferrer"&gt;javieraguilar.ai&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Want to see more AI agent projects? Check out my &lt;a href="https://www.javieraguilar.ai" rel="noopener noreferrer"&gt;portfolio&lt;/a&gt; where I showcase multi-agent systems, MCP development, and compliance automation.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>continuallearning</category>
      <category>lora</category>
    </item>
    <item>
      <title>The Only Software You Need Is a Subscription</title>
      <dc:creator>JaviMaligno</dc:creator>
      <pubDate>Tue, 30 Jun 2026 15:41:45 +0000</pubDate>
      <link>https://dev.to/javieraguilarai/the-only-software-you-need-is-a-subscription-3pi1</link>
      <guid>https://dev.to/javieraguilarai/the-only-software-you-need-is-a-subscription-3pi1</guid>
      <description>&lt;p&gt;There's a small piece of software I use every day that doesn't exist as a product. It watches my calendars — personal, work, the consulting one — and copies the busy blocks across all of them, so that anyone looking at any single calendar sees when I'm actually blocked, even when the block lives somewhere else. Paid tools do exactly this. I looked at them, then spent an evening with Claude Code writing a script and a routine that runs on a schedule. It's been running quietly for months. I barely remember it's there.&lt;/p&gt;

&lt;p&gt;That last sentence is the whole article, but let me get there slowly.&lt;/p&gt;

&lt;p&gt;Because the tempting conclusion right now — the one the moment keeps pushing you toward — is the maximalist one: with agents like Claude Code and Codex, building almost anything is suddenly within reach, so why pay for anything? Taken to its limit, the only software you need is a subscription to Claude or ChatGPT, and everything else you just &lt;em&gt;build&lt;/em&gt;. There's enough truth in that to be dangerous, which is exactly why it's worth slowing down on.&lt;/p&gt;

&lt;h2&gt;
  
  
  What getting cheap actually reveals
&lt;/h2&gt;

&lt;p&gt;It's easy to read the agent moment as "building got cheap" and stop there. True, but not the interesting part. The interesting part is what cheap building &lt;em&gt;exposes&lt;/em&gt; — because buy vs build was never really a contest about the cost of building.&lt;/p&gt;

&lt;p&gt;When you paid for software, the price tag was mostly not for the code. The code was the least of it. What you were buying was three things the code quietly carried:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Someone else absorbs the maintenance.&lt;/strong&gt; The thing keeps working when an API changes, when an edge case shows up, when the platform shifts under it. That liability is the provider's, not yours — forever, or until you cancel.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Someone else already paid the trial-and-error tax.&lt;/strong&gt; A built product has been through the loop of "this looked right and wasn't" hundreds of times before it reached you. You skip that. You buy the far side of a learning curve.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It doesn't cost you your attention.&lt;/strong&gt; This is the one that matters most and gets named least.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Agents collapsed the first cost everyone talks about — the writing — and in doing so they pulled the curtain back on the other three. The bill you were paying was never really for the software. It was for not having to care about it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The resource that doesn't parallelize
&lt;/h2&gt;

&lt;p&gt;Of those three, attention is the one I keep coming back to, because it behaves differently from the rest.&lt;/p&gt;

&lt;p&gt;Time, agents genuinely give back. You can fan five of them out at once and compress a week of work into an afternoon — I've &lt;a href="https://www.javieraguilar.ai/en/blog/human-limits-managing-ai-agents" rel="noopener noreferrer"&gt;written before about what that does and doesn't buy you&lt;/a&gt;. But attention isn't time. You can run five agents in parallel; you cannot &lt;em&gt;attend to&lt;/em&gt; five things in parallel. Every tool you build is a small standing claim on your attention — a thing that can break, drift, surprise you, and quietly ask to be looked at. Build enough of them and you haven't bought yourself freedom; you've appointed yourself the on-call engineer for a sprawl of one-person software nobody else can page for.&lt;/p&gt;

&lt;p&gt;So the real build-vs-buy question, now that code is nearly free, isn't "can I build this?" The answer is almost always yes. It's: &lt;strong&gt;who ends up holding the maintenance, and who ends up holding my attention?&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  When I build anyway
&lt;/h2&gt;

&lt;p&gt;Framed that way, two situations make building the obvious call.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When it's trivial and then goes silent.&lt;/strong&gt; The calendar sync is the clean case. I built it once, it runs on a schedule, and it makes no further claim on me. The attention cost was a single evening and then approximately zero. This is the sweet spot the maximalists are actually right about: small, well-scoped, set-and-forget. When something can be built quickly and then left to run without watching, building wins easily — you skip the subscription and you pay almost nothing in the currency that's actually scarce.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When nothing on the market fits well enough.&lt;/strong&gt; &lt;a href="https://getvitamind.app/" rel="noopener noreferrer"&gt;VitaminD Explorer&lt;/a&gt; — my solar vitamin-D calculator — started as a Claude artifact and grew into a real PWA precisely because what I wanted didn't exist in a form I'd have paid for. When the market's answer is "close, but not the thing," the build/buy math stops being about cost at all. You're not undercutting a product; you're filling a gap a product never filled.&lt;/p&gt;

&lt;p&gt;Notice that neither of these is "build because building is free." Free is the &lt;em&gt;precondition&lt;/em&gt; that puts building on the table. It isn't the reason.&lt;/p&gt;

&lt;h2&gt;
  
  
  The trap, and the limits
&lt;/h2&gt;

&lt;p&gt;The flip side is where the maximalist position quietly breaks.&lt;/p&gt;

&lt;p&gt;The trap is everything that &lt;em&gt;doesn't&lt;/em&gt; go silent. A tool that needs babysitting, that breaks in ways you can't predict, that demands a decision every time something upstream moves — that's not a saving, it's a second job you assigned yourself. Here the provider's offer is honest and worth taking: pay us and the maintenance, the drift, the 3 a.m. failure mode become &lt;em&gt;our&lt;/em&gt; problem. For anything that would otherwise sit on your attention indefinitely, that's often the better deal even when you could build it in a weekend. &lt;em&gt;Could&lt;/em&gt; and &lt;em&gt;should&lt;/em&gt; part company exactly here.&lt;/p&gt;

&lt;p&gt;And then there are the plain limits that the "just a subscription" framing waves away. Usage ceilings are real. Some things genuinely shouldn't run unattended, no matter how capable the agent — anything with money, identity, or irreversible consequences on the other end wants a human in the loop, which means it never fully leaves your attention. The reductio — &lt;em&gt;the only software you need is a subscription&lt;/em&gt; — is a useful provocation and a bad operating plan. The subscription is the workshop, not the warehouse.&lt;/p&gt;

&lt;h2&gt;
  
  
  Same logic, at company scale
&lt;/h2&gt;

&lt;p&gt;This isn't only a personal calculus, and the enterprise version is the part that's been talked to death — "the death of SaaS," every company becoming a software company, the great unbundling. I don't have much to add to that discourse except to point out that the axis it usually argues over is the wrong one. The debate fixates on cost and capability: now that we &lt;em&gt;can&lt;/em&gt; build internal tools cheaply, should we stop buying SaaS? But the deciding variable is the same one as in the personal case — attention, just spread across a team instead of a person.&lt;/p&gt;

&lt;p&gt;I've built internal monitoring systems that run on their own, watching logs and doing automated performance and code-quality reviews. Tools like that absolutely exist on the market. The reason to build wasn't a gap — it was a thin layer of customization the off-the-shelf option couldn't match cheaply, on top of something that, once built, mostly runs itself. Low standing attention cost, modest effort, a fit you couldn't buy: the &lt;em&gt;personal&lt;/em&gt; sweet spot, scaled up.&lt;/p&gt;

&lt;p&gt;The same trap scales up too. A company can build something that it then has to staff forever, pulling a team off the actual line of business to babysit internal software — which is just the on-call sprawl problem with a payroll attached. The question a company should ask isn't "can we build this instead of buying it?" It's the same two questions: &lt;strong&gt;who carries the maintenance, and whose attention does it spend?&lt;/strong&gt; A dedicated internal-tools team is sometimes exactly the right answer to that — and sometimes it's the most expensive way a company ever learned it should have just bought the thing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where I land
&lt;/h2&gt;

&lt;p&gt;I build by default now, more than I used to, and I'm not embarrassed about it. Code got cheap; that genuinely changed the math. But the version of "build everything" worth holding is narrower than the slogan: build the things that are trivial enough to leave running, or that the market never built for you — and &lt;em&gt;especially&lt;/em&gt; the ones that, once built, go quiet and stop asking for you.&lt;/p&gt;

&lt;p&gt;For everything else, the most useful thing a paid tool sells isn't the software. It's the right to stop paying attention. That's a real product, and some days it's the only one worth buying.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;A companion to &lt;a href="https://www.javieraguilar.ai/en/blog/human-limits-managing-ai-agents" rel="noopener noreferrer"&gt;Human Limits in Managing AI Agents&lt;/a&gt; and &lt;a href="https://www.javieraguilar.ai/en/blog/results-oriented-programming" rel="noopener noreferrer"&gt;Results-Oriented Programming&lt;/a&gt;. VitaminD Explorer lives at &lt;a href="https://getvitamind.app" rel="noopener noreferrer"&gt;getvitamind.app&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://www.javieraguilar.ai/en/blog/build-vs-buy-attention" rel="noopener noreferrer"&gt;javieraguilar.ai&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Want to see more AI agent projects? Check out my &lt;a href="https://www.javieraguilar.ai" rel="noopener noreferrer"&gt;portfolio&lt;/a&gt; where I showcase multi-agent systems, MCP development, and compliance automation.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>software</category>
      <category>engineering</category>
    </item>
    <item>
      <title>Results-Oriented Programming</title>
      <dc:creator>JaviMaligno</dc:creator>
      <pubDate>Wed, 24 Jun 2026 16:47:14 +0000</pubDate>
      <link>https://dev.to/javieraguilarai/results-oriented-programming-2n35</link>
      <guid>https://dev.to/javieraguilarai/results-oriented-programming-2n35</guid>
      <description>&lt;p&gt;There's a line I dropped almost in passing in &lt;a href="https://www.javieraguilar.ai/en/blog/how-much-should-you-still-know" rel="noopener noreferrer"&gt;a recent piece&lt;/a&gt;, and it's been nagging at me ever since: programming keeps drifting from verifying &lt;em&gt;how&lt;/em&gt; the code works to verifying &lt;em&gt;what&lt;/em&gt; it produces. I want to pull on that thread properly, because it's one of the quieter but more consequential shifts in how this job actually works now.&lt;/p&gt;

&lt;p&gt;To be clear about what's drifting: it isn't that we never checked results before. Tests have always existed, and writing them has always been good practice — even in the long stretches where the industry didn't write nearly as many as it should have. The shift is one of &lt;em&gt;center of gravity&lt;/em&gt;. For most of my career the weight of "reviewing" sat on the implementation: you read the diff, followed the logic, decided whether the &lt;em&gt;path&lt;/em&gt; the code took was sound, and the tests rode alongside as a safety net. The path was the primary artifact you scrutinized.&lt;/p&gt;

&lt;p&gt;That balance is inverting. More and more the result-check is the main event, and I let the path be the model's problem as long as the result holds. Call it results-oriented programming, for lack of a better name. It isn't a methodology I went looking for; it's what the work quietly turned into.&lt;/p&gt;

&lt;p&gt;The interesting part isn't the slogan. It's what "verify the result" turns out to mean, because it means wildly different things depending on what the result &lt;em&gt;is&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The easy end: when the result is a number
&lt;/h2&gt;

&lt;p&gt;The cleanest version is when the output pins to a value. My &lt;a href="https://www.javieraguilar.ai/en/projects/steel-pricing-platform" rel="noopener noreferrer"&gt;pricing system&lt;/a&gt; quotes a number: either it's the right one for that material, origin, and client, or it isn't. I don't read the code that computed it — I keep a golden set the client signed off on and tests that assert the output matches. The &lt;em&gt;oracle&lt;/em&gt; that decides "correct or not" is exact and auditable, no one's judgment in the loop at the moment of truth; Claude can refactor the engine three times over a weekend and I won't care, as long as the set stays green. When you can get here, get here — it's the strongest form of the idea.&lt;/p&gt;

&lt;h2&gt;
  
  
  The hard end: when the result is a behavior
&lt;/h2&gt;

&lt;p&gt;But most of what we build doesn't reduce to a number.&lt;/p&gt;

&lt;p&gt;A rules engine that has to reach the right &lt;em&gt;decision&lt;/em&gt; across a thousand interacting conditions. A classifier sorting a business into a regulatory category. A scheduling routine whose output is "reasonable" or "not" in ways no single assertion captures. An agent choosing, mid-task, whether to ask for clarification or just proceed. A summary that has to be faithful without being a transcript. Some of these are pure deterministic code, some are model-driven, but they share the property that matters here: "the result" is a &lt;em&gt;behavior&lt;/em&gt; or a &lt;em&gt;decision&lt;/em&gt;, and there's no golden number to diff against. Two perfectly good outputs can look nothing alike.&lt;/p&gt;

&lt;p&gt;The temptation is to retreat to the old habit: if I can't pin the output, I'll go back to reading how it got there. And often you can read it. A rules engine's branches are right there; even a model leaves a trail — its reasoning, its tool calls, the intermediate steps. The path isn't the sealed black box it's convenient to call it, and inspecting it is genuinely useful for debugging and for calibrating how much to trust the thing. But it tells you &lt;em&gt;how&lt;/em&gt; it arrived, not &lt;em&gt;that&lt;/em&gt; it arrived somewhere correct. A clean-looking trace can land on a wrong answer and a tangled one on a right answer — which was always true of code, and is precisely why we wrote tests instead of just re-reading the function. At this end of the spectrum, reading the path informs you; it doesn't settle correctness. That still has to happen at the result.&lt;/p&gt;

&lt;p&gt;So results-orientation doesn't break here, it just changes &lt;em&gt;oracle&lt;/em&gt;. Instead of an exact match you build a richer check, and it climbs a ladder of subjectivity: behavioral specs and property tests and a scenario suite for the rules engine; an eval set of representative cases with the calls you'd defend; and for the genuinely judgment-bound outputs, sometimes an LLM scoring against criteria you wrote. I've argued before about &lt;a href="https://www.javieraguilar.ai/en/blog/llm-as-judge-three-decisions" rel="noopener noreferrer"&gt;when an LLM-as-judge is even trustworthy enough to lean on&lt;/a&gt;; this is exactly where it matters, because at the far end your judge &lt;em&gt;is&lt;/em&gt; your verification. The result is still the thing under test — you've just traded a deterministic comparator for a defined-but-subjective one, and the engineering work moves into making that check as principled and repeatable as you can.&lt;/p&gt;

&lt;p&gt;The spectrum is the whole point. From "assert equals 4,200.00" to "a panel of criteria says this triage decision was reasonable," it's the same move at different resolutions: specify the result, build something that checks it, stop policing the path.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this is happening
&lt;/h2&gt;

&lt;p&gt;None of this is a philosophy I adopted. It's a response to the path getting cheap.&lt;/p&gt;

&lt;p&gt;When a model writes the implementation, two things flip at once. The path becomes nearly free to produce — and expensive, sometimes pointless, to read. A 600-line diff that Claude generated in a minute can take me an hour to review line by line, and at the end of that hour I often know &lt;em&gt;less&lt;/em&gt; about whether it's correct than a five-second glance at the test suite would tell me. The economics of attention have inverted: reading the &lt;em&gt;how&lt;/em&gt; no longer scales with the rate at which we produce it.&lt;/p&gt;

&lt;p&gt;Meanwhile the result is the only thing the user or client ever actually touches. They don't experience your control flow; they experience the price, the decision, the summary. Verification migrates to where the value and the risk actually live — the boundary between input and output — because that's the part that didn't get cheaper. Defining what "correct" means is still hard, human work. The model just took over the part in the middle.&lt;/p&gt;

&lt;h2&gt;
  
  
  The nuance that ruins the slogan
&lt;/h2&gt;

&lt;p&gt;Here's where results-oriented programming gets dangerously easy to misread. If all that matters is the output, does the code in the middle matter at all? Can it be a sludge of duplication and clever hacks, as long as the tests pass?&lt;/p&gt;

&lt;p&gt;No. And the reason is sharper than "good engineers care about craft."&lt;/p&gt;

&lt;p&gt;Internal quality — modularity, clear boundaries, security, conventions — still matters, arguably more than before. But the &lt;em&gt;reason&lt;/em&gt; why it matters has shifted, and so has the audience. We used to keep code clean for the humans who'd read it next. Increasingly, the next reader is an agent. Maintainability is drifting from maintainability-for-humans to maintainability-for-agents. A model produces better results, more reliably, in a codebase with clean seams, honest names, and tight modules — for roughly the same reasons a human does: less context to hold, fewer ways to go wrong, clearer places to make a change. Messy internals don't just offend taste; they degrade the very results you're optimizing for, just on a delay. The bad price you ship next quarter is often paid for by the tangle you let the model leave behind in this one.&lt;/p&gt;

&lt;p&gt;So results-orientation isn't "internal quality doesn't matter." It's "internal quality matters &lt;em&gt;because of&lt;/em&gt; its effect on results, not as an end you inspect for its own sake." That's a shift of priority and framing, not a license to abandon it. You stop reviewing the diff for elegance. You don't stop caring whether the system stays workable — you just hold that bar through different means: linters, architecture tests, conventions encoded where the agent will actually see them.&lt;/p&gt;

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

&lt;p&gt;Follow the line far enough and the job starts to look different.&lt;/p&gt;

&lt;p&gt;If verification lives at the boundary, and internal quality is encoded as rules the agent has to respect, then the thing you're really authoring is no longer the implementation at all. It's an &lt;em&gt;objective&lt;/em&gt; plus a set of &lt;em&gt;constraints&lt;/em&gt;: the result you want, and the rules the system must stay inside while reaching it — the stack, the business rules, the security requirements, the conventions, the working definition of "good." Then you hand that to a model inside a good harness and let it optimize toward the objective without stepping outside the constraints.&lt;/p&gt;

&lt;p&gt;That's the logical endpoint of results-oriented programming: you specify the target and the guardrails, and the model searches the path. Your leverage stops being the code you write and becomes the quality of the objective you define and the harness you put around it — the tests, the evals, the judges, the rules, the checkpoints. Get those wrong and a capable model optimizes confidently toward the wrong thing. Get them right and you've built something that improves the result without you touching the middle.&lt;/p&gt;

&lt;p&gt;Which is why I don't think "results-oriented" means "lazy." It means the hard part moved. It used to live in the writing. Now it lives in defining — precisely enough that a machine can be held to it — what you actually wanted, and in building the apparatus that holds it there. The path got cheap. Saying what "right" means got more important than ever.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;A companion to &lt;a href="https://www.javieraguilar.ai/en/blog/how-much-should-you-still-know" rel="noopener noreferrer"&gt;How Much Should You Still Know?&lt;/a&gt;, which ran into this same idea from the angle of delegation and responsibility.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://www.javieraguilar.ai/en/blog/results-oriented-programming" rel="noopener noreferrer"&gt;javieraguilar.ai&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Want to see more AI agent projects? Check out my &lt;a href="https://www.javieraguilar.ai" rel="noopener noreferrer"&gt;portfolio&lt;/a&gt; where I showcase multi-agent systems, MCP development, and compliance automation.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>software</category>
      <category>engineering</category>
      <category>testing</category>
    </item>
    <item>
      <title>How Much Should You Still Know?</title>
      <dc:creator>JaviMaligno</dc:creator>
      <pubDate>Tue, 23 Jun 2026 22:20:02 +0000</pubDate>
      <link>https://dev.to/javieraguilarai/how-much-should-you-still-know-27oh</link>
      <guid>https://dev.to/javieraguilarai/how-much-should-you-still-know-27oh</guid>
      <description>&lt;p&gt;I'm building a &lt;a href="https://www.javieraguilar.ai/en/projects/steel-pricing-platform" rel="noopener noreferrer"&gt;pricing system&lt;/a&gt; for a client right now, and I have to admit something: I couldn't tell you most of how it prices things.&lt;/p&gt;

&lt;p&gt;Not because the code is opaque — I wrote it, or rather Claude and I wrote it. Because the &lt;em&gt;business logic&lt;/em&gt; is enormous. Prices depend on the client, the material, the origin, and a long tail of extras and exceptions that each have their own rules. There are more of these than I can hold in my head, and they change. Every time I hit a gap — a price whose logic I can't find, a case the documentation doesn't cover — Claude Code and I log it in a "pending data" document. That document gets reviewed and sent to the client.&lt;/p&gt;

&lt;p&gt;Here's the part that should bother me more than it does. A few times the client has come back asking me to clarify how something is priced, and to answer I've had to ask Claude first. I'm the external consultant. I don't carry the business knowledge, and honestly I don't think I should try to. Claude has access to all the documentation and is far more reliable at retrieving the right rule than I would be at internalizing the whole thing.&lt;/p&gt;

&lt;p&gt;The instinct is to feel guilty about that. I've decided the guilt is mostly aimed at the wrong question.&lt;/p&gt;

&lt;h2&gt;
  
  
  Offloading knowledge isn't new, and it isn't automatically debt
&lt;/h2&gt;

&lt;p&gt;We've been outsourcing memory for a long time. Most of us stopped memorizing phone numbers two decades ago. We don't memorize facts we can look up; we remember &lt;em&gt;where&lt;/em&gt; to look. Psychologists have a name for it — the "Google effect" — and the sky did not fall. Knowing how to find something is itself a kind of knowledge, and it's often the more useful kind.&lt;/p&gt;

&lt;p&gt;An LLM is the next rung on that ladder. It doesn't just tell you where the answer lives; it retrieves it, applies it to your specific case, and explains it. For something like a sprawling, client-specific pricing rulebook, that's not a degradation of my work. It's the only sane way to do it. Memorizing thousands of volatile rules that belong to someone else's business would be a waste of the one thing I'm actually paid for.&lt;/p&gt;

&lt;p&gt;There's a term going around — "cognitive debt" — borrowed from technical debt, and a much-shared MIT study ("Your Brain on ChatGPT") that measured reduced neural engagement in people who wrote essays with an LLM. It's a useful phrase. But I think it gets applied too broadly. Not all offloading is debt. Buying groceries instead of farming isn't "agricultural debt." The question is never &lt;em&gt;whether&lt;/em&gt; you delegate knowledge. It's how much, when, and — the part everyone skips — what you remain on the hook for.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually changes: responsibility
&lt;/h2&gt;

&lt;p&gt;A student who delegates their learning to an LLM is only hurting themselves. If they don't understand the essay, the cost lands on them, later, privately. That's a clean trade they're free to make.&lt;/p&gt;

&lt;p&gt;I don't have that exit. I'm responsible for a pricing system that will quote real numbers to real customers. If it prices something wrong, "Claude told me" is not an answer I can give the client. The knowledge is delegable. The responsibility is not.&lt;/p&gt;

&lt;p&gt;That reframes the whole thing. The question I kept asking myself — &lt;em&gt;how much of this should I know?&lt;/em&gt; — is the wrong one. The right question is: &lt;strong&gt;do I know enough to answer for it?&lt;/strong&gt; And those are very different bars.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three questions, and where the line actually falls
&lt;/h2&gt;

&lt;p&gt;So when &lt;em&gt;is&lt;/em&gt; it enough to just be able to ask in the moment? What should you know without asking? And how deep does that understanding need to go? Let me answer with the pricing system, because the line falls in a specific place.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The individual data points — ask away.&lt;/strong&gt; What's the surcharge for this material from that origin? I will never hold that in my head, and I shouldn't. It's volatile, it's the client's domain, and the model retrieves it more reliably than I would. Being able to ask in the moment is genuinely sufficient here. This is the layer where delegation is not just acceptable but correct.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The shape of the system — know it without asking.&lt;/strong&gt; What &lt;em&gt;categories&lt;/em&gt; of rules exist, how they compose, which inputs can move a price and which can't, where the dangerous interactions are. I can't recite every surcharge, but I have to know that surcharges-by-origin are a thing that exists, or I won't even know to ask. You can't query a space whose existence you're unaware of. This is the map, and the map is mine to hold.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Deep enough to catch a wrong answer — and honest about what that means.&lt;/strong&gt; This is the real threshold, and it's not about &lt;em&gt;how much&lt;/em&gt; you know but about &lt;em&gt;what kind&lt;/em&gt;. Here's where I have to be honest, though: I can't validate every output by hand, and pretending I could would just be a different flavor of dishonesty. I'm not going to compute a price manually, and often I don't even know what the correct price &lt;em&gt;is&lt;/em&gt; — that's the client's business, not mine. What I can catch directly is the gross stuff: a number that's an order of magnitude off, a rule applied to a case it should never touch, two extras stacking that shouldn't. The basic-logic violations.&lt;/p&gt;

&lt;p&gt;The exact application — the part I can't eyeball — doesn't get validated by my judgment at all. It gets validated by &lt;em&gt;tests&lt;/em&gt;, and by a golden set the client signs off on. That's the move that actually scales: I don't hold the right answer in my head, I hold a &lt;em&gt;mechanism&lt;/em&gt; that checks it. Validation itself gets delegated — but to something deterministic and auditable, not to vibes. It's part of why programming keeps drifting toward being results-oriented: increasingly you specify and verify the behavior, not the path the model took to produce it.&lt;/p&gt;

&lt;p&gt;So the line is a little wider than "can I personally tell it's wrong." It's: &lt;strong&gt;do I have &lt;em&gt;some&lt;/em&gt; way to catch a wrong answer — my own eye for the crude errors, a test or a golden set for the exact ones?&lt;/strong&gt; If I have none of those, I'm not delegating — I'm abdicating. Delegation keeps a human, or a check the human owns, in the loop. Abdication removes both. They look identical right up until the answer is wrong, and then they could not be more different.&lt;/p&gt;

&lt;h2&gt;
  
  
  The quiet symptom: documentation only the agents read
&lt;/h2&gt;

&lt;p&gt;Here's where I notice the line getting blurry in my own work. More and more of the knowledge about my systems lives in files written for machines. &lt;code&gt;CLAUDE.md&lt;/code&gt;. &lt;code&gt;AGENTS.md&lt;/code&gt;. Skills. And in this project, that "pending data" document — half spec, half question-list, written so the agent and I can navigate a domain neither the client nor I have fully written down anywhere else.&lt;/p&gt;

&lt;p&gt;I wrote about this drift from another angle in &lt;a href="https://www.javieraguilar.ai/en/blog/software-dissolving-into-the-model" rel="noopener noreferrer"&gt;Software Is Dissolving Into the Model&lt;/a&gt;: the layer we used to call software is collapsing into instructions on one side and generated output on the other. The flip side of that convenience is this — when documentation is written &lt;em&gt;for the agent&lt;/em&gt;, who's keeping a map for the humans who remain responsible? It's easy to end up with a system that's beautifully legible to Claude and slowly going dark to everyone accountable for it.&lt;/p&gt;

&lt;p&gt;The interesting thing is that my "pending data" document accidentally does the right thing. It's not just context for the agent; it's a human-in-the-loop checkpoint. It forces a moment where a person — me, then the client — has to look at what's missing and decide. That's the pattern worth keeping. The answer to "docs only agents read" probably isn't writing fewer of them. It's deliberately designing the points where a responsible human has to stop and validate, instead of letting the loop close without one.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part nobody wants to admit out loud
&lt;/h2&gt;

&lt;p&gt;There's a social layer to all of this that the technical framing skips. Telling you I sometimes ask Claude before answering my own client costs me something. It brushes right up against impostor syndrome — the quiet fear that the moment people see how the sausage is made, they'll decide I'm not the expert they hired.&lt;/p&gt;

&lt;p&gt;The strange thing is that transparency is the cure for that feeling, not the trigger. Impostor syndrome grows in the gap between the image you project and how you actually work. Saying out loud "I asked the model and then verified the rule applies" closes that gap. There's nothing left to be found out.&lt;/p&gt;

&lt;p&gt;But I won't pretend the cost is imaginary. With some clients you genuinely will come off worse for being open about it — not because you did anything wrong, but because they don't yet understand what good AI use looks like. They hear "I asked Claude" and pattern-match it to "didn't know," when it should pattern-match to "retrieved the authoritative rule and checked it." The irony is sharp: those are often exactly the clients I'm hired to help integrate AI into their own processes. The same gap in understanding that makes transparency risky is the reason the work exists.&lt;/p&gt;

&lt;p&gt;That's why the validation line matters here too, not just technically. Transparency isn't confessing "I don't know anything, Claude does it." It's being precise about &lt;em&gt;what&lt;/em&gt; you delegate and, crucially, that you kept the ability to check it. "I asked Claude and confirmed the rule applies to this case" is a completely different sentence from "I asked Claude." One describes a professional using a tool well. The other describes the thing the client is afraid of. Being able to validate is what makes the honesty defensible — and over time, modeling that distinction is part of how clients learn to tell the two apart.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where I've landed
&lt;/h2&gt;

&lt;p&gt;I'm not going to tell you to delegate less. In the pricing project, delegating the business knowledge is the right call, and pretending otherwise would just make me slower and no more trustworthy. The line I'm drawing isn't between "implementation you can offload" and "knowledge you must keep." It's between knowledge you can still &lt;em&gt;validate&lt;/em&gt; and knowledge you've gone blind to.&lt;/p&gt;

&lt;p&gt;Three things I'm actually doing about it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Hold the map, not the territory.&lt;/strong&gt; Don't memorize the data; do know what categories of things exist and how they interact. You can't ask a question about a space you don't know is there.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Treat "can I check this?" as the real test.&lt;/strong&gt; Before I let the model own a decision, I ask whether I have a &lt;em&gt;way&lt;/em&gt; to catch it being wrong — my own eye for the crude errors, a test or golden set for the exact ones. Note that "check" rarely means "compute it myself"; usually it means owning the mechanism that does. If there's no mechanism at all, that's not delegation, and I either build one or don't ship it unattended.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Design the checkpoints, not just the prompts.&lt;/strong&gt; When knowledge moves into agent-readable docs, add the human moment back on purpose — a review step, a pending-questions doc, a sign-off — so the loop can't silently close without someone accountable in it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The honest answer to "how much should you still know?" turns out not to be a quantity at all. It's a capability: enough to know when the answer is wrong. Everything above that line, ask freely. Below it, you're not saving effort — you're just postponing the moment you find out.&lt;/p&gt;

&lt;p&gt;There's a more radical version of this idea, where instead of trusting the model's judgment you force it to externalize its reasoning into code you can actually read and test. That's the experiment in the next post — delegation you can audit, line by line.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This is the first of two pieces on delegation and understanding. The next one gets concrete: an experiment in making a model write down its world as verifiable code instead of keeping it in its head. Related reading: &lt;a href="https://www.javieraguilar.ai/en/blog/software-dissolving-into-the-model" rel="noopener noreferrer"&gt;Software Is Dissolving Into the Model&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://www.javieraguilar.ai/en/blog/how-much-should-you-still-know" rel="noopener noreferrer"&gt;javieraguilar.ai&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Want to see more AI agent projects? Check out my &lt;a href="https://www.javieraguilar.ai" rel="noopener noreferrer"&gt;portfolio&lt;/a&gt; where I showcase multi-agent systems, MCP development, and compliance automation.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>cognitivedebt</category>
      <category>claude</category>
      <category>software</category>
    </item>
    <item>
      <title>Demos That Wow vs Demos That Work</title>
      <dc:creator>JaviMaligno</dc:creator>
      <pubDate>Wed, 17 Jun 2026 10:01:27 +0000</pubDate>
      <link>https://dev.to/javieraguilarai/demos-that-wow-vs-demos-that-work-3g19</link>
      <guid>https://dev.to/javieraguilarai/demos-that-wow-vs-demos-that-work-3g19</guid>
      <description>&lt;p&gt;I've worked on two teams with opposite philosophies of how to demo to a client, and I still go back and forth on which one is right.&lt;/p&gt;

&lt;p&gt;One builds demos for &lt;strong&gt;impact&lt;/strong&gt;. Everything is mocked: the screens are polished, the flow is choreographed, and the result that lands on screen is the best possible version of itself. The backend isn't really doing the work — and crucially, the AI isn't really being called. It's a beautiful film of the product.&lt;/p&gt;

&lt;p&gt;The other builds &lt;strong&gt;functional proof-of-concepts&lt;/strong&gt;. As much as possible is real and wired up — real services, real model calls, real data — and then you iterate on top of it. The first version is rougher and less choreographed, but what you're looking at is actually happening.&lt;/p&gt;

&lt;p&gt;Both "work" in the sense that both can win a room. They just optimise for different things, and the gap between them gets a lot wider the moment AI is involved.&lt;/p&gt;

&lt;h2&gt;
  
  
  The case for the mock
&lt;/h2&gt;

&lt;p&gt;It's easy to be snobby about mocked demos, so let me steelman them first, because the reasons are good ones.&lt;/p&gt;

&lt;p&gt;A mock is &lt;strong&gt;fast&lt;/strong&gt;. You're not blocked on infrastructure, model access, data pipelines, or the ten unglamorous things that have to exist before a real flow runs end to end. You can demo a product that doesn't exist yet.&lt;/p&gt;

&lt;p&gt;A mock is &lt;strong&gt;controlled&lt;/strong&gt;. Demos fail in stupid, memorable ways — a timeout, a rate limit, a model having a bad day in front of the one person you needed to impress. A mock removes that variance. The story you rehearsed is the story they see.&lt;/p&gt;

&lt;p&gt;And a mock sells the &lt;strong&gt;vision&lt;/strong&gt;, not the current state. Early on, what you're really validating is desire: do people &lt;em&gt;want&lt;/em&gt; this? A crisp mock answers that question without you having to build the thing first. For a non-technical stakeholder deciding whether to fund the next phase, a polished mock can be exactly the right artifact.&lt;/p&gt;

&lt;p&gt;None of that is dishonest. It's a legitimate strategy with real upsides.&lt;/p&gt;

&lt;h2&gt;
  
  
  The case for the functional PoC
&lt;/h2&gt;

&lt;p&gt;The functional PoC gives up some polish and a lot of control in exchange for one thing: &lt;strong&gt;what you show is true&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That truth compounds. A functional PoC isn't thrown away after the meeting — it's the first commit of the product. You iterate on it instead of rebuilding from a slide deck. The feedback you get is real feedback, because people are reacting to real behaviour, not to your best-case storyboard. And the hard parts surface &lt;em&gt;now&lt;/em&gt;, while they're cheap, instead of after a contract is signed and the timeline is fixed.&lt;/p&gt;

&lt;p&gt;I've felt this directly. In one live demo, someone asked the assistant about errors that had occurred, and its answer blended what had gone &lt;em&gt;well&lt;/em&gt; in with what had gone wrong. The person watching reacted on the spot: this needs to be more concise. In the same session, someone asked whether it could do a particular thing that — because of an internal constraint — it simply couldn't. Two concrete pieces of feedback and two new tickets, in the span of one demo. With a mock, you can't even attempt those questions: the script answers what it was scripted to, and nothing real is being tested.&lt;/p&gt;

&lt;p&gt;It's slower to first wow. But it never has to walk anything back.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where AI widens the gap
&lt;/h2&gt;

&lt;p&gt;For ordinary software, the distance between a mock and the real thing is mostly &lt;strong&gt;polish&lt;/strong&gt;: the real version will be a bit slower, a bit less pretty, a few edge cases will misbehave. Manageable.&lt;/p&gt;

&lt;p&gt;With AI, the distance is &lt;strong&gt;substance&lt;/strong&gt; — because a mock hides the two properties that define how the product will actually feel:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Latency.&lt;/strong&gt; In a mock, the answer appears instantly (or after a scripted, tasteful little spinner). In reality, the model call takes time — sometimes a second, sometimes ten, sometimes longer if it reasons or chains tool calls. "Instant" is the single easiest thing to fake and one of the hardest to deliver. I've &lt;a href="https://www.javieraguilar.ai/en/blog/ag-ui-third-protocol" rel="noopener noreferrer"&gt;written before&lt;/a&gt; about a demo where the real fix wasn't UI at all — it was lowering the model's reasoning effort so responses came back in ~1.5s instead of 10–20s. You only discover that when the model is actually in the loop.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Non-determinism.&lt;/strong&gt; A mock always returns the perfect answer, because someone wrote it. The real model returns &lt;em&gt;a&lt;/em&gt; answer — usually good, sometimes wrong, occasionally confidently wrong, and different each time. The mock demonstrates a certainty the product doesn't have. Everything I keep harping on about &lt;a href="https://www.javieraguilar.ai/en/blog/llm-as-judge-three-decisions" rel="noopener noreferrer"&gt;evaluating LLM outputs&lt;/a&gt; exists precisely because real results vary and you have to measure that variance. A mock makes it disappear.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So when you demo AI with a mock, you're not just smoothing over rough edges. You're selling away the two risks the project actually has. The client falls in love with an instant, always-correct assistant, and then the team has to build something that is neither of those by default. That gap doesn't close itself — someone pays for it later, usually in eroded trust during the build.&lt;/p&gt;

&lt;h2&gt;
  
  
  So which one?
&lt;/h2&gt;

&lt;p&gt;Honestly, it depends on what you're trying to learn from the demo:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If you're validating &lt;strong&gt;desire&lt;/strong&gt; — "would anyone want this?" — and talking to people who think in outcomes, not architectures, a mock is often the efficient, correct tool. Don't build a backend to answer a marketing question.&lt;/li&gt;
&lt;li&gt;If you're validating &lt;strong&gt;feasibility&lt;/strong&gt; — "can we actually build this, and will it feel good?" — a mock answers the wrong question convincingly. That's where you want the real thing wired up.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;My own bias leans toward the functional PoC, and it's leaned further the more I work with AI — because with AI the risk lives exactly in what the mock paints over. A functional PoC doesn't have to be ugly or slow to build, either: the &lt;a href="https://www.javieraguilar.ai/en/blog/ag-ui-third-protocol" rel="noopener noreferrer"&gt;AG-UI demo&lt;/a&gt; I put together recently is real end to end — real model, real latency, real rendered widgets — and it's about a hundred lines. The "real is too expensive to demo" assumption is often less true than it looks.&lt;/p&gt;

&lt;p&gt;But I hold it loosely. A mock that honestly sells a vision, followed by a team that closes the gap, is a perfectly good way to build a company. The failure mode isn't mocking — it's mocking the risky parts and then quietly hoping reality will cooperate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Or run both
&lt;/h2&gt;

&lt;p&gt;It doesn't have to be binary. I've worked on projects that ran both at once: a &lt;strong&gt;mocked path&lt;/strong&gt; to guarantee a clean end-to-end walkthrough — the story you can always tell without something breaking mid-meeting — and the &lt;strong&gt;real system alongside it&lt;/strong&gt;, to probe live with different cases. The mock de-risks the narrative; the real part invites the hard questions. You get the controlled wow and the honest feedback in the same session, as long as everyone in the room knows which half is which.&lt;/p&gt;

&lt;h2&gt;
  
  
  The question I'd open up
&lt;/h2&gt;

&lt;p&gt;Maybe the real question isn't "mock or functional." It's &lt;strong&gt;which moment you're optimising for&lt;/strong&gt;: the signature at the end of the demo, or the trust at the end of the first sprint. Sometimes those point the same way. With AI, more often than I'd like, they don't.&lt;/p&gt;

&lt;p&gt;How does your team demo — and have you ever been bitten by the gap between the demo and the thing you shipped? I'd genuinely like to hear it.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Related: &lt;a href="https://www.javieraguilar.ai/en/blog/ag-ui-third-protocol" rel="noopener noreferrer"&gt;When the Chat Builds Its Own Interface&lt;/a&gt; (a functional demo, end to end) and &lt;a href="https://www.javieraguilar.ai/en/blog/llm-as-judge-three-decisions" rel="noopener noreferrer"&gt;LLM-as-Judge Is Three Decisions&lt;/a&gt; (on measuring the variance a mock hides).&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://www.javieraguilar.ai/en/blog/demos-that-wow-vs-demos-that-work" rel="noopener noreferrer"&gt;javieraguilar.ai&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Want to see more AI agent projects? Check out my &lt;a href="https://www.javieraguilar.ai" rel="noopener noreferrer"&gt;portfolio&lt;/a&gt; where I showcase multi-agent systems, MCP development, and compliance automation.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>product</category>
      <category>engineering</category>
      <category>demos</category>
    </item>
    <item>
      <title>When the Chat Builds Its Own Interface</title>
      <dc:creator>JaviMaligno</dc:creator>
      <pubDate>Tue, 16 Jun 2026 10:49:02 +0000</pubDate>
      <link>https://dev.to/javieraguilarai/when-the-chat-builds-its-own-interface-8gd</link>
      <guid>https://dev.to/javieraguilarai/when-the-chat-builds-its-own-interface-8gd</guid>
      <description>&lt;p&gt;At &lt;a href="https://lu.ma/hn9dhq7e" rel="noopener noreferrer"&gt;AI Signals x LangChain Community London #32&lt;/a&gt;, &lt;a href="https://www.linkedin.com/in/sof%C3%ADa-s%C3%A1nchez-z%C3%A1rate-91493381/" rel="noopener noreferrer"&gt;Sofía Sánchez-Zárate&lt;/a&gt; from CopilotKit gave a talk titled &lt;em&gt;"AG-UI &amp;amp; The Generative UI Spectrum."&lt;/em&gt; The demo that stuck with me: you ask for help working out a loan repayment, and instead of answering with a single number — one you'd have to ask it to recompute every time you change the rate or the term — it renders an actual working calculator &lt;em&gt;inside the chat&lt;/em&gt;. Inputs you can edit, a total that updates live. Not a screenshot of one; a real widget the agent assembled for that question, on the fly.&lt;/p&gt;

&lt;p&gt;That is generative UI: the chat stops being a text box and becomes a surface the agent can compose.&lt;/p&gt;

&lt;h2&gt;
  
  
  I've already been building this — by hand
&lt;/h2&gt;

&lt;p&gt;The reason the talk landed is that I've been collaborating on a conversational KYC project that does exactly this. The agent doesn't answer onboarding questions with paragraphs. When it needs a beneficial-ownership structure, it renders a form to add people and their relationship to the company. When it has extracted data from an uploaded document, it shows an editable confirmation panel, not a wall of text. It renders file uploads, a signature pad, a shareholder chart — all &lt;em&gt;in the conversation&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Under the hood, the pattern is straightforward. The agent (built on LangGraph) pauses with a typed &lt;code&gt;interrupt&lt;/code&gt; — a payload whose &lt;code&gt;type&lt;/code&gt; is &lt;code&gt;person_form&lt;/code&gt;, &lt;code&gt;confirm_summary&lt;/code&gt;, &lt;code&gt;signature_pad&lt;/code&gt;, &lt;code&gt;shareholder_chart&lt;/code&gt;, and so on. The React front end keeps a registry that maps each &lt;code&gt;type&lt;/code&gt; to a component, renders it, lets the user interact, and sends the result back so the graph resumes from exactly where it paused.&lt;/p&gt;

&lt;p&gt;It works really well. It is also entirely homegrown and welded to our stack: our interrupt format, our widget registry, our front end. Which is precisely why Sofía's talk hit — &lt;strong&gt;what we built vertically by hand is the thing AG-UI standardises.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What AG-UI actually is
&lt;/h2&gt;

&lt;p&gt;AG-UI (Agent-User Interaction) is a lightweight, event-based protocol for the channel between an agentic backend and the front end a human looks at. Instead of returning one final text blob, the agent emits a &lt;em&gt;stream of typed events&lt;/em&gt; the UI renders as they arrive:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Lifecycle&lt;/strong&gt; — &lt;code&gt;RunStarted&lt;/code&gt;, &lt;code&gt;RunFinished&lt;/code&gt;, &lt;code&gt;RunError&lt;/code&gt;, with optional &lt;code&gt;StepStarted&lt;/code&gt;/&lt;code&gt;StepFinished&lt;/code&gt; pairs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Text messages&lt;/strong&gt; — streamed token by token.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tool calls&lt;/strong&gt; — so the UI can show what the agent is doing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;State&lt;/strong&gt; — snapshots plus deltas to keep front end and agent in sync.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Our &lt;code&gt;interrupt&lt;/code&gt;-plus-registry mechanism is a private dialect of this. AG-UI is the attempt to make it a shared language — so a conversational backend isn't permanently married to one bespoke front end, and so human-in-the-loop (pause, let the user confirm or edit or sign, resume) is a native primitive rather than something you wire up yourself.&lt;/p&gt;

&lt;h2&gt;
  
  
  The generative UI spectrum
&lt;/h2&gt;

&lt;p&gt;The framing I found most useful was the &lt;em&gt;spectrum&lt;/em&gt; — how much of the interface the model dictates, from least to most:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Typed components.&lt;/strong&gt; The agent picks from a fixed catalogue of front-end components and fills them with data. This is exactly what our KYC widgets are: the model doesn't invent the form, it chooses &lt;code&gt;person_form&lt;/code&gt; and populates it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Declarative UI.&lt;/strong&gt; The agent emits a description of the interface and the client renders it. &lt;strong&gt;A2UI&lt;/strong&gt; (Google's spec) streams a UI tree as JSONL — a serialised layout of components and bindings the client builds, rather than a fixed catalogue the client already has. &lt;strong&gt;Open-JSON-UI&lt;/strong&gt; (OpenAI) is the same idea from the other camp; it's the lineage behind the &lt;strong&gt;interactive charts ChatGPT has started rendering inline&lt;/strong&gt; — an equivalent protocol, though not strictly the one CopilotKit proposes. (Worth clearing up a common confusion: A2UI is &lt;em&gt;not&lt;/em&gt; AG-UI. A2UI is one UI representation; AG-UI is the transport that can carry it.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Iframe-embedded.&lt;/strong&gt; &lt;strong&gt;MCP-UI&lt;/strong&gt; ships self-contained UI inside a sandboxed frame.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generated pixels.&lt;/strong&gt; At the far end, the model renders the interface itself — the territory I explored in &lt;a href="https://www.javieraguilar.ai/en/blog/software-dissolving-into-the-model" rel="noopener noreferrer"&gt;Software Is Dissolving Into the Model&lt;/a&gt;, where Flipbook and Project Genie generate every frame with no DOM at all.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;AG-UI's bet is to be the neutral pipe across that whole range: standardise the channel, and let the representation slide from typed → declarative → generated without rewriting the backend each time. The generated-pixel end is where I'd previously argued software is heading; generative UI is the pragmatic middle of the same line — structured enough to be reliable, generated enough to be flexible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why forms are the killer case
&lt;/h2&gt;

&lt;p&gt;Sofía pointed out that conversational AI over &lt;strong&gt;forms&lt;/strong&gt; is where this shines — and that DocuSign, whose entire business is documents, agreements and signatures, has a partnership with CopilotKit. That clicked immediately, because forms are exactly where plain text falls apart.&lt;/p&gt;

&lt;p&gt;A conversation is a great way to &lt;em&gt;gather&lt;/em&gt; information and a terrible way to &lt;em&gt;structure&lt;/em&gt; it. "List every shareholder with more than 25%, their nationality, and their relationship to the directors" is painful as a back-and-forth of messages and trivial as a small rendered table you fill in. Confirming fields pulled from a passport, drawing a signature, reviewing an ownership tree — none of that wants paragraphs; it wants widgets. That's the whole premise of the KYC project: replace the rigid step-by-step wizard with a conversation that still produces structured, validated, signable data. Generative UI is what makes the conversation and the structure the same surface.&lt;/p&gt;

&lt;h2&gt;
  
  
  What standardising would buy
&lt;/h2&gt;

&lt;p&gt;Our homegrown protocol works, but it's coupled. A standard like AG-UI would let the same conversational backend talk to any front end that speaks it, mix in declarative UI specs where a fixed widget catalogue is too rigid, and get human-in-the-loop pauses as a first-class part of the protocol instead of a convention we maintain. The cost is the usual one for adopting a young standard: betting on its event model and its trajectory.&lt;/p&gt;

&lt;p&gt;Given that LangGraph — which our project already runs on — is among the frameworks with AG-UI integration, the migration path from "our dialect" to "the standard" is unusually short. That makes it a real option, not a rewrite.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trying it: from hand-built pattern to SDK
&lt;/h2&gt;

&lt;p&gt;To feel the difference between my bespoke version and the standard, I built a tiny demo with the AG-UI SDK — a &lt;a href="https://ai.pydantic.dev/" rel="noopener noreferrer"&gt;Pydantic AI&lt;/a&gt; agent on Azure OpenAI, a CopilotKit React frontend, and the two widgets declared as &lt;em&gt;frontend tools&lt;/em&gt;. Ask it to estimate a loan repayment and it renders an interactive calculator in the chat; say you want to apply and it renders a form. The Python side is essentially "wire a model to AG-UI" — the generative UI lives on the client, in about a hundred lines total.&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%2Fwww.javieraguilar.ai%2Fblog%2Fag-ui-generative-ui-demo.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.javieraguilar.ai%2Fblog%2Fag-ui-generative-ui-demo.gif" alt="The AG-UI demo: the agent renders an interactive loan calculator, then an application form, right inside the chat" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's the whole point in one screen: the same loop as the KYC project — model picks a widget, client renders it, the user interacts, the result flows back — but the wiring is a protocol instead of a registry I maintain by hand. &lt;a href="https://github.com/JaviMaligno/agui-generative-ui-demo" rel="noopener noreferrer"&gt;Code on GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The middle of the spectrum
&lt;/h2&gt;

&lt;p&gt;The honest takeaway is that generative UI answers a question I left open in &lt;em&gt;Software Is Dissolving Into the Model&lt;/em&gt;: if the rendered surface is drifting from hand-coded components toward model-generated pixels, where do you stand &lt;em&gt;today&lt;/em&gt;, on a product that has to actually work? The answer is the middle of the spectrum — let the model choose and compose structured UI, keep the rendering reliable — and a protocol like AG-UI is what lets you pick your spot on that line and move along it later. I've been standing there by hand for months. It's good to see it getting a name.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Inspired by &lt;a href="https://www.linkedin.com/in/sof%C3%ADa-s%C3%A1nchez-z%C3%A1rate-91493381/" rel="noopener noreferrer"&gt;Sofía Sánchez-Zárate&lt;/a&gt;'s talk "AG-UI &amp;amp; The Generative UI Spectrum" at &lt;a href="https://lu.ma/hn9dhq7e" rel="noopener noreferrer"&gt;AI Signals x LangChain Community London #32&lt;/a&gt;. Protocol details: the &lt;a href="https://docs.ag-ui.com/introduction" rel="noopener noreferrer"&gt;AG-UI docs&lt;/a&gt; and the &lt;a href="https://dojo.ag-ui.com/" rel="noopener noreferrer"&gt;AG-UI Dojo&lt;/a&gt;. Related reading: &lt;a href="https://www.javieraguilar.ai/en/blog/software-dissolving-into-the-model" rel="noopener noreferrer"&gt;Software Is Dissolving Into the Model&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://www.javieraguilar.ai/en/blog/ag-ui-third-protocol" rel="noopener noreferrer"&gt;javieraguilar.ai&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Want to see more AI agent projects? Check out my &lt;a href="https://www.javieraguilar.ai" rel="noopener noreferrer"&gt;portfolio&lt;/a&gt; where I showcase multi-agent systems, MCP development, and compliance automation.&lt;/p&gt;

</description>
      <category>aiagents</category>
      <category>generativeui</category>
      <category>agui</category>
      <category>conversationalai</category>
    </item>
    <item>
      <title>LLM-as-Judge Is Three Decisions</title>
      <dc:creator>JaviMaligno</dc:creator>
      <pubDate>Sun, 14 Jun 2026 15:47:47 +0000</pubDate>
      <link>https://dev.to/javieraguilarai/llm-as-judge-is-three-decisions-2mkc</link>
      <guid>https://dev.to/javieraguilarai/llm-as-judge-is-three-decisions-2mkc</guid>
      <description>&lt;p&gt;Last week I went to &lt;a href="https://lu.ma/hn9dhq7e" rel="noopener noreferrer"&gt;AI Signals x LangChain Community London #32&lt;/a&gt;, and one idea from &lt;a href="https://www.linkedin.com/in/bilge-aksu-90595837/" rel="noopener noreferrer"&gt;Bilge Aksu&lt;/a&gt;'s talk on evaluation has been rattling around my head since. She put it on a slide that read &lt;strong&gt;"Context comes first"&lt;/strong&gt; — with a subtitle I keep quoting: &lt;em&gt;you can't even choose the right unit or the right dimension until you know the context.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That sentence reframed how I think about LLM-as-judge. We tend to treat "use an LLM to grade outputs" as a prompt-engineering problem: write a good rubric, ask for a score from 1 to 10, parse the number. But a judge that produces a number is not the same as a judge that produces a &lt;em&gt;useful&lt;/em&gt; number. The useful part is decided long before the prompt — in three choices that most people make implicitly and therefore make badly.&lt;/p&gt;

&lt;h2&gt;
  
  
  The trap: a score that nobody validated
&lt;/h2&gt;

&lt;p&gt;Here is the failure mode Bilge described that landed hardest for me. Your judge says a conversation was "good" — but the user never came back. Or it flags a conversation as "bad" — and that user is your most loyal returning customer. The judge's criteria and the actual signal from users point in opposite directions.&lt;/p&gt;

&lt;p&gt;When that happens, the instinct is to tweak the rubric. The real problem is usually upstream: the judge was scoring the wrong &lt;strong&gt;unit&lt;/strong&gt;, on the wrong &lt;strong&gt;dimension&lt;/strong&gt;, with too little &lt;strong&gt;context&lt;/strong&gt; to know better. A 1-to-10 "overall quality" score hides all three mistakes behind one confident-looking integer.&lt;/p&gt;

&lt;p&gt;So before writing a single line of judge prompt, I now force myself through three decisions, in order.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decision 1 — Context: the situation, decided first
&lt;/h2&gt;

&lt;p&gt;Context is &lt;em&gt;what the judge gets to see&lt;/em&gt;, and it is logically prior to everything else. Who is the user? What kind of request is this? What information was actually available when the answer was produced?&lt;/p&gt;

&lt;p&gt;A judge with the wrong context will be confidently wrong. If I ask "was this diagnosis correct?" but only show the judge the final chat message — not the logs, not the tool outputs the agent actually saw — then the judge is grading prose, not correctness. It will reward a fluent, plausible answer over a terse, right one.&lt;/p&gt;

&lt;p&gt;Getting context right means deciding deliberately: does the judge see the original question, the retrieved documents, the tool calls and their results, the full thread? Each of those is a knob, and leaving them at the default ("just the output") is how you end up with scores that correlate with verbosity instead of truth.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decision 2 — Unit: a turn, a conversation, or across sessions
&lt;/h2&gt;

&lt;p&gt;The unit is &lt;em&gt;what you score&lt;/em&gt;. The same system can be evaluated at three very different granularities, and they answer different questions:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Unit&lt;/th&gt;
&lt;th&gt;What it answers&lt;/th&gt;
&lt;th&gt;Good for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Turn&lt;/strong&gt; (one-shot)&lt;/td&gt;
&lt;td&gt;Was this single response correct?&lt;/td&gt;
&lt;td&gt;Questions with a clear, checkable answer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Conversation&lt;/strong&gt; (thread)&lt;/td&gt;
&lt;td&gt;Did the whole interaction reach the goal?&lt;/td&gt;
&lt;td&gt;Iterative, multi-step problem-solving&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Session / across sessions&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Did the user's underlying problem get solved over time?&lt;/td&gt;
&lt;td&gt;Retention, trust, real-world outcome&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This distinction became concrete when I started evaluating my &lt;a href="https://www.javieraguilar.ai/en/projects/devops-slack-bot/" rel="noopener noreferrer"&gt;production DevOps agent&lt;/a&gt; — a Slack bot that triages infrastructure questions by querying real tools (Kubernetes, cloud CLIs, CI/CD, logs).&lt;/p&gt;

&lt;p&gt;Some of its requests are genuinely one-shot: &lt;em&gt;"where do I find the logs for service X?"&lt;/em&gt; has a right answer, and a turn-level judge is the correct unit — did it point to the right place, yes or no. Others are iterative: a 500-error investigation that takes ten reasoning rounds and a dozen tool calls. Scoring only the final turn there throws away the most important information — &lt;em&gt;how&lt;/em&gt; it got there.&lt;/p&gt;

&lt;p&gt;And a third category isn't really a quality judgment at all. Tool-call count, tool-call &lt;em&gt;type&lt;/em&gt;, token usage, number of reasoning rounds, wall-clock time — these are efficiency metrics. They belong on their own axis, not blended into a "quality" score. Conflating "was it right" with "was it cheap" is one of the fastest ways to produce a meaningless number.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decision 3 — Dimension: which aspect of "good"
&lt;/h2&gt;

&lt;p&gt;The dimension is &lt;em&gt;which property you are measuring&lt;/em&gt;. "Good" is not one thing — it's accuracy, helpfulness, warmth, safety, conciseness, and a dozen others, and they trade off against each other. A single judge that scores "overall good" is silently averaging incompatible properties.&lt;/p&gt;

&lt;p&gt;The fix is boring and effective: one judge, one dimension, ideally a binary verdict plus a short written critique rather than a number on a scale. "Is this factually correct? yes/no, and why" gives you something you can act on and audit. "Rate the quality 1–10" gives you noise with a decimal point.&lt;/p&gt;

&lt;h2&gt;
  
  
  Putting it together: grading the DevOps agent
&lt;/h2&gt;

&lt;p&gt;When I validated the DevOps agent against real support history, the three decisions made the evaluation legible instead of hand-wavy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Context:&lt;/strong&gt; I took real requests from an internal support channel and gave the judge the original human resolution thread &lt;em&gt;alongside&lt;/em&gt; the bot's answer — so it was comparing like with like, not grading the bot in a vacuum.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Unit:&lt;/strong&gt; per request (turn-level for the one-shot ones, thread-level for the investigations), plus a separate efficiency track.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dimension:&lt;/strong&gt; correctness first (is the answer right?), then thoroughness (did it find what the human found — or more?), with efficiency kept on its own axis.&lt;/p&gt;

&lt;p&gt;The headline numbers, across ten real cases:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&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;Correct responses&lt;/td&gt;
&lt;td&gt;10/10 (100%)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Better than the human resolution&lt;/td&gt;
&lt;td&gt;6/10 (60%)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Worse than the human&lt;/td&gt;
&lt;td&gt;0/10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Average response time&lt;/td&gt;
&lt;td&gt;~2.5 min vs hours/days for the human cycle&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;



&lt;p&gt;Then I shipped a persistent memory system and re-ran the &lt;em&gt;same&lt;/em&gt; ten cases as a before/after benchmark — and this is where keeping efficiency on its own axis paid off. Quality (correctness) stayed flat at 100%, which is exactly what you want: the change wasn't supposed to make answers &lt;em&gt;more correct&lt;/em&gt;, it was supposed to make them &lt;em&gt;cheaper to produce&lt;/em&gt;. On that axis the gain was clear — total time dropped 57%, and one "known-answer" case went from 16 tool calls to 2 (about 7× faster) because the bot remembered the answer instead of re-investigating. Two cases didn't improve, and one actually over-investigated — which the efficiency metrics surfaced immediately, where a blended "quality" score would have hidden it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The dimension you forgot to validate
&lt;/h2&gt;

&lt;p&gt;Now back to Bilge's trap, because my own numbers contain it. "Better than the human in 60% of cases" — better on &lt;em&gt;which dimension&lt;/em&gt;, and &lt;em&gt;who decided&lt;/em&gt;? I decided, reading threads, using my own judgment as the rubric. That's a perfectly good starting point. It is not the same as the dimension that actually matters in production: did the person who asked get unblocked and trust the answer enough to act on it?&lt;/p&gt;

&lt;p&gt;That gap — between the criterion your judge optimizes and the outcome your users actually experience — is the whole game. The three decisions don't close it for you. What they do is make it &lt;em&gt;visible&lt;/em&gt;: when you've named your context, unit, and dimension explicitly, you can point at exactly which one is drifting from reality, instead of staring at a single number and wondering why it disagrees with your retention chart.&lt;/p&gt;

&lt;h2&gt;
  
  
  A checklist before you write the judge prompt
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Context&lt;/strong&gt; — What exactly does the judge see? Does it have everything a fair grader would need, and nothing that just rewards fluency?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unit&lt;/strong&gt; — Am I scoring a turn, a conversation, or a session? Does that match the question I'm actually asking?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dimension&lt;/strong&gt; — One property per judge. Is "efficiency" sneaking into my "quality" score?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verdict shape&lt;/strong&gt; — Binary + critique over a 1–10 scale, wherever I can.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Validation&lt;/strong&gt; — Have I checked the judge against a real-world signal (human labels, retention, did-they-come-back) — or am I trusting a number nobody validated?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The prompt is the last 10% of building a good eval. The first 90% is deciding what you're measuring, on what, with what in view. Context comes first.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Inspired by &lt;a href="https://www.linkedin.com/in/bilge-aksu-90595837/" rel="noopener noreferrer"&gt;Bilge Aksu&lt;/a&gt;'s talk at &lt;a href="https://lu.ma/hn9dhq7e" rel="noopener noreferrer"&gt;AI Signals x LangChain Community London #32&lt;/a&gt;. The DevOps agent it's applied to is &lt;a href="https://www.javieraguilar.ai/en/blog/building-a-production-devops-agent" rel="noopener noreferrer"&gt;described here&lt;/a&gt;. Further reading: &lt;a href="https://hamel.dev/blog/posts/llm-judge/" rel="noopener noreferrer"&gt;Hamel Husain's guide to LLM-as-judge&lt;/a&gt; and the &lt;a href="https://arxiv.org/abs/2306.05685" rel="noopener noreferrer"&gt;MT-Bench paper&lt;/a&gt; that kicked off the field.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://www.javieraguilar.ai/en/blog/llm-as-judge-three-decisions" rel="noopener noreferrer"&gt;javieraguilar.ai&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Want to see more AI agent projects? Check out my &lt;a href="https://www.javieraguilar.ai" rel="noopener noreferrer"&gt;portfolio&lt;/a&gt; where I showcase multi-agent systems, MCP development, and compliance automation.&lt;/p&gt;

</description>
      <category>aiagents</category>
      <category>evals</category>
      <category>llm</category>
      <category>observability</category>
    </item>
    <item>
      <title>Building a Production DevOps Agent: From Slack to Kubernetes</title>
      <dc:creator>JaviMaligno</dc:creator>
      <pubDate>Thu, 30 Apr 2026 09:37:13 +0000</pubDate>
      <link>https://dev.to/javieraguilarai/building-a-production-devops-agent-from-slack-to-kubernetes-33d1</link>
      <guid>https://dev.to/javieraguilarai/building-a-production-devops-agent-from-slack-to-kubernetes-33d1</guid>
      <description>&lt;p&gt;At &lt;a href="https://simplekyc.com" rel="noopener noreferrer"&gt;SimpleKYC&lt;/a&gt;, the &lt;code&gt;#devops_requests&lt;/code&gt; Slack channel is where every team goes when something breaks — or when they think something might be about to break. Pod stuck in CrashLoopBackOff? Pipeline failed? 502 from a service that was fine yesterday? It all lands there.&lt;/p&gt;

&lt;p&gt;A colleague analyzed the channel and found that &lt;strong&gt;70% of requests could be self-resolved&lt;/strong&gt; by querying the same tools the DevOps team uses: kubectl, Azure CLI, Bitbucket pipelines, health checks. The knowledge wasn't secret — it was just scattered across terminals, runbooks, and people's heads.&lt;/p&gt;

&lt;p&gt;We were already using Claude Code to investigate issues ourselves. The next step was obvious: put an agent in the channel that could do the same thing, for everyone, automatically.&lt;/p&gt;

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

&lt;p&gt;The bot is a generic LLM agent — not a collection of pre-built workflows. When someone mentions &lt;code&gt;@DevOpsBot&lt;/code&gt; in a thread, &lt;a href="https://platform.openai.com/docs/models" rel="noopener noreferrer"&gt;GPT-5.4&lt;/a&gt; (via Azure OpenAI) decides which tools to call, in what order, with what arguments. It can chain up to 20 tool calls per request, building up context as it goes.&lt;/p&gt;

&lt;p&gt;This matters. A pod crash investigation might require:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;kubectl get pods&lt;/code&gt; to find the failing pod&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;kubectl describe pod&lt;/code&gt; to check events&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;kubectl logs&lt;/code&gt; to read the error&lt;/li&gt;
&lt;li&gt;A Bitbucket API call to check the last deployment pipeline&lt;/li&gt;
&lt;li&gt;A health check on the service endpoint&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;No pre-built workflow can anticipate every combination. The LLM constructs the investigation path dynamically, adapting based on what each tool returns.&lt;/p&gt;

&lt;h3&gt;
  
  
  Six Tools, All Read-Only
&lt;/h3&gt;

&lt;p&gt;The bot has access to six tools:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;What it does&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;&lt;strong&gt;kubectl&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Kubernetes diagnostics across 9 AKS clusters&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;get pods&lt;/code&gt;, &lt;code&gt;describe&lt;/code&gt;, &lt;code&gt;logs&lt;/code&gt;, &lt;code&gt;top&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;az cli&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Azure resource queries across 5 subscriptions&lt;/td&gt;
&lt;td&gt;VM status, Postgres info, storage, APIM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Bitbucket&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;CI/CD pipeline status, repo content, config files&lt;/td&gt;
&lt;td&gt;Pipeline logs, &lt;code&gt;values.yaml&lt;/code&gt;, branch diffs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Health check&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;HTTP endpoint testing&lt;/td&gt;
&lt;td&gt;Status codes, response times, body preview&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Jira&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Issue search and context&lt;/td&gt;
&lt;td&gt;JQL queries, ticket details, comments&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Confluence&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Documentation search&lt;/td&gt;
&lt;td&gt;Runbooks, architecture docs, onboarding guides&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Every tool is &lt;strong&gt;strictly read-only&lt;/strong&gt;. kubectl blocks &lt;code&gt;delete&lt;/code&gt;, &lt;code&gt;apply&lt;/code&gt;, &lt;code&gt;patch&lt;/code&gt;, &lt;code&gt;scale&lt;/code&gt;, &lt;code&gt;exec&lt;/code&gt;, and &lt;code&gt;edit&lt;/code&gt;. Azure CLI only allows &lt;code&gt;show&lt;/code&gt; and &lt;code&gt;list&lt;/code&gt;. There's no way for the agent to modify infrastructure — by design.&lt;/p&gt;

&lt;h2&gt;
  
  
  Read-Only First: A Deliberate Choice
&lt;/h2&gt;

&lt;p&gt;This wasn't a technical limitation — it was a trust-building strategy. The agent's first job is to prove it can &lt;em&gt;understand&lt;/em&gt; the infrastructure correctly before anyone considers letting it &lt;em&gt;change&lt;/em&gt; anything.&lt;/p&gt;

&lt;p&gt;The roadmap is progressive:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Phase 1&lt;/strong&gt; (current): Read-only diagnostics — investigate and report&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Phase 2&lt;/strong&gt;: Safe actions in dev/test — restart pods, rerun pipelines&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Phase 3&lt;/strong&gt;: Approval workflows for staging/production — the agent proposes, a human approves via Slack reaction&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Phase 4&lt;/strong&gt;: Full traced autonomy — the agent acts independently with complete audit trails&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Escalation to humans isn't a phase — it's always present. The agent knows when it's out of its depth and routes to the right person. That's not a feature you add later; it's part of the human-in-the-loop design from day one.&lt;/p&gt;

&lt;p&gt;This mirrors how you'd onboard a new team member. You don't give someone production access on day one. You let them observe, ask questions, and demonstrate understanding first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Infrastructure as Knowledge
&lt;/h2&gt;

&lt;p&gt;The agent's system prompt encodes the team's accumulated infrastructure knowledge: cluster naming conventions, namespace patterns, GitOps workflows, config repo structures, and common troubleshooting paths.&lt;/p&gt;

&lt;p&gt;For example, the agent knows that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Deployments follow a GitOps pattern — editing config files in environment branches triggers ArgoCD reconciliation&lt;/li&gt;
&lt;li&gt;Kubernetes labels follow consistent naming patterns that map services to environments&lt;/li&gt;
&lt;li&gt;The promotion chain goes through multiple stages from development to production&lt;/li&gt;
&lt;li&gt;Some services run on VMs with different config structures than the K8s-based ones&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This knowledge didn't come from a single source. I had context from working with the infrastructure, the LLM explored the actual clusters and repos to fill gaps, and then a DevOps engineer shared the documentation he personally uses with Claude — that was the biggest single jump in accuracy. After that, I refined edge cases through testing.&lt;/p&gt;

&lt;p&gt;The result is that when someone asks "why is service X failing in UAT?", the agent already knows which cluster to check, which namespace, what label selector to use, and where the config repo lives. It doesn't need to be told.&lt;/p&gt;

&lt;h2&gt;
  
  
  Output Compaction: Saving 79% on Tokens
&lt;/h2&gt;

&lt;p&gt;kubectl and Azure CLI return verbose JSON. A &lt;code&gt;kubectl get pods&lt;/code&gt; across a namespace can easily be 8,000+ characters. Feed that raw into the LLM context and you burn tokens fast.&lt;/p&gt;

&lt;p&gt;The bot compacts tool outputs on the fly — parsing JSON, extracting relevant fields, and discarding noise. A raw kubectl response of 8,000 characters becomes ~1,600 characters after compaction, with no loss of diagnostic value. That's a &lt;strong&gt;79% token reduction&lt;/strong&gt; that directly translates to lower costs and faster responses.&lt;/p&gt;

&lt;p&gt;The compaction is tool-aware: it knows which fields matter for kubectl (status, restart count, images, events) vs. Azure CLI (provisioning state, FQDN, SKU) vs. Bitbucket (pipeline state, step results, error messages). If JSON parsing fails, it falls back gracefully to truncation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Security Guardrails
&lt;/h2&gt;

&lt;p&gt;Building an agent that runs shell commands against production infrastructure requires paranoia-level security:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Command validation&lt;/strong&gt;: Every tool call goes through &lt;code&gt;shlex.split&lt;/code&gt; with an array-based subprocess call — never &lt;code&gt;shell=True&lt;/code&gt;. This prevents injection by construction.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Blocklists&lt;/strong&gt;: Shell metacharacters (&lt;code&gt;;&lt;/code&gt;, &lt;code&gt;|&lt;/code&gt;, &lt;code&gt;&amp;amp;&lt;/code&gt;, backticks, &lt;code&gt;$()&lt;/code&gt;) are blocked at the input level, before any command reaches the shell.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Allowlists&lt;/strong&gt;: Each tool defines exactly which subcommands are permitted. For kubectl, that's &lt;code&gt;get&lt;/code&gt;, &lt;code&gt;describe&lt;/code&gt;, &lt;code&gt;logs&lt;/code&gt;, &lt;code&gt;top&lt;/code&gt;, &lt;code&gt;config&lt;/code&gt;, &lt;code&gt;version&lt;/code&gt;. Everything else is rejected.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Output limits&lt;/strong&gt;: Tool results are capped at 8,000 characters (before compaction) to prevent context window flooding.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Private IP blocking&lt;/strong&gt;: The health check tool blocks requests to localhost, 10.x, 192.168.x, and 172.16-31.x to prevent &lt;a href="https://en.wikipedia.org/wiki/Server-side_request_forgery" rel="noopener noreferrer"&gt;SSRF&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Moment It Clicked
&lt;/h2&gt;

&lt;p&gt;Someone asked the bot to check resource usage for a service. The Grafana query was failing, and the bot started investigating — as usual. But then something unexpected happened: it found its own bug.&lt;/p&gt;

&lt;p&gt;The bot's Grafana tool was sending &lt;code&gt;step: '1h'&lt;/code&gt; as a string, but the implementation tried to parse it as an integer. &lt;code&gt;ValueError: invalid literal for int() with base 10: '1h'&lt;/code&gt;. The bot traced the error to its own code, reported it honestly — "the error was mine" — and then pivoted: instead of giving up, it checked what it &lt;em&gt;could&lt;/em&gt; verify (the deployment, replicas, pod status, resource definitions), gave an honest assessment of what it couldn't answer yet, and proposed a fix for its own Grafana tool.&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.amazonaws.com%2Fuploads%2Farticles%2Feqfpaixsz56mbbkudejx.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.amazonaws.com%2Fuploads%2Farticles%2Feqfpaixsz56mbbkudejx.png" alt="DevOpsBot diagnosing its own bug in a Slack thread — tracing a ValueError to its Grafana tool implementation and proposing a fix" width="800" height="481"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's meta-debugging. An agent that not only investigates infrastructure issues, but catches and reports its own failures with the same rigor. No hallucinated numbers, no pretending everything is fine — just "here's what I can confirm, here's what I can't, and here's how to fix it."&lt;/p&gt;

&lt;h2&gt;
  
  
  The First Test Went Wrong
&lt;/h2&gt;

&lt;p&gt;The first live demo with the DevOps team exposed two UX problems I hadn't anticipated.&lt;/p&gt;

&lt;p&gt;The first: someone tagged &lt;code&gt;@DevOpsBot&lt;/code&gt; in an existing thread — just the mention, no additional text. They expected the bot to read the thread context and help. Instead, it silently dropped the message. I'd built a guard against empty inputs as a safety measure, but "empty mention" and "useless input" aren't the same thing. In a thread with 15 messages of context, a bare &lt;code&gt;@DevOpsBot&lt;/code&gt; is a perfectly valid request for help. This guard made the bot useless in one of the most common Slack patterns: jumping into an ongoing conversation.&lt;/p&gt;

&lt;p&gt;The second: requiring an &lt;code&gt;@mention&lt;/code&gt; at all is friction. In a fast-moving incident, typing &lt;code&gt;@DevOpsBot&lt;/code&gt; before every question feels like ceremony. But it's an acceptable tradeoff as part of the trust-building process — no accidental triggers, no noise, clear audit trail of who asked what. The alternative, auto-responding to everything in the channel, risks unintended tool calls and noise that erodes trust faster than the friction does.&lt;/p&gt;

&lt;p&gt;Both were quick fixes in code, but they taught me something about building agents for real teams: defensive defaults that make sense in isolation can break the most natural interaction patterns. Test with the actual users early — your assumptions about how people will talk to the bot are probably wrong.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Tools first was the right call.&lt;/strong&gt; Getting kubectl and Azure CLI working reliably was the quick win that let me start experimenting immediately. The system prompt refinements came naturally from watching the agent struggle with real questions — you need the tools running to know what knowledge is missing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Compaction should be built in from day one.&lt;/strong&gt; I added it after noticing token costs climbing. In hindsight, every agent that calls tools returning structured data should compact by default.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Design for thread context early, even if you implement it later.&lt;/strong&gt; The bot reads up to 20 previous messages in a thread to maintain conversation continuity. Without this, every follow-up question ("what about the UAT environment?") would lack context. You don't need it for initial testing, but keep it in mind from the start — retrofitting conversational state is harder than designing for it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;LLM&lt;/strong&gt;: GPT-5.4 via Azure OpenAI (function calling)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Slack&lt;/strong&gt;: Slack Bolt + Socket Mode (async Python)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Runtime&lt;/strong&gt;: Python 3.11, asyncio throughout&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deployment&lt;/strong&gt;: Docker → AKS via ArgoCD (GitOps)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Observability&lt;/strong&gt;: Prometheus metrics, Application Insights, structured logging&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Testing&lt;/strong&gt;: pytest (unit + integration + e2e against real Slack)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Is It Worth Building?
&lt;/h2&gt;

&lt;p&gt;If your DevOps team spends significant time on questions that amount to "look at this thing and tell me what's wrong" — yes. The investment is primarily in encoding your team's infrastructure knowledge, not in the LLM plumbing.&lt;/p&gt;

&lt;p&gt;The read-only constraint makes the risk profile low. The agent can't break anything. The worst case is a wrong answer, which is the same worst case as a human answering from memory without checking.&lt;/p&gt;

&lt;p&gt;The best case is that 70% of your &lt;code&gt;#devops_requests&lt;/code&gt; channel gets answered in seconds, and your DevOps engineers focus on the 30% that actually needs human judgment.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built with Python, Azure OpenAI, Slack Bolt, and a lot of infrastructure knowledge. Deployed on AKS via ArgoCD. &lt;a href="https://www.javieraguilar.ai/en/projects/devops-slack-bot/" rel="noopener noreferrer"&gt;View the project details&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://www.javieraguilar.ai/en/blog/building-a-production-devops-agent" rel="noopener noreferrer"&gt;javieraguilar.ai&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Want to see more AI agent projects? Check out my &lt;a href="https://www.javieraguilar.ai" rel="noopener noreferrer"&gt;portfolio&lt;/a&gt; where I showcase multi-agent systems, MCP development, and compliance automation.&lt;/p&gt;

</description>
      <category>aiagents</category>
      <category>devops</category>
      <category>slack</category>
      <category>kubernetes</category>
    </item>
    <item>
      <title>From Claude Artifact to Production PWA: Building VitaminD Explorer</title>
      <dc:creator>JaviMaligno</dc:creator>
      <pubDate>Wed, 29 Apr 2026 20:19:26 +0000</pubDate>
      <link>https://dev.to/javieraguilarai/from-claude-artifact-to-production-pwa-building-vitamind-explorer-3om3</link>
      <guid>https://dev.to/javieraguilarai/from-claude-artifact-to-production-pwa-building-vitamind-explorer-3om3</guid>
      <description>&lt;p&gt;It started with a simple question: &lt;strong&gt;can I synthesize vitamin D today in London?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I'd been reading about how latitude, season, and skin type determine whether sunlight can actually trigger vitamin D production. The science is clear — below a certain &lt;a href="https://en.wikipedia.org/wiki/Solar_zenith_angle" rel="noopener noreferrer"&gt;solar elevation angle&lt;/a&gt; (~45–50°), UVB radiation is too weak for synthesis regardless of how long you stay outside. But figuring out &lt;em&gt;when&lt;/em&gt; that window opens for a given city on a given day? That's a calculation involving &lt;a href="https://en.wikipedia.org/wiki/Position_of_the_Sun#Declination_of_the_Sun" rel="noopener noreferrer"&gt;solar declination&lt;/a&gt;, hour angles, and spherical trigonometry.&lt;/p&gt;

&lt;p&gt;So I asked Claude.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Artifact That Started Everything
&lt;/h2&gt;

&lt;p&gt;Claude didn't just answer the question — it generated an &lt;a href="https://claude.ai/public/artifacts/e1377c30-3b83-47bd-938f-6e107c21231a" rel="noopener noreferrer"&gt;interactive artifact&lt;/a&gt; with a solar elevation curve, a day-of-year scrubber, and a threshold line showing when synthesis was possible. It used &lt;a href="https://doi.org/10.1016/0038-092X(71)90055-1" rel="noopener noreferrer"&gt;Spencer's 1971 formula&lt;/a&gt; for solar declination and the equation of time, projected onto a clean SVG chart. &lt;/p&gt;

&lt;p&gt;I expected a paragraph with some numbers. Instead I got a working prototype.&lt;/p&gt;

&lt;p&gt;I played with it for a few minutes — scrubbing through the year, watching the synthesis window expand in summer and vanish in winter — and thought: &lt;em&gt;this is actually useful&lt;/em&gt;. Not just for me, but for anyone living at high latitudes wondering if they should bother going outside for their vitamin D.&lt;/p&gt;

&lt;p&gt;That's when I decided to build it for real.&lt;/p&gt;

&lt;h2&gt;
  
  
  From Toy to Product: The Technical Decisions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Next.js App Router as the Foundation
&lt;/h3&gt;

&lt;p&gt;I went with Next.js 16 and the App Router. The app needed client-side interactivity (D3 charts, geolocation, animations) but also server-side API routes (weather data proxying, push notification delivery, city search). App Router gives you both in one project without stitching together separate services.&lt;/p&gt;

&lt;h3&gt;
  
  
  D3.js on Canvas: Performance Over Convenience
&lt;/h3&gt;

&lt;p&gt;The original artifact used SVG for everything. That works for a single chart, but I wanted three visualizations: a daily solar curve, an interactive world map, and a latitude × day-of-year heatmap (130 latitudes × 365 days = 47,450 cells).&lt;/p&gt;

&lt;p&gt;SVG can't handle that on mobile. So I moved to Canvas rendering with an SVG overlay for interactive elements — hover tooltips, click targets, threshold adjusters. The world map uses an equirectangular projection with pan and pinch-to-zoom, rendering 195 countries from TopoJSON on every frame. Device pixel ratio scaling keeps it sharp on Retina displays.&lt;/p&gt;

&lt;p&gt;The tradeoff is complexity. Canvas means manual coordinate transforms, hit-testing, and redraw management. But it was necessary — the heatmap alone would have created 47K DOM nodes as SVG rectangles.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Vitamin D Math: Not Just Solar Angles
&lt;/h3&gt;

&lt;p&gt;The artifact only calculated &lt;em&gt;when&lt;/em&gt; synthesis was possible. I wanted to answer &lt;em&gt;how long you need to stay outside&lt;/em&gt; for a given dose — say, 1000 IU.&lt;/p&gt;

&lt;p&gt;This required modeling the actual photobiology. The rate depends on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;UVI&lt;/strong&gt; (UV index at the current hour)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Skin type&lt;/strong&gt; (&lt;a href="https://en.wikipedia.org/wiki/Fitzpatrick_scale" rel="noopener noreferrer"&gt;Fitzpatrick scale&lt;/a&gt; I–VI, each with a different &lt;a href="https://en.wikipedia.org/wiki/Minimal_erythema_dose" rel="noopener noreferrer"&gt;MED&lt;/a&gt; — Minimal Erythemal Dose)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exposed body area&lt;/strong&gt; (face and hands vs. t-shirt and shorts)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Age&lt;/strong&gt; (synthesis efficiency drops ~1.3% per year after 20, per &lt;a href="https://doi.org/10.1016/S0140-6736(89)92611-4" rel="noopener noreferrer"&gt;Holick et al. 1989&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And critically, it's not linear. &lt;a href="https://en.wikipedia.org/wiki/Previtamin_D3" rel="noopener noreferrer"&gt;Previtamin D3&lt;/a&gt; doesn't just accumulate — it &lt;a href="https://en.wikipedia.org/wiki/Photodegradation" rel="noopener noreferrer"&gt;photodegrades&lt;/a&gt; under continued UVB into inert compounds (lumisterol and tachysterol). This is your body's built-in overdose protection, and it means there's a ceiling on how much you can produce per session.&lt;/p&gt;

&lt;p&gt;I modeled this as a saturating exponential:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;IU(t) = IU_sat × (1 - e^(-R×t / IU_sat))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Where &lt;code&gt;IU_sat&lt;/code&gt; is the photodegradation ceiling (19,200 IU for full-body 1 MED, per &lt;a href="https://doi.org/10.1126/science.6281884" rel="noopener noreferrer"&gt;Holick 1982&lt;/a&gt;, scaled by area and age) and &lt;code&gt;R&lt;/code&gt; is the production rate based on &lt;a href="https://doi.org/10.1111/j.1365-2133.2010.09888.x" rel="noopener noreferrer"&gt;Dowdy et al. 2010&lt;/a&gt; MED tables. At low doses, it matches Holick's linear rule. At higher doses, diminishing returns kick in. The model agrees with &lt;a href="https://doi.org/10.1073/pnas.2015867118" rel="noopener noreferrer"&gt;Young et al. 2021&lt;/a&gt; (n=75, in-vivo validation).&lt;/p&gt;

&lt;h3&gt;
  
  
  Real Weather Data: Open-Meteo
&lt;/h3&gt;

&lt;p&gt;Solar geometry gives you clear-sky estimates, but clouds matter enormously. A 70% cloud cover can reduce UVB by 60%. I integrated the &lt;a href="https://open-meteo.com/" rel="noopener noreferrer"&gt;Open-Meteo API&lt;/a&gt; for hourly UV index and cloud cover — no API key needed, free for non-commercial use.&lt;/p&gt;

&lt;p&gt;The tricky part: Open-Meteo's UV index &lt;em&gt;already includes cloud cover&lt;/em&gt;. If you apply a cloud penalty on top, you double-count it. I caught this bug after noticing that exposure times on cloudy days were absurdly high. The fix: only apply the cloud factor to theoretical (solar geometry) estimates, never to real API data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a PWA and Not a Native App
&lt;/h2&gt;

&lt;p&gt;This was a deliberate choice. A vitamin D calculator needs to work on any device — iOS, Android, desktop — and I'm a solo developer. Building and maintaining native apps for two platforms plus a web version was out of the question.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Progressive_web_app" rel="noopener noreferrer"&gt;PWAs&lt;/a&gt; close most of the gap that used to matter:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Installation&lt;/strong&gt;: "Add to Home Screen" works on both iOS and Android — no app store, no review process, no $99/year Apple Developer fee&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Push notifications&lt;/strong&gt;: Supported on Android since 2015 and &lt;a href="https://webkit.org/blog/13878/web-push-for-web-apps-on-ios-and-ipados/" rel="noopener noreferrer"&gt;on iOS since 16.4&lt;/a&gt; (March 2023) — the last major blocker is gone&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Offline&lt;/strong&gt;: Service workers cache everything needed for core functionality&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Updates&lt;/strong&gt;: Deploy to Vercel, users get the new version on next visit — no "please update the app" friction&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What you &lt;em&gt;don't&lt;/em&gt; get: background location tracking, health app integrations (Apple Health, Google Fit), and a presence in the App Store for discoverability. The first two could matter for future features (wearable integrations, automatic daily tracking). The third is a real distribution disadvantage — people search app stores, not the web.&lt;/p&gt;

&lt;p&gt;For now, the tradeoff is clearly worth it. If the app grows to need native APIs (wearables, health data), a hybrid approach with &lt;a href="https://capacitorjs.com/" rel="noopener noreferrer"&gt;Capacitor&lt;/a&gt; or a thin native wrapper around the existing web app would be the natural next step — not a rewrite.&lt;/p&gt;

&lt;h3&gt;
  
  
  Offline Support
&lt;/h3&gt;

&lt;p&gt;A vitamin D calculator is most useful when you're already outside — potentially with no signal. The service worker caches all pages and static assets at install time, with a network-first strategy for dynamic content and an offline fallback page.&lt;/p&gt;

&lt;h3&gt;
  
  
  Push Notifications
&lt;/h3&gt;

&lt;p&gt;This was the feature that felt most "app-like." A Vercel cron job runs daily at 8 AM UTC, fetches real UV data for each subscriber's saved location, calculates their personal synthesis window, and sends a Web Push notification:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"London · 12 min for 1000 IU · Best hour: 13:00 (UVI 4.5) · Window: 11:00–15:00"&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The implementation uses VAPID keys with the &lt;code&gt;web-push&lt;/code&gt; library, and subscriptions are stored in Supabase. One gotcha that cost me 53 days of broken push notifications: &lt;code&gt;echo "$KEY" | vercel env add&lt;/code&gt; appends a newline that Vercel stores literally inside the VAPID key. The fix is &lt;code&gt;printf '%s'&lt;/code&gt; instead of &lt;code&gt;echo&lt;/code&gt;. A subtle shell behavior that silently corrupted a cryptographic key.&lt;/p&gt;

&lt;h2&gt;
  
  
  Six Languages and 5,000+ Cities
&lt;/h2&gt;

&lt;p&gt;The app supports English, Spanish, French, German, Russian, and Lithuanian via &lt;code&gt;next-intl&lt;/code&gt;. Each language file is ~480 lines covering UI labels, Fitzpatrick skin descriptions, and a full educational FAQ about vitamin D science.&lt;/p&gt;

&lt;p&gt;City search combines a built-in database of 50+ major cities with Supabase full-text search (&lt;code&gt;pg_trgm&lt;/code&gt;) over GeoNames' 200K+ city dataset. City names are localized — "Londres" in Spanish, "Лондон" in Russian.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Claude artifacts are underrated as prototyping tools.&lt;/strong&gt; The artifact wasn't a mockup or a diagram — it was working code with real solar math. That head start meant I could focus on product decisions (what visualizations? what user flow?) instead of re-deriving spherical trigonometry.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Canvas D3 is worth the pain for data-dense visualizations.&lt;/strong&gt; If you're rendering fewer than 1,000 elements, stick with SVG. Beyond that, Canvas is the only way to keep mobile smooth.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PWA push notifications are surprisingly powerful and surprisingly fragile.&lt;/strong&gt; The Web Push spec works well, but the operational surface area (VAPID keys, cron timing, subscription cleanup, error handling for uninstalled apps) is larger than the code suggests.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Photobiology is more nuanced than "go outside for 15 minutes."&lt;/strong&gt; The difference between Fitzpatrick I (pale, Celtic) and VI (deeply pigmented) is a factor of 6× in MED. Body area exposure matters more than most people think — face and hands alone (10% body area) can require 5× longer than a t-shirt and shorts (25%).&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://getvitamind.app" rel="noopener noreferrer"&gt;VitaminD Explorer&lt;/a&gt;&lt;/strong&gt; is free, open source, and works on any device. Search your city, set your skin type and exposure, and see exactly when and how long you need for your daily vitamin D.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://claude.ai/public/artifacts/e1377c30-3b83-47bd-938f-6e107c21231a" rel="noopener noreferrer"&gt;original Claude artifact&lt;/a&gt; is still live if you want to see where it all began.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built with Next.js 16, D3.js, Supabase, and Vercel. &lt;a href="https://github.com/JaviMaligno/vitamind" rel="noopener noreferrer"&gt;Source on GitHub&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://www.javieraguilar.ai/en/blog/from-artifact-to-pwa-vitamind" rel="noopener noreferrer"&gt;javieraguilar.ai&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Want to see more AI agent projects? Check out my &lt;a href="https://www.javieraguilar.ai" rel="noopener noreferrer"&gt;portfolio&lt;/a&gt; where I showcase multi-agent systems, MCP development, and compliance automation.&lt;/p&gt;

</description>
      <category>claude</category>
      <category>pwa</category>
      <category>nextjs</category>
      <category>d3js</category>
    </item>
    <item>
      <title>Software Is Dissolving Into the Model</title>
      <dc:creator>JaviMaligno</dc:creator>
      <pubDate>Sat, 25 Apr 2026 18:42:37 +0000</pubDate>
      <link>https://dev.to/javieraguilarai/software-is-dissolving-into-the-model-40l9</link>
      <guid>https://dev.to/javieraguilarai/software-is-dissolving-into-the-model-40l9</guid>
      <description>&lt;p&gt;Two repositories caught my eye this month. The first is &lt;a href="https://github.com/google/agents-cli" rel="noopener noreferrer"&gt;&lt;code&gt;google/agents-cli&lt;/code&gt;&lt;/a&gt;, Google's official tooling for building agents on Google Cloud — a CLI plus a bundle of markdown "skills" that any coding assistant (Claude Code, Codex, Gemini CLI, Cursor) can pick up. The second is &lt;a href="https://flipbook.page/" rel="noopener noreferrer"&gt;Flipbook&lt;/a&gt;, an experimental "browser" launched by ex-OpenAI researchers two days ago. Flipbook has no HTML, no DOM, no rendered components. Every pixel you see — including the text — is generated frame-by-frame by a video diffusion model streaming over a WebSocket.&lt;/p&gt;

&lt;p&gt;They look like they belong to different conversations. They don't. Put them next to each other and a pattern emerges: the layer we used to call "software" is being squeezed from both ends. What we write is becoming markdown. What users see is becoming a model output. The middle keeps getting thinner.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we write is becoming markdown
&lt;/h2&gt;

&lt;p&gt;Skills are repositories of instructions, not deployed services. The runtime is whatever coding agent the user happens to have installed. The artifact is a &lt;code&gt;SKILL.md&lt;/code&gt; file with maybe a couple of helper scripts.&lt;/p&gt;

&lt;p&gt;The last few weeks alone tell the story:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/othmanadi/planning-with-files" rel="noopener noreferrer"&gt;&lt;code&gt;OthmanAdi/planning-with-files&lt;/code&gt;&lt;/a&gt; is a Claude Code skill that gives the agent a Manus-style persistent planning workflow — a few markdown files defining when to write &lt;code&gt;task_plan.md&lt;/code&gt;, &lt;code&gt;findings.md&lt;/code&gt;, &lt;code&gt;progress.md&lt;/code&gt;. It has 9.2k stars. Manus, the company that built that workflow into a full product, was reportedly acquired for $2B. The IP was the pattern, not the implementation.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/VoltAgent/awesome-agent-skills" rel="noopener noreferrer"&gt;&lt;code&gt;VoltAgent/awesome-agent-skills&lt;/code&gt;&lt;/a&gt; curates 1000+ portable skills across Claude Code, Codex, Gemini CLI, Cursor and 40+ other agents. 18.7k stars.&lt;/li&gt;
&lt;li&gt;GitHub shipped &lt;a href="https://github.blog/changelog/2026-04-16-manage-agent-skills-with-github-cli/" rel="noopener noreferrer"&gt;&lt;code&gt;gh skill&lt;/code&gt;&lt;/a&gt; on April 16 — a first-class CLI primitive for installing, pinning and publishing skills directly from GitHub repositories.&lt;/li&gt;
&lt;li&gt;Google Workspace's official CLI now ships &lt;a href="https://github.com/googleworkspace/cli" rel="noopener noreferrer"&gt;100+ &lt;code&gt;SKILL.md&lt;/code&gt; files&lt;/a&gt;, one for every supported API, plus 50 curated recipes for Gmail, Drive, Docs, Calendar and Sheets.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A year ago, "ship a tool for X" meant building an SDK, a service, or at least a wrapper library. Today it increasingly means writing a folder of markdown that any agent can load on demand. The trend isn't "AI helps you write software". It's "what we used to call software is now an instruction set".&lt;/p&gt;

&lt;h2&gt;
  
  
  What users see is becoming a model output
&lt;/h2&gt;

&lt;p&gt;Now look at the other end of the pipeline. It's doing the same thing.&lt;/p&gt;

&lt;p&gt;Flipbook launched on April 23 from Zain Shah's team (ex-OpenAI, ex-Humane, ex-Apple), out of South Park Commons. The interface is generated pixel-by-pixel by &lt;a href="https://github.com/Lightricks/LTX-Video" rel="noopener noreferrer"&gt;LTX Video&lt;/a&gt;, an open-source diffusion transformer for video, optimized to stream at 1080p/24fps via WebSocket from Modal Labs serverless GPUs. There is no DOM. There are no buttons in the traditional sense. When you "click", the model generates the next frames.&lt;/p&gt;

&lt;p&gt;DeepMind has been doing the same thing at world-model scale. &lt;a href="https://blog.google/innovation-and-ai/models-and-research/google-deepmind/project-genie/" rel="noopener noreferrer"&gt;Project Genie&lt;/a&gt; rolled out to AI Ultra subscribers in January 2026, powered by Genie 3. You type a prompt. You walk around a generated world that is being generated as you move, at 24fps, with consistency held for a few minutes.&lt;/p&gt;

&lt;p&gt;These are demos. Flipbook is barely useful as a product yet. Genie's worlds last a minute or two before the model loses coherence. But the direction is clearer than the products: the rendered output of an application is becoming a model inference, not a tree of components.&lt;/p&gt;

&lt;h2&gt;
  
  
  We're the ones who should adapt
&lt;/h2&gt;

&lt;p&gt;A friend told me this week that he'd been trying to get an LLM to write internal reports following his company's exact style guidelines. After enough iteration he realized the prompt-engineering effort to keep the model on rails was taking him longer than writing the reports himself — and his company has multiple report types like that.&lt;/p&gt;

&lt;p&gt;What he said next stuck with me: &lt;em&gt;"Maybe the problem isn't that the AI can't adapt. Maybe we're the ones who should adapt. We're making it less efficient with all our constraints, when what it produces on its own is good enough for the purpose."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That insight isn't really about reports. It's the thing happening on both ends of the software stack right now.&lt;/p&gt;

&lt;p&gt;A markdown skill is what you get when you stop trying to force the model into the shape of a typed SDK with versioned interfaces and start writing in the medium the model actually thrives in: natural language with examples. Flipbook is what you get when you stop trying to force the model to emit valid React components and start letting it render the pixels directly.&lt;/p&gt;

&lt;p&gt;The friction we were adding — typed APIs, deterministic UIs, hand-coded glue — was buying us correctness, but at a cost we hadn't priced in. Every constraint also costs throughput, and a lot of those constraints existed because &lt;em&gt;humans&lt;/em&gt; needed them, not because the model did.&lt;/p&gt;

&lt;p&gt;That doesn't mean correctness goes away. It means we re-locate it. Evals replace types. Skill instructions replace SDK documentation. Frame-level objectives replace pixel-perfect Figma specs. We're trading one kind of rigor for another that fits the tool better.&lt;/p&gt;

&lt;h2&gt;
  
  
  What survives in the middle
&lt;/h2&gt;

&lt;p&gt;So if what we write is becoming skills and what users see is becoming pixels, what's left for those of us building things in the middle?&lt;/p&gt;

&lt;p&gt;I've been thinking about this in the context of my own work.&lt;/p&gt;

&lt;p&gt;The MCP servers I've built — &lt;a href="https://github.com/JaviMaligno/mcp-server-bitbucket" rel="noopener noreferrer"&gt;&lt;code&gt;mcp-server-bitbucket&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://github.com/JaviMaligno/postgres_mcp" rel="noopener noreferrer"&gt;&lt;code&gt;postgres_mcp&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://github.com/JaviMaligno/langfuse-mcp-server" rel="noopener noreferrer"&gt;&lt;code&gt;langfuse-mcp-server&lt;/code&gt;&lt;/a&gt; — already live in this new shape. They're not products with UIs. They're protocols any agent can pick up. The interface is the spec, not the screen.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://www.javieraguilar.ai/en/projects/compliance-classifier" rel="noopener noreferrer"&gt;compliance classifier&lt;/a&gt; and the &lt;a href="https://www.javieraguilar.ai/en/projects/medical-doc-parser" rel="noopener noreferrer"&gt;medical document parser&lt;/a&gt; depend on something that &lt;em&gt;doesn't&lt;/em&gt; compress into a markdown file: a curated taxonomy, edge cases extracted from real documents, an evaluation suite that proves the model handles the long tail. The orchestration around them increasingly does compress into instructions, but the domain context doesn't.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://www.javieraguilar.ai/en/projects/data-source-automator" rel="noopener noreferrer"&gt;data-source automator&lt;/a&gt;'s value is in a specific human-in-the-loop sequence we worked out the hard way — where the agent stops, what it asks, what it logs. Two years ago that lived in code. Today it lives more naturally as a skill plus an eval set.&lt;/p&gt;

&lt;p&gt;What survives in the middle is the stuff that wasn't really code in the first place: the data, the taxonomy, the eval harness, the orchestration &lt;em&gt;decisions&lt;/em&gt;. The stuff that was just "code" — the wrapper, the form, the boilerplate — heads to the skill layer on one end or the generated render on the other.&lt;/p&gt;

&lt;h2&gt;
  
  
  Engineers can't hide in the niche anymore
&lt;/h2&gt;

&lt;p&gt;There's a corollary for those of us who write the code being squeezed: the gap between idea and shipped product is getting short enough that we can't stay tucked into a development niche and still add value.&lt;/p&gt;

&lt;p&gt;When the middle layer was thick, you could be "the backend person" or "the LLM ops person" and contribute through a slice of the stack. The product owner had the idea, designers handled the surface, engineers handled the plumbing, and the handoffs were the work. When the middle is thin, those handoffs cost more than they coordinate. The model renders a lot of the surface. The agent carries a lot of the plumbing.&lt;/p&gt;

&lt;p&gt;What's left for engineers looks more like product judgment than implementation — which problem to solve, what "good enough" means in this domain, where the model needs to be on rails and where it doesn't, what failure looks like and how to catch it. Even when you're not building your own product, you can't hide in your niche. You have to take ownership of the outcome to be trusted with it — because the part you used to be paid for, the implementation, is the part that's compressing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical takeaways
&lt;/h2&gt;

&lt;p&gt;Three things I'm acting on:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Ship the skill, not the wrapper.&lt;/strong&gt; If you're building anything that lives behind an LLM, the wrapper code is the part with the shortest half-life. The skill — instructions, examples, evals — is what you actually own.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stop fighting the medium.&lt;/strong&gt; If you're spending more time constraining the model than the model is spending producing useful output, you're solving the wrong problem. Either accept what it produces or pick a different tool.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Invest where the moat is.&lt;/strong&gt; The new defensible thing isn't "we built it well". It's "we have the data, evals and orchestration that prove this works in our domain". That's where I'm putting time on my own products.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Software isn't disappearing. It's redistributing itself across the stack — and the middle, the part we used to call "the application", is getting a lot thinner than I expected even six months ago.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Repositories referenced: &lt;a href="https://github.com/google/agents-cli" rel="noopener noreferrer"&gt;google/agents-cli&lt;/a&gt; · &lt;a href="https://github.com/othmanadi/planning-with-files" rel="noopener noreferrer"&gt;OthmanAdi/planning-with-files&lt;/a&gt; · &lt;a href="https://github.com/VoltAgent/awesome-agent-skills" rel="noopener noreferrer"&gt;VoltAgent/awesome-agent-skills&lt;/a&gt;. Demos: &lt;a href="https://flipbook.page/" rel="noopener noreferrer"&gt;Flipbook&lt;/a&gt; · &lt;a href="https://blog.google/innovation-and-ai/models-and-research/google-deepmind/project-genie/" rel="noopener noreferrer"&gt;Project Genie&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://www.javieraguilar.ai/en/blog/software-dissolving-into-the-model" rel="noopener noreferrer"&gt;javieraguilar.ai&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Want to see more AI agent projects? Check out my &lt;a href="https://www.javieraguilar.ai" rel="noopener noreferrer"&gt;portfolio&lt;/a&gt; where I showcase multi-agent systems, MCP development, and compliance automation.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agentskills</category>
      <category>generativeui</category>
      <category>software</category>
    </item>
  </channel>
</rss>
