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
- Live
- www.tachyon.run
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
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
-
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.
-
Intent travels as a file drop. When an agent calls
tachyon_intentwith 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. -
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-aireturns 503 from what is left. Git writes are planned behind an explicit--allow-git-writeflag. The trade-off is that you leave Tachyon to commit, which is the point. -
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_jsonusespreserve_orderpurely to match Go's key order. The cost is real: every change lands twice. -
Polling first, events later. Background status polls fingerprint
HEADand 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:
| Go | Rust | |
|---|---|---|
| Index, Linux kernel | 520 ms | 327 ms |
| Index, Django | 137 ms | 48 ms |
| Index, TypeScript | 671 ms | 291 ms |
| Full scan, Linux kernel | 3,779 ms | 1,696 ms |
| Full scan, Kubernetes | 666 ms | 380 ms |
| Peak memory, Linux kernel | 293 MB | 71 MB |
| Binary size | 12 MB | 5.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-worldFreeOSMemorystall. 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/metabefore 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
initializetakes 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.