← Writing

Core Swap · Part 4 of 5

Agent Fluency

Brief

I will never read Terminal Delight’s emulator core. Agents will, every time something about pictures, history or replies needs to change, so whether an agent can work on a core fluently carried a quarter of the weight in choosing one, and nothing had measured it.

With two cores left, rio-vt and libghostty-vt, six fresh agents got the same small job, three per core: build a terminal, feed it a recorded picture, report where the picture and the cursor landed and what the terminal said back, then feed it 18,000 lines of text and count the history it kept. The right answers were written down before any agent reported, and every agent’s program was re-run rather than taken at its word.

All three rio-vt agents passed all four checks. All three libghostty-vt agents failed the same one. Its history setting is documented as lines and counts bytes, so asking for 10,000 lines kept 620. They also used about 37% more tokens and 43% more tool calls getting there.

Story hands the same flat-pack kit to six assemblers. Technical has the task, the sealed answers, every agent’s result and the build-quality checks.

Portfolio

Story

Same kit, six assemblers

I will never read Terminal Delight’s emulator core, the library that turns a program’s output into the grid of cells a terminal draws. I said so, in nearly those words, when I weighed what mattered in choosing a new one, and it made the second-heaviest weight a question nobody had measured: can an agent work on this core fluently? Agents are the ones who will change it, every time something about pictures, history or replies needs to move.

Two cores were left by then: rio-vt, the core inside the Rio terminal, and libghostty-vt, Ghostty’s. Building the test harnesses for both had already turned up surprises on the Ghostty side, among them a Zig compiler and a copy of Ghostty’s source needed at build time, a picture decoder the program using it has to bring along, and a history setting counted in the wrong unit. My reply was “The surprises from ghostty are unsettling”. Still, one agent’s experience of a library is an anecdote, and the real question was whether the surprises belonged to that agent or to the kit.

So six agents who had never seen either core got the same kit and the same instructions, three per core, the way you might hand the same flat-pack wardrobe to three people and watch where each one stops. The task had four steps. Make a terminal of a set size that keeps 10,000 lines of history. Feed it the recorded bytes of kitten icat drawing one picture. Report where the picture and the cursor ended up and what the terminal wanted to say back to the program. Then feed a fresh terminal 18,000 lines of ordinary text and count the rows it was holding. They could read the library’s own documentation and source and nothing anyone had written about it elsewhere, and they wrote down every surprise as they hit it, in order. What counted as right went into a file before any of them reported: one picture 64 cells by 19 at the top left, the cursor on row 19, the picture protocol’s OK among the replies, and 10,040 rows within 2%. Each check was then re-run from the agent’s own program instead of taken from its report.

All three rio-vt agents passed all four steps. All three libghostty-vt agents passed three and stopped at the same one, like three people stuck at the same page of the same instructions. Its history setting is documented, in the Rust documentation and in the C header underneath it, as a number of lines. It counts bytes. Asking for 10,000 kept 620 rows, and no setting takes a line count at all. Two agents reported 620. The third worked out the byte budget by halving its way toward the answer and reached 10,295 rows, just outside the 2% the check allowed.

The libghostty-vt agents also worked harder for the same distance: about 37% more tokens and 43% more tool calls, in about the same eleven or twelve minutes. Their first builds took three to four minutes against one to two for rio-vt, because building Ghostty’s core means fetching Ghostty’s source and compiling it with Zig.

Neither kit came with perfect instructions. Every rio-vt agent found the README had drifted from the code. Its example of listening for events doesn’t compile, it says the default features are empty when they aren’t, and it never mentions the switch that turns picture decoding on, without which a PNG is dropped while the terminal still tells the program all is well. Every libghostty-vt agent found that the library’s own picture decoder can’t be constructed, and each wrote one of its own in 25 to 40 lines. rio-vt’s wrong turns cost time, and libghostty-vt’s stopped the job.

Underneath, the order runs the other way. Ghostty’s core is the more rigorously engineered of the two, with over two thousand test blocks, a fuzzing job that throws random input at it, and runs under a memory checker. rio-vt is a single crate, and its own test suite passed here, 553 tests of 553 on the features Terminal Delight would use. The trouble sat in the layer Terminal Delight would hold, the Rust wrapper between it and Ghostty’s core, and that was the part every agent was assembling.

I’d love to work with you. See the portfolio, or get in touch.

Portfolio

Technical

Six agents, answers sealed first

Agent fluency carried weight 4 of 15.75 in the sealed weights and had no measurement, and it was large enough to reorder the two cores left. This part measures it the way it will be exercised: fresh agents, one library each, a task Terminal Delight needs done, and the right answers written before anyone reported.

The task

Six agents received the same prompt, three with rio-vt and three with libghostty-vt, with only the crate name and a one-line description changed. Build a Cargo binary that (1) creates a 120×40 terminal with 10×22-pixel cells and 10,000 lines of scrollback, (2) feeds it icat.bin, the raw output of kitten icat drawing a PNG, in 4,096-byte chunks, (3) prints every stored placement, the cursor, the terminal’s replies and the non-empty visible lines as JSON, and (4) feeds a fresh terminal text.bin, 18,311 lines, and prints how many rows it holds. The test was blind: the crate’s own docs, docs.rs, its registry source and its upstream repository were allowed; the harnesses from the earlier parts were not. Each agent kept a notes.md of every surprise in the order it hit them.1

The answers were committed first: one 64×19 placement at the top left, the cursor on row 19, a Kitty OK among the replies, and 10,040 rows held, within 2%.2 Every check was then re-run from each agent’s own program, and every re-run reproduced the agent’s output byte for byte.3

Results

AgentPictureCursorRepliesHistoryRows heldTokensTool callsMinutesCold build
rio-vt 1✓✓✓✓10,040173k5811.262 s
rio-vt 2✓✓✓✓10,040142k5110.8103 s
rio-vt 3✓✓✓✓10,040173k4411.480 s
libghostty-vt 1✓✓✓✕10,295226k6411.7212 s
libghostty-vt 2✓✓✓✕620216k7412.2223 s
libghostty-vt 3✓✓✓✕620224k8011.6178 s

rio-vt passed 12 of 12 checks and libghostty-vt 9 of 12. Means: 163k against 222k tokens, 51 against 73 tool calls, 11.1 against 11.8 minutes, 82 against 204 seconds to a first build. libghostty-vt’s max_scrollback is documented in its Rust docs and its C header as lines and is a byte budget: 10,000 keeps 620 rows, and nothing takes a line count. One agent bisected a budget of 10,838,016 bytes and landed on 10,295 rows, outside the 2% band.3

The checker had a fault of its own. check.py first counted rio-vt agent 1’s stored-image record, 640×400 pixels with no row, as a second placement and failed its picture check. It now requires a row and a column; that agent passes, and no other verdict moved.3

What all three agents on a core found

Only findings every agent on a core reported independently are listed.3

rio-vt:

  • The graphics feature is missing from the README, and without it PNG pictures are dropped silently while the support query still answers OK.
  • The README says the default feature set is empty; Cargo.toml defaults to pty.
  • The README’s EventListener snippet implements an event() method the trait doesn’t have.
  • No documented way to read placements back: a public map whose rows are absolute and have to be converted.
  • The quick-start’s CrosswordsSize::new leaves the cell size at 0, which silently drops every picture (read in source, not hit).

libghostty-vt:

  • max_scrollback documented as lines, counted as bytes; blocks step four.
  • The crate’s own PNG decoder can’t be constructed, its doc example doesn’t compile, and its decode path looks broken; each agent wrote its own in 25 to 40 lines.
  • Building needs Zig and a network clone of Ghostty, which the main crate’s docs don’t say; Zig’s cache escapes to ~/.cache/zig unless an undocumented variable is set.
  • No cell pixel size at creation; it is set through resize(), whose doc calls unchanged dimensions a no-op.
  • File-path pictures are off by default and undocumented in the Rust docs; with q=2 they drop silently.

Build quality

rio-vt 0.5.28libghostty-vt 0.2.1
Codeone crate, 34,955 lines of Rust, 19 unsafe, no TODO or FIXMEGhostty’s Zig core, 105,163 lines, 40 TODO or FIXME; Rust bindings, 8,544 lines, 367 unsafe
Tests566 test functions; on Terminal Delight’s features, 553 passed, 0 failed2,382 Zig test blocks and 11 binding tests; Ghostty’s test-lib-vt would not run here
CIfmt, clippy, tests on Linux, macOS and Windowsseven targets including freestanding and WASM, fuzzing, Valgrind; bindings: Nix, an iOS check, Miri
Fuzzingnone foundyes, with a corpus
Buildcargo and one C++11 fileZig 0.15.2 and a clone of Ghostty; about 900 MB of target and cache
How it is builtno AGENTS.md or AI policy; Copilot review on; one person carries 92% of commitsAGENTS.md, CLAUDE.md and an AI policy for outside contributors

Ghostty’s test-lib-vt failed to link in Debug under Zig 0.15.2 and failed a compile-time assert in ReleaseSafe; its CI runs inside Nix, which this machine doesn’t have. Both failures are about building it outside its own toolchain.4

Fluency scores the checks passed averaged with token efficiency: rio-vt 1.0, libghostty-vt 0.741.5

What would falsify this

Three agents per core is a small sample, all of one kind, on one task that exercises pictures and history and nothing else. If agents asked to do other things Terminal Delight needs, such as selection, resizing or reading history back for search, found rio-vt the harder of the two, fluency would stop favouring it. A libghostty-vt release that takes a line count for history, or documents the byte budget, removes the step every agent stopped at. And the fluency formula, checks averaged with tokens, is the least settled choice in this part, though scoring checks alone or tokens alone puts libghostty-vt at 0.75 or 0.73 against rio-vt’s 1.0, which barely moves the final sums.

I’d love to work with you. See the portfolio, or get in touch.

Portfolio

Sources

  1. The prompt all six agents received — Identical for both cores apart from the crate name and a one-line description of it.
  2. The right answers, committed first — Committed on 2026-09-25 before any agent reported.
  3. Every agent’s program, notes and output, with the re-run results — fluency/agents holds each agent’s program, notes.md and output.json; results.json holds the re-run checks, rows held, tokens, tool calls, minutes and the findings every agent on a core reported.
  4. Build-quality checks for both cores — Line, unsafe and TODO counts from static_facts.py; repository facts from repo_facts.sh; the attempt to run Ghostty’s own tests in ghostty_lib_tests.sh.
  5. The fluency score — Checks passed averaged with the ratio of the lower mean token count to each core’s; results in vibes/final-2026-09-25.json.