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 went | What it looked like |
|---|---|
| Rediscovery | Every 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 hand | About 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 output | Grepping a 19 MB build log and JUnit XML files; throwaway scripts to measure screenshot differences. |
| Machine-specific tests | Tests 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. |
| Coordination | Several 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-verifying | 30-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… | Use | Instead of |
|---|---|---|
| find a type's declaration | source_declaration: the exact declaration with a bounded excerpt, ambiguity preserved | grep, then reading whole files |
| understand how parts connect | architecture_snapshot (C4 levels), app_graph_nodes, app_graph_node, app_graph_path, kernel_graph, graph_query, graph_describe | reading graph wiring code |
| find wiring mistakes | app_graph_findings: missing providers, duplicate ports, cycles, unreachable screens | reviewing by hand |
| know whether a run passed, and why not | check_result: outcome, failures with file and line, the revision, and how much of the output was understood | reading the log |
| know whether an operation is safe | operation_plan: effect, environments and approval requirements; never starts anything | reading scripts |
| see a database's shape | database_schema | reading migrations |
| see a running app | devtools_elements, devtools_logs, devtools_requests, devtools_sockets | screenshots |
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.
SurfaceRetirementGuardturned 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 singleVersion.kt, does the same for builds.- The docs site publishes
llms.txt,graph.jsonandoutline.jsonfor 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:
- 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.
- Runs them on the build worker.
- Reduces the results with the reducer from Conductor §12.
- 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.jsonthe 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:windowTeston 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
verifyon 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.
- The goal, in one paragraph, with the owner's decisions quoted.
- What it owns: files or areas. What others own, and a rule to stage only its own hunks.
- The rules, by link: no comments, least code, no destructive Git, never print secrets, maintained libraries only, and the project's design bar.
- Where to look first: the MCP reads in section 2, the context pack, and the mapping or token files as queries.
- How to verify: one command, its lock or queue, and what "green" means. Include the screenshot comparison when looks matter.
- 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
- Now, no code: put section 2's reads and section 4's brief into every agent prompt.
- The reducer and
verify(3.2), on the inputs already on disk. This is also the Conductor's first step after usage counting (§20). - The codemod runner (3.1), starting with the H6 mapping as its first test: rerun one H6 step on an old commit and compare.
- The hermetic kit and its guard (3.5), then the rest of the design-bar guards (3.4).
- Context packs (3.3), generated from the ABI dump and
outline.json. - Per-agent workspaces and the merge queue (3.6).
- 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.
Related docs
- The Reaktor Control Plane: the read and change tools agents call
- Reaktor Conductor: what a turn costs, reduction, verification and measurement
- Reaktor Agent Workspace: harness integration, typed findings, isolation and the outcome ledger
- Formal methods:
Findingand the verification ladder - Reaktor Surface: the vocabulary and the guard that keep UI work cheap to check
- Hangar: the workbench where this session's work landed