<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
  <title>Antithetical Labs</title>
  <link>https://antithetical-labs.com/blog/</link>
  <description>Notes on micro-tools, automations, and putting AI to work in small businesses.</description>
  <language>en</language>
  <lastBuildDate>Wed, 02 Sep 2026 00:00:00 GMT</lastBuildDate>
  <atom:link href="https://antithetical-labs.com/feed.xml" rel="self" type="application/rss+xml" />
  <item>
    <title>The judge liked it better without the citations</title>
    <link>https://antithetical-labs.com/blog/the-judge-liked-it-better/</link>
    <guid isPermaLink="true">https://antithetical-labs.com/blog/the-judge-liked-it-better/</guid>
    <pubDate>Wed, 02 Sep 2026 00:00:00 GMT</pubDate>
    <description>I seeded nine defects into research memos. An LLM judge rewarded two of them. Typed relation checks caught all nine and named the broken object.</description>
    <content:encoded><![CDATA[<p>I deleted every citation from a research memo then asked an LLM
judge to score both the original and the stripped versions. The stripped
one scored higher.</p>
<p>Removing all evidence citations across ten memos only moved the
judge's score by +0.17. When the memo cites only the evidence that
cuts <em>against</em> its own thesis, the score goes up by 0.03. The judge's own run-to-run noise is 0.27. Both defects are
invisible to it. A set of typed relation checks caught both at 100%,
and told me which object was broken.</p>
<h2 id="why-i-ran-it">Why I ran it</h2>
<p>Ontologies for agents are a super hot topic right now, and that's no
surprise. However, almost everything written about them is
architecture-diagram theoretical. Luckily, ontologies were a major part of a project that I've
been involved in recently and the difference they made was palpable. I knew
immediately that I wanted to quantify the effect and write up a proper
article. This article uses a toy ontology and measures it against the
default alternative: asking a strong model to score outputs 1 to 10.</p>
<p>&quot;Ontology&quot; is an overloaded word, so for this article we'll use
this definition: a typed inventory of the objects your agent's output claims
to contain as well as the relations that must hold between them.</p>
<p>In this experiment we'll use five object types (a memo, market-state claims,
theses, trades, risks) and ten relations (&quot;every trade traces to a thesis&quot;,
&quot;a bearish thesis cites at least one bearish item&quot;). Nothing philosophical,
no knowledge graph, no OWL: a JSON Schema and ten Python functions.
If you can name your agent's nouns and the ways they can disagree, I guarantee
that you can build an ontology.</p>
<p>Everything below is reproducible from
<a href="https://github.com/antithetical-labs/llm-research-evals">the repo</a>:
the harness, every memo, every extraction, every judgment, and the
audit trail. Fully synthetic data. Total API cost about $35.</p>
<h2 id="the-setup">The setup</h2>
<p>The workload: write an institutional-style research memo from a data
packet. The packet has two instruments, a position the account already
holds, 60 days of prices, and five news items. One packet leans bearish
while the account is long; the other leans bullish while the account is
short. The natural memo is &quot;reduce the long&quot; or &quot;cover the short&quot;, and
there is genuine counter-evidence to weigh in both.</p>
<p>Four models write the memos (Opus 5, GPT-5.6, Kimi K3, Gemini 3.1 Pro,
all through OpenRouter, reasoning pinned low). Ten runs each per
packet: 80 memos, plus two I wrote by hand as known-good fixtures.</p>
<p>Every memo gets scored twice, from identical information:</p>
<ul>
<li><strong>Raw eval</strong>: an LLM judge scores it 1 to 10 against a short rubric
(analysis, evidence, actionability, risk). Three samples per memo, so
the judge's own variance is measured, not assumed.</li>
<li><strong>Ontology</strong>: a model extracts the memo's typed objects into a schema
(market_state claims with sources, theses with direction and cited
evidence, trades, risks), then ten deterministic checks run over the
extraction. Pure Python, no model involved.</li>
</ul>
<p>A check is a relation between objects, and a failure names its object:</p>
<figure class="code-block"><pre class="code"><code>FAIL trade_direction_agrees [tr1] side &#39;buy&#39; against a bearish thesis
</code></pre></figure>
<p>That line is the design argument in miniature. The raw eval outputs a
number. The ontology outputs which relation broke, on which object.
One of these you can act on.</p>
<p>The news items in the packet carry <code>polarity</code> and <code>scope</code> fields that
make evidence checkable (&quot;is at least one cited item actually negative,
and about this instrument?&quot;). Those fields are the answer key, so the
writer, the extractor, and the judge all get the packet with them
stripped. Nobody being evaluated ever sees them.</p>
<h2 id="seeding-defects">Seeding defects</h2>
<p>Comparing scores on good memos tells you little: both arms say &quot;fine&quot;
and you learn nothing about what happens when an agent breaks. So the
real experiment is destructive. I defined nine defect classes, one per
relation I care about, before any scoring ran:</p>
<ol>
<li><strong>direction_flip</strong>: the trade contradicts its own thesis (bearish
case, buy order)</li>
<li><strong>ghost_citation</strong>: a thesis cites a news item that doesn't exist</li>
<li><strong>unsupported_thesis</strong>: the thesis cites only evidence that cuts
against it</li>
<li><strong>no_evidence</strong>: every citation removed</li>
<li><strong>wrong_risk</strong>: the risk section guards the wrong instrument</li>
<li><strong>orphan_trade</strong>: a second trade with no thesis behind it</li>
<li><strong>no_risk</strong>: the invalidators deleted</li>
<li><strong>invented_fact</strong>: a material claim that appears nowhere in the
packet</li>
<li><strong>ignored_position</strong>: all mention of the existing position erased</li>
</ol>
<p>Each defect gets planted as a prose edit into ten clean base memos (the
eight cleanest model-written ones, two per model, plus my two
fixtures). Both arms only ever see prose, so the defect has to live in
the prose. An LLM applies the edit under a change-exactly-one-thing
instruction, every seed ships with its unified diff, and a gate rejects
any seed whose diff exceeds a per-defect line budget. The gate fired
four times in 90 seeds; I read all four diffs and accepted them (long,
citation-dense memos legitimately need more lines to strip).</p>
<p>Ninety seeded memos, each extracted twice and judged three times, next
to their clean bases.</p>
<h2 id="the-result">The result</h2>
<div class="figure-row"><figure class="figure"><img src="https://antithetical-labs.com/img/chart-defects.png" alt="Paired horizontal bar panels for nine defect classes: judge score change against a shaded noise band on the left, share of seeded memos flagged by relation checks on the right; the three evidence defects sit at or near zero judge movement while their check bars reach 100%" width="1441" height="727" decoding="async"><figcaption class="figure-caption">Judge score change and check detection, per defect class</figcaption></figure></div>
<p>(&quot;Flagged&quot; = any relation check fired on the seeded memo. Judge
movement is against the paired clean base, noise floor 0.27. Exact
numbers for every class are in the repo's <code>analysis.json</code>.)</p>
<p>Read the top three rows against the bottom five. The judge prices
defects that make the prose read wrong. A trade that contradicts its
own thesis costs 3.7 points; a suspicious unsourced fact costs 3.3. If
a human skimming the memo would frown, the judge frowns.</p>
<p>But the defects that make research <em>untrustworthy</em> rather than
<em>awkward</em> are exactly the ones it can't see. A memo that cites nothing
reads confident and clean, so it scores fine. Slightly better than
fine: the citations were apparently costing style points. A memo whose
own citations argue against it reads like a memo with citations. The
judge measures &quot;sounds like institutional research&quot;. The checks measure
&quot;is internally coherent research&quot;. These are different properties, and
the gap between them is where an agent quietly fails in production.</p>
<p>The judge is a strong current model (Opus 5), not a strawman. And the
rubric explicitly asks about evidence grounding. It doesn't matter: a
scalar squeezed out of holistic reading keeps rewarding surface.</p>
<h2 id="the-clean-memos-agree">The clean memos agree</h2>
<p>On the 80 unseeded memos, judge score and violation count barely
correlate: r = 0.106. And the judge's ranking of the four writers is
its own small indictment:</p>
<div class="figure-row"><figure class="figure"><img src="https://antithetical-labs.com/img/chart-writers.png" alt="Paired horizontal bar panels for the four writer models: mean judge score on the left, relation violations per memo on the right; Kimi ranks third on score with by far the lowest violation rate" width="1318" height="517" decoding="async" loading="lazy"><figcaption class="figure-caption">Judge ranking against violation rate, per writer</figcaption></figure></div>
<p>The judge's two favorite writers have seven times the violation rate of
the writer it ranks third. Kimi writes the most internally consistent
memos in the pool and gets no credit for it, because consistency isn't
what the score measures. (The judge is also Opus judging Opus at a flat
8.95 with almost no spread; I didn't measure self-preference properly,
so I'll only note the smell.)</p>
<h2 id="where-the-ontology-arm-struggles">Where the ontology arm struggles</h2>
<p>Two defect rows are weak, and the reason is worth more than the number.</p>
<p><code>no_risk</code> (45%) and <code>ignored_position</code> (50%) are <em>diffuse</em> properties.
A well-written memo doesn't keep its risk awareness in the Risk
section: it threads trigger levels, invalidators, and &quot;what makes us
buyers of the rest&quot; through the whole argument. Same for the position:
the good memos are position-aware in their bones. A bounded prose edit
cannot fully remove either property, and the extractor finds the
residue and builds objects from it. The seeding method works for
localized defects and degrades for diffuse ones. That's a limit of my
experiment, not a discovery about ontologies.</p>
<p>Extraction is the arm's load-bearing wall, and it will lie to you
politely if you let it. My first extraction prompt said &quot;extract what
the memo says&quot;. The extractor, holding the packet, quietly repaired
broken memos: it back-filled citation ids it recognized from the news
text and marked position-awareness the memo didn't have. Two defect
classes came back at 0% detection because the extraction layer had
fixed the defects on the way through. The fix was prompt-level brute
force (&quot;if the memo describes an event but does not cite its id, do
NOT add the id&quot;) plus measuring extraction stability across two
samples per memo (stable check-sets on 149 of 172). If you run checks
over an extraction, audit the extraction. Transcription is not repair.</p>
<h2 id="what-id-actually-take-to-production">What I'd actually take to production</h2>
<p>The toy maps onto any agent whose output is &quot;recommend an action from
evidence&quot;: support triage, code-review bots, medical intake, deal
memos. The objects rename; the shape survives. Claims with sources,
judgments with direction and evidence, actions that trace to
judgments, guards that watch the judgments. The checks are relations
between them, and every one is a boring pure function.</p>
<p>What the two arms are for, after running both: the judge is a smoke
detector for prose quality, and it's decent at that. The relation
checks are the instrument for the failures that matter, because they
catch the invisible class, they localize (<code>[tr1]</code>, not &quot;could be more
thorough&quot;), and they regression-test for free: a check that fires
today fires tomorrow, while a judge score drifts with the judge.</p>
<p>Build order if you're starting from zero: define the objects your
agent's output claims to contain, write the five relations that would
embarrass you most if they broke, and only then argue about rubrics.
The extraction pass costs one model call and the checks cost nothing.
My whole experiment, 80 generated memos, 90 seeded ones, 344
extractions, 516 judgments, came to about $35.</p>
<p>Harness, data, and every audit diff:
<a href="https://github.com/antithetical-labs/llm-research-evals">antithetical-labs/llm-research-evals</a>.</p>
]]></content:encoded>
  </item>
  <item>
    <title>Convergence you chose</title>
    <link>https://antithetical-labs.com/blog/convergence-you-chose/</link>
    <guid isPermaLink="true">https://antithetical-labs.com/blog/convergence-you-chose/</guid>
    <pubDate>Sun, 16 Aug 2026 00:00:00 GMT</pubDate>
    <description>Banning the slop patterns works perfectly. It just moves the model to a different set of patterns.</description>
    <content:encoded><![CDATA[<p><a href="https://antithetical-labs.com/blog/the-most-likely-page/">Last time</a> I measured what four models build when
the brief says nothing about design. They converge, the palette follows your
product category, and telling them to &quot;be distinctive&quot; strips the gloss without
moving the design anywhere.</p>
<p>So: what does move it. Two interventions, same briefs, same four models, five
runs each.</p>
<ul>
<li><strong>forbid</strong>: name the four commonest patterns and ban them. &quot;Do not use
gradients of any kind. Do not use a dark background with a single bright
accent colour. Do not use frosted-glass or blur effects. Do not use
fully-rounded pill-shaped buttons.&quot;</li>
<li><strong>direct</strong>: one sentence pointing somewhere. &quot;Take the visual direction from
acid design, futuristic, digital, high-energy.&quot;</li>
</ul>
<p>I said at the end of part 1 that one of these produces pages with no personality
whatsoever, and that it's the one I expected to work. It's the ban, and it does
exactly what I asked it to.</p>
<h2 id="the-ban-is-obeyed">The ban is obeyed</h2>
<div class="figure-row"><figure class="figure"><img src="https://antithetical-labs.com/img/chart-banned-vs-not.png" alt="Dumbbell chart of nine patterns before and after the ban; the four named ones collapse to zero while eyebrow labels and numbered markers rise" width="1333" height="734" decoding="async"><figcaption class="figure-caption">Every measured pattern, untouched against forbidden</figcaption></figure></div>
<p>The four named patterns land at 0%, 0%, 0% and 3%. One page in 39 slipped a
gradient through and that is the entire violation set. &quot;Models are bad at
negation&quot; is not true here, at least for concrete visual features you can name.</p>
<p>Nothing I didn't name lands anywhere near zero either, with one honest
exception: emoji fell to 5%, two pages in thirty-nine, without being mentioned.
Everything else unnamed sits between 15% and 74%.</p>
<h2 id="the-patterns-come-in-bundles">The patterns come in bundles</h2>
<p>Look at glow shadow, though. It drops 42 points and I never mentioned it.</p>
<p>That makes sense once you notice a glow needs a dark background to be visible,
and dark backgrounds <em>were</em> banned. But it turns out to be the general case, not
a coincidence:</p>
<div class="figure-row"><figure class="figure"><img src="https://antithetical-labs.com/img/chart-clusters.png" alt="Dot chart of the eight strongest pattern pairs, each above 0.4, with the within-brief values beside the pooled one" width="1404" height="711" decoding="async" loading="lazy"><figcaption class="figure-caption">Phi correlation between patterns, untouched pages</figcaption></figure></div>
<p>These features are not independent. A pill button and an eyebrow label have
nothing to do with each other visually, one is a button shape and the other is a
line of small caps, and they land on the same page 67.5% of the time. Their phi
correlation is 0.82, and all eight of the strongest pairs sit above 0.4.</p>
<p>The obvious objection is that this is the product category showing through: dev
tools are dark and glowy, back-office tools are rounded and light, so of course
things co-occur. Splitting by brief doesn't collapse it. The grey dots are each
brief's own value, 0.88 and 0.79 for pill and eyebrow, and the weakest of the
sixteen is 0.36, nowhere near zero.</p>
<p>So an attractor isn't a feature, it's a package. Cut one strand and you pull the
others, which is why a ban on four things reaches a fifth.</p>
<h2 id="what-you-land-on-instead">What you land on instead</h2>
<p>Ban the package and the model doesn't run out of ideas. It picks up a different
package and holds it just as tightly.</p>
<p>The two patterns that came through untouched are pure page furniture, an eyebrow
label over a heading and numbered section markers, and both went <em>up</em>. Meanwhile
every forbidden page is white, in both briefs, with a within-brief spread in background
lightness of 0.009 and 0.012 (standard deviation). Tighter than any other
condition in the experiment, and only the high-effort rerun of the same
prohibition beats it.</p>
<div class="figure-row"><figure class="figure"><img src="https://antithetical-labs.com/img/shot-forbid-vs-direct.png" alt="Eight landing pages: the top row white and sparse, the bottom row black with acid green and magenta" width="1840" height="744" decoding="async" loading="lazy"><figcaption class="figure-caption">Same brief, same four models, two kinds of instruction</figcaption></figure></div>
<p>Top row is the ban. They're competent and they're interchangeable. I can't tell
you which model made which, and neither could you.</p>
<div class="figure-row"><figure class="figure"><img src="https://antithetical-labs.com/img/chart-richness.png" alt="Strip plot of coloured elements per page across five conditions, lowest for banned patterns and highest for direction plus grounding" width="1277" height="598" decoding="async" loading="lazy"><figcaption class="figure-caption">Every page, both briefs, all four models</figcaption></figure></div>
<p>They're also the emptiest pages in the experiment: median 24 coloured elements
against 35 untouched.</p>
<p>The category doesn't vanish here either, it relocates. The prohibition names
background colour, so matching backgrounds prove nothing on their own. But under
the identical ban, on identical white pages, the dev tool still carries twice the
colour and two thirds the page length:</p>
<figure class="table-block"><div class="table-scroll"><table><thead>
<tr>
<th scope="col"></th>
<th class="align-right" scope="col">dev tool</th>
<th class="align-right" scope="col">back-office</th>
</tr>
</thead>
<tbody>
<tr>
<td>coloured elements</td>
<td class="align-right">36</td>
<td class="align-right">17</td>
</tr>
<tr>
<td>share of elements rounded</td>
<td class="align-right">0.13</td>
<td class="align-right">0.25</td>
</tr>
<tr>
<td>page height, px</td>
<td class="align-right">2636</td>
<td class="align-right">3913</td>
</tr>
</tbody>
</table></div></figure>
<p>Everything got quieter. Nothing got resolved.</p>
<h3 id="is-that-just-the-small-thinking-budget">Is that just the small thinking budget?</h3>
<p>Reasoning is pinned low across this experiment, which leaves an obvious reading:
a constrained model on a small budget takes the cheapest compliant path, and the
white page is laziness rather than a destination.</p>
<p>So I ran the identical prohibition again with effort raised. Same prompt, byte
for byte, one knob. It came out at 33 times the reasoning, a median of 2,060
tokens a page against 63, across 24 pages.</p>
<p>The palette doesn't move. Every page lands between 0.952 and 0.994, dark
backgrounds stay at 0%, and the banned patterns stay gone. What the thinking
buys is bulk: 84% more elements, 54% more colour, a fifth more page. The model
builds a more thorough version of the same white document.</p>
<p>It's the attractor, not the budget.</p>
<h2 id="what-one-sentence-of-direction-buys">What one sentence of direction buys</h2>
<p>Now the other half.</p>
<p>Before writing any markup, the directed models were asked to say what the
aesthetic actually is. Forty documents, median 717 words, written before a
single tag. I expected four different guesses at a vague phrase.</p>
<div class="figure-row"><figure class="figure"><img src="https://antithetical-labs.com/img/chart-grounding.png" alt="Bar chart of terms appearing across 40 grounding documents, with flyer and rave at 40 out of 40" width="1196" height="625" decoding="async" loading="lazy"><figcaption class="figure-caption">What the models say acid design is, before building</figcaption></figure></div>
<p>Every single document reaches for rave flyers. Thirty-nine of forty reach for
monospace, thirty-eight name Y2K, thirty-six name chrome and thirty-six name
magenta, and thirty-three place it in the nineties. Seven cite The Designers Republic by
name. One of Claude's runs goes further and lists David Rudnick, Bráulio Amado,
Studio Moross and the Pangram Pangram foundry.</p>
<p>They are not guessing. They are recalling the same movement:</p>
<blockquote>
<p>Acid graphics are the visual residue of three lineages colliding. First, rave
and jungle flyer culture of the early-to-mid nineties: photocopied,
over-saturated, illegible-on-purpose, printed on cheap stock in fluorescent
spot inks. Second, chrome-era 3D […]. Third, the anti-design / post-Swiss
reaction of the 2010s […]</p>
</blockquote>
<p>That's Claude. Here's GPT, unprompted by any of it:</p>
<blockquote>
<p>Start with a near-black base—charcoal, ink, or a subtly tinted ultraviolet
black—rather than pure black. Use a small set of synthetic accents at full
intensity: toxic lime, electric violet, hot magenta, signal cyan, and
occasional warning orange.</p>
</blockquote>
<p>Which explains why the direction works at all. I wasn't asking for invention. I
was naming a coordinate the training data holds densely, and four models walked
to it from four different starting points.</p>
<p>And you can watch them arrive. Untouched, the accent hue is all over the place:
across their ten runs each model reaches for three or four different hue
families, so there is no single &quot;favourite&quot; to report. All forty untouched pages
split green 15, purple 11, orange 11, magenta 3.</p>
<p>Directed, <strong>32 of those 40 pages land in one family.</strong> Claude 8 out of 10, kimi
and gemini 9, gpt 6. All four go 100% dark. Pages carrying a genuinely saturated
colour, above 0.25 max chroma, go from 8% untouched to 70%, and coloured
elements from 35 to 73.</p>
<p>The vendor fingerprint I spent half of part 1 documenting turns out to be about
a finger's pressure of instruction deep. Not all of it: Claude still put an
eyebrow label on 100% of its directed pages, exactly as it does untouched, while
its pill buttons dropped from 100% to 10%. Gemini still refused eyebrows
entirely. Direction overrode the palette and left the component vocabulary
alone, which is exactly the split part 1 predicted.</p>
<p>One thing the numbers hide: that family is magenta, and the pages look green. My
extractor takes the highest-chroma element as the accent, and on an acid palette
the magenta beats the acid green that dominates the page.</p>
<h2 id="does-the-grounding-step-earn-its-turn">Does the grounding step earn its turn?</h2>
<p>Everything above is the grounded pipeline. The obvious question is how much of it
the grounding turn is doing, so: <code>direct1</code> gets the identical sentence and goes
straight to HTML, no writing-it-down step.</p>
<figure class="table-block"><div class="table-scroll"><table><thead>
<tr>
<th scope="col"></th>
<th class="align-right" scope="col">sentence only</th>
<th class="align-right" scope="col">sentence + grounding</th>
</tr>
</thead>
<tbody>
<tr>
<td>coloured elements</td>
<td class="align-right">56</td>
<td class="align-right">73</td>
</tr>
<tr>
<td>DOM elements</td>
<td class="align-right">142</td>
<td class="align-right">197</td>
</tr>
<tr>
<td>pages 100% dark</td>
<td class="align-right">78%</td>
<td class="align-right">100%</td>
</tr>
<tr>
<td>run-to-run spread in background</td>
<td class="align-right">0.052</td>
<td class="align-right">0.018</td>
</tr>
</tbody>
</table></div></figure>
<p>Nearly three times tighter run to run, and a third more on the page.</p>
<p>The single clearest thing the grounding turn does is visible per model. Given
just the sentence, GPT went dark on <strong>one of its ten pages</strong>; the other nine came
back light. Given the turn to write down what acid design is first, it went dark
on all ten. It isn't that GPT couldn't do acid design. It's that &quot;take the visual
direction from acid design&quot; alone didn't reliably get it there, and four hundred
words of its own prose did.</p>
<p>Peak saturation went slightly <em>down</em> with grounding, 0.276 to 0.256. Thinking
first made the pages more considered, not more extreme.</p>
<h2 id="the-model-is-going-to-converge-either-way">The model is going to converge either way</h2>
<p>Every condition here converges. Nothing said, banned, directed: each lands the
four models in more or less the same place as each other, and the tightest
convergence of the lot belongs to the ban.</p>
<p>So convergence was never the problem. The question is only ever who picked the
destination.</p>
<p>Banning strips a layer. You peel off the package the model reaches for first and
it settles onto the next one down, which nobody chose and which happens to be a
white page with an eyebrow label. Direction names the destination, and because
the destination is something the training data holds richly, one sentence is
enough to move four models off four different defaults.</p>
<p>Which is why &quot;make it less generic&quot; has never worked for me, and I now know what
it costs rather than just that it doesn't work. It's subtraction with no target.
The slop leaves, exactly as instructed, and something equally fixed arrives to
take its place.</p>
<h2 id="what-this-doesnt-show">What this doesn't show</h2>
<ul>
<li><strong>One direction, tested once.</strong> Every directed page here aims at acid design,
and the grounding documents are the reason it worked: the models already hold
a dense, shared account of it. A direction the training data holds thinly
would be the real test and I haven't run it.</li>
<li><strong>&quot;High effort&quot; is not one treatment.</strong> At the same setting kimi spent 5,690
reasoning tokens a page and gpt spent 336. The comparison holds across all
four, but the knob plainly means different things to different vendors.</li>
<li><strong>Reasoning is pinned low everywhere except that one A/B</strong>, so the direction
results have not been checked against a bigger thinking budget.</li>
<li><strong>The format bans Tailwind</strong>, same as part 1, same caveat.</li>
<li><strong>39 forbidden pages, not 40.</strong> One cell never completed, twice, and the
harness raises rather than writing a partial row, so the failure is described
in the code and not in the published <code>runs.jsonl</code>.</li>
</ul>
<p>Harness, all 263 pages, the grounding documents and every measurement:
<a href="https://github.com/antithetical-labs/llm-frontend-evals">antithetical-labs/llm-frontend-evals</a>.</p>
]]></content:encoded>
  </item>
  <item>
    <title>The most likely page</title>
    <link>https://antithetical-labs.com/blog/the-most-likely-page/</link>
    <guid isPermaLink="true">https://antithetical-labs.com/blog/the-most-likely-page/</guid>
    <pubDate>Wed, 12 Aug 2026 00:00:00 GMT</pubDate>
    <description>Four models, 240 landing pages, and what they reach for when nobody tells them what to do.</description>
    <content:encoded><![CDATA[<p>I gotta confess: I have no design training and a pretty utilitarian taste, so
when I need a UI I just ask a model and then squint at it until I am satisfied.
Lately I've been squinting more. The output has a sameness to it that I can spot
and couldn't describe, which is annoying, so I measured it.</p>
<p>Spoiler for the whole post: the pages are good. Genuinely better than what I'd
put together by hand. They're just all the same page. Which shouldn't surprise
anyone, because a model reaching for the most likely next token is going to
reach for the most likely design, and &quot;be distinctive&quot; is not a direction you
can point that at. Distinctive isn't a thing you can be. It's a thing you can be
<em>towards</em>.</p>
<h2 id="the-setup">The setup</h2>
<p>Four models, all through OpenRouter on one key, mostly so I wouldn't have to
babysit four billing dashboards:</p>
<ul>
<li>Claude Opus 5</li>
<li>GPT-5.6-sol</li>
<li>Kimi K3</li>
<li>Gemini 3.1 Pro</li>
</ul>
<p>Every model gets the same two briefs, and these stay fixed for the whole series:</p>
<ul>
<li><strong>Streamforge</strong>, a dev tool. Live-streaming platform where developers
broadcast themselves building software with AI assistants. Viewers watch the
prompts, the model's output and the code evolve in real time, and can fork any
stream's repo.</li>
<li><strong>Backlot</strong>, a back-office tool. Brand-deal contracts, invoices, sponsor
deadlines and per-platform payouts for full-time content creators who are not
finance people.</li>
</ul>
<p>Two of them, rather than one, to separate what a model always does from what the
product pulls out of it. Short version: the palette flips, the components don't.</p>
<p>Output format is pinned everywhere: one self-contained HTML document, all CSS in
a single <code>&lt;style&gt;</code> block, no frameworks, no build step, and a fixed set of
sections. Six prompting conditions, five runs each. 240 cells, 239 completed,
$44.39.</p>
<p>Harness, raw pages and every measurement:
<a href="https://github.com/antithetical-labs/llm-frontend-evals">antithetical-labs/llm-frontend-evals</a>.</p>
<p>This post is the first two conditions: saying nothing, and saying &quot;avoid generic
AI aesthetics.&quot;</p>
<h2 id="what-does-a-model-do-when-you-say-nothing">What does a model do when you say nothing?</h2>
<p>The briefs don't mention colour, type or layout. Not once. Here's the background
lightness of all 40 untouched pages:</p>
<div class="figure-row"><figure class="figure"><img src="https://antithetical-labs.com/img/chart-genre.png" alt="Strip plot of background lightness for 40 pages, clustered dark for the dev tool and near-white for the back-office tool" width="1175" height="556" decoding="async"><figcaption class="figure-caption">Every page, no design instruction given</figcaption></figure></div>
<p>So it's not a dark mode preference. It's genre retrieval: dev tool means dark,
back-office tool for non-technical people means light and editorial. The model
looks up what your category is supposed to look like and ships that. Confident
as hell about a product it met ninety seconds ago.</p>
<p>The copy converges too. Nineteen of twenty dev-tool headlines open with &quot;Watch&quot;,
and three vendors independently wrote a sentence within one word of &quot;Watch
software get built, prompt by prompt.&quot; That one's on me though: my brief says
&quot;Viewers watch the prompts&quot;, so the word was already sitting there. The
back-office headlines, no such verb in their brief, share zero words across
models. Still funny how far a single word drags the copy behind it.</p>
<h2 id="do-the-four-models-actually-differ">Do the four models actually differ?</h2>
<p>Genre sets the palette. Underneath that they're pretty different. Coral is the
model's own rate, grey is the four-model average behind it:</p>
<div class="figure-row"><figure class="figure"><img src="https://antithetical-labs.com/img/chart-fingerprint.png" alt="Four panels, one per model, showing each model&#39;s rate for eight patterns against the four-model average" width="1409" height="547" decoding="async" loading="lazy"><figcaption class="figure-caption">Which patterns each model reaches for on its own</figcaption></figure></div>
<p>Gradients are universal, ~90% everywhere. Everything else splits:</p>
<ul>
<li><strong>Gemini just doesn't do eyebrows.</strong> That little uppercase kicker above a
heading: 7 of its 60 pages. The other three run 55 to 58 out of 60.</li>
<li><strong>Gemini won't make a pill button either.</strong> Zero out of ten. Claude and Kimi
are at 100%. Somewhere in that post-training there is an opinion about
border-radius.</li>
<li><strong>Claude cannot leave a surface alone.</strong> Gradient, frosted glass and pill on
100% of its untouched pages.</li>
<li><strong>Kimi is the only one that reaches for a serif headline</strong>, and only sometimes
(3 of 10).</li>
<li><strong>Page weight varies 3x.</strong> Gemini's median page is 88 DOM elements. GPT's is
261.</li>
</ul>
<p>Which is the actually useful bit here. If you switch models and the output feels
different in a way you can't name, this is it. The palette follows your product
no matter who you ask. The component vocabulary is your vendor's.</p>
<p>So there isn't one most likely page. There are four, one per vendor. Each of
them still only has the one, though.</p>
<h2 id="is-the-slop-checklist-still-right">Is the slop checklist still right?</h2>
<p>There are real lists for this, not just vibes.
<a href="https://github.com/pbakaus/impeccable">pbakaus/impeccable</a> ships 59 detector
rules and opens by naming the tells directly: &quot;Inter for everything,
purple-to-blue gradients, cards nested in cards, gray text on colored
backgrounds, the rounded-square icon tile above every heading.&quot;
<a href="https://www.mindstudio.ai/blog/claude-design-avoid-ai-slop-design-system">MindStudio's design-system post</a>
adds blue or indigo accents, rounded-full pills, 50px padding everywhere, drop
shadows, glassmorphism and gradient hero sections.</p>
<p>Six of those I can measure. Scorecard:</p>
<div class="figure-row"><figure class="figure"><img src="https://antithetical-labs.com/img/chart-tells.png" alt="Bar chart of pattern frequency, with checklist-named patterns in a darker shade" width="1270" height="522" decoding="async" loading="lazy"><figcaption class="figure-caption">The published checklists, scored against 40 untouched pages</figcaption></figure></div>
<p>On texture they're dead right. Gradient 90%, pill 72%, glassmorphism 62%, shadow
or glow 57%. No notes.</p>
<p>On colour they're stale. &quot;Blue or indigo as the primary accent&quot; lands on 28% of
pages. Green and acid are the biggest group at 38%, red and orange next at 32%.
Purple-to-blue was probably correct whenever those lists were written. Acid
green has moved in since.</p>
<p>Which is the structural problem with ban lists. The mode moves. Whatever was
most likely last year got written down, and by the time it's a rule the models
have drifted somewhere else. You're always describing the previous convergence.</p>
<p>The font one I can't score fairly, and I want to be upfront about it: my output
format required a system font stack, so I discouraged Inter myself. 30% of pages
put it in the stack anyway.</p>
<p>Then there's the half nobody names. Eyebrow labels 70%, dingbats 60%, numbered
section markers 52%. Not on either list.</p>
<p>The dingbat row is my favourite. 60% of untouched pages have a <code>✓</code> or a <code>✦</code> in
the markup, mostly ticking off feature bullets. Real emoji are way rarer at 22%.
I nearly shipped these as one number, which would have published the
checkmark's frequency under the rocket's name.</p>
<p>Here's one page carrying nearly all of it:</p>
<div class="figure-row"><figure class="figure"><img src="https://antithetical-labs.com/img/shot-exemplar.png" alt="Screenshot of a dark landing page with eyebrow labels, pill buttons, a glass card and a glowing accent" width="1212" height="838" decoding="async" loading="lazy"><figcaption class="figure-caption">Nearly every measured pattern, on one page</figcaption></figure></div>
<p>Three eyebrows (&quot;BUILT FOR PEOPLE WHO SHIP&quot;, &quot;HOW IT WORKS&quot;, &quot;OPEN BETA&quot;), pill
buttons, a glass card over a gradient, a glow on the accent, three numbered
steps, checkmarks down the feature list. Accent is orange, not indigo. Run it
past a detector looking for purple-to-blue and it comes back clean.</p>
<p>As my own sidenote, I originally recorded <em>zero</em> rounded corners across all 239
pages. Great line. Completely wrong: I was taking the median corner radius over
every large element, and only ~10% of large elements are rounded, so the median
never sees them. Rounding is sparse, not absent.</p>
<p>That one had a second life, which is the more useful lesson. The classifier in
the next section tests for &quot;zero border-radius&quot;, and it was testing it with that
same median. So the check passed on every page in the run, and the bucket that
was supposed to mean <em>broadsheet</em> quietly became a catch-all holding 65 pages,
some of them 45% rounded. A dead measure doesn't just give you one wrong number.
It gives you every number computed downstream of it, and those look fine.</p>
<h2 id="does-make-it-distinctive-do-anything">Does &quot;make it distinctive&quot; do anything?</h2>
<p>Obvious next move: just tell it not to. The folk remedy, in the wording people
actually use, is &quot;avoid generic AI-generated aesthetics, make it distinctive.&quot; I
appended exactly that and ran the 40 pages again.</p>
<div class="figure-row"><figure class="figure"><img src="https://antithetical-labs.com/img/chart-nudge.png" alt="Dumbbell chart of pattern rates before and after the nudge, beside a stacked bar of which looks the pages land in" width="1445" height="613" decoding="async" loading="lazy"><figcaption class="figure-caption">What the nudge moves, and what it does not</figcaption></figure></div>
<p>The polish comes off hard. Pill buttons drop 57 points, frosted glass 45.</p>
<p>The right panel needs a word of setup. Anthropic ships a <code>frontend-design</code> skill
with Claude that names three looks AI design falls into: cream-and-serif,
near-black-with-one-bright-accent, and broadsheet. I classified every page
against those three.</p>
<p>The nudge trades between them and doesn't get you out. Dark-and-neon halves, 25
pages down to 13, and broadsheet picks up 10 of the 12 it loses. The number
sitting outside all three barely moves: 14 untouched, 13 nudged.</p>
<p>That list is missing one, though. Every near-white back-office page is
hairline-ruled and dense but has rounded cards, which is the opposite of
broadsheet, so it matches none of the three. That's just the ordinary light SaaS
look, and it's most of what the second brief produces.</p>
<p>Two things go <em>up</em>. Numbered markers +30 points, eyebrows +5, median DOM
elements 124 to 173. Told to be distinctive, every model added more stuff. &quot;Be
distinctive&quot; parses as &quot;add something.&quot;</p>
<p>Same model, same brief, one sentence of difference:</p>
<div class="figure-row"><figure class="figure"><img src="https://antithetical-labs.com/img/shot-bare-vs-nudge.png" alt="Two dark landing pages side by side, the second with square buttons and mono type instead of pills and a glass card" width="1848" height="718" decoding="async" loading="lazy"><figcaption class="figure-caption">Claude, same brief, one sentence of difference</figcaption></figure></div>
<p>Still dark. Still one bright green accent. Buttons went square, glass card gone,
type went mono, and there's a strikethrough gimmick in the headline. It looks
different. It doesn't look like a different decision, it looks like the same
decision with the gloss sanded off.</p>
<p>Which is about what I'd expect. Telling a model what not to do is a constraint,
and a constraint narrows the distribution without moving it. You get the same
design with fewer features on it. Removal is not direction.</p>
<p>And this is the whole thing, I think. None of these models is bad at design.
They build a clean, competent, well-spaced page every single time, which is more
than I manage. What they can't do is be <em>distinctive</em> on request, because
distinctive means &quot;away from the mode&quot; and nothing in the prompt says which way.
Ask for the most likely page and you get it. Ask for a less likely page and you
still get the most likely one, minus the pill buttons.</p>
<h2 id="what-this-doesnt-show">What this doesn't show</h2>
<ul>
<li><strong>Reasoning is pinned low on every model.</strong> At default settings one model
burned 16k tokens thinking before writing a single tag and another took under
a minute. Uncontrolled variable, not a feature.</li>
<li><strong>Plain HTML and CSS only</strong>, no Tailwind, no React. Utility classes push
colour and spacing into class names and a framework's default scale, which is
the exact thing I'm measuring. Most real frontend work happens in Tailwind, so
this is a deviation from practice and I'd rather say so.</li>
<li><strong>Two briefs separate genre from model. They don't characterise a genre.</strong>
Everything above about dev tools going dark rests on exactly one dev tool.</li>
<li><strong>One cell never finished</strong>, dying identically twice at 16,380 bytes with
three reported completion tokens. Left it as a gap.</li>
</ul>
<p>Next up, the obvious follow-up: if vague negation does nothing, does a specific
one work? I banned the four patterns by name, and separately gave one sentence
of actual direction to aim at. One of those produces pages with no personality
whatsoever. It's the one I expected to work.</p>
]]></content:encoded>
  </item>
</channel>
</rss>
