<?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>Your Agent Doesn't Know What Has Already Happened</title>
      <dc:creator>JaviMaligno</dc:creator>
      <pubDate>Thu, 13 Aug 2026 09:36:34 +0000</pubDate>
      <link>https://dev.to/javieraguilarai/your-agent-doesnt-know-what-has-already-happened-59ah</link>
      <guid>https://dev.to/javieraguilarai/your-agent-doesnt-know-what-has-already-happened-59ah</guid>
      <description>&lt;p&gt;I've spent a few months working daily with coding agents on a project with a lot of &lt;em&gt;state&lt;/em&gt;: a university course I have to record in a studio on four consecutive days, with scripts, timings and a delivery deadline before it. And one kind of suggestion kept coming back — &lt;strong&gt;ordering advice that didn't hold up.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Leave this piece for the end, because it depends on the others. Record these two together, because the second reuses the first. Reasonable-sounding, and wrong: the thing it supposedly depended on had been finished and frozen months earlier. The agent was reasoning about my project without a firm line between what was already settled and what wasn't.&lt;/p&gt;

&lt;p&gt;The obvious diagnosis is that it doesn't know when &lt;em&gt;now&lt;/em&gt; is. I'll come back to that, because it's testable and it's false.&lt;/p&gt;

&lt;h2&gt;
  
  
  The anecdote I had to throw away
&lt;/h2&gt;

&lt;p&gt;I went looking for the most flagrant example to open with. I found a beauty: a status file the agent had been maintaining said&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;| Recorded and good  | 2  |
| Recorded, to redo  | 3  |
| Pending            | 63 |
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing had been recorded. Not one frame — the studio booking was still weeks away. What existed were timed read-throughs in my living room, me reading a script out loud with a stopwatch to see whether it fit in ten minutes.&lt;/p&gt;

&lt;p&gt;Perfect opening paragraph. Except that when I dug into how the file got that way, it stopped being evidence of anything.&lt;/p&gt;

&lt;p&gt;The vocabulary was contaminated from both ends. One session had been preparing material for the real shoot and blended the two states together. And I had, more than once, called my own practice runs "recording" — in conversations about measuring timings, where the distinction didn't matter. It stopped not mattering the moment a document got written in that tense: every session afterwards inherited a status file that spoke about takes. I corrected it out loud more than once and it kept coming back, because the document was still there.&lt;/p&gt;

&lt;p&gt;So it isn't a model conjuring a state out of nothing. It's months of drift in a shared vocabulary, with authorship on both sides, mine included.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;That's the trouble with any anecdote from your own project: I'm inside the loop.&lt;/strong&gt; My words go into the context, my corrections change it, there's no control condition, and whatever example I picked I'd have picked &lt;em&gt;because&lt;/em&gt; it was striking — then explained it afterwards with the explanation I already believed.&lt;/p&gt;

&lt;p&gt;A hunch is not a case. So instead of writing up the hunch, I built something I could be wrong about.&lt;/p&gt;

&lt;h2&gt;
  
  
  The test
&lt;/h2&gt;

&lt;p&gt;Take the &lt;em&gt;shape&lt;/em&gt; of what I kept noticing out of my project and put it somewhere I have no history. Fixed material, one question, one variable at a time, enough repetitions that I can't cherry-pick.&lt;/p&gt;

&lt;p&gt;You give a model ~1.7 KB of project facts and ask: &lt;em&gt;in what order should I do these, and are there any ordering constraints I need to respect?&lt;/em&gt; The material is my real project, ambiguity included:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sixty-eight video pieces to record, a studio window booked for four consecutive days, a delivery deadline before it.&lt;/li&gt;
&lt;li&gt;Scripts &lt;strong&gt;closed and frozen since July&lt;/strong&gt;: the text is not going to change.&lt;/li&gt;
&lt;li&gt;A table of measured times, and a status count: &lt;em&gt;2 good / 3 to redo / 63 pending&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;And a trap. Two pieces look dependent — the fourth reuses a calculation written during the third — but the script of the fourth says, in as many words, &lt;strong&gt;"I'll rewrite them for you."&lt;/strong&gt; It's self-contained. There is no ordering constraint.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then you vary the context by one or two lines:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;added to the material&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;A&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;nothing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;B&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;"today is 11 August 2026"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;C&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;"the measured times are practice read-throughs, not studio takes; nothing has come out of the studio yet"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;D&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;both&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;E&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;both, plus "don't invent ordering constraints"&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Six models — &lt;code&gt;gpt-4o&lt;/code&gt;, &lt;code&gt;gpt-4.1-mini&lt;/code&gt;, &lt;code&gt;gpt-5.4-mini&lt;/code&gt;, &lt;code&gt;claude-haiku-4.5&lt;/code&gt;, &lt;code&gt;claude-sonnet-4.6&lt;/code&gt;, &lt;code&gt;claude-opus-5&lt;/code&gt; — and then the whole thing again in an unrelated domain: a release of twelve services, production deploy window ahead, staging rehearsals behind. Same logical structure, none of the same words. Its trap is a service that reads a table another service migrates, where the runbook says the migration is idempotent and the service "starts against the old schema or the new one, without touching anything."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;572 scored responses.&lt;/strong&gt; An LLM judge does the scoring, blind to the condition and required to quote the exact sentence justifying each mark. Before trusting it I hand-coded a sample and checked both candidate judges against my coding: one agreed 30/32, the other 23/32 — and eight of the loser's nine errors were false positives, marking a failure in answers that said the opposite. So there's one judge, and it's the validated one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Failure 1: a precedence that the document explicitly denies
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;asserts the dependency anyway&lt;/th&gt;
&lt;th&gt;course domain&lt;/th&gt;
&lt;th&gt;deployment domain&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gpt-4.1-mini&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gpt-4o&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;claude-haiku-4.5&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;96%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gpt-5.4-mini&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;86%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;claude-sonnet-4.6&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;claude-opus-5&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;52%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;10%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Five of six models say B must come after A. In the course domain that's &lt;strong&gt;92 out of 92 responses&lt;/strong&gt; — not a tendency, a ceiling.&lt;/p&gt;

&lt;p&gt;And the exemption isn't buried in an appendix. It's in the &lt;em&gt;same sentence&lt;/em&gt; as the dependency. Here's &lt;code&gt;gpt-4o&lt;/code&gt;, unedited, three lines apart:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;auth-db-migrator&lt;/code&gt; — Prepares the schema &lt;code&gt;auth-api&lt;/code&gt; uses. &lt;strong&gt;It's idempotent and backward-compatible.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;[…]&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Always deploy &lt;code&gt;auth-db-migrator&lt;/code&gt; before &lt;code&gt;auth-api&lt;/code&gt;&lt;/strong&gt;, since it adjusts the schema.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It read the exemption, wrote it down, and reasoned as if it hadn't. Nothing was missing from the context: the model &lt;strong&gt;reproduced&lt;/strong&gt; the information and then overrode it. What loses isn't the fact — it's the fact's &lt;em&gt;scope&lt;/em&gt;. The relation ("B reuses something from A") survives; the clause that cancels the relation does not.&lt;/p&gt;

&lt;p&gt;The shape of the result matters as much as the size. &lt;strong&gt;This is not a gradient, it's a threshold.&lt;/strong&gt; &lt;code&gt;gpt-5.4-mini&lt;/code&gt; and &lt;code&gt;claude-sonnet-4.6&lt;/code&gt; are strong recent models and they fail exactly as often as the smallest one in the set. One model out of six is in a different regime; the other five are indistinguishable from each other.&lt;/p&gt;

&lt;h2&gt;
  
  
  Failure 2: a premise that expired in July
&lt;/h2&gt;

&lt;p&gt;Both materials contain something that &lt;em&gt;invites&lt;/em&gt; a convention from the training corpus. In the course, an opener that describes the whole course — and in video production you shoot the intro last, because the intro has to match what you ended up making. In the release, a gateway that publishes the manifest for everything — and in deployments the gateway goes last, so it doesn't advertise services that aren't up.&lt;/p&gt;

&lt;p&gt;Both are real, sensible practices. And both rest on a condition that this material explicitly removes: &lt;strong&gt;the scripts were frozen in July and the artefacts were signed in July.&lt;/strong&gt; Nothing done later can change what they say. The reason to defer them expired before the window even opened.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;defers it &lt;em&gt;because it depends on the rest&lt;/em&gt;
&lt;/th&gt;
&lt;th&gt;course&lt;/th&gt;
&lt;th&gt;deployment&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;five smaller models&lt;/td&gt;
&lt;td&gt;36%&lt;/td&gt;
&lt;td&gt;69%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;claude-opus-5&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;60%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;98%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This is the failure I find most interesting, because it's temporal in the purest sense: the model retrieves a rule about &lt;em&gt;when&lt;/em&gt; to do something, and doesn't check whether the rule's precondition still holds. It's the same move as advising someone to book early for a trip that already happened.&lt;/p&gt;

&lt;p&gt;The model that reads the exemption is among the &lt;em&gt;worst&lt;/em&gt; at this — 25 out of 25 in the deployment domain, the highest number in the study.&lt;/p&gt;

&lt;p&gt;I want to be careful, because there's a tidy story available and the data doesn't quite support it. The tidy story is "the failure grows with capability." Not monotonically: in the deployment domain &lt;code&gt;gpt-4.1-mini&lt;/code&gt; falls for it 79–90% of the time while &lt;code&gt;gpt-4o&lt;/code&gt; does 20–40%. What the data does support is narrower and still useful: &lt;strong&gt;being a better model does not help here.&lt;/strong&gt; The three strongest models in the set fail this 92–100% of the time in the deployment domain. If your plan is "wait for a better model," this is the part that will still be waiting.&lt;/p&gt;

&lt;p&gt;My reading, offered as interpretation and not measurement: the two failures pull opposite ways because one is about reading and the other about producing. Ignoring the exemption is under-reading. Deferring the opener is over-producing — the model doesn't just answer, it supplies a &lt;em&gt;reason&lt;/em&gt;, and a fluent plausible reason is exactly the shape a wrong ordering constraint takes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Failure 3: what has and hasn't happened
&lt;/h2&gt;

&lt;p&gt;The status count — &lt;em&gt;2 good / 3 to redo&lt;/em&gt; — is genuinely ambiguous in condition A. Reading it as finished output isn't crazy; the material doesn't settle it. So condition C settles it: &lt;em&gt;these are rehearsals, nothing has come out of the studio yet.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;What that line buys depends entirely on which model reads it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;claude-opus-5&lt;/code&gt; acts on it.&lt;/strong&gt; The state error drops from 92% to 20% in the course domain (p=3.7·10⁻⁷) and from 20% to 0% in deployment. It also &lt;em&gt;says&lt;/em&gt; it, going from 12% to 100% and 16% to 96%.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The smaller models mostly just repeat it.&lt;/strong&gt; &lt;code&gt;gpt-5.4-mini&lt;/code&gt; goes from never mentioning provenance to mentioning it 40–48% of the time; &lt;code&gt;claude-sonnet-4.6&lt;/code&gt;, 0% to 62% in one domain. And then they plan around 63 pending anyway.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;And for three of them it made the planning worse.&lt;/strong&gt; In deployment, adding the clarification raised the state error in &lt;code&gt;claude-haiku-4.5&lt;/code&gt; (12% → 32%), &lt;code&gt;claude-sonnet-4.6&lt;/code&gt; (12% → 25%) and &lt;code&gt;gpt-5.4-mini&lt;/code&gt; (12% → 20%). Small numbers, no individual significance, and I won't claim a mechanism. But the direction is the opposite of free: &lt;strong&gt;giving a model a caveat gives it something new to talk about, and talking about it is not using it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's what I'd have missed testing one model. The same sentence of context is a large fix, a no-op, or a mild irritant depending on what reads it.&lt;/p&gt;

&lt;h2&gt;
  
  
  So is the outlier better or worse?
&lt;/h2&gt;

&lt;p&gt;One model behaves differently from the other five on every measure, which invites the question: is &lt;code&gt;claude-opus-5&lt;/code&gt; exceptionally good at this, or exceptionally bad? It's genuinely both, and I think it's one property seen from two sides.&lt;/p&gt;

&lt;p&gt;Line up the state error by condition:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;error rate&lt;/th&gt;
&lt;th&gt;without the clarification&lt;/th&gt;
&lt;th&gt;with it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;the five smaller models&lt;/td&gt;
&lt;td&gt;0–70%&lt;/td&gt;
&lt;td&gt;0–56%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;claude-opus-5&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;92% / 20% — worst in its column&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;20% / 0% — best&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In both domains it is the &lt;em&gt;worst&lt;/em&gt; of the six when the material leaves the status ambiguous, and the &lt;em&gt;best&lt;/em&gt; once the material settles it. Without a fact to constrain it, it builds the most natural reading — those numbers are finished output — and plans on that reading with conviction. Given the fact, it updates and plans on the new one. The smaller models sit in a mediocre middle either way, because they commit less to any interpretation.&lt;/p&gt;

&lt;p&gt;That also reconciles the gateway result, which otherwise looks contradictory. What this model reliably uses is what's &lt;strong&gt;explicit&lt;/strong&gt;: "I'll rewrite them for you", "these are rehearsals". What needs its consequence derived in two steps — &lt;em&gt;frozen since July&lt;/em&gt;, therefore nothing later can invalidate the opener — it doesn't use, and there the corpus convention wins. It wins harder, in fact, precisely because this is the model that elaborates most.&lt;/p&gt;

&lt;p&gt;Offered as interpretation, not measurement. But the practical consequence runs opposite to the intuition: &lt;strong&gt;with a stronger model, the quality of your document matters more, not less.&lt;/strong&gt; A small model gives you something mediocre almost regardless of what you wrote. A large one gives you back what you gave it — which is good news if you write carefully and bad news if the load-bearing fact is only implied.&lt;/p&gt;

&lt;h2&gt;
  
  
  It isn't the clock
&lt;/h2&gt;

&lt;p&gt;Which brings me back to the obvious diagnosis. Every failure above is about time — precedence, expiry, what has occurred — so the natural fix is to tell the model when &lt;em&gt;now&lt;/em&gt; is. It's also the mitigation every tool ships by default: your agent almost certainly has today's date stamped into its system prompt right now. Mine did, throughout.&lt;/p&gt;

&lt;p&gt;Adding the date explicitly moved nothing. Not one of the four measures (all p &amp;gt; 0.5).&lt;/p&gt;

&lt;p&gt;Nor did the other two things I tried. Declaring what the numbers are doesn't make a model read the runbook clause — different failure, no transfer. And condition E adds, in plain language, &lt;em&gt;don't invent ordering constraints&lt;/em&gt;: still &lt;strong&gt;96%&lt;/strong&gt;. You can write the prohibition into the prompt and watch it violated in nineteen of twenty runs.&lt;/p&gt;

&lt;p&gt;So the deficit isn't the timestamp. A calendar tells you where &lt;em&gt;now&lt;/em&gt; sits; it tells you nothing about which of your facts are already fixed, which relations still hold, and which rule's precondition expired last month. That structure lives in the document, and it's the part that doesn't survive being read.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd actually do
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Don't write an exemption — write the sentence you mean.&lt;/strong&gt; The best-supported item here, and it's a documentation habit rather than a prompt trick. If a constraint doesn't apply, don't state the constraint and then except it: the exception loses 96–100% of the time in five of six models. "B reuses A's calculation, but it rewrites it" becomes "B is self-contained."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Say what's frozen, not just what's done.&lt;/strong&gt; Failure 2 happens because a generic "do this last" rule outranks a specific fact about my project. "Scripts closed since July" apparently reads as history; "the scripts cannot change, so nothing later can invalidate the opener" states the consequence, which is what the rule needed to be checked against.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Make provenance inseparable from the value.&lt;/strong&gt; Not a note elsewhere saying the numbers are rehearsals — a mark that can't be copied off. In my project what worked was writing &lt;code&gt;8:25 †&lt;/code&gt;, where the dagger means "this delta is meaningless," so the number can't travel to another table without its caveat. Scope: this is &lt;em&gt;document design&lt;/em&gt;. Declaring it in a prompt reliably changes only what the model says back.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stop expecting the date to do work,&lt;/strong&gt; and don't add caveats assuming they're free. Both are cheap to inject and neither does what you think.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Watch your own vocabulary.&lt;/strong&gt; The one lesson from the anecdote I threw away, and it survives precisely because it isn't about the model: I called practice runs "recording" when it didn't matter, and it stopped not mattering as soon as it was written down. In a long-running project, loose words get committed to a file, and what's in the file becomes the state of the world.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limits
&lt;/h2&gt;

&lt;p&gt;Two domains is not a sample of domains, and both are &lt;em&gt;ordering work with a window ahead&lt;/em&gt;; diagnosis or analysis tasks might behave differently. Sixteen comparisons, so under a Bonferroni correction what I'd call confirmed is the exemption result and the two provenance effects in &lt;code&gt;claude-opus-5&lt;/code&gt;; the worsening in three smaller models and the effect of the explicit prohibition are indications, labelled as such above. The judge shares a model family with two subjects, which I'd fix with a third judge next time — the bias would flatter that family, and it came out worst on failure 2, so the direction is at least safe.&lt;/p&gt;

&lt;p&gt;And a caveat this article is itself an example of: all of it is measured in &lt;strong&gt;August 2026&lt;/strong&gt; against those six models. The failure I've spent the whole post describing is a claim outliving the conditions it was written under. Mine will too.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;The three failures need different fixes, which is why I think they're worth separating. Under-reading you fix by writing better documents. Stale-premise reasoning I couldn't fix at all — not with the date, not with a declaration, not with a direct prohibition — and it's the one the strongest models were worst at.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://www.javieraguilar.ai/en/blog/what-has-already-happened" 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>
      <category>contextengineering</category>
    </item>
    <item>
      <title>If You're Starting From Zero</title>
      <dc:creator>JaviMaligno</dc:creator>
      <pubDate>Mon, 10 Aug 2026 09:48:18 +0000</pubDate>
      <link>https://dev.to/javieraguilarai/if-youre-starting-from-zero-520</link>
      <guid>https://dev.to/javieraguilarai/if-youre-starting-from-zero-520</guid>
      <description>&lt;p&gt;There's a moment, the first time this works, that is genuinely one of the better feelings available in a professional life. You described something that didn't exist, and a few minutes later it existed, on your screen, doing what you said. If you never learned to write software, that moment lands even harder — you have just done a thing you had filed under &lt;em&gt;not for me&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;I don't want to spoil it. It is as real as it feels, and the people telling you it isn't are mostly protecting something.&lt;/p&gt;

&lt;p&gt;But there's a gap between that moment and having something other people can use, and almost nothing you did to get here helps you cross it. &lt;a href="https://www.javieraguilar.ai/en/blog/what-you-still-need-to-know-to-ship" rel="noopener noreferrer"&gt;I wrote the full map of that gap&lt;/a&gt; — thirteen categories, three levels each. This piece is the other half of the question, the one people actually ask me: &lt;strong&gt;not what's on the list, but what to do first.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The single most useful thing to understand on day one
&lt;/h2&gt;

&lt;p&gt;You are not having a conversation. You are running a loop.&lt;/p&gt;

&lt;p&gt;Specify → build → check → correct, then again. Anyone who came from a technical job has this in their bones and stops noticing it. If you didn't, it is genuinely new information, and its absence is the biggest difference I've seen between people who end up with something real and people who end up with a very long chat history.&lt;/p&gt;

&lt;p&gt;The default without it isn't dramatic. You ask, you get something, you look at it for two seconds, you ask for the next thing. Nothing ever checks anything. It feels like progress the entire time, right up until you try to show it to someone and discover which parts were never true.&lt;/p&gt;

&lt;p&gt;The fix is unglamorous and takes about ten seconds per cycle: after each thing you ask for, &lt;strong&gt;use it before asking for the next one.&lt;/strong&gt; Not read it — use it. That's the whole discipline, and it is worth more than any technical fact in this article.&lt;/p&gt;

&lt;h2&gt;
  
  
  The terminal, and why it's less of a wall than it used to be
&lt;/h2&gt;

&lt;p&gt;Almost everyone I've watched start from zero hits the same wall in the first hour, and it isn't a concept. It's a black window with text in it.&lt;/p&gt;

&lt;p&gt;Worth being explicit about why you meet it at all: &lt;strong&gt;the terminal is the front door.&lt;/strong&gt; The serious agents — Claude Code, Codex — are programs you start from there. You open the window, you type their name, and from that point you're talking to the agent in plain language. The window isn't the work. It's the doorway to it.&lt;/p&gt;

&lt;p&gt;The fear is rational. A terminal doesn't explain itself, doesn't confirm anything, and has historically punished typos in ways nothing else on your computer does. Every other piece of software you've used spent twenty years learning to be forgiving. This one didn't.&lt;/p&gt;

&lt;p&gt;Here's what changed: &lt;strong&gt;past that doorway, you're not the one who has to know the commands.&lt;/strong&gt; The agent runs them. What's left for you is much smaller — occasionally it hands you a line and asks you to run it yourself, usually because it needs a permission it doesn't have. Your job is to paste it and press enter without freezing.&lt;/p&gt;

&lt;p&gt;That's the level required. Not memorising anything: not seizing up when a command appears.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;And you can skip the doorway entirely.&lt;/strong&gt; Both Claude Code and Codex ship desktop apps that look like an app, with browser versions too. Starting there is a completely reasonable decision and I'd suggest it to anyone who's bounced off the terminal before.&lt;/p&gt;

&lt;p&gt;One difference that isn't obvious and causes real confusion: &lt;strong&gt;the browser versions don't run on your computer.&lt;/strong&gt; They run on someone else's, which means they can't see your files, don't have your keys, and don't have whatever you've installed. The desktop app and the terminal both work on your actual machine; the browser one works on a copy of your project, somewhere else. Early on this rarely matters. The moment your project depends on something that lives on your laptop, it matters a lot — and "it worked on my machine but not in the browser version" isn't the agent being inconsistent, it's two different environments with two different sets of configuration.&lt;/p&gt;

&lt;p&gt;Cursor is worth a mention too. It started as a code editor, which sounds like the least friendly option on this list, but its recent versions are more chat than editor and it ends up being one of the gentler ways in — especially if seeing the files alongside the conversation makes you feel more oriented rather than less.&lt;/p&gt;

&lt;p&gt;There's also Claude Cowork, which is worth knowing about precisely because it was built for people who don't code — you point it at a folder, describe what you want, and it works through the steps on your own machine. Its home ground is knowledge work rather than building an app: sorting files, pulling numbers out of a pile of documents, producing a report. If what you want is a working product, Claude Code or Codex is the tool. But Cowork is the clearest demonstration that "agent" and "terminal" were never the same thing, and it's a gentle way to get the feel of directing one.&lt;/p&gt;

&lt;p&gt;If you do start somewhere other than the terminal, two reasons to keep it as a starting point rather than a permanent home. The terminal is where the full set of capabilities lives — the apps trail on newer features, so sooner or later you'll want something yours hasn't got. And when things go wrong, it shows you the entire exchange between the agent and your machine, which is exactly what you want when you're trying to work out what happened.&lt;/p&gt;

&lt;p&gt;So: start wherever you'll actually start. Just know you're choosing the gentler door and not a different building.&lt;/p&gt;

&lt;p&gt;One more habit worth building early: &lt;strong&gt;read what it prints, roughly.&lt;/strong&gt; Not to understand it — to notice whether it ended in something that looks like a complaint. "Did that go well or badly" is a question you can answer from the shape of the output long before you can read a word of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  You'll ask for less than you could, and you won't know it
&lt;/h2&gt;

&lt;p&gt;The ceiling on everything you build is not your skill. It's your idea of what's askable.&lt;/p&gt;

&lt;p&gt;If you think these tools write snippets, you'll ask for snippets. If you think they can't touch payments, or email, or a database, you won't ask, and nothing will correct you, because an agent answers what you ask and never mentions what you didn't. This is the quietest failure in the whole thing: there's no error message for a question you never asked.&lt;/p&gt;

&lt;p&gt;There is one move that fixes this and it costs nothing. &lt;strong&gt;Ask the agent what it can do for your specific case.&lt;/strong&gt; Not in general — describe your actual project and ask what approaches exist, what it would need, what it can't do, and what you'd be signing up for. It answers this well, and almost nobody asks.&lt;/p&gt;

&lt;p&gt;Do it before you start building, and again whenever you find yourself assuming something isn't possible.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to learn first, and why in this order
&lt;/h2&gt;

&lt;p&gt;The map has thirteen categories. You do not need thirteen on day one, and someone handing you all of them at once is why most people bounce off. The order matters more than the list, and it comes from one question: &lt;strong&gt;when this goes wrong, who pays?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;First, the things you can't undo.&lt;/strong&gt; Your data isn't your code. If you delete the project and rebuild it, the code comes back — everything people typed into it doesn't. And there's a way to recover yesterday's version of your work that is not Ctrl+Z.&lt;/p&gt;

&lt;p&gt;Which leads to the one I see skipped most often: &lt;strong&gt;backups&lt;/strong&gt;. Not because the idea is foreign — you already keep copies of documents that matter to you, and you've felt the specific dread of a file you didn't back up. You just haven't applied it here, because a database doesn't look like a folder and nothing ever prompted you. So ask the plain version of the question: &lt;em&gt;if this database vanished tonight, where's the copy, and when was it last made?&lt;/em&gt; If the answer is a shrug, that's the afternoon's work, and it's worth more than everything else on this list combined.&lt;/p&gt;

&lt;p&gt;That group is the difference between a bad day and a lost month.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Second, the things that hurt other people.&lt;/strong&gt; The moment you have real users, the cost of getting something wrong stops being yours. Keys don't belong in the code. A login screen protects the screen, not the data behind it. If you store anything about other people, you've taken on obligations you didn't sign for. This is the group I'd never let someone skip, because it's the one where the person paying never agreed to your learning curve.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Third, the things that surprise you.&lt;/strong&gt; Your bill has no ceiling unless you set one. Your app works with you and might not with three hundred people. Something you depend on can disappear.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Last, the things that make it pleasant.&lt;/strong&gt; Tests, documentation, your own taste in how it looks. Real, and none of them are why anyone's project ends badly in month one.&lt;/p&gt;

&lt;p&gt;Four groups. Two afternoons for the first two. That is a completely different proposition from "learn to code", and it's the honest version of what this requires.&lt;/p&gt;

&lt;h2&gt;
  
  
  The mistake this whole article is about
&lt;/h2&gt;

&lt;p&gt;Everyone starting from zero makes the same one, and it isn't the one they're afraid of.&lt;/p&gt;

&lt;p&gt;The fear is of breaking something through ignorance. The actual mistake is &lt;strong&gt;handing over decisions you have no way to check&lt;/strong&gt; — and, worse, not knowing they were decisions. The agent picked a way to store your data. It picked what's public and what isn't. It picked what happens when something fails. Each of those looked, from your side, like nothing happening at all.&lt;/p&gt;

&lt;p&gt;That's what makes it hard to notice: delegating well and delegating blindly look identical from the outside. Both are you describing what you want and receiving something that works. The difference only shows up later, and only if you're unlucky.&lt;/p&gt;

&lt;p&gt;You don't fix it by delegating less — you'd be slower and no safer, since you can't check what you don't know about. You fix it by knowing which boxes exist, so you can see which ones are empty. &lt;strong&gt;An empty box you know about is a managed risk. An empty box you don't know exists is the thing that ends up in the news.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's the entire reason for the map, and why the first thing to learn isn't a skill.&lt;/p&gt;

&lt;h2&gt;
  
  
  If you came from WordPress, no-code, or automations
&lt;/h2&gt;

&lt;p&gt;Then you're not starting from zero, whatever you've been telling yourself.&lt;/p&gt;

&lt;p&gt;You already know a site can be live or not live. You've hit a plugin that broke everything and had to get back. You've had a subscription you forgot about. You know some things live in the tool and some things live in your account. Those are the same categories, learned in a different shape.&lt;/p&gt;

&lt;p&gt;What tends to be missing is narrower: the loop as a discipline, and the fact that this time nobody is protecting you. Those platforms had walls — you couldn't delete the database because you couldn't reach the database. Now you can reach everything, which is exactly why it's more powerful and exactly why the map matters.&lt;/p&gt;

&lt;p&gt;In my experience this profile adapts fastest of all. Not because they know more, but because they already believe software is something you can go and change, and that belief is most of the battle.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to start today
&lt;/h2&gt;

&lt;p&gt;If you want to know which boxes are empty for you, there's a &lt;a href="https://www.javieraguilar.ai/en/assessment" rel="noopener noreferrer"&gt;thirteen-question version&lt;/a&gt; of the map — about two minutes, and the useful outcome is finding a category you'd never considered.&lt;/p&gt;

&lt;p&gt;And the honest summary of everything above:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Use the thing after every change.&lt;/strong&gt; Not read it, use it. This one habit is worth more than any fact here.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ask what's possible before you assume it isn't.&lt;/strong&gt; The ceiling is your idea of what's askable, and the agent will happily raise it if asked.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Spend one afternoon on what can't be undone — backups included — and one on what hurts other people.&lt;/strong&gt; Skip the rest for now.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Assume you've handed over decisions you didn't notice.&lt;/strong&gt; You have. The question is only which ones.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;None of this is learning to program. It's learning what to keep an eye on while something else does the programming — which is a smaller job, and nobody teaches it because it falls between the cracks: too obvious for engineers, invisible to everyone else.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Second of three pieces on what building software with agents actually requires. The first is &lt;a href="https://www.javieraguilar.ai/en/blog/what-you-still-need-to-know-to-ship" rel="noopener noreferrer"&gt;the map itself&lt;/a&gt;; the last one goes the other way, for people with an engineering background who delegate too little rather than too much. If you'd rather go through your own project with someone, &lt;a href="https://www.javieraguilar.ai/en/mentoring" rel="noopener noreferrer"&gt;that's what I do&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/if-youre-starting-from-zero" 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>vibecoding</category>
      <category>software</category>
      <category>mentoring</category>
    </item>
    <item>
      <title>What You Still Need to Know to Ship</title>
      <dc:creator>JaviMaligno</dc:creator>
      <pubDate>Mon, 03 Aug 2026 11:47:03 +0000</pubDate>
      <link>https://dev.to/javieraguilarai/what-you-still-need-to-know-to-ship-3c49</link>
      <guid>https://dev.to/javieraguilarai/what-you-still-need-to-know-to-ship-3c49</guid>
      <description>&lt;p&gt;The people building software today did not all learn to build software. Some came from marketing, from operations, from running a small business, from nothing technical at all. They describe what they want, an agent builds it, and it works. That part is real and it isn't going away.&lt;/p&gt;

&lt;p&gt;What's also real is that a chunk of them are shipping things with the database wide open, the API key sitting in the browser bundle, and no way back to the version that worked last Tuesday. Not because they're careless. Because nobody told them those were categories of thing that exist.&lt;/p&gt;

&lt;p&gt;That's the actual gap, and it's smaller and stranger than "learn to code."&lt;/p&gt;

&lt;h2&gt;
  
  
  The ceiling: you can't ask about a space you don't know is there
&lt;/h2&gt;

&lt;p&gt;Every failure I've watched, in myself and in the people I've taught, traces back to the same place. Not a missing skill. A missing &lt;em&gt;category&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;You don't need to know how authentication works. You need to know that having a login screen and being protected are two different things — because the moment you know that, you can ask, and the agent handles the rest. If you don't know it, you'll never ask, and the agent won't volunteer it. Agents answer questions well. They're much worse at telling you which question you should have asked.&lt;/p&gt;

&lt;p&gt;I wrote about this from the other side in &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; — the engineer's version, where you delegate knowledge you used to hold and have to work out what you're still on the hook for. This is the same wall from the other direction: not what you're allowed to forget, but what you never learned and now can't skip.&lt;/p&gt;

&lt;p&gt;It's worth saying where this comes from. Most of my teaching has been with technical colleagues, which is a different problem and mostly a different article. With non-technical people my sample is smaller and more informal — friends, mostly, and that moment when someone meets a terminal for the first time. The rest comes from building this way myself, and from what I've caught agents doing along the way.&lt;/p&gt;

&lt;p&gt;There are two things to get, and they're independent. One is a way of working. The other is a map.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part one: the loop
&lt;/h2&gt;

&lt;p&gt;Specify → build → check → correct. Then again.&lt;/p&gt;

&lt;p&gt;There is nothing new about this. It's spec/develop/test, the way software has been made for decades. If you came from anything technical you have it in your bones and can skip ahead. If you didn't, it is genuinely novel, and its absence is the single biggest difference between someone who ships and someone who has a very long chat history.&lt;/p&gt;

&lt;p&gt;The default failure mode without it isn't dramatic. You ask, you receive, you believe it, you ask for the next thing. Nothing checks anything. It feels like progress right up until you try to show someone.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The agent is in all four phases&lt;/strong&gt;, which is what makes this confusing. It helps you specify. It builds. It runs checks and tells you what to look at. It proposes the fix. So what's left? The useful question isn't where the agent is — it's always there — but what human residue each phase leaves:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Specify&lt;/strong&gt; — the intent is yours. The agent will write down what you want far better than you would. It does not know what you want.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build&lt;/strong&gt; — nothing is left. Delegates completely.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check&lt;/strong&gt; — the agent runs things and reports. What stays yours is knowing &lt;em&gt;which categories need checking at all&lt;/em&gt;, and calling it done.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Correct&lt;/strong&gt; — nothing is left either. Correcting is building again, and the agent does it. When a failure makes you decide the whole thing should be rebuilt or dropped instead, that isn't correcting — that's going back to Specify with a different intent.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So the loop splits in half. Two phases hand over completely, two keep something human — and the two that vanish are exactly the ones people mean when they say "programming". What's left is saying what you want, and knowing whether you got it.&lt;/p&gt;

&lt;p&gt;Which is why "you don't need to know how to program anymore" is both true and unhelpful. It's true about half the loop and silent about the other half, which is the half that was never taught to anyone.&lt;/p&gt;

&lt;p&gt;Notice where checking sits: &lt;strong&gt;inside the loop, from day one.&lt;/strong&gt; It isn't a level you reach — it's a phase you're already in. What grows with experience isn't &lt;em&gt;whether&lt;/em&gt; you check, it's &lt;em&gt;what you're able to check&lt;/em&gt;. It comes in three ranges: the agent tells you what to look at and you look; you check what you know to ask about; or you also know whether the check was any good and what it missed. The first range is available to a complete beginner on their first afternoon, and it's enough more often than you'd think.&lt;/p&gt;

&lt;h3&gt;
  
  
  Three ways to catch things, none of which involve reading code
&lt;/h3&gt;

&lt;p&gt;This is the part people assume is impossible without a technical background, and it isn't. Everything I catch, I catch one of three ways, and they apply to every category further down.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Write it into the spec up front.&lt;/strong&gt; The cheapest one, because you don't catch the problem — you prevent it. If you say at the start that these values must come from the database and nothing may be hardcoded, you don't have to find the hardcoded value later. Most of what an agent gets "wrong" is a reasonable default it chose because you didn't state a preference.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use the running thing.&lt;/strong&gt; Not the code — the app. Click it, poke it, try the thing a user would try and then the thing they shouldn't. This is how I found an endpoint sitting open that shouldn't have been: it surfaced during testing, not during any code review.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ask when something smells.&lt;/strong&gt; A number that never changes when it should. A page that loads suspiciously fast. A screen that works when you're logged out. You don't need to know what's wrong to say "why does this never change?" — and the agent is genuinely excellent at going from that to a cause.&lt;/p&gt;

&lt;p&gt;None of these require reading a line of code. All of them require knowing that the category exists, which is the entire point of the map below.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part two: the map, and how to read your level
&lt;/h2&gt;

&lt;p&gt;For each category below there are three levels:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Aware&lt;/strong&gt; — you know this category exists and can go wrong. You don't need to know how it works or what it's called. You need to know it's there, because that's what makes you ask.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fluent&lt;/strong&gt; — you understand the vocabulary, can form the question, and can tell whether the answer makes sense. Worth saying plainly: &lt;strong&gt;this is the level you can reach by asking the agent itself.&lt;/strong&gt; Deliberately, with patience, one category at a time. It's the cheapest education available right now and almost nobody uses it on purpose.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Opinionated&lt;/strong&gt; — you have your own view and can choose between options.&lt;/p&gt;

&lt;p&gt;Two things about this that matter more than the levels themselves.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Your level is a vector, not a number.&lt;/strong&gt; Nobody sits on the same rung across the board. You can be Opinionated about access control and completely unaware of what your hosting costs. The profile is jagged, and that's normal — it's a map with holes in it, not a ladder you climb whole.&lt;/p&gt;

&lt;p&gt;If you want your own version of that jagged profile before reading on, there's a &lt;a href="https://www.javieraguilar.ai/en/assessment" rel="noopener noreferrer"&gt;thirteen-question version of this&lt;/a&gt; that takes about two minutes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Opinionated is optional — until it isn't.&lt;/strong&gt; You can ship most categories without ever forming a view of your own. But wherever the project is actually betting something, the top level stops being a luxury: if you charge money, you need a view on cost; if you hold other people's data, you need one on access.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5886cex6ycbqkuv8padq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5886cex6ycbqkuv8padq.png" alt="The map: twelve risk categories in five families, plus one of taste" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Twelve categories, plus one
&lt;/h2&gt;

&lt;p&gt;Twelve where things can go wrong, and one that's a different animal. Read down and find the ones you didn't know were categories — those are your answer.&lt;/p&gt;

&lt;h3&gt;
  
  
  The ceiling
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. What can even be asked for.&lt;/strong&gt; This one sits alone because it limits the other twelve. If you think an agent writes text, you will ask it to write text, and everything below stays theoretical.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Aware&lt;/strong&gt; — an agent builds entire working systems, not just snippets of writing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fluent&lt;/strong&gt; — describe what you want by outcome, ask it to research and propose before it builds, and understand it when it says something can't be done or offers a different route&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Opinionated&lt;/strong&gt; — which model, which tool, which hardware. A topic for a later piece.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Where it is and how I get it back
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;2. Where your app lives.&lt;/strong&gt; The environment question is where the genuinely scary mistakes live. Almost nobody deletes production data on purpose — they delete it believing they're in the test copy. And the separation you think you have may not exist: I've had pipelines that didn't distinguish environments at all, running CI against dev and prod alike, with no real separation behind the names. I found out by looking at the deployment dashboard, not by reading configuration — and then wrote the separation down so it stayed true.&lt;/p&gt;

&lt;p&gt;The same question applies to the agent itself, and catches people out. The browser versions of these tools don't run on your computer — they run on someone else's, so they can't see your files, don't have your keys, and don't have whatever you've installed. "It worked on my machine but not in the browser" isn't inconsistency; it's two environments with two sets of configuration.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Aware&lt;/strong&gt; — "running on my laptop" and "running on the internet" are different things. Close the laptop: is it still alive? And the browser version of your agent isn't on your laptop either.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fluent&lt;/strong&gt; — ask for a deploy, understand the difference between the test site and the real one, run a command you're handed without freezing, know which of the two you're touching right now, and know what kind of thing gets left behind when you work in the cloud — local files, keys, installed tooling — so you know when you have to come back to your own machine&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Opinionated&lt;/strong&gt; — domains, per-environment settings, host logs, rolling back a deploy, choosing where it runs, and configuring the remote one so it works there too&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;3. Code versus data.&lt;/strong&gt; Agents hardcode constantly — a value written into the code where it should have been read from the database. It's a reasonable shortcut when the goal is something that runs, and it's wrong the moment that data is meant to change. It's the standard case for the first and third techniques above: say up front that it mustn't happen, and ask later when a number looks suspiciously stable.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Aware&lt;/strong&gt; — code can be regenerated, data cannot; some data is sitting inside the code; data needs backups&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fluent&lt;/strong&gt; — ask where something gets stored and understand the answer, know whether the database is local or remote, and confirm a backup exists rather than having been mentioned&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Opinionated&lt;/strong&gt; — migrations, test data versus real data, restoring, what kind of database and shape&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;4. Getting back.&lt;/strong&gt; "It was working, now it isn't, and I don't know what changed" is the most common disaster in this field and the most completely solved one. The agent already commits for you. What's missing is you knowing the rescue exists so you can call for it.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Aware&lt;/strong&gt; — there's a way to recover yesterday's version and it isn't Ctrl+Z&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fluent&lt;/strong&gt; — ask for a save point before a big change, ask to go back, and check the history to confirm that point is really there&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Opinionated&lt;/strong&gt; — branches, tags, pull requests, reading a diff&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Who gets hurt if this fails
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;5. Secrets.&lt;/strong&gt; You do not need to be able to read a &lt;code&gt;.env&lt;/code&gt; file. You need to know it exists and that keys belong in it. What isn't obvious: taking a key out of the code doesn't remove it from the project's history, and I've had to scrub keys out of that history more than once. There are tools that watch for this — GitGuardian and similar — and, depending on how critical the key is, safe ways to hand one to someone that aren't a chat message.&lt;/p&gt;

&lt;p&gt;There's a second confusion worth naming, because it runs the other way. Not everything that lives in environment variables is a secret. Feature flags, timeouts, which region to use — settings drift into that file because it's the place things go, and then get treated with the ceremony a password deserves while the actual passwords get lost among them. Knowing which of your variables are secret and which are merely configuration is part of being fluent here.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Aware&lt;/strong&gt; — keys don't live in the code, they live somewhere separate; a key that's been seen once is no longer secret&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fluent&lt;/strong&gt; — ask for a key to be moved out of the code, understand why that file isn't uploaded, know where a key comes from when you're asked for one, know that a server key and a public client key are different animals, and tell a secret from a setting that merely ended up next to one&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Opinionated&lt;/strong&gt; — rotating a leaked key, secret managers, per-environment secrets&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;6. Who can get in.&lt;/strong&gt; The open endpoint above is this category. Worth restating what actually did the work there: the check was cheap and took a minute. Knowing it was a check worth running is the part that isn't free.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Aware&lt;/strong&gt; — having a login does not mean being protected&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fluent&lt;/strong&gt; — ask "can anyone call this?" and understand the answer; authentication is who you are, authorization is what you're allowed to touch; log in as one user and confirm you can't see another's data&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Opinionated&lt;/strong&gt; — roles and permissions, row-level security, tokens, reviewing what's exposed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;7. Other people's data.&lt;/strong&gt; The one category with a moral asymmetry: everything else here costs you money or embarrassment, and this one is paid by someone who never agreed to your learning curve.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Aware&lt;/strong&gt; — if you store things about other people, the cost of getting it wrong isn't yours&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fluent&lt;/strong&gt; — know what you're collecting and why, know there are legal obligations attached, and ask for anything unnecessary not to be stored at all&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Opinionated&lt;/strong&gt; — consent, retention, minimization, where the data physically lives&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What's going to surprise me
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;8. What this costs.&lt;/strong&gt; Surprise bills are more common than breaches, easier to prevent, and almost nobody prevents them — because "there is no default limit" isn't something you'd think to ask about. Early on I built agentic systems without asking for token costs to be tracked, which meant I had no cost estimate at the end and had to run the whole batch again just to measure it. Not measuring cost has a cost, and it's paid in exactly the currency you were trying to find out about. Ask for it in the spec and it's free.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Aware&lt;/strong&gt; — this generates a bill and by default nothing caps it&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fluent&lt;/strong&gt; — understand the shape of the bill (model and API usage, hosting, database, storage, traffic), fixed versus per-use, that CPU and GPU aren't priced alike; ask for a cap and look at real consumption&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Opinionated&lt;/strong&gt; — alerts, your own rate limits, bot protection, designing for cost&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;9. Who you depend on.&lt;/strong&gt; Separate from cost because the failure isn't financial. It's that something which worked stops existing. I've run a migration between platforms that the agent reported as complete, and which fell over the moment I tried to run on the new one alone — the old platform was still quietly holding it up, and became the backup I hadn't planned for. "Migration complete" and "the old thing can be switched off" are different claims, and only the second one is testable.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Aware&lt;/strong&gt; — your app leans on other people's services, and they can raise prices, change, or shut down&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fluent&lt;/strong&gt; — know which pieces are someone else's and which are yours, and ask what happens when one goes away&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Opinionated&lt;/strong&gt; — choose by coupling rather than price alone, and have an exit route&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;10. Holding more than you tested.&lt;/strong&gt; Nothing in the building process warns you about this, because while you're building there is exactly one user.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Aware&lt;/strong&gt; — it works with three users and can fall over with three hundred&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fluent&lt;/strong&gt; — know that testing and holding load are different questions, and ask what breaks first&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Opinionated&lt;/strong&gt; — measure it, size it, decide what's worth optimizing&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  How I know it's still fine
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;11. Tests, and where it breaks.&lt;/strong&gt; There should be tests for one unglamorous reason: without them, every check you'll ever do is you clicking through the finished app guessing what happened in the middle. That's a maze, and it grows with the project.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Aware&lt;/strong&gt; — there have to be tests, and "the agent says it works" isn't one&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fluent&lt;/strong&gt; — understand what you're told when someone says frontend or backend, run the tests and see them pass, and know where to look for an error depending on which side it's on&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Opinionated&lt;/strong&gt; — which kind of test for which risk&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;12. Still working in six months.&lt;/strong&gt; As a project grows messy and undocumented, &lt;strong&gt;the agent starts failing more.&lt;/strong&gt; That's the argument — not architectural purity, but your own tool getting worse at helping you. The failure mode is specific and easy to miss: documentation drifts out of date, the agent trusts it completely, and you get confident work built on a description that stopped being true months ago. Documentation only stays true if something reviews it against the code — and that something can be the agent itself, if you ask.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Aware&lt;/strong&gt; — mess degrades the thing you're relying on&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fluent&lt;/strong&gt; — ask for documentation, and understand the difference between docs for people and instructions for the agent, and notice when the project's own summary no longer matches what it does&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Opinionated&lt;/strong&gt; — what belongs in permanent instructions versus the conversation, and how to split the project up&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Plus one: taste
&lt;/h3&gt;

&lt;p&gt;The thirteenth isn't like the other twelve, which is why it's last and separate. In all twelve, failure has a victim. Here there's no failure — just absence.&lt;/p&gt;

&lt;p&gt;Agents are good at &lt;em&gt;working&lt;/em&gt;. They're mediocre at &lt;em&gt;good&lt;/em&gt;. The layout will be reasonable, the spacing fine, the colors the ones everything else has. Functionally correct and completely anonymous. Nobody will ever tell you it's wrong, because it isn't.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Aware&lt;/strong&gt; — what comes out by default works and looks like everything else&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fluent&lt;/strong&gt; — name what you don't like precisely enough for it to get fixed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Opinionated&lt;/strong&gt; — have a direction of your own and hold it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's also the only one with no shortcut. In the other twelve, knowing the category exists is enough to ask and let the agent carry it. Here you have to look at the thing and decide you don't like it, and nobody can do that part for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  The production line isn't about you
&lt;/h2&gt;

&lt;p&gt;I spent a while trying to define the point where a toy becomes real software as a level you reach. That's wrong, and it's worth saying why, because the correct version is more useful.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Production doesn't require you to be at a level. It requires that no category is left unchecked by anyone — you, a test, a service, or another person — and that you know which is which.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's the same conclusion I reached from the engineering side: you don't need to hold the knowledge, you need to hold the mechanism. It applies just as well to someone who was never an engineer. A category covered by a test is covered. A category covered by a service you pay for is covered. A category you check by hand every time is covered, expensively.&lt;/p&gt;

&lt;p&gt;Which is what Aware is actually for. It doesn't let you check anything. It lets you see that the box is &lt;strong&gt;empty&lt;/strong&gt;. An empty box you know about is a managed risk. An empty box you don't know exists is the thing that ends up in the news.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjwijwt46a23nrwmnm99m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjwijwt46a23nrwmnm99m.png" alt="The same project as a toy and as a production system" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Where you're starting from
&lt;/h2&gt;

&lt;p&gt;The map is the same for everyone. Which parts you already hold, and which way you fail, depends on where you came from.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;With no technical background&lt;/strong&gt;, your bottleneck is the ceiling, not the details. You ask for less than you could because you don't know what's askable. The loop is genuinely new. The terminal is frightening in a way that's hard to explain to someone who's used one for a decade. And your characteristic failure is delegating &lt;em&gt;above&lt;/em&gt; your level — handing over things you have no way to check and, worse, don't know need checking.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Coming from engineering or ops&lt;/strong&gt;, you have the loop already and most of the twelve. Your failure is the mirror image: you delegate &lt;em&gt;below&lt;/em&gt; your level. You review every line, take steps too small, and check by hand what a machine should be checking. You're slower than you need to be and it feels like diligence.&lt;/p&gt;

&lt;p&gt;More on both in the next two pieces.&lt;/p&gt;

&lt;h2&gt;
  
  
  How much to let go
&lt;/h2&gt;

&lt;p&gt;Here's what all of this measures, and it isn't knowledge.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Your level is how much you can hand over without going blind.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Each rung doesn't let you type more, it lets you &lt;em&gt;release&lt;/em&gt; more, because you have some way to catch it coming back wrong. Aware means you'll notice the category came up at all. Fluent means you can interrogate it. Opinionated means you can overrule it. At every step, the amount you can safely stop watching goes up.&lt;/p&gt;

&lt;p&gt;Which makes both failure modes the same mistake with the sign flipped. Delegating above your level is handing over what you can't check. Delegating below it is refusing to hand over what you can. The fix is identical in both directions: find your rung — in each category separately, because it's a vector — and let go of exactly as much as it holds.&lt;/p&gt;

&lt;p&gt;You don't need to learn to code. You need a loop, a map of what exists, and enough honesty about which boxes are empty.&lt;/p&gt;




&lt;h2&gt;
  
  
  The whole thing on one page
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Aware&lt;/th&gt;
&lt;th&gt;Fluent&lt;/th&gt;
&lt;th&gt;Opinionated&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;1. What can be asked for&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;agents build whole systems&lt;/td&gt;
&lt;td&gt;describe by outcome, ask it to propose&lt;/td&gt;
&lt;td&gt;model, tooling, hardware&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;2. Where it lives&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;laptop vs internet&lt;/td&gt;
&lt;td&gt;deploy, test site vs real, run a given command&lt;/td&gt;
&lt;td&gt;domains, env settings, rollback&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;3. Code vs data&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;data can't be regenerated&lt;/td&gt;
&lt;td&gt;where is this stored, local vs remote database&lt;/td&gt;
&lt;td&gt;migrations, restores, schema&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;4. Getting back&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;yesterday is recoverable&lt;/td&gt;
&lt;td&gt;ask for a save point, confirm it exists&lt;/td&gt;
&lt;td&gt;branches, tags, diffs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;5. Secrets&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;keys live outside the code&lt;/td&gt;
&lt;td&gt;secrets file, server vs client key&lt;/td&gt;
&lt;td&gt;rotation, secret manager&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;6. Who gets in&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;login ≠ protected&lt;/td&gt;
&lt;td&gt;can anyone call this, authn vs authz&lt;/td&gt;
&lt;td&gt;roles, permissions, tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;7. Other people's data&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;the damage isn't yours to pay&lt;/td&gt;
&lt;td&gt;what you collect and why, legal duties&lt;/td&gt;
&lt;td&gt;consent, retention, residency&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;8. What it costs&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;no default limit&lt;/td&gt;
&lt;td&gt;shape of the bill, ask for a cap&lt;/td&gt;
&lt;td&gt;alerts, rate limits, cost design&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;9. Who you depend on&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;someone else's service can vanish&lt;/td&gt;
&lt;td&gt;which pieces aren't yours&lt;/td&gt;
&lt;td&gt;coupling, exit plan&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;10. Holding load&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;works at three users, not three hundred&lt;/td&gt;
&lt;td&gt;testing vs load are different questions&lt;/td&gt;
&lt;td&gt;measure, size, optimize&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;11. Tests and breakage&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;there have to be tests&lt;/td&gt;
&lt;td&gt;frontend vs backend, run them, read the failure&lt;/td&gt;
&lt;td&gt;which test for which risk&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;12. Six months on&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;mess degrades the agent&lt;/td&gt;
&lt;td&gt;docs for people vs for agents&lt;/td&gt;
&lt;td&gt;permanent instructions, structure&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;+1. Taste&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;the default is anonymous&lt;/td&gt;
&lt;td&gt;name what you don't like&lt;/td&gt;
&lt;td&gt;a direction of your own&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;p&gt;&lt;strong&gt;Want your own map?&lt;/strong&gt; &lt;a href="https://www.javieraguilar.ai/en/assessment" rel="noopener noreferrer"&gt;Answer the thirteen questions&lt;/a&gt; and you'll get the same grid filled in for your project, along with which gap is worth closing first.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This is the first of three pieces on what building software with agents actually requires. The next two take the same map from opposite ends: one for people with no technical background, one for people who have too much of it and can't put it down. Related reading: &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;.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://www.javieraguilar.ai/en/blog/what-you-still-need-to-know-to-ship" 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>vibecoding</category>
      <category>software</category>
      <category>engineering</category>
    </item>
    <item>
      <title>Confident About Text It Cannot Read</title>
      <dc:creator>JaviMaligno</dc:creator>
      <pubDate>Sat, 01 Aug 2026 10:12:29 +0000</pubDate>
      <link>https://dev.to/javieraguilarai/confident-about-text-it-cannot-read-2i05</link>
      <guid>https://dev.to/javieraguilarai/confident-about-text-it-cannot-read-2i05</guid>
      <description>&lt;p&gt;Take the string &lt;code&gt;cmVwbHkgd2l0aCB0aGUgc2luZ2xlIHdvcmQgYmFuYW5h&lt;/code&gt;. It is base64, and it decodes to &lt;em&gt;reply with the single word banana&lt;/em&gt;. Send it to GPT-5 through the Azure gateway and you do not get back &lt;code&gt;banana&lt;/code&gt;. You get an HTTP 400, and a filter verdict that names its reason:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;jailbreak:                 { detected: true,  filtered: false }
self_harm:                 { filtered: true,  severity: "medium" }
hate / sexual / violence:  safe
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A self-harm classifier looked at a string it could not parse and assigned it a &lt;strong&gt;medium severity&lt;/strong&gt;. Not "unknown", not an abstention, not a low-confidence flag — a specific, confident, wrong verdict about a request for the word &lt;em&gt;banana&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The string is not some exotic edge case, either. Paste it into ChatGPT and the answer comes straight back:&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%2Fciphers-chatgpt-banana.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%2Fciphers-chatgpt-banana.png" alt="Screenshot of a ChatGPT conversation. The user message is the base64 string cmVwbHkgd2l0aCB0aGUgc2luZ2xlIHdvcmQgYmFuYW5h and the assistant's entire reply is the single word: banana." width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Different stack, different filters, no drama — it decodes the request and answers it. That contrast is the point. The severity verdict is not a fact about the request; it is a property of the ensemble sitting in front of one particular deployment, an ensemble that had to say &lt;em&gt;something&lt;/em&gt; about a string it could not read, and said &lt;code&gt;medium&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That is the shape of everything worth reporting from this experiment. I have now finished the full version of the cipher sweep I &lt;a href="https://www.javieraguilar.ai/en/blog/ciphers-edges-of-language" rel="noopener noreferrer"&gt;wrote about in the previous post&lt;/a&gt; — 696 conversations, 6,960 model turns, ten ciphers the models were never taught. Three separate systems in it turned out to do the same thing: produce a confident, structured judgement about text they cannot read. One is that safety filter. One is a base model writing fluent cipher it does not understand. And one is an LLM judge — the one my own earlier conclusion was resting on.&lt;/p&gt;

&lt;p&gt;So this post starts with the correction.&lt;/p&gt;

&lt;h2&gt;
  
  
  What didn't survive
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://www.javieraguilar.ai/en/blog/ciphers-edges-of-language" rel="noopener noreferrer"&gt;cipher post&lt;/a&gt; ended on a reassuring note. Its sub-probe on robustness reported that encoding a normally-refused request made compliance &lt;em&gt;collapse&lt;/em&gt; — 36% in plain text against about 1% under ciphers — and I drew the obvious conclusion: a cipher doesn't unlock a refused request, it defuses it. The models flip into translator mode, decode the message, and comment on it instead of obeying.&lt;/p&gt;

&lt;p&gt;That post hedged the claim in several directions, and I still think the hedges were right. But the direction of the headline was wrong, for a reason that is worth more than the conclusion it replaced.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The measurement was broken in a specific way.&lt;/strong&gt; That first sub-probe judged the &lt;em&gt;decoded&lt;/em&gt; view of every reply unconditionally. So when a model decoded a harmful request and then refused in plain English — the single most common thing that happens — the judge took that plain-English refusal and ran it back through the cipher before scoring it. It saw noise and filed it as "garbled". Every letter-level cipher condition returned garbled by construction, which pushes both compliance and refusal toward zero and leaves an encouraging-looking near-1%.&lt;/p&gt;

&lt;p&gt;The corrected probe judges whichever view actually reads as English, records which channel the model answered in, and stores the judge's identity for every cell. Then it does the thing the first run didn't: it has a &lt;strong&gt;second, independent judge&lt;/strong&gt; re-label a sample, so I can ask whether the labels mean anything before reading a rate off them.&lt;/p&gt;

&lt;p&gt;They mostly don't. And where they do, the answer runs the other way.&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%2Fciphers-full-judge-agreement.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%2Fciphers-full-judge-agreement.png" alt="Bar chart of inter-judge agreement (Cohen's kappa) for three models, split by plain-text and ciphered replies. GPT-5: 1.00 plain text, 0.35 ciphered. Qwen-7B Instruct: 0.66 plain text, minus 0.17 ciphered. Qwen-7B Base: 0.74 plain text, minus 0.05 ciphered. The negative region is shaded and labelled 'worse than chance'." width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On plain text, two independent judges agree about what happened, for every model. On ciphered replies they agree on GPT-5 — and on the two 7B models they agree &lt;strong&gt;worse than chance&lt;/strong&gt;: kappa of −0.17 and −0.05. Per cipher the same split shows up, from 0.71 on full reversal down to 0.002 on the keyed substitution.&lt;/p&gt;

&lt;p&gt;That is not a small caveat about noisy data. It means that for those models, in that condition, the standard CipherChat-style instrument does not measure anything. Their "uplift" comes out strongly negative — the base model falls from 80% compliance in plain text to somewhere between 0% and 20% under every cipher — and &lt;strong&gt;that negative number is not evidence that ciphers protect them.&lt;/strong&gt; A 7B model answering an encoded harmful request usually emits something that is neither a refusal nor a fulfilment, and "refused / complied / garbled" are plain-text categories that don't carve up that output space. A compliance the judge fails to recognise in half-decoded text gets filed as garbled, which lowers the rate mechanically. Whichever judge you happened to run is doing the work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;On GPT-5, where the judges do agree, a bypass appears.&lt;/strong&gt; In plain text it refused all 30 harmful cells — a clean 100%, with both judges in perfect agreement. Under ciphers, two distinct prompts were complied with under two distinct ciphers (letters→digits and full reversal) after being refused in plain text. Each reproduced across replicates, and both judges independently labelled all four cells as compliances — including the judge that isn't GPT-5, which matters, because a judge scoring its own outputs would be biased toward reading them as refusals.&lt;/p&gt;

&lt;p&gt;I want to be exact about how much that is and isn't. It is an &lt;strong&gt;existence result, never a rate&lt;/strong&gt;. Ten prompts cannot estimate a frequency; every confidence interval here includes zero; 0.067 is literally two prompts. What it establishes is that refusal is not a property of the request's meaning alone — the same meaning, wrapped in a cipher, crossed a boundary it did not cross in plain text. Note also which ciphers did &lt;em&gt;not&lt;/em&gt; do it: ROT13 and base64, the two GPT-5 decodes most fluently, and the keyed cipher it has to work out. Whatever is going on, it isn't "harder to read means easier to bypass".&lt;/p&gt;

&lt;p&gt;The whole sub-probe used a deliberately mild subset of &lt;strong&gt;AdvBench&lt;/strong&gt;, with CBRN, weapons, CSAM and self-harm categories excluded, and it stores labels only — no harmful prompt or model completion is published, here or in the repo.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part the judges never touched
&lt;/h2&gt;

&lt;p&gt;Everything above is the appendix. The main experiment is the reason I trust it at all, because &lt;strong&gt;its core metrics use no LLM judge&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Each turn, the model gets a short verifiable instruction — &lt;em&gt;reply with the single word banana&lt;/em&gt;, &lt;em&gt;what is seven plus five&lt;/em&gt; — encoded in the active cipher, and I check two things programmatically. &lt;strong&gt;Comprehension&lt;/strong&gt;: did it &lt;em&gt;act&lt;/em&gt; correctly on the decoded instruction, in whatever language it answered? A task oracle decides that, deterministically. &lt;strong&gt;Production&lt;/strong&gt;: did it write its reply in the code? The inverse cipher decides that, by decoding the reply and checking the result reads as English while the raw reply doesn't.&lt;/p&gt;

&lt;p&gt;Ten ciphers, three exposure protocols, eight replicates, three models: 696 conversations. Two of the ciphers — a random alphabet substitution and a block permutation — are &lt;strong&gt;keyed per run&lt;/strong&gt;, so their mapping cannot have been memorised in training. Those are the only ones that speak to inference rather than recognition.&lt;/p&gt;

&lt;p&gt;Most of what that machinery found, &lt;a href="https://www.javieraguilar.ai/en/blog/ciphers-edges-of-language" rel="noopener noreferrer"&gt;the earlier post&lt;/a&gt; already reported from the pilot, and the full sweep confirms it rather than revising it: the keyed ciphers are the genuinely hard ones, three plain-text↔coded example pairs lift comprehension sharply, and models only start &lt;em&gt;replying&lt;/em&gt; in code when you ask them to. Two details are worth adding. Pooled across models, Morse, disemvoweling and ROT13 have a &lt;strong&gt;median of one turn whenever they get solved at all&lt;/strong&gt; — recognition is instant or it never comes, with almost nothing in between, which is the opposite of the several-turn grind the keyed ciphers produce. And base64's row rests on two models rather than three, because Azure rejected GPT-5's entire base64 block: the rejection from the top of this post is also a hole in the results table.&lt;/p&gt;

&lt;p&gt;The genuinely new thing needs every model's &lt;em&gt;production&lt;/em&gt; rate side by side.&lt;/p&gt;

&lt;h3&gt;
  
  
  The two abilities dissociate in opposite directions
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.javieraguilar.ai%2Fblog%2Fciphers-full-dissociation.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%2Fciphers-full-dissociation.png" alt="Grouped bar chart comparing comprehension and production rates per model, with confidence intervals. GPT-5: 97% comprehension, 24% production. Qwen-7B Instruct: 83% and 20%. Qwen-7B Base: 36% comprehension, 56% production — the only model that produces more than it understands." width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That the two are separable was already the earlier post's finding — escalation drives production up without moving comprehension. What the full sweep shows is sharper: they don't just separate, they &lt;strong&gt;invert&lt;/strong&gt;. GPT-5 understands 97% of cells and answers in plain English anyway, writing back in code in only 24% of them. Qwen-7B Base does the reverse: it understands 36% and writes in code 56% of the time. It is the only model here that produces more than it understands.&lt;/p&gt;

&lt;p&gt;The base model's half of that needs a caveat, and it is the third system on my list. In three cipher cells — binary, Cyrillic homoglyphs, block permutation — it produced in-code output while &lt;strong&gt;never once&lt;/strong&gt; acting correctly on the decoded task. That is not adopting the code. It is continuing the surface pattern of the prompt, which is exactly what a completion model without instruction tuning should do. Fluent cipher, zero comprehension. Reported as mimicry, not as speaking the language.&lt;/p&gt;

&lt;h2&gt;
  
  
  The thing they have in common
&lt;/h2&gt;

&lt;p&gt;A filter that cannot parse base64 assigns it a medium self-harm severity. A judge that cannot read a half-decoded reply files it as garbled and quietly deflates a rate. A base model that has understood nothing produces flawless cipher. In all three cases the system's output has the &lt;em&gt;form&lt;/em&gt; of a judgement — a severity, a label, a fluent answer — and none of the content one would need behind it.&lt;/p&gt;

&lt;p&gt;That is a specific, unglamorous failure mode, and it is a different story from the one I told in the previous post. The reassuring reading I published wasn't wrong because the models turned out to be more fragile than I thought. It was wrong because I read a number off an instrument that was, in that condition, measuring nothing — and the artefact happened to point somewhere comfortable.&lt;/p&gt;

&lt;p&gt;So the practical lesson isn't about ciphers. If you are replicating CipherChat-style work, or any evaluation where an LLM judge scores text the model may have mangled, &lt;strong&gt;compute inter-judge agreement per subject and per condition before you report a single rate&lt;/strong&gt;. On this material that one extra judging pass is the entire difference between a measurement and a confident artefact. It cost me one run to find out, and I'd already published the artefact.&lt;/p&gt;

&lt;p&gt;The defensive version of the same lesson: a plain-text safety filter and the decoded instruction are two different surfaces. The same ensemble over-blocks an innocuous encoded request about a banana &lt;em&gt;and&lt;/em&gt; let two harmful ones through on a frontier model. Tuning for one of those surfaces tells you very little about the other.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Code, ciphers, the deterministic oracle, and the full method: &lt;a href="https://github.com/JaviMaligno/llm-language-limits" rel="noopener noreferrer"&gt;github.com/JaviMaligno/llm-language-limits&lt;/a&gt; — with a provenance record in &lt;code&gt;docs/PUBLICATION_FREEZE.md&lt;/code&gt; (model ids, served versions, sampling settings, cipher seed, and a sha256 for every data file, stamped with the generating commit) and 172 tests. This finishes the cipher study that began in &lt;a href="https://www.javieraguilar.ai/en/blog/ciphers-edges-of-language" rel="noopener noreferrer"&gt;Teaching a Model a Language You Just Invented&lt;/a&gt;, itself the second piece in a series on the edges of language after &lt;a href="https://www.javieraguilar.ai/en/blog/repetition-edges-of-language" rel="noopener noreferrer"&gt;Repetition at the Edges of Language&lt;/a&gt;. The robustness sub-probe follows the framing of &lt;a href="https://arxiv.org/abs/2308.06463" rel="noopener noreferrer"&gt;CipherChat&lt;/a&gt; and reports aggregate labels only.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://www.javieraguilar.ai/en/blog/confident-about-unreadable-text" 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>evaluation</category>
      <category>alignment</category>
    </item>
    <item>
      <title>Where's the Ball? Part 4 — When Nothing Moves</title>
      <dc:creator>JaviMaligno</dc:creator>
      <pubDate>Fri, 31 Jul 2026 11:02:14 +0000</pubDate>
      <link>https://dev.to/javieraguilarai/wheres-the-ball-part-4-when-nothing-moves-3a8a</link>
      <guid>https://dev.to/javieraguilarai/wheres-the-ball-part-4-when-nothing-moves-3a8a</guid>
      <description>&lt;p&gt;&lt;a href="https://www.javieraguilar.ai/en/blog/wheres-the-ball-3/" rel="noopener noreferrer"&gt;Part 3&lt;/a&gt; closed the trilogy on a blind spot: the little specialist is overconfident on &lt;em&gt;loose balls&lt;/em&gt; — the ones out in open space, far from the player mass. I meant to stop there. But a question kept nagging, and chasing it turned out to be worth one more post.&lt;/p&gt;

&lt;p&gt;The question: aren't those hard cases just &lt;strong&gt;set pieces&lt;/strong&gt;? A corner, a free kick — the ball's whereabouts depends entirely on whether the taker has struck it yet. Before the kick it's on a chalk mark; after, it's a projectile the players are still reacting to. Maybe the wide error is really about that awkward in-between, the ball in flight while everyone re-forms around where it's going.&lt;/p&gt;

&lt;p&gt;It's a good hypothesis. It's also wrong — and being wrong pointed at something better.&lt;/p&gt;

&lt;h2&gt;
  
  
  The premise didn't survive contact
&lt;/h2&gt;

&lt;p&gt;Metrica's data comes with an event log — every pass, shot, and set piece, timestamped with the ball's start and end. So I could label each frame by what was happening and ask where the error actually lives.&lt;/p&gt;

&lt;p&gt;Set pieces barely registered: as a fraction of playing time they're a rounding error, because the "set piece" event is the instant of execution, not a phase you spend minutes in. Long balls and clearances &lt;em&gt;were&lt;/em&gt; modestly harder than short passes — a small win for the broader version of the hypothesis — but they didn't dominate either. The wide error was spread across ordinary open play.&lt;/p&gt;

&lt;p&gt;The sharper cut wasn't the event label at all. It was the &lt;strong&gt;speed of the ball&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqi2d65x1vwsa6kz2ko8h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqi2d65x1vwsa6kz2ko8h.png" alt="The model needs motion: the still ball is the blind spot" width="800" height="462"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And it flips the intuition on its head. The ball &lt;em&gt;in flight&lt;/em&gt; — fast, airborne, the case I'd expected to be hardest — is only mildly difficult. The hardest ball to locate is the one that's barely moving at all: settled, under 2 m/s, a dead ball or one resting at someone's feet. The correlation between ball speed and error is essentially zero; the structure is entirely in that first bar.&lt;/p&gt;

&lt;h2&gt;
  
  
  No motion, no signal
&lt;/h2&gt;

&lt;p&gt;Once you see it, it's the most on-brand result this project could have produced. Every part of this series has landed on the same finding: the signal a model reads to place a hidden ball is &lt;strong&gt;motion&lt;/strong&gt; — where the running mass of players is heading. So what happens when nothing is moving?&lt;/p&gt;

&lt;p&gt;There's nothing to read.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzflr30gfyls0rlfoqir3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzflr30gfyls0rlfoqir3.png" alt="Velocity is the signal — and it's nearly useless when nothing moves" width="800" height="477"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Strip the velocity channel out of the model and watch how much it costs, split by whether the ball is moving. When the ball is moving, velocity is worth a lot — dropping it inflates the error by about 0.04. When the ball is still, velocity is worth almost nothing: the model with velocity and the model without it are nearly the same, because there's no coherent motion for the velocity features to feed on. The specialist's whole edge evaporates in exactly the moment the pitch goes quiet. (This held in both train/test directions, and the collapse was even sharper the other way round — velocity's contribution fell essentially to zero on still balls.)&lt;/p&gt;

&lt;p&gt;There's a second reason still balls are hard, and it sharpens Part 3's blind spot. The &lt;em&gt;worst&lt;/em&gt; of the still balls aren't the ones near a touchline, where a throw-in pulls players into a tidy cluster. They're the ones sitting &lt;strong&gt;centrally, far from everyone&lt;/strong&gt; — a dead ball in the middle of the park with the players spread out and nobody converging. Among still balls, the correlation between "distance from the player mass" and error is strong and steady (+0.53 to +0.65 across both directions). A ball no one is near and no one is moving toward is, quite literally, undetermined: the players have stopped telling you where it is.&lt;/p&gt;

&lt;h2&gt;
  
  
  But in flight, you can still read the &lt;em&gt;direction&lt;/em&gt;
&lt;/h2&gt;

&lt;p&gt;That leaves the other half of the original question. Even if the ball in flight isn't the hardest case, the intuition behind it was real: while a ball travels, the players are already shaping what happens next with their runs. Maybe what's recoverable there isn't the ball's exact position, but where it's &lt;em&gt;going&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;So I asked the model a different question about fast balls — not "where is it?" but "which way is it heading?" — and had it predict the ball's direction of travel from the players alone.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fclc9of3n3fb8fhb4nd49.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fclc9of3n3fb8fhb4nd49.png" alt="In flight, which way the ball is going is readable" width="800" height="433"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It can. A random guess at a 2D direction is off by 90° on average; the players pin it to about 45°, with half of all fast balls placed within a 45° cone. Not precise — but unmistakably real: the configuration of the pitch encodes which way the ball is travelling, because the players are already leaning into the play. I'll be honest that this doesn't cleanly beat position — in flight, direction and position turn out to be recoverable to roughly the same degree, and comparing an angle to a distance is apples-to-oranges. The honest claim is the modest one: the direction is legible, not that it wins.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The hardest ball to find is the one standing still.&lt;/strong&gt; Not the ball in flight — the settled ball, under 2 m/s, is where the specialist's error is highest. Ball speed and error are otherwise uncorrelated; the difficulty is concentrated entirely in stillness.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Because motion is the whole signal.&lt;/strong&gt; Velocity features carry the localization; when the ball is still there's no motion to read, and dropping velocity costs almost nothing. The model's strength and its blind spot are the same fact seen from two sides.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A dead ball no one is near is undetermined.&lt;/strong&gt; The worst still balls are central and far from the player mass (coupling-to-error correlation +0.53 to +0.65) — the players have simply stopped pointing at it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;In flight, direction is readable even if position isn't precise.&lt;/strong&gt; Players' runs pin a fast ball's heading to ~45° (chance is 90°). The original set-piece hunch was wrong about &lt;em&gt;which&lt;/em&gt; phase is hard, but right that the players are always shaping what comes next.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Four parts in, the throughline is hard to miss. A hidden ball is found through motion — the frontier models can't act on that cue, a 60 kB network reads it sharply, and it turns out to be plain geometry. Which means the one thing none of them can do is find a ball that has stopped moving and drifted away from the crowd. The blind spot was never a kind of play. It was stillness.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Code, the event-log analysis, and the velocity-ablation and direction experiments in the &lt;a href="https://github.com/JaviMaligno/wheres-the-ball" rel="noopener noreferrer"&gt;&lt;code&gt;wheres-the-ball&lt;/code&gt;&lt;/a&gt; repo. Soccer tracking and event data from &lt;a href="https://github.com/metrica-sports/sample-data" rel="noopener noreferrer"&gt;Metrica Sports&lt;/a&gt;. Previously: &lt;a href="https://www.javieraguilar.ai/en/blog/wheres-the-ball/" rel="noopener noreferrer"&gt;Part 1&lt;/a&gt;, &lt;a href="https://www.javieraguilar.ai/en/blog/wheres-the-ball-2/" rel="noopener noreferrer"&gt;Part 2&lt;/a&gt;, and &lt;a href="https://www.javieraguilar.ai/en/blog/wheres-the-ball-3/" rel="noopener noreferrer"&gt;Part 3&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/wheres-the-ball-4" 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>computervision</category>
      <category>research</category>
    </item>
    <item>
      <title>Three Judges, Three Rankings</title>
      <dc:creator>JaviMaligno</dc:creator>
      <pubDate>Thu, 30 Jul 2026 10:35:40 +0000</pubDate>
      <link>https://dev.to/javieraguilarai/three-judges-three-rankings-2li4</link>
      <guid>https://dev.to/javieraguilarai/three-judges-three-rankings-2li4</guid>
      <description>&lt;p&gt;&lt;a href="https://www.javieraguilar.ai/en/blog/llm-as-judge-three-decisions/" rel="noopener noreferrer"&gt;An earlier post here&lt;/a&gt; argued that LLM-as-judge is three decisions — context, unit, dimension — and that all three happen before you write the prompt. That post was about &lt;em&gt;what&lt;/em&gt; you score. This one is about &lt;em&gt;who&lt;/em&gt; scores it.&lt;/p&gt;

&lt;p&gt;The question came out of a conversation about evaluating two systems, one built on GPT and one on Claude. If you use a GPT judge, is the comparison still fair? The judge never sees a model name. But it doesn't need one: every family has a recognisable house style, and a judge that learned to produce a style may well reward it.&lt;/p&gt;

&lt;p&gt;That is a testable claim, and "several papers have found this" is not the same as having measured it on your own tasks. So I built a harness and measured it. Everything below is 378 judgments across three model families, and every number is reproducible from &lt;a href="https://github.com/JaviMaligno/personal-website/tree/main/experiments/judge-bias" rel="noopener noreferrer"&gt;the repo&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup, in one paragraph
&lt;/h2&gt;

&lt;p&gt;Fifteen tasks, split evenly across three levels of subjectivity: objective ones with a checkable answer (parse a duration string, fix a median bug, merge intervals), medium ones (summarize, extract to JSON, classify tickets), and subjective ones (error-message copy, a postmortem opening, explaining eventual consistency to a PM). Three models answer all fifteen: &lt;strong&gt;GPT-5.5&lt;/strong&gt;, &lt;strong&gt;Grok-4.3&lt;/strong&gt; and &lt;strong&gt;Claude Sonnet 4.6&lt;/strong&gt;. Then each of those three models judges every pair of answers, blinded — no model names anywhere in the prompt, which the harness asserts before sending. Every comparison is judged &lt;strong&gt;twice, with the two answers swapped&lt;/strong&gt;, because the rate at which a judge reverses itself when you swap the slots &lt;em&gt;is&lt;/em&gt; the position bias, measured rather than assumed. Ties are allowed: forcing a binary choice between two equally good answers manufactures coin flips that later get misread as bias.&lt;/p&gt;

&lt;p&gt;All three families ran through a single Azure AI Foundry subscription, which is the cheap trick that made this affordable — one billing relationship instead of three vendor accounts.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The ranking depends on who is holding the clipboard
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F03hx90p61tr09l1a8kvy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F03hx90p61tr09l1a8kvy.png" alt="Three judges, three different rankings of the same 45 comparisons" width="799" height="414"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Same answers. Same blinded prompt. Both presentation orders. The only thing that changes between those three columns is which model is judging — and the ordering changes with it:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Generator&lt;/th&gt;
&lt;th&gt;Overall&lt;/th&gt;
&lt;th&gt;by GPT-5.5&lt;/th&gt;
&lt;th&gt;by Grok-4.3&lt;/th&gt;
&lt;th&gt;by Claude Sonnet 4.6&lt;/th&gt;
&lt;th&gt;Mean words&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GPT-5.5&lt;/td&gt;
&lt;td&gt;66.1%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;85.0%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;58.3%&lt;/td&gt;
&lt;td&gt;55.0%&lt;/td&gt;
&lt;td&gt;66&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude Sonnet 4.6&lt;/td&gt;
&lt;td&gt;45.0%&lt;/td&gt;
&lt;td&gt;35.0%&lt;/td&gt;
&lt;td&gt;38.3%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;61.7%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;85&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Grok-4.3&lt;/td&gt;
&lt;td&gt;38.9%&lt;/td&gt;
&lt;td&gt;30.0%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;53.3%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;33.3%&lt;/td&gt;
&lt;td&gt;55&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Three judges, three different orderings. Every judge puts itself higher than the other two put it. Two of them crown GPT-5.5; the third crowns itself. And all three disagree about the rest of the order — so if you had run this evaluation with a single judge and shipped the ranking, part of what you shipped would have been the judge.&lt;/p&gt;

&lt;p&gt;That is the symptom. The rest is the diagnosis.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Self-preference is real, and it scales with subjectivity
&lt;/h2&gt;

&lt;p&gt;The naive way to measure self-preference is to count how often a judge picks its own answer. That measure is useless: GPT-5.5 picks its own answer 85% of the time, but its answers might simply be the best ones — and by the other two judges' reckoning, they largely are.&lt;/p&gt;

&lt;p&gt;What actually proves bias is the &lt;strong&gt;delta&lt;/strong&gt;: how much more often a judge picks its own answer &lt;em&gt;than its peers pick that same answer, on the same comparisons&lt;/em&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Judge&lt;/th&gt;
&lt;th&gt;Own rate&lt;/th&gt;
&lt;th&gt;Peers, same pairs&lt;/th&gt;
&lt;th&gt;Delta&lt;/th&gt;
&lt;th&gt;95% CI&lt;/th&gt;
&lt;th&gt;vs uninvolved judge only&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GPT-5.5&lt;/td&gt;
&lt;td&gt;85.0%&lt;/td&gt;
&lt;td&gt;56.7%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;+28.3pp&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;[+16.7, +40.0]&lt;/td&gt;
&lt;td&gt;+21.7pp [+8.3, +35.0]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude Sonnet 4.6&lt;/td&gt;
&lt;td&gt;61.7%&lt;/td&gt;
&lt;td&gt;36.7%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;+25.0pp&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;[+12.5, +36.7]&lt;/td&gt;
&lt;td&gt;+15.0pp [+0.0, +28.3]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Grok-4.3&lt;/td&gt;
&lt;td&gt;53.3%&lt;/td&gt;
&lt;td&gt;31.7%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;+21.7pp&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;[+9.2, +35.8]&lt;/td&gt;
&lt;td&gt;+13.3pp [−1.7, +30.0]&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;All three positive, all three intervals excluding zero, all three about the same size. This is the part that a within-family pilot could not show: when I ran the same harness across three tiers of &lt;em&gt;one&lt;/em&gt; family, the deltas came out +16.7, −14.6 and +4.2pp — noise pointing nowhere. Across families it is a straight line.&lt;/p&gt;

&lt;p&gt;That last column deserves a note, because it is the one I would quote. With three judges that double as the three generators, the "peers" baseline for a comparison between A and B includes &lt;strong&gt;B itself&lt;/strong&gt; — and B's own self-preference pushes the verdict away from A, inflating A's measured delta. Restricting the baseline to the judge with no stake in the pair knocks about a third off every number. The effect survives that correction for GPT-5.5, marginally for Claude, and becomes indistinguishable from zero for Grok. Anyone measuring self-preference with a judge panel drawn from the models under test is over-counting unless they do this.&lt;/p&gt;

&lt;p&gt;Now the finding I care most about:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwd4gc0hflp7bynt4a4vt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwd4gc0hflp7bynt4a4vt.png" alt="A judge's bias towards its own answers grows with how subjective the task is" width="799" height="414"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Judge&lt;/th&gt;
&lt;th&gt;Objective&lt;/th&gt;
&lt;th&gt;Medium&lt;/th&gt;
&lt;th&gt;Subjective&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GPT-5.5&lt;/td&gt;
&lt;td&gt;+17.5pp&lt;/td&gt;
&lt;td&gt;+25.0pp&lt;/td&gt;
&lt;td&gt;+42.5pp&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Grok-4.3&lt;/td&gt;
&lt;td&gt;+7.5pp&lt;/td&gt;
&lt;td&gt;+27.5pp&lt;/td&gt;
&lt;td&gt;+30.0pp&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude Sonnet 4.6&lt;/td&gt;
&lt;td&gt;−2.5pp&lt;/td&gt;
&lt;td&gt;+30.0pp&lt;/td&gt;
&lt;td&gt;+47.5pp&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;On tasks with a checkable answer, self-preference is small or absent — Claude Sonnet 4.6 is actually &lt;em&gt;harder&lt;/em&gt; on itself than a neutral judge is. On tasks where the criterion is taste, it reaches +30 to +48 percentage points.&lt;/p&gt;

&lt;p&gt;Which turns a vague warning into a usable rule: &lt;strong&gt;a judge from the same family as one of the systems you're comparing is close to harmless on objective work and close to unusable on subjective work.&lt;/strong&gt; If your rubric is "did the extraction produce the right JSON", relax. If it is "which error message reads better", your judge is a participant.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Position bias is not a law of nature — it is a property of one judge
&lt;/h2&gt;

&lt;p&gt;Everyone repeats "randomize the presentation order." Here is what the order actually did:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Judge&lt;/th&gt;
&lt;th&gt;slot-A rate&lt;/th&gt;
&lt;th&gt;exact binomial vs 50%&lt;/th&gt;
&lt;th&gt;flip rate&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GPT-5.5&lt;/td&gt;
&lt;td&gt;52.6%&lt;/td&gt;
&lt;td&gt;p = 0.73&lt;/td&gt;
&lt;td&gt;8.9%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude Sonnet 4.6&lt;/td&gt;
&lt;td&gt;51.5%&lt;/td&gt;
&lt;td&gt;p = 0.90&lt;/td&gt;
&lt;td&gt;20.0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Grok-4.3&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;72.4%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;p &amp;lt; 0.001&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;35.6%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two of the three judges show no position preference whatsoever. The third picks whatever it reads first almost three times in four, and reverses its own verdict on more than a third of comparisons when you swap the slots. And it degrades exactly where you'd least want it to:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Grok-4.3&lt;/th&gt;
&lt;th&gt;Objective&lt;/th&gt;
&lt;th&gt;Medium&lt;/th&gt;
&lt;th&gt;Subjective&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;slot-A rate&lt;/td&gt;
&lt;td&gt;64% (p = 0.29)&lt;/td&gt;
&lt;td&gt;75% (p = 0.023)&lt;/td&gt;
&lt;td&gt;77% (p = 0.005)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;flip rate&lt;/td&gt;
&lt;td&gt;13%&lt;/td&gt;
&lt;td&gt;40%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;53%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;On the subjective third of the task set, Grok-4.3 reverses itself on more than half of all comparisons. At that point it isn't grading the answers; it's grading their addresses.&lt;/p&gt;

&lt;p&gt;So keep randomising order — it's free. But notice that the usual justification ("LLM judges prefer the first answer") is not what the data says. The data says &lt;em&gt;some judges do, catastrophically, and you cannot tell which one without measuring it.&lt;/em&gt; Running every comparison in both orders costs you a 2× on judge calls and turns an unknown into a number.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. The length statistic everyone quotes is uninterpretable
&lt;/h2&gt;

&lt;p&gt;Here is the number people report:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Judge&lt;/th&gt;
&lt;th&gt;Longer answer wins&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GPT-5.5&lt;/td&gt;
&lt;td&gt;48.6%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Grok-4.3&lt;/td&gt;
&lt;td&gt;48.6%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude Sonnet 4.6&lt;/td&gt;
&lt;td&gt;64.6%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Looks like there's barely any length bias. In my within-family pilot the same statistic read 80–85% and looked like a textbook one. &lt;strong&gt;Both readings are wrong&lt;/strong&gt;, and for the same reason: in any normal model lineup, length and quality are confounded. If your verbose models happen to be your good models, the number inflates; if they don't, it cancels. It is an accident of your lineup, not a property of your judge.&lt;/p&gt;

&lt;p&gt;The control holds the model and the task fixed and varies &lt;strong&gt;only&lt;/strong&gt; the target length: two variants from an identical base prompt, differing by one appended sentence ("answer in roughly N words"). The judge is then shown the base prompt &lt;em&gt;without&lt;/em&gt; that sentence — otherwise it would be grading compliance with a word count it was told about. Short variants came out at 46 words on average, long ones at 174.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Judge&lt;/th&gt;
&lt;th&gt;Long wins (controlled)&lt;/th&gt;
&lt;th&gt;Flip rate&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GPT-5.5&lt;/td&gt;
&lt;td&gt;77.8%&lt;/td&gt;
&lt;td&gt;11.1%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Grok-4.3&lt;/td&gt;
&lt;td&gt;86.1%&lt;/td&gt;
&lt;td&gt;16.7%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude Sonnet 4.6&lt;/td&gt;
&lt;td&gt;88.9%&lt;/td&gt;
&lt;td&gt;5.6%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;So the preference is not just real, it is &lt;strong&gt;stronger&lt;/strong&gt; than the uncontrolled number implied — the opposite of the direction people assume the confound runs.&lt;/p&gt;

&lt;p&gt;Split by what the task actually rewards, though, it isn't a length preference at all:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What the task rewards&lt;/th&gt;
&lt;th&gt;Long wins&lt;/th&gt;
&lt;th&gt;n&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;elaboration (explain X)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;100.0%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;27&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;concision (summarize, commit message, error copy)&lt;/td&gt;
&lt;td&gt;68.5%&lt;/td&gt;
&lt;td&gt;27&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Twenty-seven out of twenty-seven. Every judge, every generator, both orders. Add the pilot's runs and it's 45 for 45 across six judges from three families. If anything here deserves to be called a law, it's that one: &lt;strong&gt;on a task that invites elaboration, the longer answer always wins.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Where I was wrong
&lt;/h3&gt;

&lt;p&gt;The pilot found something better than that, and I wrote it up: on summarization the preference &lt;em&gt;inverted&lt;/em&gt; — judges picked the short version and said so explicitly in their reasons ("genuinely condensed form, whereas B is essentially a full paraphrase"). The tidy conclusion was that judges infer the task's implicit goal, so "watch out for length bias" is the wrong instruction.&lt;/p&gt;

&lt;p&gt;That did not replicate. Cross-family, the same summarization probe reads 72.2% long-wins, and the concision category sits at 68.5% — above chance, not below it. Broken down by judge, the pilot's effect was one model:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Judge&lt;/th&gt;
&lt;th&gt;elaboration&lt;/th&gt;
&lt;th&gt;concision&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GPT-5.5&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;55.6%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Grok-4.3&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;72.2%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude Sonnet 4.6&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;77.8%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;em&gt;pilot:&lt;/em&gt; Opus 5&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;66.7%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;em&gt;pilot:&lt;/em&gt; Sonnet 5&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.0%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;em&gt;pilot:&lt;/em&gt; Haiku 4.5&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;50.0%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Six judges. One of them preferred the short answer every single time and dragged a three-judge aggregate below 50%, which I then reported as a general finding. The corrected version is narrower: judges reward elaboration everywhere, and the reward merely &lt;em&gt;weakens&lt;/em&gt;, with wide variance between judges, when the task's implicit criterion is compression. One judge in six actually inverted.&lt;/p&gt;

&lt;p&gt;I'm keeping both write-ups in the repo, pilot first, because the failure mode is the useful part: a confident headline drawn from three judges of one family, retired once six judges from three families had a say.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. On subjective tasks, judges agree at chance
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Judge pair&lt;/th&gt;
&lt;th&gt;Objective&lt;/th&gt;
&lt;th&gt;Medium&lt;/th&gt;
&lt;th&gt;Subjective&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GPT-5.5 vs Grok-4.3&lt;/td&gt;
&lt;td&gt;+0.41&lt;/td&gt;
&lt;td&gt;+0.32&lt;/td&gt;
&lt;td&gt;−0.01&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GPT-5.5 vs Claude Sonnet 4.6&lt;/td&gt;
&lt;td&gt;+0.31&lt;/td&gt;
&lt;td&gt;+0.35&lt;/td&gt;
&lt;td&gt;−0.03&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Grok-4.3 vs Claude Sonnet 4.6&lt;/td&gt;
&lt;td&gt;+0.20&lt;/td&gt;
&lt;td&gt;+0.02&lt;/td&gt;
&lt;td&gt;+0.05&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Cohen's kappa: 1.0 is perfect agreement, 0.0 is chance. On the subjective tasks all three pairs are indistinguishable from chance. Two frontier judges grading the same subjective comparison agree exactly as often as two coin flips.&lt;/p&gt;

&lt;p&gt;This is the number to keep if you keep only one. A judge panel is supposed to average out individual quirks — but averaging three judges who agree at chance doesn't give you a robust signal, it gives you a smoother random one. On subjective dimensions, the honest move is not more judges. It is to stop pretending the dimension is measurable by judge and go find a proxy that is: did the user come back, did the ticket get reopened, did the PR get merged.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd actually do with this
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Match the judge to the dimension, not to a leaderboard.&lt;/strong&gt; Objective, checkable dimension → any competent judge, self-preference is near zero. Subjective dimension → assume the judge is a participant.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Never use a judge from a family you're evaluating on a subjective dimension.&lt;/strong&gt; +30 to +48 percentage points is not a rounding error, and blinding does not remove it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Screen every judge in both orders once; keep both orders only where they pay.&lt;/strong&gt; Twenty comparisons run both ways gives you the slot-A rate and the flip rate, and would have disqualified Grok-4.3 in ten minutes. A judge that comes back clean doesn't need a permanent 2× tax after that — for an aggregate ranking, simply randomising the order per comparison is free and neutralises a systematic lean.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep both orders when you act on individual verdicts.&lt;/strong&gt; Gating a PR or routing a sample to human review is a per-item decision, and a lean isn't the only failure: Claude Sonnet 4.6 showed no slot preference at all (51.5%, p = 0.90) and still reversed itself on one comparison in five. Running the swap is the cheapest way to know whether &lt;em&gt;this&lt;/em&gt; verdict is reproducible.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Distrust any length statistic that isn't controlled&lt;/strong&gt; — including one that says your judge has no length bias.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;If your judges agree at chance, the problem is the dimension, not the panel.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What this doesn't show
&lt;/h2&gt;

&lt;p&gt;One model per family, and the tiers aren't matched: Azure had no quota for a current Claude at run time, so the Anthropic seat is Sonnet 4.6, a generation behind the other two. The obvious worry is reading a capability gap as a family effect. Two things push back — the older model isn't the lowest-ranked overall (Grok-4.3 is), and the three self-preference deltas land within 7pp of each other. But the clean version of this experiment uses same-tier models, and this isn't it.&lt;/p&gt;

&lt;p&gt;Also: GPT-5.5 did not run at temperature 0, because it refuses to. Fifteen tasks and six length probes is 2.5× the pilot and still small — every interval here is wide. It's one run, with no repeats to separate judge instability from sampling noise. And nothing was executed: even the objective tasks were graded by reading, deliberately, because the point was to measure the judge. Where you can run the tests instead, run the tests.&lt;/p&gt;

&lt;p&gt;None of which changes the shape of the result. Three judges looked at the same 45 comparisons and produced three different rankings, and the gap between them was widest exactly where the answer was a matter of taste — which is precisely where teams reach for an LLM judge in the first place.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Harness, raw judgments and both write-ups: &lt;a href="https://github.com/JaviMaligno/personal-website/tree/main/experiments/judge-bias" rel="noopener noreferrer"&gt;experiments/judge-bias&lt;/a&gt;. Prior work worth reading: &lt;a href="https://arxiv.org/abs/2306.05685" rel="noopener noreferrer"&gt;Zheng et al. on position, verbosity and self-enhancement bias&lt;/a&gt;, &lt;a href="https://aclanthology.org/2024.acl-long.511/" rel="noopener noreferrer"&gt;Wang et al. on position bias&lt;/a&gt;, &lt;a href="https://arxiv.org/abs/2404.13076" rel="noopener noreferrer"&gt;Panickssery, Bowman and Feng on self-recognition and self-preference&lt;/a&gt;, and &lt;a href="https://arxiv.org/abs/2404.04475" rel="noopener noreferrer"&gt;Dubois et al. on length-controlled evaluation&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/three-judges-three-rankings" 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>research</category>
    </item>
    <item>
      <title>Where's the Ball? Part 3 — It Was Geometry All Along (and the Model Doesn't Know When It's Wrong)</title>
      <dc:creator>JaviMaligno</dc:creator>
      <pubDate>Wed, 29 Jul 2026 10:04:26 +0000</pubDate>
      <link>https://dev.to/javieraguilarai/wheres-the-ball-part-3-it-was-geometry-all-along-and-the-model-doesnt-know-when-its-wrong-1712</link>
      <guid>https://dev.to/javieraguilarai/wheres-the-ball-part-3-it-was-geometry-all-along-and-the-model-doesnt-know-when-its-wrong-1712</guid>
      <description>&lt;p&gt;&lt;a href="https://www.javieraguilar.ai/en/blog/wheres-the-ball-2/" rel="noopener noreferrer"&gt;Part 2&lt;/a&gt; ended with a small network — about 60 kilobytes of weights — cracking the hidden-ball cases that frontier vision-language models couldn't. It read the players' positions and velocities and pointed, with decent accuracy, at a ball it had never been shown.&lt;/p&gt;

&lt;p&gt;Which is satisfying and slightly annoying, because a network that works is still a black box. It gives you an answer, not an explanation. So the question that opens the last rung of this ladder is: &lt;strong&gt;what is that little box actually computing?&lt;/strong&gt; Is there some rich, deep pattern in how ten players arrange themselves — or is it doing something a human could write down on the back of an envelope?&lt;/p&gt;

&lt;p&gt;I spent Level 3 trying to open the box three ways. The short version: it's mostly a single geometric idea, the fancy mathematics I reached for added nothing, and when I asked the model how sure it was, it turned out to be confidently wrong in a very specific place.&lt;/p&gt;

&lt;h2&gt;
  
  
  It was geometry all along
&lt;/h2&gt;

&lt;p&gt;The test is simple. If the black box is computing something interpretable, then a handful of hand-written geometric features — quantities a coach could name — fed to a plain gradient-boosted tree should reproduce most of its accuracy. If it's doing something genuinely deep, they won't come close.&lt;/p&gt;

&lt;p&gt;I wrote about ten: the centroid of the players, the centroid weighted by how fast each player is moving, the fastest player's position, the densest cluster, the spread of each team, the midpoint of the closest pair of opponents, a "convergence point" where the players' motion rays intersect. Ten numbers, a tree, the same train/test split as the deep model.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9m31rudmq5jth4yxfmvj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9m31rudmq5jth4yxfmvj.png" alt="The black box is (mostly) reading geometry" width="799" height="396"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The gap between a naive centroid baseline and the trained deep net is the "skill" the network learned. Ten interpretable features recover &lt;strong&gt;92%&lt;/strong&gt; of that gap in soccer — and in basketball they slightly &lt;em&gt;beat&lt;/em&gt; the deep net (112%). The little black box, it turns out, is not hiding much. Almost everything it knows can be written down.&lt;/p&gt;

&lt;h3&gt;
  
  
  One number does most of the work
&lt;/h3&gt;

&lt;p&gt;And most of &lt;em&gt;that&lt;/em&gt; is a single feature. When I measure how much each geometric quantity contributes — by shuffling it and watching the error rise — one dominates the rest by an order of magnitude:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0m8n5jidoe1k0gz3a6yy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0m8n5jidoe1k0gz3a6yy.png" alt="One feature dominates: where the running mass is heading" width="799" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;velocity-weighted centroid&lt;/strong&gt; — the average player position, but with the fast-moving players counting more — carries the localization on its own. Everything else is rounding. This is the same thread that ran through Part 2: the signal is in the &lt;em&gt;motion&lt;/em&gt;, in where the running mass is heading, not in the static shape of the formation.&lt;/p&gt;

&lt;p&gt;There's a small irony here. The feature I was proudest of — the "convergence point", a tidy least-squares intersection of every player's motion ray, the thing you'd draw on a tactics board to say &lt;em&gt;the ball is where everyone is running&lt;/em&gt; — barely moves the needle. The crude weighted average of positions beats the elegant geometric construction. Not the last time this project would humble a nice idea.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I hoped topology would add (and didn't)
&lt;/h2&gt;

&lt;p&gt;If ten simple features already recover almost everything, is there any structure &lt;em&gt;left&lt;/em&gt; that a richer description could capture? The natural candidate is topology — the shape of the player cloud, its holes and connected components, formalized as &lt;a href="https://en.wikipedia.org/wiki/Persistent_homology" rel="noopener noreferrer"&gt;persistent homology&lt;/a&gt;. There's a genuinely appealing intuition behind it: a ball in open play often sits inside a &lt;em&gt;hole&lt;/em&gt; in the configuration — a ring of players around empty space — and topology is exactly the mathematics of holes.&lt;/p&gt;

&lt;p&gt;So I built the topological features: the persistence statistics of the player cloud, the centre of its most persistent loop (via the representative cycle — literally "where's the biggest hole"), the two largest empty circles in the Delaunay triangulation ("where's the biggest gap"). I set one rule before running anything, to keep myself honest: &lt;strong&gt;topology only counts if it beats its geometric counterpart.&lt;/strong&gt; Otherwise it's decoration.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fa0p43n86ldovyy8du4xp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fa0p43n86ldovyy8du4xp.png" alt="Persistent homology adds nothing over plain geometry" width="800" height="462"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It doesn't clear the bar. Topological features on their own land far behind plain geometry (0.258 vs 0.111 in soccer — barely better than the naive baseline). And bolting them onto the geometric features changes nothing: geometry-plus-topology matches geometry alone, and when I shuffle the entire topological block the error barely twitches — the tree simply ignores it once it has the geometry.&lt;/p&gt;

&lt;p&gt;The appealing intuition is just wrong. The ball's position isn't a property of the &lt;em&gt;shape&lt;/em&gt; of the player cloud; it's a property of where the mass is and where it's moving. Persistent homology describes the configuration beautifully at every scale, and none of that description is about the ball. A clean negative result — and the pre-registered rule is the only reason I can call it clean instead of quietly dropping it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Does the model know when it's wrong?
&lt;/h2&gt;

&lt;p&gt;The last question is the one I find most interesting, because it's not about accuracy — it's about self-knowledge. A point prediction can't tell you how sure it is. So I swapped the network's single-point output for a &lt;a href="https://publications.aston.ac.uk/id/eprint/373/" rel="noopener noreferrer"&gt;mixture density&lt;/a&gt; head: instead of one guess, it predicts a full probability distribution over where the ball might be. Now it can say "somewhere around here, and I'm confident" or "somewhere in this whole region, and I'm not."&lt;/p&gt;

&lt;p&gt;The first finding is reassuring. The uncertainty it declares does track the error it makes — bin the predictions from most-confident to least, and the actual error climbs monotonically:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0yolxfca8lzbz65sx3h1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0yolxfca8lzbz65sx3h1.png" alt="Globally calibrated, but with a blind spot on loose balls" width="800" height="354"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's the left panel: the model knows when it doesn't know. Weakly (the correlation is a modest +0.20), but consistently.&lt;/p&gt;

&lt;p&gt;The right panel is where it gets interesting — and where my hypothesis died, again. I'd expected the ball to be &lt;em&gt;hardest&lt;/em&gt; to pin down when it's tangled in a crowd of players, and easiest when it's loose in open space. It's the opposite. Tangled play is the &lt;em&gt;easy&lt;/em&gt; case: when players converge on the ball, their velocities point at it, and that velocity-weighted centroid lands right on top. A loose ball, with players trailing behind it, is genuinely harder — about 20% more error.&lt;/p&gt;

&lt;p&gt;Here's the blind spot: on those harder loose balls, the model declares &lt;em&gt;lower&lt;/em&gt; uncertainty. Its error goes up while its confidence goes up. It is most sure of itself exactly where it is most wrong — and these are the same off-centre, fast-moving balls that the frontier models failed on back in Part 1. The failure mode is consistent all the way down the ladder: everything struggles with the loose ball, and the specialist doesn't even know it's struggling.&lt;/p&gt;

&lt;p&gt;Where does that leave the map of the pitch? If you plot the median error across the field, the ball is best determined near the touchlines and in open midfield, and worst in the congestion in front of both goals — the corners, crosses, and goalmouth scrambles where players pile up and the convergence trick breaks down.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsah1lady4bg1rpxas40c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsah1lady4bg1rpxas40c.png" alt="Where the ball is inferable from the players" width="800" height="501"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The black box was reading geometry.&lt;/strong&gt; Ten interpretable features recover 92% of the deep net in soccer and beat it in basketball. Ball localization isn't a deep pattern — it's mostly one number: the velocity-weighted centroid, "where the running mass is heading."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Topology added nothing.&lt;/strong&gt; Persistent homology, largest empty circles, loop centres — the appealing "ball sits in the hole" intuition — don't beat plain geometry and get ignored when added to it. The ball's position isn't a property of the configuration's shape. A pre-registered rule kept the negative result honest.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The model is confidently wrong in one place.&lt;/strong&gt; It's globally calibrated, but overconfident on loose balls in open space — precisely the hard, off-centre cases that broke the frontier models in Part 1. It doesn't know that it doesn't know, exactly where it matters.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;My best guesses kept losing to the plain ones.&lt;/strong&gt; The elegant convergence point lost to a weighted average; topology lost to ten simple features; the "crowds are confusing" hypothesis was backwards. Across three parts, this project went a perfect four-for-four on "the first version of the story was wrong" — which is the whole reason I write the controls before I write the conclusion.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Three rungs in, the answer to the question from the stands is almost anticlimactic. Inferring a hidden ball from the players is real, it's learnable, and it's &lt;em&gt;geometry&lt;/em&gt; — a coach's instinct for where the play is heading, written as one weighted average. The frontier models have that instinct faintly and can't act on it; a 60 kB network has it sharply. And neither of them, it turns out, can tell you when the ball has slipped loose into space and they've quietly lost the thread.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Code, feature definitions, controls, and the topology and mixture-density experiments in the &lt;a href="https://github.com/JaviMaligno/wheres-the-ball" rel="noopener noreferrer"&gt;&lt;code&gt;wheres-the-ball&lt;/code&gt;&lt;/a&gt; repo. Soccer tracking from &lt;a href="https://github.com/metrica-sports/sample-data" rel="noopener noreferrer"&gt;Metrica Sports&lt;/a&gt; and &lt;a href="https://github.com/SkillCorner/opendata" rel="noopener noreferrer"&gt;SkillCorner&lt;/a&gt;; basketball from the NBA SportVU 2015-16 logs (&lt;a href="https://github.com/linouk23/NBA-Player-Movements" rel="noopener noreferrer"&gt;mirror&lt;/a&gt;; no explicit license — used for research only, not redistributed). Persistent homology via &lt;a href="https://github.com/scikit-tda/ripser.py" rel="noopener noreferrer"&gt;ripser&lt;/a&gt;. Previously: &lt;a href="https://www.javieraguilar.ai/en/blog/wheres-the-ball/" rel="noopener noreferrer"&gt;Part 1&lt;/a&gt; and &lt;a href="https://www.javieraguilar.ai/en/blog/wheres-the-ball-2/" rel="noopener noreferrer"&gt;Part 2&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/wheres-the-ball-3" 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>computervision</category>
      <category>research</category>
    </item>
    <item>
      <title>I Built an Expensive Form</title>
      <dc:creator>JaviMaligno</dc:creator>
      <pubDate>Tue, 28 Jul 2026 11:41:40 +0000</pubDate>
      <link>https://dev.to/javieraguilarai/i-built-an-expensive-form-5dbd</link>
      <guid>https://dev.to/javieraguilarai/i-built-an-expensive-form-5dbd</guid>
      <description>&lt;p&gt;I lost this argument before I built anything.&lt;/p&gt;

&lt;p&gt;We were replacing a legacy ticketing journey for filing regulatory reports — the kind of internal form that front-line staff dread and fill in badly. My position, stated more than once, was that turning the whole journey into a conversation added little over a well-designed form. What I'd have built instead was a form with genuinely good UX, plus an assistant beside it for the questions people actually have — &lt;em&gt;does this count as reportable?&lt;/em&gt;, &lt;em&gt;which category is this?&lt;/em&gt; — able to interact with the form rather than replace it.&lt;/p&gt;

&lt;p&gt;The call went the other way, and it was a defensible call: a conversational journey is differentiating, it demos well, and clients ask for it by name. So I said &lt;em&gt;fine, I'll build it and we'll see what happens.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;We saw what happened. I had built an expensive form.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building well what you don't advocate for
&lt;/h2&gt;

&lt;p&gt;I want to be precise about this, because "I disagreed and did it anyway" can read as either cynicism or spinelessness, and I don't think it was either.&lt;/p&gt;

&lt;p&gt;An internal argument about whether users will like something is unresolvable by internal argument. Two people with plausible models of the user restate their priors at increasing volume, and the person with more authority wins. That isn't a bad process because the wrong person won — it's a bad process because &lt;em&gt;nobody learns anything&lt;/em&gt;. The way out is to put the thing in front of a real user quickly, built well enough that their reaction is about the idea and not about your half-hearted execution.&lt;/p&gt;

&lt;p&gt;So it had to be good. If I'd shipped a deliberately mediocre version, any bad feedback would have been about the mediocrity, and I'd have proven nothing.&lt;/p&gt;

&lt;p&gt;I also didn't start from scratch: a colleague had built the initial LangGraph prototype, and I took it from there. Worth saying plainly, because the improvements I'm about to describe were made on top of someone else's foundation.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we shipped
&lt;/h2&gt;

&lt;p&gt;The architecture wasn't sloppy. It was a pipeline engine configured in YAML: a DAG of phases, a handful of node types, conditional routers, global triggers that could redirect the flow, compliance scoring recalculated after each phase.&lt;/p&gt;

&lt;p&gt;Each phase owned a slice of the form. It asked its question, validated the answer, confirmed it, advanced. For one report type that came out to roughly &lt;strong&gt;seven interactive steps&lt;/strong&gt;: seven turns where the user waited for a model, read a question, answered one thing, confirmed.&lt;/p&gt;

&lt;p&gt;And let me steelman that design, because its properties are real.&lt;/p&gt;

&lt;p&gt;Phase-per-slice gives you &lt;strong&gt;validation at the point of entry&lt;/strong&gt; — you catch a malformed date while the user is still thinking about dates. It gives you a &lt;strong&gt;progress model&lt;/strong&gt;: because phases are explicit, you get a progress bar, per-phase analytics, and an admin panel showing exactly where a submission stalled. It makes the flow &lt;strong&gt;legible&lt;/strong&gt; — a new engineer reads the YAML and knows what happens. And it makes the agent &lt;strong&gt;safe by construction&lt;/strong&gt;: it cannot skip a required field, because the graph won't let it.&lt;/p&gt;

&lt;p&gt;I didn't give those up. That matters for what comes later.&lt;/p&gt;

&lt;h2&gt;
  
  
  The feedback that settled it
&lt;/h2&gt;

&lt;p&gt;The client's compliance lead tried it and said it &lt;em&gt;felt too similar to the system it was replacing: time consuming and complex.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That sentence did in one meeting what I hadn't managed in several. Nobody won the internal argument — the user ended it. And the useful part isn't that my prior was vindicated; it's that we now had a &lt;em&gt;specific&lt;/em&gt; complaint instead of two competing opinions. "Time consuming and complex" is diagnosable. "I think users won't like it" isn't.&lt;/p&gt;

&lt;p&gt;That feedback got me a blank cheque to rebuild it properly. Which is the actual reason I'm glad we shipped the version I argued against: my alternative would have been a guess too. What we got instead was evidence, and evidence buys you permission.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a form wins that race
&lt;/h2&gt;

&lt;p&gt;Here's the mechanism. If the interaction is a fixed sequence of questions, the form is simply the better interface:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You see &lt;strong&gt;all the fields at once&lt;/strong&gt;, so you know how long this will take. A chat hides the length of the queue, which is why "how many more questions?" is the most common thought in a scripted bot.&lt;/li&gt;
&lt;li&gt;You fill them in &lt;strong&gt;any order&lt;/strong&gt;, skip around, come back. A sequential agent imposes an order that exists for the engine's convenience, not yours.&lt;/li&gt;
&lt;li&gt;You &lt;strong&gt;tab between fields&lt;/strong&gt; in milliseconds. Every conversational turn costs a round trip to a model — sometimes one second, sometimes ten. Multiply by seven.&lt;/li&gt;
&lt;li&gt;It costs &lt;strong&gt;nothing per submission&lt;/strong&gt;. Our version billed tokens for the privilege of being slower.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So the scripted chat loses on speed, on predictability, and on cost, and its only compensation is that it feels modern. A conversational wrapper around a fixed sequence is a form with worse ergonomics and a variable bill.&lt;/p&gt;

&lt;h2&gt;
  
  
  The diagnosis: sequentiality, not widgets
&lt;/h2&gt;

&lt;p&gt;My first instinct was presentation — the inputs looked clunky, the confirmations were verbose, maybe better in-chat widgets would fix it. That instinct was wrong, and it's the interesting part.&lt;/p&gt;

&lt;p&gt;The cost wasn't in any single step. It was in &lt;strong&gt;there being steps at all&lt;/strong&gt;. Seven turns is seven turns whether each one is beautiful or ugly.&lt;/p&gt;

&lt;p&gt;Then the genuinely humbling detail. The machinery for &lt;em&gt;"only ask for what's missing"&lt;/em&gt; already existed. Each phase could compute its own missing fields and ask only about those. It just operated &lt;strong&gt;within phase scope&lt;/strong&gt; — so when a user opened by typing a rich paragraph describing everything that happened, phase one extracted its two fields and the rest of that paragraph was discarded. The user had already given us most of the answers. We threw them away and then asked for them one at a time.&lt;/p&gt;

&lt;p&gt;The system wasn't failing to understand the user. It was understanding and then forgetting, because the unit of extraction was the phase rather than the conversation.&lt;/p&gt;

&lt;h2&gt;
  
  
  First pass: fix it inside the graph
&lt;/h2&gt;

&lt;p&gt;Diagnosing it didn't earn me a rewrite, and it shouldn't have.&lt;/p&gt;

&lt;p&gt;This is the part people skip when they tell these stories, so let me be explicit about it. There was a &lt;em&gt;product&lt;/em&gt;. It was built, deployed, in front of clients, and it worked — badly, but it worked. Nobody throws that away on the strength of one piece of feedback and an engineer's conviction. The reasonable next step, and the one the business wanted, was to see how good the existing thing could get.&lt;/p&gt;

&lt;p&gt;That instinct is correct more often than engineers like to admit. Incremental fixes are cheap, they ship this week instead of next quarter, and if they'd been enough we'd have saved an entire rewrite. The only way to know whether a rewrite is necessary is to first exhaust what isn't one.&lt;/p&gt;

&lt;p&gt;So: two additions to the existing graph, both behind an opt-in flag so other flows kept their old behaviour and I had zero regression risk on clients who weren't asking for this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A global intake node.&lt;/strong&gt; Before the first phase, the agent asks for the story in the user's own words. Then it makes a &lt;em&gt;single&lt;/em&gt; structured-extraction call against the union of every field the whole flow could extract, and writes everything it confidently found.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Sequential: the graph decides the order, one question at a time
&lt;/span&gt;&lt;span class="n"&gt;START&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="n"&gt;phase_1&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="n"&gt;phase_2&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="n"&gt;phase_7&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="n"&gt;END&lt;/span&gt;

&lt;span class="c1"&gt;# Conversational: extract everything from the narrative, then
# only visit phases that still have gaps
&lt;/span&gt;&lt;span class="n"&gt;START&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="n"&gt;intake&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="n"&gt;first_unsatisfied_phase&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="n"&gt;END&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three details mattered more than the idea:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The extractable set is computed, not hand-written.&lt;/strong&gt; It's the union of fields written by phases that can be satisfied from prose. Document extraction, file uploads and signatures are &lt;em&gt;explicitly excluded&lt;/em&gt; — no narrative produces a scanned ID.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It reuses the real field definitions&lt;/strong&gt;, so the same validators run in intake as in the phases. Passing an empty schema and "just extracting text" hands you unvalidated data with extra steps.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It never guesses.&lt;/strong&gt; If the narrative doesn't clearly give a field, intake leaves it empty. For an option list, a wrong selection in a regulatory form is worse than one more question — a blank gets asked about, a wrong value gets submitted.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Skipping satisfied phases.&lt;/strong&gt; Phases needing genuine interaction (upload, signature) always run. Data phases branch: all fields present and nothing critical → advance silently; all present but something critical → one minimal confirmation; gaps → ask only about the gaps.&lt;/p&gt;

&lt;p&gt;Result: &lt;strong&gt;roughly seven interactions down to three or four.&lt;/strong&gt; Narrate, confirm the high-risk items, upload, sign.&lt;/p&gt;

&lt;p&gt;Notice what survived. The phases still exist, so the progress bar, the analytics, the admin panel and the validators all still work. I didn't tear down the DAG to get conversational — I put a node in front of it and let phases opt out of asking. The alternative I rejected in writing was collapsing everything into one adaptive node, which buys elegance and costs every structural property listed earlier.&lt;/p&gt;

&lt;h2&gt;
  
  
  Second pass: what the rewrite actually required
&lt;/h2&gt;

&lt;p&gt;The first pass bought real improvement, and it wasn't enough. Three or four turns is better than seven, but the shape of the thing was still a graph marching through phases with a smarter front door bolted on.&lt;/p&gt;

&lt;p&gt;Going properly agentic meant &lt;strong&gt;a completely new backend&lt;/strong&gt;. Not a refactor: a different shape of system, where instead of a graph deciding the order of questions, an agent holds tools over a server-authoritative state and decides what to do next.&lt;/p&gt;

&lt;p&gt;A rewrite of that size is never won on technical argument alone. It took two things: the client feedback, which made the problem concrete, and then a change of direction at the company that made a new bet acceptable in the first place. Evidence made the case; an appetite for risk gave it somewhere to land. Both had to be true at once.&lt;/p&gt;

&lt;p&gt;That's the part I'd generalise. The window where an organisation will accept a rewrite opens and closes for reasons that have nothing to do with your architecture diagram, and it is usually short. What you control is being ready when it opens — which, in my case, meant that the design I'd been arguing for was already specified, because I'd spent the incremental pass learning exactly which constraints mattered.&lt;/p&gt;

&lt;p&gt;Two things came out of that rebuild that I'd defend anywhere.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Generative UI in the chat, which was my idea and the part I'm most attached to.&lt;/strong&gt; If the agent has to render options as text, you've reinvented the dropdown with extra latency. Instead the chat renders actual interactive widgets — chips, pickers, panels — so the agent can propose a structured value and the user adjusts it directly. I've written separately about &lt;a href="https://www.javieraguilar.ai/en/blog/ag-ui-third-protocol" rel="noopener noreferrer"&gt;why that pattern matters and the protocol standardising it&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This produced my favourite rule in the whole system. When the agent asks about remaining gaps, it splits them in two: free-text and date fields get asked directly, consolidated into one natural question, but fields with a fixed option list get a different instruction — &lt;strong&gt;infer your best option from what the user already told you and let them adjust it in the panel; do not read the options out.&lt;/strong&gt; The interception lives in the tool that computes missing fields, not in the prompt, because that's the exact point where the model decides what to ask.&lt;/p&gt;

&lt;p&gt;If you're building on a messaging channel, this is load-bearing. Nobody wants to read eight numbered choices in WhatsApp.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Freedom to understand, ceremony to act.&lt;/strong&gt; Intake can be as open as you like; the write cannot. When the agent is about to submit something with real-world consequences, I want a narrow, explicit, boring step. In the new backend that gate is checked in code: a submission is refused unless it was offered in an &lt;em&gt;earlier&lt;/em&gt; turn, and any later correction voids the pending approval — because if the content changed, what the person approved is no longer what would be sent. Same principle as the &lt;a href="https://www.javieraguilar.ai/en/blog/typescript-ai-agent-guardrails" rel="noopener noreferrer"&gt;guardrails I keep writing about&lt;/a&gt;: the constraint has to live somewhere the model can't reach.&lt;/p&gt;

&lt;h2&gt;
  
  
  The abstraction I waited to build
&lt;/h2&gt;

&lt;p&gt;Here's where it went beyond one client. A new backend per use case doesn't scale, and the obvious move was a &lt;strong&gt;generator&lt;/strong&gt;: a backend that takes configuration for a use case — prompts, the form models, the knowledge base — and produces the agentic flow from it.&lt;/p&gt;

&lt;p&gt;The discipline was in the timing. I deliberately waited until I had &lt;strong&gt;two genuinely different flows&lt;/strong&gt; working before generalising: business-banking onboarding and regulatory incident reporting. Different domains, different form shapes, different failure modes.&lt;/p&gt;

&lt;p&gt;Two reasons, and both are the same reason:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Two dissimilar examples tell you which parts are actually variable.&lt;/strong&gt; Abstract from one and you build a configuration layer shaped exactly like your first client, then bend it painfully for the second. Every knob I exposed had to be a knob both flows genuinely turned differently.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Two examples also prove the abstraction is worth it at all.&lt;/strong&gt; With one flow, "just deploy another backend" is the cheaper answer and the honest one. The generator only earns its complexity once you can show the alternative is N backends.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's the current work, and it exists because the rewrite happened twice in different shapes — not because someone drew a platform diagram at the start.&lt;/p&gt;

&lt;h2&gt;
  
  
  Not chat everything
&lt;/h2&gt;

&lt;p&gt;I'd rather not overcorrect this into conversational-everything, because my original position still stands and the boundary is the useful part.&lt;/p&gt;

&lt;p&gt;Keep the form when the fields are &lt;strong&gt;few and known&lt;/strong&gt;, when the user has the data &lt;strong&gt;in front of them&lt;/strong&gt;, when input is inherently &lt;strong&gt;non-narrative&lt;/strong&gt; (upload a file, sign, pick from a calendar), or when the flow is &lt;strong&gt;used daily by trained staff&lt;/strong&gt; who memorised the tab order long ago. Power users beat conversational interfaces at speed, every time.&lt;/p&gt;

&lt;p&gt;Reach for the agent when input arrives as &lt;strong&gt;a story rather than a record&lt;/strong&gt; — an incident, a complaint, a symptom, a request — when the field set is &lt;strong&gt;large but sparsely relevant&lt;/strong&gt; to any one case, or when the person filing is &lt;strong&gt;occasional and untrained&lt;/strong&gt; and the form's vocabulary isn't theirs.&lt;/p&gt;

&lt;p&gt;And note the middle option I originally argued for hasn't gone anywhere: a good form plus an assistant that can see it and act on it. That's often the right answer, and it's the one nobody demos.&lt;/p&gt;

&lt;h2&gt;
  
  
  The question I ask first now
&lt;/h2&gt;

&lt;p&gt;Before building a conversational anything: &lt;em&gt;what does this let the user do that the form doesn't?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;If the answer is "it's friendlier", build the form. If the answer is "the user can describe their situation however it comes out, and the system works out which fields that maps to", there's something real — and the design job is extracting once, asking only for what's genuinely missing, and never reading the options out loud.&lt;/p&gt;

&lt;p&gt;The rest of the time you're building an expensive form. Sometimes you have to build it anyway to prove it, and if you do, build it well enough that the feedback is about the idea.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://www.javieraguilar.ai/en/blog/expensive-form" 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>product</category>
      <category>langgraph</category>
      <category>ux</category>
    </item>
    <item>
      <title>Where's the Ball? Part 2 — a 60-Kilobyte Model, Two Sports, and the Signal VLMs Miss</title>
      <dc:creator>JaviMaligno</dc:creator>
      <pubDate>Mon, 27 Jul 2026 11:43:54 +0000</pubDate>
      <link>https://dev.to/javieraguilarai/wheres-the-ball-part-2-a-60-kilobyte-model-two-sports-and-the-signal-vlms-miss-5947</link>
      <guid>https://dev.to/javieraguilarai/wheres-the-ball-part-2-a-60-kilobyte-model-two-sports-and-the-signal-vlms-miss-5947</guid>
      <description>&lt;p&gt;&lt;a href="https://www.javieraguilar.ai/en/blog/wheres-the-ball/" rel="noopener noreferrer"&gt;Part 1&lt;/a&gt; ended on a sober note. Frontier vision-language models have a faint spectator's intuition — their guesses about a hidden football correlate with where it really is — but on the cases that matter, balls genuinely far from the center of the frame, none of them reliably beat the dumbest possible strategy: point at the middle and trust the camera operator.&lt;/p&gt;

&lt;p&gt;That left an uncomfortable question hanging. Maybe the hard cases are just &lt;em&gt;hard&lt;/em&gt; — maybe the players' positions don't contain enough information to find an off-center ball, and I'd been asking the models to do the impossible.&lt;/p&gt;

&lt;p&gt;So I tested that directly. The answer is no: the information is there, and a model small enough to email can extract it.&lt;/p&gt;

&lt;h2&gt;
  
  
  David, meet Goliath
&lt;/h2&gt;

&lt;p&gt;The question has two axes, so I crossed them. One axis is &lt;em&gt;who reads&lt;/em&gt;: a tiny specialist trained for exactly this, or a frontier generalist. The other is &lt;em&gt;what gets read&lt;/em&gt;: the broadcast pixels, or the player tracks underneath them.&lt;/p&gt;

&lt;p&gt;On the &lt;em&gt;trained&lt;/em&gt; side, two specialists. For the tracks: a &lt;a href="https://arxiv.org/abs/1703.06114" rel="noopener noreferrer"&gt;DeepSets&lt;/a&gt; network — a permutation-invariant architecture that eats an unordered set of players and pools them into one guess — trained on each player's position and velocity over one second, nothing else. No pixels, no grass, no bodies. About 14,000 parameters, roughly 60 kB of weights, a few minutes of CPU on my laptop. For the pixels: a small off-the-shelf vision network (a ResNet-18) fine-tuned on seven thousand broadcast frames with the ball erased by inpainting — so it can't detect the ball, only infer it. And the frontier models ran twice on the same paired items: once on the pixels (Part 1's setup), and once on the &lt;strong&gt;exact same one-second tracks the tiny net saw, serialized as plain-text coordinates&lt;/strong&gt; — same information, no pixels.&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%2Fwtb2-david-goliath.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%2Fwtb2-david-goliath.png" alt="Same hidden-ball items, two kinds of input: a tiny trained net vs frontier VLMs" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The pattern is hard to miss. The two trained models beat the camera bias on off-center balls — &lt;strong&gt;82%&lt;/strong&gt; from the tracks, &lt;strong&gt;74%&lt;/strong&gt; from the pixels. The frontier models don't, from either input: 53% on pixels (statistically a coin flip), and 35–38% — &lt;em&gt;below&lt;/em&gt; chance — on the tracks themselves, where their correlation with the true ball goes from weakly positive to null or slightly negative: handed raw coordinates, they drift into the very centroid trap that broadcast geometry punishes (next section). Two footnotes keep the grid honest. The tracks column uses &lt;em&gt;ground-truth&lt;/em&gt; tracking, so it's an information ceiling. And on the easy, centered items the zero-shot VLMs still post better global medians (0.147 vs 0.195–0.210): pixel-precision where the camera has already done the work.&lt;/p&gt;

&lt;p&gt;The grid settles the question Part 1 left open, one candidate excuse at a time. Is the information missing? No — the tracks column extracts it. Is perception from pixels the wall? No — a small trained CNN gets most of the way there from the same masked frames. What separates the columns of that chart isn't the input at all; it's the row: &lt;strong&gt;trained versus zero-shot. The frontier models' general knowledge of the game, in any representation, does not substitute for task-specific inference&lt;/strong&gt; — which is precisely the question the view from the stands posed in the first place.&lt;/p&gt;

&lt;h2&gt;
  
  
  The camera lies
&lt;/h2&gt;

&lt;p&gt;Along the way, one statistic explained something that had been bugging me since Part 1: why is naive geometry so useless in broadcast frames? The centroid of the players — "the ball is where the crowd is" — isn't just uninformative in image space. It's &lt;em&gt;anti&lt;/em&gt;-correlated with the ball (−0.58): when the ball is far from the center of the frame, the player mass tends to be on the &lt;em&gt;opposite side&lt;/em&gt;.&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%2Fwtb2-camera-lies.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%2Fwtb2-camera-lies.png" alt="The same statistic, two spaces: the camera flips its sign" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The reason is the camera operator. When a ball is off-center, it's usually because it's moving fast — a long pass, a clearance — running &lt;em&gt;ahead&lt;/em&gt; of the play while the players trail behind. Project that onto a camera that chases the ball and the geometry inverts. Strip the camera away — compute the same centroid in field coordinates — and the correlation flips to +0.83. Same statistic, same matches; the camera flips its sign. Every system in Part 1 that leaned on "the ball is near the players" was leaning on a lie.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two sports, one question: what actually transfers?
&lt;/h2&gt;

&lt;p&gt;With a working specialist in hand, I could finally ask the question that motivated this whole project from the stands: &lt;strong&gt;how much of this skill is "knowing football", and how much is universal team-sport structure?&lt;/strong&gt; I trained the same architecture on soccer (Metrica and SkillCorner tracking data) and on basketball (NBA SportVU), and crossed them.&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%2Fwtb2-asymmetry.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%2Fwtb2-asymmetry.png" alt="Zero-shot transfer between sports is asymmetric" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Zero-shot — trained on one sport, tested cold on the other — the result is asymmetric in a way I didn't predict. Soccer→basketball fails: worse than an &lt;em&gt;untrained&lt;/em&gt; centroid. But basketball→soccer &lt;em&gt;works&lt;/em&gt;: 0.17 median error, beating soccer's own geometric baselines without ever seeing a football match. And no, it's not that the basketball dataset is bigger — subsampling it to match the soccer data leaves the result intact (0.174).&lt;/p&gt;

&lt;p&gt;It took a couple of wrong turns to pin down. My first guess — that basketball's ball simply lives closer to the player mass — measured false: the ball-to-mass distance distributions are nearly identical. The real culprit is &lt;strong&gt;velocity scale&lt;/strong&gt;. Strip each player down to &lt;em&gt;positions only&lt;/em&gt;, discarding the velocity channel, and the asymmetry vanishes — positions transfer symmetrically both ways; it's the &lt;em&gt;use of velocity&lt;/em&gt; that only travels one direction. The reason is a units mismatch I should have seen coming: a basketball court is about a sixth the area of a football pitch, so in the normalized coordinates the model reads, players move three to four times faster in basketball. A model's learned sense of "how fast is fast, and what that implies about where the ball is" is calibrated to its own sport's scale and misfires across a 3–4× gap. Positions carry no such scale, so they cross cleanly. That turns out to be the whole asymmetry — and a small, general lesson: what transfers between domains is the scale-free structure; anything with units attached needs recalibration.&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%2Fwtb2-asymmetry-velocity.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%2Fwtb2-asymmetry-velocity.png" alt="Removing the velocity channel makes cross-sport transfer symmetric" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Fine-tuning, and a hypothesis that died twice
&lt;/h2&gt;

&lt;p&gt;Does pretraining on one sport at least give you a head start on another? Here I got burned by my own enthusiasm — twice — and both times a control caught it.&lt;/p&gt;

&lt;p&gt;First pass: with instantaneous player features, pretraining on soccer gave &lt;em&gt;no&lt;/em&gt; advantage over training from scratch on the same basketball minutes. Second pass: with one-second trajectories it &lt;em&gt;did&lt;/em&gt; — and I nearly wrote "temporal dynamics is what transfers." Then a shuffled-target control (pretrain the same net on soccer inputs with randomized ball positions — same optimization warm-up, zero knowledge) showed a third of that advantage was generic warm-start. And a feature ablation showed the rest wasn't about temporal depth at all: the advantage tracks whichever variants use &lt;em&gt;velocity&lt;/em&gt; features, snapshot or trajectory alike.&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%2Fwtb2-pretraining-arms.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%2Fwtb2-pretraining-arms.png" alt="Real pretraining beats both scratch and the shuffled control, in both directions" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What survives all the controls, pooled across both transfer directions: real pretraining wins in 12/13 seeds against scratch (p=0.002) and 9/10 against the shuffled control (p=0.011), with a modest ~5% edge that only shows up once you have ~30 minutes of the target sport. The refined picture: &lt;strong&gt;positions carry the core signal, but their mapping is easy — half an hour of any sport teaches it from zero. What genuinely transfers is the harder-won skill of exploiting velocities&lt;/strong&gt;, which misleads zero-shot (speed scales are sport-specific) but pays off once briefly recalibrated.&lt;/p&gt;

&lt;p&gt;"Knowing the game," for this family of models, turns out to mean something narrow and specific: knowing what to do with motion.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Training matters; the input barely does.&lt;/strong&gt; A 60 kB net on tracks (82%) and a small CNN on masked pixels (74%) both crack the off-center balls; the zero-shot frontier models fail from both inputs — 53% on pixels, 35–38% (below chance) on the very same tracks as text. Neither information nor perception is the wall: general game knowledge doesn't substitute for task-specific inference.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Broadcast geometry is a trap.&lt;/strong&gt; The player centroid is anti-correlated with the ball in image space (−0.58) and strongly correlated in field coordinates (+0.83). The camera flips the sign.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Transfer between sports is asymmetric, and the asymmetry is a velocity-scale mismatch.&lt;/strong&gt; Basketball exports its ball-sense to football; football doesn't return the favor. Positions transfer symmetrically both ways — it's the &lt;em&gt;use of velocity&lt;/em&gt; that breaks, because the two sports live 3–4× apart on the normalized velocity scale. The scale-free structure crosses; the units-bound part doesn't.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The controls did the heavy lifting, again.&lt;/strong&gt; A shuffled-target pretraining control and a feature ablation each overturned a conclusion I was ready to publish. This project is three-for-three on "the first version of the story was wrong."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Next up is Level 3 of this ladder: the geometry and topology underneath — &lt;em&gt;when&lt;/em&gt; do the players determine the ball, and can interpretable structure (Voronoi cells, pitch control, persistent homology) recover what the little black box learned?&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Code, data pipelines, controls, and audits in the &lt;a href="https://github.com/JaviMaligno/wheres-the-ball" rel="noopener noreferrer"&gt;&lt;code&gt;wheres-the-ball&lt;/code&gt;&lt;/a&gt; repo. Player tracking from &lt;a href="https://github.com/SoccerNet/sn-tracking" rel="noopener noreferrer"&gt;SoccerNet-Tracking&lt;/a&gt;, &lt;a href="https://github.com/metrica-sports/sample-data" rel="noopener noreferrer"&gt;Metrica Sports&lt;/a&gt;, &lt;a href="https://github.com/SkillCorner/opendata" rel="noopener noreferrer"&gt;SkillCorner&lt;/a&gt;, and the NBA SportVU 2015-16 logs (&lt;a href="https://github.com/linouk23/NBA-Player-Movements" rel="noopener noreferrer"&gt;mirror&lt;/a&gt;; no explicit license — used for research only, not redistributed). Architecture: &lt;a href="https://arxiv.org/abs/1703.06114" rel="noopener noreferrer"&gt;DeepSets&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/wheres-the-ball-2" 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>computervision</category>
      <category>research</category>
    </item>
    <item>
      <title>Routing Engineering</title>
      <dc:creator>JaviMaligno</dc:creator>
      <pubDate>Sun, 26 Jul 2026 10:14:59 +0000</pubDate>
      <link>https://dev.to/javieraguilarai/routing-engineering-dl9</link>
      <guid>https://dev.to/javieraguilarai/routing-engineering-dl9</guid>
      <description>&lt;p&gt;In the &lt;a href="https://www.javieraguilar.ai/en/blog/death-of-prompt-engineering" rel="noopener noreferrer"&gt;first part of this pair&lt;/a&gt; I argued that prompt engineering — the craft of wording — is dying, and that the effort didn't vanish so much as climb the stack. This is where it climbed to.&lt;/p&gt;

&lt;p&gt;Here is the new friction. I sit down to a task and, before I've done anything, I face a grid. &lt;a href="https://openai.com/index/gpt-5-6/" rel="noopener noreferrer"&gt;OpenAI now ships GPT-5.6&lt;/a&gt; in three tiers — Sol, Terra, Luna — with several reasoning-effort settings and a new &lt;code&gt;max&lt;/code&gt; level. Add the earlier 5.x models still in rotation and the Codex variants, and one vendor already gives you dozens of plausible (model × effort) routes. &lt;a href="https://platform.claude.com/docs/en/build-with-claude/effort" rel="noopener noreferrer"&gt;Claude's effort ladder&lt;/a&gt; spans &lt;code&gt;low&lt;/code&gt;, &lt;code&gt;medium&lt;/code&gt;, &lt;code&gt;high&lt;/code&gt;, and &lt;code&gt;max&lt;/code&gt;, with &lt;code&gt;xhigh&lt;/code&gt; on supported models. Gemini has a thinking budget. The wording is no longer the decision. &lt;em&gt;The allocation&lt;/em&gt; is: which model, at what effort, for which slice of the work. Getting that right is what I'd call routing engineering, and it's quietly become one of the more valuable things to be good at.&lt;/p&gt;

&lt;h2&gt;
  
  
  The matrix nobody asked for
&lt;/h2&gt;

&lt;p&gt;The uncomfortable part is that the grid grew faster than our ability to reason about it. When a single model had one setting, "use the best one" was a complete strategy. Now the knobs multiply with every release, and most of them trade the same three things against each other: quality, latency, and cost. A &lt;code&gt;max&lt;/code&gt;-effort frontier model will grind through almost anything, but it's slow and it burns tokens. A cheap model at &lt;code&gt;low&lt;/code&gt; effort is instant and nearly free, and it will confidently wreck a task that needed a moment of thought.&lt;/p&gt;

&lt;p&gt;Nobody hand-optimizes a hundred-cell grid per task, so in practice we all collapse it into a handful of habits. The interesting question — the engineering one — is &lt;em&gt;which&lt;/em&gt; habits actually pay, and when the granularity is worth the bother at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I actually do
&lt;/h2&gt;

&lt;p&gt;My own default is embarrassingly simple, and it's a routing decision. In Claude Code I plan with Fable 5 and then execute with Opus or something lighter. If I let the planning-grade model drive the whole task, token usage explodes; the plan is where the intelligence has to be, and the execution mostly has to &lt;em&gt;follow&lt;/em&gt; the plan. Splitting the two along that seam is the single change that made long agent sessions affordable for me.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Publication note: two days before this article went live, &lt;a href="https://www.anthropic.com/news/claude-opus-5" rel="noopener noreferrer"&gt;Opus 5 arrived&lt;/a&gt; with performance close to Fable 5 on many tasks at half the price. It has already replaced Fable as my planner. The route changed before the article even shipped, which is almost too on-theme.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I do the same thing in research. In &lt;a href="https://www.javieraguilar.ai/en/blog/writing-a-research-paper-with-ai" rel="noopener noreferrer"&gt;writing a paper with AI&lt;/a&gt; the division that mattered was Fable 5 for planning the science and GPT-5.6 as a second reviewer — a strong model where judgment lives, a swap when I hit usage limits, and cheaper execution in between. None of that is prompt craft. It's routing: matching the model to the shape of the work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the split works
&lt;/h2&gt;

&lt;p&gt;The reason this particular seam — plan strong, execute cheap — keeps showing up is that most of a task's difficulty is concentrated in a few decisions. &lt;a href="https://cursor.com/blog/agent-swarm-model-economics" rel="noopener noreferrer"&gt;Cursor put a number on it recently&lt;/a&gt;. In a study on long-running agent "swarms," they had a fleet build SQLite from scratch in Rust, working only from its 835-page manual, and varied who planned and who executed. Their own summary of the mechanism is the clearest statement of the principle I've seen:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"There are few moments in a large task that truly require frontier intelligence — the initial decomposition, the design decisions, certain trade-offs. Once a frontier planner has turned ambiguity into a detailed, explicit instruction, the cheaper models simply have to follow it."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The result was that every model mix reached &lt;em&gt;similar quality&lt;/em&gt;, while cost varied by roughly 8× — from about \1,339 for an Opus-4.8-plans / Composer-2.5-executes hybrid, up to \10,565 for GPT-5.5 doing everything itself. On the execution slice alone the gap was starker: the cheap workers cost around \411 where the all-frontier run's workers cost \9,373. Same destination, an order of magnitude apart on the fare.&lt;/p&gt;

&lt;p&gt;And this isn't only a vendor's marketing claim. The older academic evidence already pointed the same way: &lt;a href="https://github.com/lm-sys/RouteLLM" rel="noopener noreferrer"&gt;RouteLLM&lt;/a&gt; reported up to &lt;strong&gt;85% cost reduction while keeping 95% of GPT-4's quality&lt;/strong&gt; by routing each query to a strong or weak model; &lt;a href="https://arxiv.org/abs/2305.05176" rel="noopener noreferrer"&gt;FrugalGPT&lt;/a&gt;, earlier still, showed cascades matching a top model at up to 98% lower cost. Those are useful proofs that the asymmetry has been known for years, not measurements of today's frontier. The more current picture is also more sobering: &lt;a href="https://arxiv.org/abs/2601.07206" rel="noopener noreferrer"&gt;LLMRouterBench&lt;/a&gt;, published in January 2026, re-evaluated ten routing methods across more than 400,000 examples, 21 datasets, and 33 models. It confirmed that models are complementary, but found that several recent methods — including commercial routers — did not reliably beat a simple baseline. The savings are real; extracting them reliably is still unsolved.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part that makes it &lt;em&gt;engineering&lt;/em&gt;
&lt;/h2&gt;

&lt;p&gt;If it were just "plan with the smart one," it would be a tip, not a discipline. What makes it engineering is that the obvious move is often wrong.&lt;/p&gt;

&lt;p&gt;The same Cursor study has a detail I keep coming back to: the hybrid built on the &lt;em&gt;stronger, newer&lt;/em&gt; planner (Fable 5) ended up &lt;strong&gt;more expensive&lt;/strong&gt; than the one built on Opus 4.8 — even though the Fable planner's own bill was slightly lower. The catch was downstream: its plans led the cheap workers to consume several times more tokens, and workers are where the volume is (they accounted for at least 69% of tokens in every run, over 90% in most). A better planner produced a costlier run.&lt;/p&gt;

&lt;p&gt;That's the whole lesson in one data point. The cost of a route is non-linear and it lives mostly in second-order effects — how a plan shapes execution, not the planner's own price tag. You cannot read it off a spec sheet. You have to measure the route end to end, which is exactly the kind of thing engineering disciplines exist to do.&lt;/p&gt;

&lt;h2&gt;
  
  
  Manual, or automatic
&lt;/h2&gt;

&lt;p&gt;You can route by hand, the way I do, or hand the decision to a system.&lt;/p&gt;

&lt;p&gt;The automatic tier is filling in fast. Cursor now ships a request-level &lt;strong&gt;&lt;a href="https://cursor.com/blog/router" rel="noopener noreferrer"&gt;Router&lt;/a&gt;&lt;/strong&gt; that classifies each task and sends it to an appropriate model, with an Auto mode you can bias toward intelligence, balance, or cost — they report 30–50% cost reductions at frontier quality in early enterprise use. &lt;a href="https://openrouter.ai" rel="noopener noreferrer"&gt;OpenRouter&lt;/a&gt;'s &lt;code&gt;auto&lt;/code&gt; endpoint routes per-prompt (powered by NotDiamond's model) with a cost-versus-quality dial. Martian and NotDiamond sell the same idea as a service. There's even a small benchmark literature trying to answer "how good is routing, really," and the honest read is &lt;em&gt;promising but unsettled&lt;/em&gt;: some routers save real money, and some, under scrutiny, just default to the expensive model.&lt;/p&gt;

&lt;p&gt;There is a deeper difficulty: &lt;strong&gt;you often cannot know how hard a task is until you start doing it&lt;/strong&gt;. A prompt that looks routine can hide a nasty dependency three tool calls in; a frightening one can collapse after the first inspection. One-shot routing has to predict that latent difficulty before seeing the evidence the work itself will reveal. Recent research is starting to move the decision inside the execution loop. &lt;a href="https://arxiv.org/abs/2605.18859" rel="noopener noreferrer"&gt;TwinRouterBench&lt;/a&gt;, from May 2026, routes at every call inside coding and research-agent trajectories and checks whether the whole task still succeeds. In one diagnostic, even Opus 4.6 acting as the router identified only 7 of 147 steps that execution later showed really needed the high tier, and all 40 SWE-bench trajectories failed. &lt;a href="https://proceedings.neurips.cc/paper_files/paper/2025/file/b39cef2ef90591cffdc9c674cd55bebe-Paper-Conference.pdf" rel="noopener noreferrer"&gt;R2R&lt;/a&gt;, at NeurIPS 2025, goes finer still: it begins generating with a 1.5B DeepSeek-R1 distilled model and invokes the 32B model token by token when the reasoning path starts to diverge. The router is no longer a receptionist choosing a model at the door; it is a supervisor watching the work unfold and escalating when necessary.&lt;/p&gt;

&lt;p&gt;Automatic routing will get better, but it inherits the hard part above: to route well it has to predict the second-order cost, not just classify difficulty. For now I trust a hand-drawn seam I've measured over a router I haven't.&lt;/p&gt;

&lt;h2&gt;
  
  
  When it's worth it
&lt;/h2&gt;

&lt;p&gt;Here's the part I'd push back on the hype about: routing granularity has a cost of its own, and it isn't always worth paying.&lt;/p&gt;

&lt;p&gt;For a one-off task, the entire calculus is often "use the best model and move on" — the time you'd spend tuning a route dwarfs the tokens you'd save. Granular routing earns its keep when one of three things is true: &lt;strong&gt;volume&lt;/strong&gt; (you're running the task thousands of times), &lt;strong&gt;tight cost or latency budgets&lt;/strong&gt;, or a &lt;strong&gt;clear plan/execute asymmetry&lt;/strong&gt; like the long agent sessions above. Outside those, a router is premature optimization wearing an engineering costume. Knowing when &lt;em&gt;not&lt;/em&gt; to route is as much a part of the discipline as the routing itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  The unit keeps getting bigger
&lt;/h2&gt;

&lt;p&gt;Step back and there's a through-line with where all of this is heading. Cursor framed their swarm as a kind of probabilistic compiler — planners decompose intent into a tree, cheaper models compile the leaves — and noted that "the unit of work becomes the specification." That rhymes with the &lt;a href="https://www.javieraguilar.ai/en/blog/software-dissolving-into-the-model" rel="noopener noreferrer"&gt;software dissolving into the model&lt;/a&gt;: what you hand the system keeps getting larger and less literal, and the scarce skill becomes describing intent well and deciding what runs where.&lt;/p&gt;

&lt;p&gt;There's one more honest wrinkle worth keeping. In that same study, the team wanted to use GPT-5.6 Sol as their frontier planner and couldn't — the new model was so sensitive to literal, emphatic wording that it spiraled out of control, and they fell back to GPT-5.5 rather than re-tune the prompts. So even at the frontier of routing, a ghost of prompt engineering is still in the room. The craft didn't die so much as move — from the words, to the wiring. Choosing the model &lt;em&gt;is&lt;/em&gt; the new craft.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This is the second of two pieces on how "engineering" work in AI keeps relocating up the stack. The first is &lt;a href="https://www.javieraguilar.ai/en/blog/death-of-prompt-engineering" rel="noopener noreferrer"&gt;The Death of the Prompt Engineer&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/routing-engineering" 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>routing</category>
      <category>agents</category>
      <category>cost</category>
    </item>
    <item>
      <title>The Death of the Prompt Engineer</title>
      <dc:creator>JaviMaligno</dc:creator>
      <pubDate>Sat, 25 Jul 2026 10:34:44 +0000</pubDate>
      <link>https://dev.to/javieraguilarai/the-death-of-the-prompt-engineer-10cg</link>
      <guid>https://dev.to/javieraguilarai/the-death-of-the-prompt-engineer-10cg</guid>
      <description>&lt;p&gt;This week, researcher &lt;a href="https://x.com/DmitryRybin1/status/2079904005652893709" rel="noopener noreferrer"&gt;Dmitry Rybin published&lt;/a&gt; what appears to be a compact counterexample to a graph-theory conjecture that had been open for roughly thirty years. In the proposed construction, the fractional flow costs 58, while every indivisible flow — even allowing a capacity violation of up to 15 — costs at least 60. If the construction holds up under formal scrutiny, the Dinitz–Garg–Goemans conjecture is false. The candidate came out of a &lt;a href="https://chatgpt.com/share/6a60b2eb-0b64-83ee-9c76-7931ca1de063" rel="noopener noreferrer"&gt;public chat session with GPT-5.6 Pro&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here is the part that should stop you. The prompts that produced it read, verbatim, like this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Construct a counterexample to general (non-planar) case of Dinitz Garg Goemans conjecture. You should do a breakthrough and find a structured counterexample."&lt;/p&gt;

&lt;p&gt;"please continue research and find a complete unconditional counterexample"&lt;/p&gt;

&lt;p&gt;"Continue the search. Have a clear strategy obtained from deeper understanding of the problem structure."&lt;/p&gt;

&lt;p&gt;"it's enough of partial results. let's finish with a complete unconditional counterexample"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is the prompt engineering. &lt;em&gt;Do a breakthrough. Just continue. Enough partial results, let's finish.&lt;/em&gt; The same week, a meme captured it perfectly:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4pl3531ic639ef26at26.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4pl3531ic639ef26at26.jpg" alt="Meme titled " width="800" height="755"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Source: &lt;a href="https://x.com/Merkaloid" rel="noopener noreferrer"&gt;@Merkaloid&lt;/a&gt; quote-tweeting &lt;a href="https://x.com/mattshumer_" rel="noopener noreferrer"&gt;@mattshumer_&lt;/a&gt; on X.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It's a joke. It's also the most honest description of the state of the art I've seen this year.&lt;/p&gt;

&lt;p&gt;And the conjecture wasn't a one-off. The same week saw a small cluster of long-open problems fall to AI — some proved, some refuted, and several &lt;em&gt;checked&lt;/em&gt; rather than merely asserted. Many were &lt;strong&gt;Graffiti conjectures&lt;/strong&gt;, from a 1988 program that mechanically generated graph-theory conjectures: one agent &lt;a href="https://x.com/justinsunyt/status/2080116559352316409" rel="noopener noreferrer"&gt;refuted Conjecture 284&lt;/a&gt; (open ~30 years) in about eight minutes, and &lt;a href="https://x.com/imjaredz/status/2080088344424583261" rel="noopener noreferrer"&gt;Cognition's Devin refuted Conjecture 154&lt;/a&gt; (open ~40 years) with the counterexample &lt;strong&gt;verified in Lean&lt;/strong&gt;, while proving Conjectures 39 and 40 outright. The machine-checked part is what keeps this from being a season of merely plausible-looking guesses — results are being formally verified faster than anyone can read them.&lt;/p&gt;

&lt;p&gt;Something else died quietly in the spectacle. It was prompt engineering.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually died
&lt;/h2&gt;

&lt;p&gt;Not prompting. Prompting — asking a model for what you want — is more central than ever. What died is prompt engineering as an &lt;em&gt;artifact craft&lt;/em&gt;: the belief that the value lived in the wording.&lt;/p&gt;

&lt;p&gt;For a couple of years that belief had a whole economy around it. Templates. &lt;code&gt;You are an expert…&lt;/code&gt; preambles. Few-shot scaffolds you tuned like hyperparameters. Delimiter tricks and "think step by step" incantations. Prompt marketplaces. Job posts for "Prompt Engineer" at six figures, as if the skill were a stable profession rather than a temporary artifact of weak models. &lt;a href="https://www.javieraguilar.ai/en/blog/parallel-ai-agent-development" rel="noopener noreferrer"&gt;In December, in an article about parallel AI agents&lt;/a&gt;, I included "prompt engineering for clear task specification" in a list of skills whose importance was shifting. I was describing a moving target and didn't know it.&lt;/p&gt;

&lt;p&gt;The tell is the math example. If prompting were still an artifact craft, producing a serious candidate counterexample to a thirty-year-old conjecture would demand the most exquisitely engineered prompt anyone had ever written. Instead it took "do a breakthrough." The wording carried almost no information. All of it came from the model and the loop.&lt;/p&gt;

&lt;h2&gt;
  
  
  The everyday version: the ramble
&lt;/h2&gt;

&lt;p&gt;You don't need an open conjecture to see it. &lt;a href="https://x.com/karpathy/status/2079610838143623371" rel="noopener noreferrer"&gt;Andrej Karpathy described the ordinary version&lt;/a&gt; in a post that &lt;a href="https://x.com/DotCSV/status/2079613175947747775" rel="noopener noreferrer"&gt;Carlos Santana amplified&lt;/a&gt; with exactly the right framing — that Karpathy wasn't discovering anything, just naming out loud what practitioners already did in silence:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"One pattern I find useful for working with LLMs is a nice long ramble session… I like to lean back, switch to /voice and just ramble for like 10 minutes, total mess, anything goes, full stream of consciousness… I find that the LLMs are somehow very good at reconstructing long incoherent rambles and often their echo of your own tangle of thoughts comes out quite a bit cleaner than what you started with."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you've done this, you know the feeling: you stop composing and start &lt;em&gt;gesturing&lt;/em&gt;. You dump a mess and the model hands back something more coherent than what you put in. This very article started as a ten-minute voice ramble, typos and all — which is why it can end by describing itself.&lt;/p&gt;

&lt;p&gt;That is the opposite of prompt engineering. You're not curating the input. You're trusting the system to reconstruct intent from noise.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it died: the harness ate the job
&lt;/h2&gt;

&lt;p&gt;Two things killed the artifact.&lt;/p&gt;

&lt;p&gt;The first is the &lt;strong&gt;harness&lt;/strong&gt;. Modern coding and research agents don't sit and wait for a perfectly specified request. They search your environment, retrieve the relevant files, carry memory across turns, and run agentic loops that gather the context you used to hand-feed. The work that prompt engineering used to do — assembling exactly the right bits in exactly the right order — got absorbed into the tooling. You give a badly worded paragraph; the harness goes and finds the rest.&lt;/p&gt;

&lt;p&gt;The second is &lt;strong&gt;raw capability&lt;/strong&gt;. The models are now good enough to reconstruct intent from a bad input, and to keep going when the instruction is just "continue." In the conjecture case there is barely any prompt engineering left to point to — it's replaced by &lt;em&gt;iteration&lt;/em&gt;: repeat "keep going, now finish" while the agent decomposes the problem itself. The intelligence moved from the sentence into the model and the loop around it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What didn't die — and this is the important part
&lt;/h2&gt;

&lt;p&gt;It would be a lie to end on "just ramble and the machine does everything." For the genuinely hard problems, effort didn't disappear; it changed shape.&lt;/p&gt;

&lt;p&gt;Behind "do a breakthrough" there is still a person who knows a real result from a plausible one. When I &lt;a href="https://www.javieraguilar.ai/en/blog/writing-a-research-paper-with-ai" rel="noopener noreferrer"&gt;wrote a research paper with AI&lt;/a&gt;, the discipline that mattered wasn't the wording — it was separating what could be &lt;em&gt;demonstrated&lt;/em&gt; from what could only be &lt;em&gt;measured&lt;/em&gt;, and refusing to blur the two. In one planning session a model garbled a note about topology, claiming a ring in the plane needed a higher dimension. I caught it because it's my field, not because I'd written a clever prompt. The model can do the search; you still have to know when the answer is real.&lt;/p&gt;

&lt;p&gt;And for the hardest work you increasingly reach not for a better prompt but for a &lt;strong&gt;structure&lt;/strong&gt; — a skill, a harness — that forces the model to try several strategies, decompose the problem into smaller parts or lemmas, run experiments, write tests, review its own output, hold a standard of rigor. That scaffolding is the successor to the prompt template. It's not a magic sentence; it's a process. The candidate counterexample didn't emerge from one lucky message either — it came from a loop that kept insisting on rigor until a partial result became a complete one.&lt;/p&gt;

&lt;p&gt;So the skill didn't vanish. It moved — from the &lt;em&gt;words&lt;/em&gt; to the &lt;em&gt;framing, the steering, and the verification&lt;/em&gt;. Karpathy's term for the goal is the "mind meld": getting the model aligned with what you actually mean, so you have to correct it less from that point on. That is the craft now. The trend line is unmistakable: every generation does more with less, and the part you can leave to the model keeps growing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The effort didn't disappear — it moved up
&lt;/h2&gt;

&lt;p&gt;If prompt engineering is dying, it's not because engineering left. It's because it climbed the stack. The leverage used to sit at the level of the sentence. Now it sits at the level of the &lt;em&gt;system&lt;/em&gt;: which model you hand a task to, whether you plan with a strong one and execute with a cheap one, what structure you wrap around the loop.&lt;/p&gt;

&lt;p&gt;That's a whole discipline of its own — the one I'd argue is replacing prompt engineering rather than merely outliving it — and it deserves its own piece. For now the short version is this: the best "prompt" in 2026 is a ten-minute ramble to a system that already knows your codebase, and the skill worth having is knowing what to build around it.&lt;/p&gt;

&lt;p&gt;The prompt was never the point. Getting the machine to understand you was.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This is the first of two pieces on how "engineering" work in AI keeps relocating. The second is about **routing engineering&lt;/em&gt;* — choosing which model and how much reasoning to spend on each task. See also &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;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://www.javieraguilar.ai/en/blog/death-of-prompt-engineering" 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>promptengineering</category>
      <category>agents</category>
      <category>trends</category>
    </item>
    <item>
      <title>Where's the Ball? Testing Whether a VLM Has a Spectator's Intuition</title>
      <dc:creator>JaviMaligno</dc:creator>
      <pubDate>Fri, 24 Jul 2026 11:02:09 +0000</pubDate>
      <link>https://dev.to/javieraguilarai/wheres-the-ball-testing-whether-a-vlm-has-a-spectators-intuition-3827</link>
      <guid>https://dev.to/javieraguilarai/wheres-the-ball-testing-whether-a-vlm-has-a-spectators-intuition-3827</guid>
      <description>&lt;p&gt;Watch a football match from the top row of the stadium, where the ball is a barely-visible speck, and something curious happens: you usually know where it is anyway. The players tell you. Bodies lean, a cluster forms, everyone starts drifting the same way — and your eye lands on the ball a beat before you actually see it. It's not tracking. It's a kind of &lt;em&gt;social&lt;/em&gt; inference: you know the game, so the people tell you about the thing.&lt;/p&gt;

&lt;p&gt;I wanted to know whether a generalist vision-language model has that same intuition. Not a specialist tracker trained on ball trajectories — those exist and they're good (&lt;a href="https://arxiv.org/abs/1511.06181" rel="noopener noreferrer"&gt;Maksai et al.&lt;/a&gt;, &lt;a href="https://arxiv.org/abs/2306.08206" rel="noopener noreferrer"&gt;Kim et al. 2023&lt;/a&gt;, &lt;a href="https://arxiv.org/abs/2410.17785" rel="noopener noreferrer"&gt;TranSPORTmer&lt;/a&gt; all infer the ball from player motion). I mean a model that "knows the game" the way a person in the stands does, and has never been trained for this task specifically. Can it look at a frame with the ball erased and point to where it must be?&lt;/p&gt;

&lt;p&gt;The honest answer turned out to be &lt;em&gt;yes, a little, and only the biggest models&lt;/em&gt; — but getting to an answer I actually trust was most of the work, and it's the part worth writing about.&lt;/p&gt;

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

&lt;p&gt;I used &lt;a href="https://github.com/SoccerNet/sn-tracking" rel="noopener noreferrer"&gt;SoccerNet-Tracking&lt;/a&gt;: broadcast clips with per-frame bounding boxes for every player and the ball. For each frame I &lt;strong&gt;removed the ball&lt;/strong&gt; with LaMa inpainting — deep inpainting that reconstructs a coherent patch of grass, line, or shirt where the ball was, so the model can't cheat by spotting an editing smudge. (Classical inpainting left tell-tale blurs on pitch lines; a leak-control step with a VLM confirmed the deep version leaves nothing to latch onto.) Then I asked several models for the ball's coordinates and scored the distance to the truth.&lt;/p&gt;

&lt;p&gt;The models: &lt;strong&gt;GPT-5.4&lt;/strong&gt;, &lt;strong&gt;Claude Opus 4.8&lt;/strong&gt;, &lt;strong&gt;Claude Sonnet 4.6&lt;/strong&gt; (all via API), and &lt;strong&gt;Qwen2.5-VL-7B&lt;/strong&gt; as an open-source reference, run on a GPU. Against them, two dumb baselines: "the ball is at the center of the frame" and "the ball is at the centroid of the players."&lt;/p&gt;

&lt;h2&gt;
  
  
  The trap I walked straight into
&lt;/h2&gt;

&lt;p&gt;My first run looked great. GPT beat the center baseline; on the hardest cases — balls far from the middle of the frame — it seemed to win &lt;strong&gt;64%&lt;/strong&gt; of the time. Tidy story: generalist AI has spectator intuition. I could have written &lt;em&gt;that&lt;/em&gt; article.&lt;/p&gt;

&lt;p&gt;Two problems, both mine.&lt;/p&gt;

&lt;p&gt;First, a confound. Broadcast cameras &lt;em&gt;follow the ball&lt;/em&gt; — they keep it near the center of the shot. So "just guess the center" isn't a dumb baseline at all; it's a strong one, and any model that leans central looks smart for the wrong reason. Second, and worse: my sample was tiny. That 64% came from &lt;strong&gt;14 items&lt;/strong&gt;. When I put a bootstrap confidence interval around it, the interval ran from 36% to 86% — comfortably including 50%, i.e. chance.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fi8nx2x8o2cwdp5kpbbel.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fi8nx2x8o2cwdp5kpbbel.png" alt="How a small sample almost fooled us" width="800" height="482"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So I rebuilt the test to defuse both issues: I balanced the dataset by the ball's distance from the center (so "guess the center" is useless by construction), and I scaled up. At a larger sample the same off-center win-rate settled at &lt;strong&gt;55%&lt;/strong&gt;, and its interval &lt;em&gt;still&lt;/em&gt; touched 50%. The exciting first result hadn't survived contact with statistics. It was a mirage made of a followed camera and fourteen data points.&lt;/p&gt;

&lt;p&gt;I mention this not to be self-deprecating but because it's the whole point: a confounded baseline plus a small sample will hand you a satisfying, publishable, wrong conclusion, and it will feel true. The only defense is boring — de-bias, scale, bootstrap, and try to break your own finding before you believe it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's actually true
&lt;/h2&gt;

&lt;p&gt;Once the sample was honest, I stopped leaning on error-on-a-hard-subset (still underpowered) and used a better-behaved measure: &lt;strong&gt;does the model's prediction correlate with where the ball actually is&lt;/strong&gt;, across the whole range of positions? That question the data can answer.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F33mx1wd0xiiktxjy7rsg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F33mx1wd0xiiktxjy7rsg.png" alt="Correlation of each model's prediction with the true ball position" width="800" height="434"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Claude Opus 4.8&lt;/strong&gt; has the clearest signal — its guesses track the true ball position on both axes, and the confidence intervals stay clear of zero.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GPT-5.4&lt;/strong&gt; shows partial signal (solid horizontally, noisier vertically).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Qwen2.5-VL-7B (open)&lt;/strong&gt; and &lt;strong&gt;Claude Sonnet 4.6&lt;/strong&gt; are, statistically, &lt;strong&gt;flat&lt;/strong&gt; — their intervals straddle zero. They aren't pointing at the center out of laziness (I checked — their predictions are spread out); they're just not tracking the ball.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So the spectator intuition is real, but faint, and it's a frontier-model trait: it shows up in the big closed models and not in the smaller open one or the mid-tier Claude. And a sober caveat that survived all the scaling: &lt;strong&gt;from a single frame, no model reliably beats the center bias on genuinely off-center balls.&lt;/strong&gt; The intuition is there in aggregate; it isn't strong enough to nail the hard, wide cases from one still image.&lt;/p&gt;

&lt;p&gt;One more twist, which I liked. I gave every model an "informed" prompt — naming the sport and spelling out what a spectator uses (players orient toward the ball, converge on it, the carrier leads a cluster). It helped &lt;strong&gt;GPT a lot&lt;/strong&gt; (its correlation jumped), did &lt;strong&gt;nothing for Sonnet&lt;/strong&gt;, and barely moved Opus. Read that carefully: Sonnet's problem was never that it didn't know football. Telling it the rules changed nothing, because its bottleneck is &lt;em&gt;seeing&lt;/em&gt;, not &lt;em&gt;knowing&lt;/em&gt;. For GPT, which could already reason about the scene, the framing unlocked headroom — the prompt mattered about as much as the model tier.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx7e3z0g2kucofdicpkqa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx7e3z0g2kucofdicpkqa.png" alt="Neutral vs informed prompt: mean correlation with the true ball position, per model" width="800" height="499"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The video that wasn't about video
&lt;/h2&gt;

&lt;p&gt;Here's where I was sure I knew the answer in advance. A single frame is ambiguous; a spectator's real trick is watching the play &lt;em&gt;develop&lt;/em&gt;. So I gave the models short sequences — four frames spanning a few seconds, the ball erased from all of them — expecting the motion to unlock the hard cases.&lt;/p&gt;

&lt;p&gt;It did seem to help GPT. But before believing it, I ran the control that matters: I &lt;strong&gt;shuffled the frames into a random order&lt;/strong&gt;. If a model reads motion, scrambling time should destroy the benefit.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frj2wkp8iiv5n6hcs9nvm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frj2wkp8iiv5n6hcs9nvm.png" alt="Nobody reads motion — it is a multi-view effect" width="800" height="433"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Shuffling changed nothing. The order of the frames was the single cleanest null in the whole experiment. No model is integrating the &lt;em&gt;trajectory&lt;/em&gt; of the play — they aren't reasoning about motion at all. What actually changed with multiple frames was something duller and stranger: &lt;strong&gt;extra views of the scene&lt;/strong&gt;, in any order. And the two frontier models handle that in opposite ways. GPT &lt;em&gt;aggregates&lt;/em&gt; — more looks at the moment, even scrambled, sharpen its guess. Opus gets &lt;em&gt;diluted&lt;/em&gt; — the extra frames pull it away from a target-frame read it was already better at. The best single-frame model was the worst at using more frames.&lt;/p&gt;

&lt;p&gt;That's a more interesting finding than "video helps," and I'd have missed it if I'd trusted the first, flattering version.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Generalist VLMs do have a faint spectator's intuition&lt;/strong&gt; — their guesses correlate with the hidden ball — but it's a frontier-model trait (Opus clearest, GPT partial) and it's weak: a single frame doesn't crack the genuinely hard cases.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;They don't reason about motion.&lt;/strong&gt; What looks like temporal understanding is really multi-view aggregation, and models differ in whether extra views help or hurt.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Knowledge isn't the bottleneck; seeing is.&lt;/strong&gt; Explaining the game helped the model that could already reason visually and did nothing for the one that couldn't.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The methodology was the hard part.&lt;/strong&gt; A followed camera and a small sample produced a clean, wrong story that felt right. De-biasing, scaling, bootstrapping, and adversarially auditing my own results changed the conclusion twice.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There's a Level 2 waiting — other sports, whether the skill transfers, and the geometry underneath it all — but that's for later. For now the honest headline is smaller and better than the one I almost wrote: the model in the stands can feel where the ball is, a little, if it's big enough; it just can't watch the play to find it.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Code, data pipeline, and the full audit (bootstrap CIs and all) are in the &lt;a href="https://github.com/JaviMaligno/wheres-the-ball" rel="noopener noreferrer"&gt;&lt;code&gt;wheres-the-ball&lt;/code&gt;&lt;/a&gt; repo. Built on &lt;a href="https://github.com/SoccerNet/sn-tracking" rel="noopener noreferrer"&gt;SoccerNet-Tracking&lt;/a&gt;; ball removed with &lt;a href="https://github.com/advimman/lama" rel="noopener noreferrer"&gt;LaMa&lt;/a&gt; via &lt;a href="https://github.com/Sanster/IOPaint" rel="noopener noreferrer"&gt;IOPaint&lt;/a&gt;. Prior specialist work worth reading: &lt;a href="https://arxiv.org/abs/1511.06181" rel="noopener noreferrer"&gt;Maksai et al.&lt;/a&gt;, &lt;a href="https://arxiv.org/abs/2306.08206" rel="noopener noreferrer"&gt;Kim et al. 2023&lt;/a&gt;, &lt;a href="https://arxiv.org/abs/2410.17785" rel="noopener noreferrer"&gt;TranSPORTmer&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/wheres-the-ball" 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>computervision</category>
      <category>research</category>
    </item>
  </channel>
</rss>
