← Work

Tachyon

A local, read-only workbench for reviewing code that agents wrote, with an MCP surface agents use to search the code and explain their changes

Role
Built on px0, extended solo
Status
v1.1.1 released
Period
Sep 2026

Rust · Axum · Tokio · Go · Vanilla JS · LSP · MCP · SSE · GitHub Actions · Cloudflare R2 · Next.js

Problem

When agents write most of a diff, the developer's job shifts to review, navigation and audit. The tools for that are in the wrong places. An IDE is built for typing and is heavy to keep open over five agent worktrees. GitHub review happens after the PR exists, which is too late to steer. And neither tells you why a hunk changed, only that it did.

I wanted to open any worktree an agent is working in, watch its diff change live, read the agent's reasons next to the code, and mark a file reviewed against the exact bytes I read.

Where it started

Tachyon is built on px0 by Arpit Bhayani, an MIT-licensed, read-only code navigation tool: a fast index, fuzzy and regex search, syntax highlighting, all in one small binary. His commits are the first two days of the history, and the license and copyright are his.

The planning doc weighed three routes. A fresh backend would mean rebuilding the reader, search, virtualization, navigation and packaging before reaching anything new. A sidecar around the unmodified base would mean separate state, duplicated file reads, and awkward synchronized diffs. Building directly on px0 kept all of that and left the review model to add. From the third day on, the work is mine: the review workbench, the worktree registry and live diff, pooled language servers, the Rust engine, the MCP and intent layer, the release pipeline, and the site.

Architecture

Browser UI
vanilla JS, virtualized
Agents over MCP
stdio JSON-RPC
HTTP and SSE API
frozen contracts
Index and search
gitignore-aware walk
Review store
SHA-256 snapshots
LSP pool
at most 4 warm
Rust engine
default since 1.0
Go engine
legacy build
Repository
any registered worktree
One binary serves a browser UI for people and a stdio MCP server for agents

Reading. The server binds to 127.0.0.1 and starts listening before the index finishes. The walk honours .gitignore, lists ignored folders without entering them, and rejects symlinked directories. Search runs on a worker pool with a whole-file pre-check that drops files that cannot match, and a search cancels when the browser aborts it. The frontend keeps about 60 rows of a file in the DOM at once and caches the tree offline.

Navigation. Language servers start lazily, one session per worktree and build root, at most four warm, retired after five idle minutes, with crash backoff from 5 seconds to 2 minutes. Java, Kotlin and Spring have adapters. Without a server, navigation falls back to regex.

Review. A worktree selector lists changed files. Diffs render unified or split, held on a snapshot so the view does not jump while an agent is still writing; a banner says new changes are available. There are five comparison kinds: unstaged, staged, since I started observing, since my last review, and against a branch. Switching worktree re-roots the server without a restart.

Agents. tachyon mcp exposes find_files, grep, outline, definition and tachyon_intent over stdio. It spawns no language server, makes no git writes and opens no network connection. A context pack endpoint returns outline, search hits, tests and blame for a target in one round trip, capped at about 32 KB.

Key decisions

  1. A version is its bytes, not its timestamp. Snapshots are content-addressed by SHA-256. "Mark reviewed" pins the exact pair of versions I looked at, and the UI refuses to mark a file reviewed if it cannot pin both. Touching a file without changing it does not reset a review; changing one byte does.

  2. Intent travels as a file drop. When an agent calls tachyon_intent with why, decision, problem, what and risks, the MCP server writes a JSON drop atomically, temp file then rename, into an incoming folder, with no hashes. On the next diff request the server imports it and pins the bytes as they are now. The card shows verified while those bytes hold and stale once either side changes. A shell script and an OpenCode plugin write the same format, so any harness can explain itself.

  3. Read-only, and no AI at review time. Tachyon reads code and shows what agents did; it does not run agents. I removed the AI review buttons, and -no-ai returns 503 from what is left. Git writes are planned behind an explicit --allow-git-write flag. The trade-off is that you leave Tachyon to commit, which is the point.

  4. Two engines, byte-for-byte. I ported the server to Rust (Axum, Tokio, rayon, jemalloc) and made it the default at 1.0, keeping Go as tachyon-go. Both embed the same UI and must return identical JSON; serde_json uses preserve_order purely to match Go's key order. The cost is real: every change lands twice.

  5. Polling first, events later. Background status polls fingerprint HEAD and the full change signature of each worktree, and publish small invalidations over SSE; the event bus replays recent history on reconnect and demands a resync on gaps. There is no filesystem-event dependency yet, so the poll loop is also the degraded-mode fallback. The cost is a few seconds of latency before a new agent edit shows up.

Numbers

Go against Rust, on an Apple M3 Max with Tachyon 1.0.0, warm second runs:

GoRust
Index, Linux kernel520 ms327 ms
Index, Django137 ms48 ms
Index, TypeScript671 ms291 ms
Full scan, Linux kernel3,779 ms1,696 ms
Full scan, Kubernetes666 ms380 ms
Peak memory, Linux kernel293 MB71 MB
Binary size12 MB5.5 MB

Fuzzy search was a wash. On Tachyon's own tree on macOS, Tachyon used 22 MB of memory against 487.5 MB for Zed and 2,618.7 MB for Cursor; Vim with --clean used 7.9 MB.

For agents, I timed one task on a 72 GB workspace of 115,724 files: find a symbol and read up to ten files that use it. find, git grep and cat took 27.8 seconds; the MCP tools took 6.3; one context pack took 2.7. Two caveats from the same page: tokens were estimated as bytes divided by four, and git grep ties Tachyon on brute-force search.

What broke

  • A stale frontend that would not leave. A year-long immutable cache header served old JavaScript after an upgrade. Content-hash ETags replaced it.
  • Fast typing stacked searches. Each keystroke started a full-tree search and none were cancelled. Searches now abort when the client does.
  • Memory that was not real. On macOS the reported memory was about ten times too high because it read Go's MemStats.Sys. Closing a tab also forced a stop-the-world FreeOSMemory stall. Both were fixed, and the highlight cache dropped from 512 to 64 MB and the LSP pool from eight to four.
  • A benchmark race. The TypeScript run read /api/meta before the walk had finished and reported an index time that was too good. The harness now waits for ready.
  • The Rust port shipped without --update. Fixed in 1.1.1 with an atomic binary swap.
  • Language servers are slow and fragile. The Spring Boot server's initialize takes one to twenty minutes, far past a 30 second default, and one upstream Kotlin server build expired outright and had to be pinned to a newer one.

Results

Six releases in five days, 0.5.0 on 15 September to 1.1.1 on 19 September 2026. A one-line installer prefers the Rust build and falls back to Go, with binaries served from Cloudflare R2; GitHub Actions cross-builds the Go engine for 15 platforms and the Rust engine on Linux, macOS and Windows.

Other developers use it too. For me, Tachyon is the code-search MCP server in my agentic workflow, and the intent cards are how agents there tell me why a hunk exists.

What I'd change

  • Retire the Go engine. Parity was the right call for a safe switch to Rust. Now it doubles the cost of every feature.
  • Finish the git write gate. Staging and committing from the review view, behind the flag, is drafted but not shipped.
  • Count tokens properly. Bytes divided by four is fine for a ratio and wrong for a budget; the agent benchmark should use a real tokenizer.
Next case studySwitchboard →