We asked Kimi K3 to review its own work
It caught design defects our automated checks couldn’t — and shipped one they caught that it missed.
K3 writes a self-contained web page. We render it in a real browser at three viewport widths and hand the screenshots back to the same model as image input. It looks at what it built, says what’s wrong, and revises. One model closes that loop; every other model on DigitalOcean’s Serverless Inference would need a handoff to a separate vision model.
What to watch
Each round produces three screenshots and a “What it saw” list. That list is the point. Iteration 1 is just a first draft — the interesting part is what the model notices about its own render on iteration 2 and after. Real example from a recorded run:
“the headline’s longest line ended ~25% from the panel’s right edge and the paragraph lines ~16% short, leaving the darkest stretch of the gradient with no text over it”
A model describing a defect in its own output to within a few percent of panel width, then fixing it. Every such observation we spot-checked was correct.
Each iteration takes two to five minutes — K3 runs with thinking always on at maximum effort, and there is no way to turn that off. The endpoint is also returning 429 Platform overloaded often since the open weights landed, which the client retries with backoff, so a run can stall before it starts. Live runs are rate limited; the Lab page has the recorded set.
What we measured
Where it was good
- Solved a vision-only problem without looking. Asked to put readable text over a gradient running
#fdfdfdto#0b0f1a, it reasoned “classic solution: a scrim” on turn one and built a counter-gradient one — 0.68 alpha over the light end fading to 0.30 over the dark — so contrast stays even across the width. - Reads renders precisely. Spatial observations quantified to a few percent of container width, correct on every one we checked.
- Found a defect our rubric missed. A masthead at
70rembeside a headline column at62rem, left edges not aligned. We had no check for that until it told us. - The vision is genuinely grounded. Verified against hand-constructed images whose contents were known by construction, not stock photos it could guess about.
Where it fell short
- It declares victory on unfinished work. It shipped an 11.52px label at 390px wide, on a brief that explicitly required readability at 390px, while reporting “no functional defects in this render”. Moonshot list “excessive proactiveness” as a known trait; this is what that looks like measured.
- It stops early on easier work. Two iterations on the pricing table with requirements still unmet.
- It is slow. Three to five minutes per iteration. Thinking is always on at maximum effort and cannot be turned off.
reasoning_effortis not monotonic. In our probehighproduced more reasoning thanmax. Don’t build a cost model on it.
The part we did not expect
We built an automatic rubric to score each render — overflow, clipping, contrast, overlap, alignment. It was wrong seven times, and every time in the direction that looks like a discovery. It called legible white-on-blue buttons invisible, because a gradient has no single background colour to compare against. It flagged every three-column layout as misaligned. It reported a heading as overlapping itself, because a nested <span>’s box sat three pixels below its parent’s.
Each of those is fixed now, with a regression test. But the pattern is the finding: on rendered interfaces, the model read its own output more reliably than the automated checker we could write. So the rubric on the Lab page is a sanity check, not the scoreboard — the critiques are the evidence.
How it works
The loop
- K3 emits one HTML file, all CSS and JS inline.
- Playwright renders it at 1440, 768 and 390px via
setContent, never written to disk. - Screenshots are downscaled to 1280px and re-encoded as JPEG, because image tokens dominate the cost.
- They go back as base64 data URIs alongside the current source.
- K3 critiques the render and returns a revision.
Containment
The renderer executes model-authored JavaScript, so every outbound request from the page is aborted, each render gets a fresh browser context, Chromium’s sandbox stays on, and there are hard timeouts because an infinite loop is a plausible output. Previews in your browser run in a sandboxed iframe with a null origin. The page cannot phone home, and the tests assert that rather than assuming it.