Ploy Switches Production AI from Opus 4.8 to GPT-5.6 Sol: Time Cut by More Than Half, Cost Down 27%, No Quality Loss
- Ploy switched the default model behind its website-building AI agent from Claude Opus 4.8 to GPT-5.6 Sol, which launched that same day
- Its own benchmarks show: for the same kind of website-building task, time dropped from 8:00 to 3:42, cost fell from $3.06 to $2.22, and the visual score rose from 0.936 to 0.970
- During migration, they found GPT-5.6 invents default values for tool-call parameters it doesn't need (like offset: 0), which at one point silently caused 52% to 64% of file reads to return empty content
- OpenAI's prompt caching mechanism is completely different from Anthropic's; after redesigning the cache layering with a workspace-level cache key, the first-call cache hit rate went from 0% to 83.7%
- The team also found their own eval framework had a hidden bias toward the old model it was used to, so the framework itself had to be fixed before its results could be trusted
Ploy Switched Its Default Model to GPT-5.6 Sol
On July 9, 2026, AI website-building product Ploy switched its agent's default model from Claude Opus 4.8 to GPT-5.6 Sol, which OpenAI released that same day.
Switching models turned out to be far more complicated than swapping an API endpoint. Ploy's agent has to plan pages, read the codebase, write components, generate images, screenshot its own output, and then judge whether it's actually done. Moving this whole pipeline from Claude to GPT-5.6, the team discovered — one failed eval after another — that what gets treated as "the model" is actually each provider's own set of behavioral habits, and the whole stack had quietly grown into a shape built around the old model.

Below is a sample from its brand-page-rebuild eval suite, where the agent has to redo a brand homepage to match a reference design:
| Average per completed build | Claude Opus 4.8 (n=11) | GPT-5.6 (n=10) |
|---|---|---|
| Cost | $3.06 | $2.22 |
| Time | 8:00 | 3:42 |
| Input tokens | 2.60M | 1.70M |
| Output tokens | 33.0K | 17.1K |
| Visual score | 0.936 | 0.970 |
GPT-5.6's code is leaner. In one paired sample, Opus generated a globals.css with 17,957 characters and 174 CSS variables (large chunks of the color scale went unused), while GPT-5.6 used only 2,508 characters and 45 variables to render a comparable — sometimes better — page.
Check Whether the Eval Framework Is Fair Before Judging Which Model Is Stronger
Before comparing two models' scores head-on, you have to ask one thing first: is the eval framework itself fair? Ploy ran the same eval suite across both model families, and the most surprising finding was that the framework had already been tuned around the old model — without the team realizing it.
On the first cross-model run, the bias showed up in a few specific places:
- The tool-call budget was set around Opus's sequential calling style. GPT-5.6 fans out a bunch of calls in parallel, blowing through the budget even on tasks it actually got right.
- The eval runner didn't support batch file reads — a move Opus rarely used but GPT-5.6 used constantly.
- One dataset was missing a
minScorethreshold and silently inherited the default value of 1.0. So a GPT-5.6 hero section that scored 0.98 got marked "failed," and an Opus task that passed every individual check also counted as a "failure." Both were defensible design directions — what tripped them up was an invisible threshold.
Tracing this round's raw failures back, about a third of them turned out to be caused by the eval framework's assumptions, not actual model behavior — and the two models didn't trip over them equally.

The takeaway is direct: when evaluating a challenger model, read through the traces line by line before trusting the pass rate — otherwise you're grading the new model on "how much it resembles the old one."
Design Style: Clean and Attractive, but Prone to Sameness
Ploy's overall read on GPT-5.6's design ability: it's very good at clean, modern layouts with tidy grids, but left unguided, it converges toward that look every time. Under the old eval framework built for Opus 4.8, GPT-5.6 tended to ignore existing design systems and produce pages that were crisp and restrained, but unmistakably generic at a glance.

How to guide it toward world-class brand fidelity is a topic for another post. This is just noting a real capability trait — not really a pitfall.
The Model Invents a Fake Value for Parameters It Doesn't Need
Ploy's code tool has 25 top-level parameters, of which only action is required — the rest are optional. Claude sends only the two or three it actually needs and omits the rest. GPT-5.6 fills in all 25 every single time, inventing a plausible-looking value for whatever it doesn't need: offset gets 0, timeout gets 120000, siteId gets a UUID that's all zeros.
Here's the crux: at the interface level, there's no way to tell an invented value apart from a real one. offset: 0 looks like a perfectly normal parameter, so Ploy's file-read implementation took it at face value — which meant 52% to 64% of GPT-5.6's file reads silently came back empty. But the tool returns success: true either way, so the model has no idea it just read an empty file. It just quietly keeps doing worse work, piling on more and more calls.
Over three days of production traffic, the share of code(read) calls carrying all 25 parameters was different enough that it barely looks like the same tool:
In absolute numbers: all 6,635 of GPT-5.6's calls came fully loaded; only 4 of Opus 4.8's 2,898 calls did; and none of Sonnet 5's 1,933 calls did.
Prompting can't fix this. Ploy tried every way it could think of to "talk it out of it," and all of them failed outright:
Still filled in all 25.
Still filled in all 25.
strict modeSame behavior either way (per Ploy's own testing), and using it also requires stripping pattern, format, and array bounds validation out of every schema — an even bigger cost.
This is a behavior baked into how the model generates function calls at a fundamental level — instructions can't talk it out of it, so the only option is to design around it.
The fix that actually worked was a schema transformation at the provider boundary: only for OpenAI-family models, rewrite every optional property as "required but nullable" (using anyOf: [T, null]), so the model has a proper way to explicitly say "I'm not using this." Then, at the one checkpoint every tool call passes through, strip those nulls back out — the tool side doesn't need a single line changed. The net effect: the model sees a schema that lets it be honest, and the tool still gets the same input it always got.
// Before: 25 keys, every one carrying an invented value
{ "action": "read", "file_paths": [...], "offset": 0, "timeout": 120000, ... }
// After: 25 keys, 4 real values, 21 explicit nulls (stripped before the tool runs)
{ "action": "read", "file_paths": [...], "offset": null, "timeout": null, ... }
The Two Providers' Prompt Caching Are Fundamentally Different Designs
On the surface both providers call it prompt caching, but underneath that one word "caching" sit two completely different architectures. This is the single thing in the migration that needed the most care: before it was set up correctly, GPT-5.6 looked about 50% more expensive than Opus — and that extra cost came entirely from Ploy's own cache configuration, nothing to do with model pricing.
Ploy's prompt starts with a roughly 29K-token static prefix (tool schemas plus the core system prompt) that's identical in every single conversation. How the two providers cache that prefix is worlds apart:
cache_control. That 29K static prefix is shared org-wide — any conversation, any workspace draws from the same copy, and there's no need to worry about how many requests per minute it can handle. The hit rate stays steady at 92% to 96%; caching is basically invisible.OpenAI's official mechanism is explicit: mark with prompt_cache_breakpoint, paired with a required prompt_cache_key. The key is exactly where the design really forks, because it's part of the cache's identity. Same prompt, different key, and the hit rate is 0. Each key maps to one cache node, and that node can handle roughly only 15 requests per minute — go over that, and OpenAI routes the overflow to other, cold cache nodes.
prompt_cache_key is like a reserved-seat tag at a library: people holding the same tag get seated in the same section and can pick up right where they left off next time. Same key means sharing the same batch of cache entries; change the key, and it's like switching sections — none of the previously stored cache carries over.
So "whether to enable caching" turns into a real design decision: what level do you bind this key to? The three approaches each play out differently:
A new conversation never hits the shared prefix — first-call hit rate is 0% (Ploy hit this pitfall in practice, and it was expensive).
All requests hash to the same cache node, and production traffic instantly blows through the 15-requests-per-minute budget; requests get bounced to cold, uncached nodes, and you're back to misses.
All conversations within the same customer workspace share cache entries, and the traffic pressure on any single key stays low.
What Ploy ultimately shipped was a workspace-level key: requests get hashed to a throughput-limited cache node, which effectively gives every customer workspace its own dedicated lane:
The key alone wasn't enough — Ploy also split the system prompt into three tiers of breakpoints, each handling one job, aligned with the structure it uses for Anthropic:
After the fix, GPT-5.6's per-suite eval cost dropped below Opus's. Every dollar of the price gap the team had been staring at turned out to be a misconfigured cache — nothing to do with model pricing.
There's also one structural limitation that can't be worked around: on OpenAI, sharing that static prefix across workspaces just isn't structurally possible. Anthropic can share it because its cache is partitioned by organization, not by key. On GPT-5.6, once a given workspace's cache sits idle and expires, the next call has to pay for a cold 29K write again — about $0.18. That's a real cost, but it's bounded and predictable.
The Previous Turn's Reasoning Record Can Suddenly Go Missing — One Flag Fixes It
The last pitfall is the shortest one, but it can genuinely break a conversation. By default, GPT-5.6's Responses API stores the previous turn's reasoning as a "server-side item reference": the server keeps a numbered record, and the next turn just references that number. Ploy's conversations started intermittently throwing mid-conversation errors: Item 'rs_...' not found.
A server-side pointer is like handing you a pickup receipt instead of the item itself. Once the receipt expires, you can't claim your goods anymore. store: false is the equivalent of handing the goods straight to you — no more worrying about an expired receipt.
rs_...), and the next turn just queries that ID. Once the ID expires, it throws Item 'rs_...' not found.store: false — tells the SDK to request "encrypted reasoning content" instead, turning reasoning replay into a self-contained data blob rather than a pointer reference to server-side state; otherwise it can throw Item 'rs_...' not found mid-conversation
One more thing cost Ploy an extra afternoon of debugging: once the round-trip carries server-side reasoning state, the effective upstream prompt can still change — even if the bytes you're sending strictly only append, with not a single one touched.
After the Migration: The Final Scorecard and a Checklist for Similar Teams
With all four pitfalls patched one by one, the numbers promised up front held up: about 2.2x faster, 27% cheaper, visual score flat-to-slightly-up; after reconfiguring the cache, first-hit rate went from 0% to 83.7%, missed-cache input tokens dropped 28%, and per-suite cost fell below Opus; after the schema fix, empty file reads hit zero and tool calls dropped by about 30%.
These four steps have a required order — fix the framework first, or you can't trust any of the numbers that follow:
minScore defaulting to 1.0 were lurking underneath. About a third of the first round's failures turned out to be the framework's fault, not the model's.anyOf: [T, null] at the provider boundary, then strip the nulls before execution.prompt_cache_breakpoint plus a prompt_cache_key, with the key bound per workspace, and the system prompt split into three tiers, A / B / C. First-hit rate went from 0% to 83.7%.store: false, replacing the server-side pointer with an encrypted, self-contained data blob, fixing the Item 'rs_...' not found error.If you're comparing the cost of two models and one of them still has a cold cache, you're comparing your own configuration, not the models.Ploy's official engineering blog
Swapping an AI's Brain: The Hard Part Isn't Plugging in a New API → It's Rewiring Every Hidden Difference in Tool Parameters, Caching, and Reasoning Records
AI website-building product Ploy switched its agent's default model from Claude to GPT-5.6 — faster and cheaper, but the migration hit four invisible pitfalls along the way. One page, with charts, covers it all.
↓ Read it in one page · includes one animated chart
Ploy is an AI website-building product: tell it you need a site, and it sends out an AI agent (a self-directing AI program) to plan the pages, write the code, generate images, and even screenshot its own work to check if it's done. On July 9, 2026, it switched this agent's default "brain" from Claude Opus 4.8 to GPT-5.6 Sol, which OpenAI released that same day.
Same agent, same dev toolkit — only the default brain changed. In Ploy's own benchmark, having the agent rebuild a brand homepage to match a reference design, on average:
Cost $3.06
Visual score 0.936
(out of 1.0 — the closer to the reference design, the higher)
Cost $2.22 (27% cheaper)
Visual score 0.970
Same job, more than half the time saved
The team also found GPT-5.6 has a quirk: left unguided, its designs lean clean but generic, and it tends to ignore the existing brand style (this is a capability trait, covered more in the full article). But what actually breaks results are four other invisible pitfalls. The sneakiest one: it quietly reads empty files and thinks it succeeded.
GPT-5.6 has a habit: every time it calls a tool, it fills in every parameter field, and invents a plausible-looking value for whatever it doesn't need. The trouble is, at the interface level, there's no way to tell an invented value from a real one.
Converting "about 2.2x faster" into how long the same website-building task actually takes to wait for:
Taking over from Claude
just keeps reading it again and again
still fills all 25
the model honestly fills null, then it's stripped before execution.
these four things all need rewiring:
- × Eval framework secretly favors the old model
- × How tool parameters get filled
- × How repeated content gets billed cheaply
- × How the last turn's thinking gets stored
→ ③ Rebuild cache → ④ Make reasoning self-contained
it's rewiring every old habit hiding in the corners.
Members can copy or download the full Markdown, and let their AI subscribe and ask.
Found this useful? Leave your email — every new explainer, the moment it's out. Unsubscribe anytime.


