Building Reaktor with agents: where the tokens go, and how to spend fewer

Written 6 October 2026, from one working session in which coding agents finished the Hangar-on-Surface migration (Hangar, Surface).

This page is the practical companion to two design documents. The Conductor §9–§20 explains what an agent turn costs and why reducing tool output and verifying for free come first. The Agent Workspace covers native harness integration, typed findings and the outcome ledger. This page adds the evidence from a real session and turns it into concrete, ordered work.

1. What one session cost

On 6 October 2026, about 30 agent runs worked in parallel on Reaktor and Hangar:

  • they repaired a five-project build;
  • they fixed review findings;
  • they converted every list in Hangar to Surface collections;
  • they retired Hangar's old UI kit, moving about 3,000 call sites onto Surface.

Together they reported about 19 million tokens. A single agent typically used 300,000 to 950,000 tokens.

The work succeeded: all 413 desktop flows pass, and a guard enforces the result. But most of the spend was not on the part only a model can do. In order of size, as observed:

Where the tokens wentWhat it looked like
RediscoveryEvery agent re-read the same long plans (one is 1,100 lines), a 417-line mapping and a 2,558-line token table, and large source files. Each re-learned how to run flows, which lock to take and where outputs land.
Mechanical edits by handAbout 1,600 text calls, 1,260 literal sizes and 200 retired names, each converted by reading the file and writing the edit. The old→new mapping already existed as data.
Raw outputGrepping a 19 MB build log and JUnit XML files; throwaway scripts to measure screenshot differences.
Machine-specific testsTests that passed only on one machine, because they read a home-folder cache, an ignored kubeconfig, a live chat socket, the machine's language setting or a real window. Agents spent hours diagnosing these. The disk also filled twice.
CoordinationSeveral agents shared one worktree, so a half-finished edit broke everyone's compile, Git's index lock collided, and agents had to stage single hunks. A second agent product fixed the same failures in parallel in the main checkout.
Waiting and re-verifying30-minute serial flow runs, one-hour full builds, and a rebuilt test snapshot after every change.

None of these needs a frontier model. Each has a deterministic answer that Reaktor either already has or can cheaply build.

2. Use what exists today

These already work. In the 6 October session no agent used the MCP reads, because the briefs never mentioned them. The fix costs nothing: name them in every brief.

When an agent needs to…UseInstead of
find a type's declarationsource_declaration: the exact declaration with a bounded excerpt, ambiguity preservedgrep, then reading whole files
understand how parts connectarchitecture_snapshot (C4 levels), app_graph_nodes, app_graph_node, app_graph_path, kernel_graph, graph_query, graph_describereading graph wiring code
find wiring mistakesapp_graph_findings: missing providers, duplicate ports, cycles, unreachable screensreviewing by hand
know whether a run passed, and why notcheck_result: outcome, failures with file and line, the revision, and how much of the output was understoodreading the log
know whether an operation is safeoperation_plan: effect, environments and approval requirements; never starts anythingreading scripts
see a database's shapedatabase_schemareading migrations
see a running appdevtools_elements, devtools_logs, devtools_requests, devtools_socketsscreenshots

These are the read tools of the desktop's MCP server (control plane, phase P0).

Four practices already in the repositories save tokens the same way:

  • Guards as tests. SurfaceRetirementGuard turned a long design review into a test that names each violation's file and line. Agents fix what it reports without re-reading the rules. In H6 it took about 3,000 findings to zero, and it now prevents regressions.
  • Headless flows and a coverage ledger. Hangar's flows run windowless against fixture data, and every flow id must have a ledger row. They prove behaviour cheaply and deterministically.
  • Screenshot sheets. Every view at three window sizes, compared against a reference. They prove "nothing visible changed" without anyone looking.
  • One vocabulary and one build config.
    • Surface's typed roles (type, ink), named tokens and parts leave an agent far fewer choices per edit.
    • dependeasy, with its single Version.kt, does the same for builds.
    • The docs site publishes llms.txt, graph.json and outline.json for agents that want the catalog without HTML.

3. What to build, highest leverage first

Each item gives the problem as it showed up, the proposal, a shape, and how to tell it worked. None of these is built yet, except where marked.

3.1 Codemods for mechanical migrations

Problem. H6 already had its mapping as data:

  • 64 scale tokens;
  • 207 component dimensions;
  • an exact old-call → new-call table, each entry proven pixel-identical.

Agents still applied it one site at a time, at full model rates.

Proposal. A codemod runner built on Kotlin's first-party Analysis API.

  • It reads a mapping file.
  • It rewrites the sites it can prove are covered.
  • It leaves the rest as typed findings for an agent.

The model only does the exceptions.

Shape.

$ reaktor codemod apply surface-text --module engine
rewritten  1,612 sites in 77 files
left       14 sites (ambiguous ink, 9; custom style, 5) → findings/surface-text.json
interface Codemod {
    val id: String
    fun rewrite(site: CallSite): Rewrite
}

sealed interface Rewrite {
    data class Edit(val replacement: String, val imports: Set<String>) : Rewrite
    data class Defer(val reason: String) : Rewrite
}

It worked when a migration like H6's text step needs less than a tenth of the tokens. Measure input tokens per accepted change, as Conductor §18 describes.

3.2 One verify call with typed results

Problem.

  • Agents either ran everything, which took an hour, or guessed which tests mattered, and then read the raw output.
  • Affected-test selection has no data source today (Conductor §13).

Proposal. verify(changedFiles), as a command and as an MCP tool. It does four things:

  1. Picks the checks from three sources the repositories already hold: the module graph, the flow ledger's coverage, and the JUnit XML that every test task already writes.
  2. Runs them on the build worker.
  3. Reduces the results with the reducer from Conductor §12.
  4. Returns check_result-shaped failures.

Shape.

$ reaktor verify --changed
ran      engine:jvmTest (31 classes), flows F-DATA, F-AUTH (61), guard
FAILED   2 / 214
F-DATA-22  step 14  expected visible "That line was cleared."  (20 s)
DataInspectorsInteractionTest.rowSelectionShowsLinks  DataInspector.kt:118
log      artifact://run/8241

It worked when:

  • no agent reads a raw Gradle log during a migration wave;
  • the median time to green after an edit drops below five minutes.

3.3 Context packs per module

Problem. Every agent re-read long documents and files to learn a module's API and rules. The hand-written agent files at the repository roots drift (Conductor §17).

Proposal. Generate a small pack per module that an agent reads first. It has three parts:

  • The API. The public declarations, from Kotlin's built-in ABI dump, which is first-party and already part of the Kotlin Gradle plugin.
  • The rules. A ten-line, hand-written fragment with the module's invariants and its one verify command.
  • Queries instead of long docs. MCP queries over long plans and tables, by heading or by key, using the outline.json the docs build already generates. "Which token is 22 dp in Splitter?" then costs one call, not 2,558 lines.

Shape.

$ reaktor context reaktor-surface-compose
reaktor-surface-compose — Compose adapters for Surface kernels
api        142 public declarations (digest, 1.6k tokens)
rules      one machine per collection; Modifier + slots; no label/tone props
verify     :reaktor-surface-compose:jvmTest
see        /docs/reaktor-surface-contracts-and-interaction

It worked when an agent's first 20% of tokens drops, measured from AgentUsage, before its first edit.

3.4 Guards for the whole design bar

Problem. Review rounds cost about 100,000 tokens each and still missed things. The H6 guard showed that a rule checked by a test is cheaper and stricter than a rule checked by a reader.

Proposal. One guard per rule, each emitting findings with file and line. They should cover:

  • no comments in new code;
  • one state machine per collection (no per-row machines);
  • no label, intent or tone props on Surface parts;
  • versions only in Version.kt, and no version catalog;
  • no raw activation or literal sizes outside Surface. Built for Hangar on 6 Oct.
  • hermetic tests (section 3.5).

Findings should use the Finding shape from formal methods, so that agents and the Agent Workspace's typed findings read them the same way.

It worked when a design review finds nothing a guard could have found.

3.5 A hermetic test kit

Problem. In one session, these tests passed only on the original machine:

  • a render test read a 26 MB API spec from the home folder;
  • a catalog test needed an ignored kubeconfig and a generated worker bundle;
  • an end-to-end check kept the JVM alive through a live chat socket;
  • a date test failed under another language setting;
  • several tests opened real windows.

Each took an agent hours to find.

Proposal. A test kit in Reaktor, plus a guard that rejects tests touching the real environment. The kit provides:

  • FakeWorkspace: a temporary mirror of a checkout, built from symlinks, with stand-ins for ignored operator files. It is deleted without following links. Built once on 6 Oct for the production-catalog test; generalise it.
  • FakeHome: a throwaway home folder per test JVM. Built for the engine tests on 6 Oct.
  • A fixed clock and a fixed language setting.
  • FakeNetwork, which fails on any real host.
  • No device discovery.
  • Headless by default, with real-window tests in their own task. Built as :reaktorDesktop:windowTest on 6 Oct.

It worked when a fresh clone on a fresh machine passes every check task without network, devices or a display.

3.6 One workspace per agent, behind a merge queue

Problem. Agents sharing one worktree broke each other's compiles mid-edit, collided on Git's index lock, and had to stage single hunks. The build offloader could not build worktrees, so all agents competed for one machine, and the disk filled twice. A second agent product, working in the main checkout, fixed the same failures in parallel without either side knowing.

Proposal.

  • A worktree per agent, with remote builds that accept worktrees.
  • A merge queue that runs the guards and verify on the combined result before it lands.
  • Area claims. Each agent declares the files it owns, and the queue refuses overlapping claims. That makes duplicated work visible before it starts.
  • A disk budget per workspace, with old build outputs pruned automatically.

The Agent Workspace §8 covers isolation; this adds the queue and the claims.

It worked when a five-agent wave has no cross-agent compile breaks and no duplicated fixes.

3.7 Scaffolds for the common shapes

Problem. Agents wrote the same structures token by token:

  • a Surface control: kernel, adapter, appearance and test;
  • a pane: graph-owned state, collection, flow and ledger row.

They also re-learned the conventions each time.

Proposal.

$ reaktor new control Stepper --appearance machine-signal
$ reaktor new pane Billing --collection table
$ reaktor new flow F-DATA-61 --area data

It worked when a new control or pane passes its guards and flows on the first build.

3.8 Structured output from every script

Problem. The flow runner, the suite and the screenshot comparison print prose. Agents wrote throwaway Python to count differing pixels and find where they were.

Proposal. Every script also prints one short JSON summary.

  • Flows: their failures, each with step, expectation and timeout.
  • Screenshots: each differing view with its changed regions, pixel counts and largest channel difference.

On 6 October that last measurement decided an owner question: one icon change moved up to 38/255 at 1× rather than the assumed 2/255.

It worked when no agent writes a helper script to read Reaktor's own tools.

3.9 Stable ids by construction

Problem. Flows broke when menus reordered (menu/action-2) or when titles carried live counts.

Proposal. Surface derives test and semantic ids from collection keys and action labels. Done for menu actions on 6 Oct; extend it to every collection row and region.

It worked when reordering a menu or renaming a count changes no flow.

3.10 Build hygiene

Problem.

  • Seven Reaktor tasks are incompatible with the configuration cache.
  • Gradle's transform cache grew to 28 GB.
  • The build worker lacked an iOS platform that every iOS step needed.

Proposal.

  • Make every task configuration-cache safe.
  • Prune caches on a schedule.
  • Check that each worker has the toolchains the build matrix needs, failing with a message that names the missing component.

It worked when a full build on a clean worker either passes or fails only on code.

4. How to brief an agent

A brief that worked well on 6 October had six parts. Writing them once and reusing them cost less than any single agent's rediscovery.

  1. The goal, in one paragraph, with the owner's decisions quoted.
  2. What it owns: files or areas. What others own, and a rule to stage only its own hunks.
  3. The rules, by link: no comments, least code, no destructive Git, never print secrets, maintained libraries only, and the project's design bar.
  4. Where to look first: the MCP reads in section 2, the context pack, and the mapping or token files as queries.
  5. How to verify: one command, its lock or queue, and what "green" means. Include the screenshot comparison when looks matter.
  6. The report: at most 300 words. Lead with the result. Give the root cause and the proof for each item, and list what's left.

5. Acceptance criteria

Track four numbers per wave, using the instruments Conductor §18 already names (AgentUsage per turn, summed per run):

  • input tokens per accepted change;
  • human interventions per wave;
  • time from edit to green;
  • the share of review findings a guard could have caught.

The comparison that matters is the same wave run twice, with and without the tools above. Reducing tokens counts only if correctness and human effort stay at least as good. That is the Agent Workspace's outcome rule.

6. Order of work

  1. Now, no code: put section 2's reads and section 4's brief into every agent prompt.
  2. The reducer and verify (3.2), on the inputs already on disk. This is also the Conductor's first step after usage counting (§20).
  3. The codemod runner (3.1), starting with the H6 mapping as its first test: rerun one H6 step on an old commit and compare.
  4. The hermetic kit and its guard (3.5), then the rest of the design-bar guards (3.4).
  5. Context packs (3.3), generated from the ABI dump and outline.json.
  6. Per-agent workspaces and the merge queue (3.6).
  7. Scaffolds (3.7), once the guards define what "correct" means for the generated code.

The gate is the Conductor's: if steps 1 and 2 don't move input tokens per accepted change, stop and re-measure before building more.