CB
Christian Battaglia
Developer · Builder · Creator
HomeBlogMusicProdEngProjectsUsesAboutQuotes

Anneal: Why I'm Building a Rust-Native Extension Host for VSCode

Every IDE makes you choose: ecosystem or performance. Anneal is a Rust-native VSCode extension host that runs existing JS extensions on embedded V8 with pre-compiled bytecode, supports native Rust extensions through the same interface, and ships with local-first AI. This is the manifesto — why open source, why the VSIX ecosystem, why local-first, and why the GUI vs TUI debate misses the point.
Christian Battaglia

Christian Battaglia

March 1, 2026

50 min read

Rust
V8
VSCode
IDE
extensions
Anneal
developer tools
open source
AI
local-first
Kreuzberg
sqlite-vec
OpenCode
Claude Code
Cursor

Every IDE Makes You Choose

Pick two:

  1. The ecosystem — 50,000+ extensions, familiar keybindings, the editor your muscle memory knows
  2. Native performance — sub-millisecond response, compiled extensions, no garbage collection pauses
  3. Local AI — semantic code search, context-aware completions, all running on your machine, offline

VSCode and Cursor give you #1. Zed gives you #2. Nobody gives you all three.

That's the gap I'm building into.

But honestly, if this were just about performance, I wouldn't bother. Editors are fast enough. What this is really about is something bigger: who owns the developer's most intimate tool?

Your editor knows everything. Every character you type, every file you open, every search query, every undo. It sees your thought process in real-time. And right now, the trend in developer tools is to send all of that to someone else's cloud and hope they're good stewards.

I think there's a better path.

The Problem Nobody Talks About

Open VSCode. Install 30 extensions. Notice the lag.

Here's what's actually happening: every single extension — Prettier, ESLint, GitLens, your theme, your icon pack's activation hook — runs in a single Node.js process called the Extension Host. One process. One thread for JavaScript. One V8 heap shared across everything.

When GitLens diffs your blame annotations while ESLint lints your file while TypeScript rebuilds its project graph, they're all fighting for the same event loop. One badly-written extension can stall everything. One memory leak slowly degrades your entire editing experience.

Microsoft engineered this isolation brilliantly — the extension host is a separate process that communicates with the editor UI via a well-defined binary protocol over IPC. Extensions can't crash the editor. But they can sure as hell make it feel slow.

Cursor inherited this architecture wholesale. They added incredible AI features on top — and they're genuinely great at it — but the foundation is still Node.js running your extensions single-threaded. Same bottleneck. Same heap pressure. They changed the ceiling; they didn't change the floor.

Zed took the opposite approach: rewrite everything in Rust, use WASM for extensions. It's blazing fast. But the ecosystem is tiny. No Prettier. No ESLint. No GitLens. You're starting from zero. And after years of funded development with a great team, they still don't have extension parity. Rebuilding an ecosystem is the hardest problem in software.

Lapce tried too — another Rust editor, WASI-based extensions. Same problem. Beautiful architecture. Empty extension marketplace.

The pattern is clear: you can have native performance or you can have the ecosystem. Nobody has cracked both.

Dissecting the Patient: What's Actually Running on My Machine

I don't like arguing from theory. Let me show you what's actually happening right now on my MacBook while I write this post in Cursor.

65 Processes, 9.5 GB of RAM

Running ps aux | grep Cursor reveals 65 separate processes consuming 9.5 GB of resident memory. For a text editor. Here's where it all goes:

Process TypeCountTotal RSS
Renderer (Chromium)5 windows~4.3 GB
Extension Hosts10 processes2.5 GB
Language Servers29 processes1.8 GB
GPU process1136 MB
Shared process190 MB
File watchers2~150 MB
Terminal PTY host168 MB

The Chromium overhead is what it is — that's the cost of Electron. The interesting story is the extension hosts: 10 processes eating 2.5 GB, plus the 29 language server child processes they spawn eating another 1.8 GB. That's 4.3 GB just for extensions.

Three Types of Extension Host

Vanilla VSCode runs one extension host per window. Cursor doesn't. It runs three distinct types per window, each in its own process:

1. extension-host (user) — Your Normal Extensions

PID 91634  [window 1]  160 MB
PID 91635  [window 2]  312 MB
PID 89683  [window 3]   82 MB
PID 90724  [window 4]  434 MB

These are the traditional extension hosts running ESLint, Prettier, Tailwind CSS, YAML, JSON, Markdown, spell checker, GitHub Actions, Docker — everything from the marketplace. Each open window gets its own. Window 4 is the one with this project open, and it's at 434 MB because of all the language servers it has spawned.

Each user extension host spawns 7-10 language server child processes via --node-ipc:

eslintServer.js              --clientProcessId=90724
tailwindServer.js            --clientProcessId=91635
code-spell-checker/main.cjs  --clientProcessId=90724
yaml/languageserver.js       --clientProcessId=91635
json/jsonServerMain           --clientProcessId=91634
markdown/serverWorkerMain     --clientProcessId=90724
html/htmlServerMain           --clientProcessId=91635
github-actions/server-node.js --clientProcessId=90724
dockerfile-language-server    --clientProcessId=90724
compose-language-service      --clientProcessId=90724

Every single one of these is a separate Node.js process. 29 language servers across 4 windows. Each one has its own V8 heap, its own event loop, its own memory overhead. The --clientProcessId flag tells you which extension host spawned it.

2. extension-host (retrieval-always-local) — Cursor's Intelligence Engine

PID 91636  [window 1]  186 MB
PID 91637  [window 2]  830 MB   ← the big one
PID 90751  [window 4]  222 MB

This is where it gets interesting. These hosts run two Cursor-proprietary extensions: cursor-retrieval (codebase indexing and search) and cursor-always-local (local features, git workers). Window 2's retrieval host is eating 830 MB by itself — that's a single Node.js process with a Rust native addon doing file indexing.

Each of these hosts also spawns a gitWorker.js subprocess:

PID 90829  parent=90751  gitWorker.js  51 MB
PID 91841  parent=91636  gitWorker.js  49 MB
PID 91889  parent=91637  gitWorker.js  48 MB

3. extension-host (agent-exec) — The AI Sandbox

PID 91639  [window 1]   84 MB
PID 91638  [window 2]  138 MB
PID 90752  [window 4]  129 MB

This is where agent conversations execute. The cursor-agent-exec extension lives here — the one with shell access, file editing, tool use. Every agent turn I take in this conversation runs in one of these processes. The sandboxed shell commands, the file reads, the grep searches — all mediated through this extension host.

The IPC Pipes

Using lsof -p on extension host PID 91634 reveals the pipe topology:

FD 1   PIPE → stdout to main process
FD 2   PIPE → stderr to main process
FD 13  PIPE ↔ bidirectional pair with FD 14   (main IPC channel)
FD 20  PIPE ↔ bidirectional pair with FD 21   (secondary channel)
FD 27  PIPE ↔ bidirectional pair with FD 28   (tertiary channel)
FD 54  UNIX socket → /tmp/vscode-git-*.sock   (git extension)
FD 95  UNIX socket → /tmp/ssh-askpass-*.sock   (SSH auth)

Three bidirectional pipe pairs plus Unix domain sockets. The main channel carries the binary RPC protocol. The secondary and tertiary channels likely handle bulk data transfer and diagnostic streams. This is the exact protocol I've been implementing in Anneal's anneal-host crate — 13-byte header framing over these pipes.

The retrieval host (PID 91637) has a similar layout but with 118 open file descriptors total — the extra FDs are file handles for the indexing engine reading your codebase.

The Binary: Cursor Helper (Plugin)

Every extension host is the same binary:

/Applications/Cursor.app/Contents/Frameworks/
  Cursor Helper (Plugin).app/Contents/MacOS/Cursor Helper (Plugin)

Mach-O 64-bit executable arm64
222 KB

It's Electron's utility process binary — a stripped Node.js runtime. It boots by loading bootstrap-fork.js, which reads VSCODE_ESM_ENTRYPOINT from the environment and dynamically imports:

vs/workbench/api/node/extensionHostProcess.js  (4.6 MB bundled)

That single 4.6 MB JavaScript file is the entire extension host runtime — the vscode.* API implementation, the extension activation lifecycle, the provider registry, everything. It's what Anneal replaces.

Cursor's Secret: The Rust Native Addon

Here's the finding that made me sit up straight. Inside cursor-retrieval, there's a native Node addon:

@anysphere/file-service/file_service.darwin-universal.node
23 MB — Mach-O universal binary (x86_64 + arm64)

The otool -L output shows it was compiled from:

/Users/runner/work/everysphere/everysphere/_work/1/s/target/
  aarch64-apple-darwin/release/deps/libfile_service.dylib

This is compiled Rust. Built in Anysphere's (Cursor's parent company) CI from a repo called everysphere. Running strings on the binary reveals exactly what Rust crates are compiled inside:

CratePurpose
libgit2-sys 0.17 + gix 0.74 & 0.77Git operations (dual implementation — C bindings AND pure Rust)
grep-searcher, grep-matcher, grep-regexRipgrep's core search engine
aho-corasickMulti-pattern string matching
globsetFast glob/pattern matching
walkdirRecursive directory traversal
regex / regex-syntax / regex-automataFull regex engine
tokio 1.47Async runtime
memmap2Memory-mapped file I/O
bstrByte string handling
jiffDate/time library
tracing-subscriberStructured logging/observability

Cursor already ships a Rust binary for their most performance-critical path. They took the ripgrep search engine, the gix git library, and a file walker, compiled them into a 23 MB native addon, and loaded it into a Node.js process via NAPI. The cursor-retrieval extension calls into this Rust code for file indexing, code search, and git operations.

This is the engine behind the 830 MB retrieval host on my machine.

Cursor's Built-In Extensions

Cursor ships 16 proprietary extensions alongside the standard VSCode ones:

ExtensionBundle SizeWhat It Does
cursor-agent38 MBThe AI agent — composer, chat, inline edits. The biggest JS bundle.
cursor-agent-exec4.1 MBAgent execution sandbox — shell, file ops, tool use
cursor-always-local3.8 MBLocal features, experimentation, git workers
cursor-retrieval3.5 MB JS + 23 MB native RustCodebase indexing and search
cursor-mcp1.5 MBModel Context Protocol server
cursor-shadow-workspace1.1 MBBackground apply workspace
cursor-browser-automation276 KBBrowser testing MCP
cursor-file-serviceFile operations
cursor-commitsCommit intelligence
cursor-deeplinkURL scheme handler
cursor-worktree-textmateWorktree grammars
cursor-polyfills-remoteRemote development polyfills
cursor-ndjson-ingestStreaming JSON ingestion
cursor-ios-simulator-connectiOS development
cursor-android-emulator-connectAndroid development
theme-cursorThe Cursor theme

The cursor-agent bundle is 38 MB of JavaScript. That's the AI brain — and it runs in a Node.js process. Every agent turn serializes context through the V8 heap, through JSON-RPC, through the IPC pipes, to the renderer, then back.

What This Tells Us

The picture is clear: Cursor is already doing half of what Anneal proposes, but from the wrong direction.

Cursor's architecture:

Electron main process
  └─ spawns Node.js extension host (Cursor Helper Plugin binary)
       └─ loads JavaScript extensions
       └─ loads Rust native addon via NAPI (@anysphere/file-service)
            └─ ripgrep + gix + walkdir + tokio compiled to .node

Anneal's architecture:

Electron main process
  └─ spawns Rust extension host (anneal-host binary)
       └─ embeds V8 for JavaScript extensions (rusty_v8)
       └─ loads native Rust extensions directly (dylib/trait impls)
       └─ runs intelligence stack in-process (sqlite-vec, Kreuzberg)

Same insight: Rust is needed for performance-critical editor operations. Opposite execution: Cursor bolts Rust onto Node.js from the outside as a native addon. Anneal makes Rust the foundation and embeds V8 inside it.

The consequences are significant:

  • Memory: Cursor's retrieval host runs two allocators in the same process — Rust's jemalloc/system allocator for the native addon and V8's heap for JavaScript. They can't share memory. A string that exists in Rust has to be copied to V8 to be used in JavaScript. That's partly why a single retrieval host eats 830 MB. When Rust owns the process, there's one allocator, one memory space.
  • Concurrency: The Rust addon in Cursor can do async I/O on tokio threads, but the results still funnel through Node's event loop to reach JavaScript. Anneal's Rust host can dispatch results directly — to V8 isolates, to native extensions, to the IPC channel — without touching a JavaScript event loop.
  • Startup: Cursor loads a 4.6 MB JavaScript file to boot the extension host runtime. Anneal's extension host is a compiled Rust binary — cold start is the time it takes to exec() a binary and open pipes. Plus V8 bytecode caching for the JS extensions on top.
  • Bundle size: cursor-agent is 38 MB of JavaScript that V8 has to parse, compile, and execute on every process start. With bytecode pre-compilation, that 38 MB could load in under 3ms instead of the hundreds of milliseconds it takes to cold-compile.

And there's one more thing Anneal gets for free that Node.js has been unable to ship for years.

The V8 Pointer Compression Story

In February 2026, Matteo Collina and the Platformatic team published benchmark results for V8 pointer compression in Node.js — a feature that reduces each heap pointer from 64 bits to 32 bits, cutting JavaScript memory usage by roughly 50% with only a 2-4% latency increase. Chrome has had this enabled since 2020. Node.js still doesn't turn it on by default.

Why? Two historical reasons. First, pointer compression originally forced all V8 isolates in a process to share a single 4 GB memory "cage." That was fine for Chrome (one tab = one process), but fatal for Node.js where worker threads share a process. Cloudflare and Igalia eventually solved this with IsolateGroups — per-isolate cages instead of a process-wide one — but the Node.js integration only landed in late 2025 and still requires a compile-time flag. Second, legacy native addons built with NAN (Native Abstractions for Node.js) break under pointer compression because NAN exposes V8's internal pointer layout directly. Any addon that hasn't migrated to Node-API is incompatible.

So Node.js sits on a 50% memory reduction that it can't enable by default because of backwards compatibility constraints. The result: every VSCode extension host, every language server, every Cursor retrieval process runs with uncompressed 64-bit pointers, using twice the heap they need to.

Anneal doesn't have this problem.

When you embed V8 through rusty_v8, you control the build flags. You enable pointer compression from day one. You use IsolateGroups — each JS extension gets its own V8 isolate with its own 4 GB cage, which is more than any extension will ever need. There are no NAN addons to break because Anneal doesn't use NAN — Rust extensions use the trait system, and JS extensions use the standard vscode.* API shimmed through V8's C++ embedding API.

The math is straightforward. The 10 extension host processes on my machine use 2.5 GB of RSS. A significant portion of that is V8 heap. With pointer compression, the V8 heap portion drops by ~50%. That's potentially over a gigabyte of savings just by flipping a build flag that Node.js can't flip.

And it compounds. Pointer compression doesn't just save memory — it makes the garbage collector faster. Smaller objects fit in fewer cache lines. The young generation fills more slowly, triggering fewer minor GCs. Major GC has less to scan. Compaction moves fewer bytes. The Platformatic benchmarks showed p99 latency actually dropped by 7% with pointer compression enabled, because GC pauses were shorter and less frequent.

For an editor where GC pauses directly translate to UI jank — a completion menu that hangs, a cursor that stutters, a syntax highlight that lags — this matters. Anneal's embedded V8 isolates run with compressed pointers and shorter GC pauses from the first release. No compile-time flag. No Docker image swap. No waiting for the Node.js project to solve backwards compatibility. It's just... on.

This isn't theoretical. The numbers are on my machine. 65 processes. 9.5 GB. 29 language servers. Three extension host types per window. The most performance-critical piece is already compiled Rust, just bolted onto Node.js from the outside. And the V8 heaps powering every one of those processes are twice as large as they need to be because Node.js can't turn on a feature that Chrome has had for six years.

The Insight

VSCode's extension host talks to the editor through a well-defined protocol — 13-byte header framing with binary RPC messages. Not JSON-RPC. A custom binary protocol designed for speed. The editor UI doesn't know or care what language the extension host is written in. It only sees protocol messages.

You can replace Node.js with Rust and the editor doesn't notice.

I've been deep in the VSCode source. The protocol has three layers:

  1. Transport: MessagePort, named pipe, or WebSocket — depending on local/remote/web
  2. Wire framing: 13-byte headers (message type, sequence ID, ACK, data length) + payload. State machine reads header, then body, repeat.
  3. RPC: Custom binary format — 12 message types, short/long string encoding, buffer references with $$ref$$ markers, proxy identifiers for service routing

The initialization handshake is elegant: the Rust host sends Ready (one byte: 0x02), receives JSON init data (workspace info, extension list, environment), sends Initialized (one byte: 0x01), and then both sides enter the RPC loop. From that point on, it's just typed binary messages: requests, acknowledgements, replies.

I've implemented this in Rust. It works. The handshake completes. RPC messages parse. The hard part isn't the protocol — it's the API surface behind it.

What Anneal Is

Anneal is a Rust-native VSCode extension host.

It replaces the Node.js extension host process with a Rust binary that:

  • Runs JS extensions on embedded V8 — using Deno's battle-tested rusty_v8 bindings (v146+, stable, Chrome release cadence). Each extension gets its own V8 isolate. Pre-compiled bytecode means extensions load faster than they would in Node.js.
  • Runs Rust extensions natively — through the same trait-based API. Zero serialization overhead. Direct function calls. The same CompletionProvider interface works in both languages.
  • Ships local-first AI — semantic code search powered by sqlite-vec for vector storage and Kreuzberg for document extraction. Your entire project — code, docs, everything — indexed locally. No cloud dependency.

The name comes from metallurgy: annealing is the process of heating metal and cooling it slowly to make it stronger and less brittle. The VSCode extension ecosystem is brittle — single-threaded, crash-prone, memory-hungry. Anneal makes it resilient.

The Bytecode Advantage

Here's the thing that got me started. I was experimenting with V8's bytecode compilation in Rust — building a pipeline that takes JavaScript, compiles it to V8 bytecode using EagerCompile, extracts the code cache via UnboundScript::create_code_cache(), and measures the difference. The speedup is dramatic:

Bundle SizeCold CompileWith Bytecode CacheTime Saved
50KB115μs19μs83%
100KB102μs12μs88%
250KB353μs32μs91%
500KB597μs43μs93%
1MB1,514μs86μs94%

For a typical VSCode extension (100-500KB bundled), pre-compiled bytecode eliminates ~90% of the compilation overhead. When you have 30 extensions loading at startup, those microseconds compound into the difference between "instant" and "why is my editor still loading?"

Now think about Cursor's cursor-agent extension at 38 MB. Cold-compiling 38 MB of JavaScript takes real time — likely in the range of 50-100ms on a fast machine. With bytecode pre-compilation, that drops to under 5ms. Multiply that by the 16 Cursor extensions plus your marketplace extensions, across all three extension host types, across every window. Startup time goes from "wait for it" to imperceptible.

The code cache is version-locked to the exact V8 build — which is fine when you control the runtime. We pin the V8 version in Cargo.toml and recompile caches on upgrades. A solved problem.

The Dual Interface

This is the most interesting architectural decision and the one I think has the most long-term implications: Rust and JavaScript extensions share the same API.

// A Rust extension implements the trait directly
impl CompletionProvider for MyRustExtension {
    fn provide_completions(&self, uri: &Uri, position: &Position) -> Vec<CompletionItem> {
        // Native speed, zero serialization
        vec![CompletionItem { label: "hello".into(), .. }]
    }
}
// A JS extension implements the same interface
class MyJSExtension implements CompletionProvider {
    provideCompletions(uri: Uri, position: Position): CompletionItem[] {
        // Runs on embedded V8, bridged via Fast API
        return [{ label: "hello", ... }];
    }
}

The Rust traits are the source of truth. TypeScript interfaces are auto-generated. When the extension host needs completions, it calls the trait method — if the extension is Rust, it's a direct function call with zero serialization. If it's JavaScript, it crosses the V8 Fast API bridge with minimal overhead.

Existing VSCode extensions work through a compatibility shim that maps vscode.* API calls to the trait system. You don't rewrite your extensions. They just run in a better runtime.

Why this matters: it creates a migration path. You start with your existing JS extensions running on embedded V8. Performance-critical extensions can be incrementally rewritten in Rust against the same interface. No big bang migration. No ecosystem split. Just a gradient from interpreted to native, at whatever pace makes sense.

Consider Cursor's own architecture: they have a 38 MB JavaScript agent and a 23 MB Rust native addon in the same process, communicating through NAPI bindings. With Anneal's dual interface, that boundary disappears. The agent logic could be partly Rust (for performance-critical paths like file diffing, AST parsing, context assembly) and partly JavaScript (for the flexible, rapidly-iterating UI integration) — and both sides use the same type system, the same provider pattern, the same extension lifecycle.

Why Open Source Is the Only Answer

Let me be direct about this: an IDE must be open source.

Your editor is the most intimate tool you use as a developer. It sees every keystroke, every file, every thought process. It's more personal than your browser. And the trend right now is toward closed-source AI IDEs that process your code on their servers — Cursor, Windsurf, GitHub Copilot — each asking you to trust a company with your entire codebase in exchange for productivity features.

That's a deal you shouldn't have to make.

Here's what happens with closed-source developer tools:

  • Pricing changes. Cursor is $20/month today. What's it tomorrow? You've built muscle memory and workflows around it. Switching costs are enormous. They know this.
  • Acquisition. Every VC-backed dev tool company is either going public or getting acquired. When the acquirer has different priorities, your tool becomes a distribution channel for something you didn't sign up for.
  • Privacy policy changes. "We don't train on your code" is a policy decision, not an architectural guarantee. Policies change when incentives change.
  • Enshittification. Cory Doctorow named the pattern: first be good for users, then be good for business customers, then extract maximum value. Every platform walks this path eventually.

Open source breaks this cycle. The code is inspectable. The data flow is auditable. If the maintainer goes sideways, you fork. If the company raises prices, you self-host. The community owns the foundation.

Look at the precedents:

  • Neovim saved Vim from stagnation. The community forked, modernized the architecture, and now it's the most actively developed terminal editor.
  • Linux ate commercial Unix. Because when your infrastructure depends on something, you need the right to inspect and modify it.
  • Chromium is open source even though Chrome isn't. Google understood that a browser engine needs trust from the ecosystem to achieve adoption.

The best developer tools have always been open source because developers demand visibility into their tools. We literally read code for a living. We're not going to trust a black box with our most productive hours.

And here's the thing I found while dissecting Cursor's process tree: that 23 MB Rust native addon? @anysphere/file-service? Closed source. Compiled from a private repo called everysphere. The extension host code, the agent logic, the retrieval engine — none of it is available for inspection. You can't audit what it indexes. You can't verify what it sends to the network. You can't see what cursor-ndjson-ingest does with your data. You're trusting a compiled binary with read access to every file in your workspace.

Anneal will be open source. Not "open core." Not "source available." Open source. The intelligence stack, the extension host, the protocol implementation — all of it. If you want to build on it, fork it, sell it, go ahead.

The business model, if there ever is one, is the same as every successful open source company: sell the hosted version, the support, the enterprise features. Not the tool itself. The tool belongs to everyone.

Why Stay in the VSIX Ecosystem

The second hard question: why not build a new extension format? Why carry the baggage of VSCode's VSIX packages, package.json contribution points, activation events?

Because the VSIX ecosystem represents millions of person-hours of development that you cannot replicate.

Prettier has been in continuous development since 2017. ESLint since 2013. The TypeScript language server is maintained by Microsoft with dozens of engineers. GitLens is a single developer's life's work. The Python extension has integration with debugpy, Jupyter, virtual environments, conda — years of edge cases handled.

You're not going to rewrite these. Nobody is. Zed has been trying for years with a well-funded team and still doesn't have full parity on any of them. Lapce same story.

The VSIX format itself is actually well-designed:

  • package.json contribution points — declarative. Themes, snippets, keybindings, grammars, languages. These don't need any API at all. An extension that only declares contributions works with zero code changes. That's roughly 40% of the marketplace.
  • Activation events — lazy loading. Extensions only spin up when needed. onLanguage:python means the Python extension doesn't touch memory until you open a .py file. The onStartupFinished event lets critical extensions like Cursor's retrieval engine start after the UI is interactive.
  • The provider patternregisterCompletionItemProvider, registerHoverProvider, etc. Uniform, composable, well-typed. This is the same pattern I'm using for the Rust trait system.

The problem was never the contract. It was the runtime. The vscode.d.ts API surface is large (~823 symbols across 15 namespaces), but the core that 80% of extensions use is only ~130 symbols. Commands, workspace configuration, language providers, diagnostics, text document model. Implement that, and the overwhelming majority of extensions just work.

Look at what I found in Cursor's extension manifests: cursor-retrieval uses extensionKind: ['workspace'] and activationEvents: ['onStartupFinished']. cursor-always-local uses extensionKind: ['ui'] and activates on onStartupFinished plus onResolveRemoteAuthority:background-composer. cursor-agent activates on * (everything). These are all standard VSIX patterns. Even Cursor's most proprietary extensions use the same package.json contract as every marketplace extension.

The remaining 20% — webviews, terminals, debug adapters, notebooks — is the long tail. You get there eventually. But you ship with 80% on day one.

Why Kreuzberg

Most "AI code search" tools only index code files. Your .ts, .py, .rs files. Maybe .md if you're lucky. And that's... fine? If all you care about is code.

But real projects aren't just code. Real projects have:

  • Architecture docs (often PDFs) that explain why the system is designed the way it is
  • API specs (OpenAPI YAML, Protocol Buffer definitions) that define the contracts
  • Design docs (Google Docs exports, Notion pages, Confluence) that capture decisions
  • Compliance docs (SOC2 policies, HIPAA requirements, security runbooks)
  • Research papers (PDFs) that the algorithm implementations are based on
  • Meeting notes, PRDs, RFCs, incident postmortems
  • Images with text (whiteboard photos, architecture diagrams, screenshots of error messages)

When you ask your AI assistant "how does the authentication flow work?", the answer might be in a code file. Or it might be in the architecture doc from 2023. Or the RFC that proposed the redesign. Or the incident postmortem that explains why the token refresh logic is weird.

If your index only sees code, you're searching with one eye closed.

Kreuzberg opens both eyes. It's a polyglot document intelligence framework with a Rust core. 75+ file formats. PDFs, Office docs, images (with OCR via Tesseract/PaddleOCR), HTML, XML, emails, archives, academic formats. SIMD-optimized. Streaming parsers for multi-GB files. No GPU required.

And because it has a Rust core, it runs natively inside the Anneal extension host. No subprocess. No Python dependency. No Docker container. Just a library call.

Compare this to what Cursor ships: their cursor-retrieval extension bundles a 23 MB Rust addon that handles code search and git operations. Impressive. But it only indexes code files. Kreuzberg would let the same indexing pipeline understand every document in your project — the PDF spec your API implements, the Confluence doc your PM wrote, the architecture diagram someone whiteboarded and photographed.

The indexing pipeline becomes: file watcher detects changes, Kreuzberg extracts text from any file in the project, a code-aware chunker splits it into meaningful pieces (respecting function boundaries for code, section boundaries for docs), a local embedding model generates vectors, sqlite-vec stores them.

Your IDE doesn't just understand your code. It understands your project. Every document. Every format. Every language. Offline.

Local-First Intelligence: A Philosophical Position

Let me be precise about what "local-first" means and why I believe it's the correct architectural decision, not just a feature checkbox.

Your code is your most sensitive intellectual property. Every variable name encodes a business concept. Every architecture decision reveals competitive strategy. Every TODO comment exposes technical debt you haven't told investors about. Every git commit message is a record of your team's thinking.

When you send this to a cloud service for indexing, you're trusting that company with:

  • Your unreleased features
  • Your security vulnerabilities (before they're patched)
  • Your hiring plans (encoded in the code you're writing)
  • Your customer data patterns (encoded in your schema design)
  • Your competitive moats (encoded in your algorithms)

"We don't train on your code" is a policy decision, not an architectural guarantee. Policies change when companies get acquired, when they raise their next round, when they pivot their business model. The only guarantee is architecture.

Local-first is an architectural guarantee. Your index is a SQLite file on your disk. The embedding model runs on your hardware. The vector search happens in-process. There is no network call. There is no server. There is no policy to change because there is no third party.

This isn't hypothetical paranoia. This is a real constraint for:

  • Defense contractors working in SCIFs where network access is physically impossible
  • Healthcare companies bound by HIPAA where code touching PHI schemas can't leave the network
  • Financial institutions with SOC2 and regulatory requirements around data residency
  • Any company that takes its IP seriously and doesn't want its codebase sitting in someone else's vector database

But it's also just... better UX. Local-first means:

  • Works on airplanes. No degraded experience when you're offline.
  • Works instantly. No network latency on every search query.
  • Works forever. No subscription expiration that suddenly makes your codebase unsearchable.
  • Works privately. No wondering what happens to your queries after they leave your machine.

The tools that index locally are better tools, full stop. The only reason more tools don't do it is because it's harder to build. You can't just throw everything at an API and call it a day. You have to ship the embedding model. You have to optimize the vector search. You have to handle incremental re-indexing when files change. You have to do it all within the memory and CPU constraints of a developer's laptop.

And you can't just bolt it on. Look at Cursor's retrieval host — 830 MB for a single workspace. That's what happens when you run a Rust search engine inside a Node.js process that's also running JavaScript extensions. Two allocators. Two memory models. Two garbage collectors (Rust doesn't have one, but V8 does, and they step on each other). The Rust addon allocates memory that V8 doesn't know about, so V8's heap heuristics are wrong, so it triggers GC at the wrong times, so the whole process gets bloated.

When Rust owns the process, the memory story is clean. One allocator. One memory space. V8 isolates get their own heaps (as they should), but the host controls the ceiling. The intelligence stack runs in the same address space as the extension host. No IPC. No serialization boundary. No duplicated data structures. An indexed code chunk can be handed directly from the vector database to the completion provider without a single copy.

It's harder. But it's right.

The GUI vs TUI Debate, and Why It Misses the Point

There's a hot debate in the AI coding tools space right now: GUI editors (Cursor, Windsurf) vs TUI/CLI tools (Claude Code, aider, goose, OpenCode). The argument goes something like:

Team TUI says: The terminal is composable. It follows the Unix philosophy. You can pipe, redirect, script. It works over SSH. It's accessible. It doesn't lock you into a specific editor. Power users prefer it.

Team GUI says: Coding is inherently visual. Syntax highlighting, indentation, bracket matching, inline diagnostics, git blame decorations — you need to see your code. Discoverability matters. Not everyone wants to memorize commands. The context window of a visual editor is richer than a terminal scroll buffer.

Here's my take: both sides are right, and framing it as a choice is the mistake.

The terminal is great for autonomous, multi-file operations — "refactor the authentication module across 15 files." You describe intent, the AI executes, you review the diff. The terminal's simplicity is a feature here: less UI means less distraction from the task of understanding what the AI did.

The editor is great for interactive, context-rich editing — you see the code, you see the lints, you see the type errors, you see the blame annotations, and you make surgical changes with full visual context. The AI assists inline. You accept or reject in real-time.

These aren't competing paradigms. They're different tools for different parts of the same workflow. The developer who uses Claude Code in the terminal for big refactors and Cursor in the editor for feature work isn't confused — they're sophisticated.

OpenCode: The Best Case Study for This Problem

OpenCode is the project that has gone furthest toward solving the multi-interface problem — and the place where it gets stuck is exactly where Anneal picks up.

With 100,000+ GitHub stars and 2.5 million monthly developers, OpenCode is the most successful open-source AI coding agent. And its architecture is genuinely clever: a Bun/JavaScript backend that exposes an HTTP server (using Hono), with multiple clients connecting to it:

  • TUI: A Go-based terminal interface built with Bubble Tea. Beautiful. Fast. The primary interface most developers use.
  • Web UI: opencode web starts the backend server and makes it accessible in a browser. You can even share it over your local network with mDNS discovery and authentication.
  • Desktop App: A Tauri-based application (now in beta) for macOS, Windows, and Linux. It's "just another client" to the same backend server.
  • ACP (Agent Client Protocol): A JSON-RPC protocol over stdio that lets editors like Zed, JetBrains IDEs, and Neovim run OpenCode as a subprocess.

This is the right idea. One intelligence backend, many interfaces. The server does the work — running tools, calling LLMs, managing sessions, executing bash commands, editing files — and the clients are just views into that work. You can even run opencode attach to connect a TUI to an already-running web server, giving you both interfaces simultaneously on the same session.

OpenCode is also provider-agnostic (75+ LLM providers via the AI SDK), has LSP integration for feeding diagnostics back to the model, supports multi-session parallel agents, and lets you define custom agents with their own system prompts, tool sets, and model choices. It's a remarkably well-designed system.

But it doesn't have an editor.

This is the crucial gap. OpenCode has a TUI, a web UI, and a desktop app — but none of them are a code editor. They're all agent interfaces. You describe what you want, the agent reads files, makes edits, runs commands, and shows you the results. But you never directly edit code in OpenCode. There's no syntax highlighting, no bracket matching, no inline diagnostics, no go-to-definition, no refactoring tools. The /editor command exists but it just opens your system's $EDITOR (vim, nano, whatever) in a temp file — and it's buggy, often opening the wrong file entirely.

This means OpenCode always lives alongside an editor. You run OpenCode in one terminal, and you have Cursor or VSCode or Neovim open in another window to actually look at and interact with the code. The ACP integration helps — it lets Zed or JetBrains host OpenCode as a subprocess — but then OpenCode becomes subordinate to the editor's UI, losing its own TUI and web interfaces.

The architecture tells the story:

OpenCode (the agent):
  Bun/JS backend (HTTP server + Hono)
    ├─ Go TUI client (Bubble Tea)
    ├─ Web client (browser)
    ├─ Tauri desktop client
    └─ ACP client (editor subprocess)
        ├─ Zed
        ├─ JetBrains
        └─ Neovim

  Tools: bash, read, write, edit, webfetch, LSP diagnostics
  Intelligence: LLM providers (cloud APIs)
  Indexing: none (relies on LLM context window + file reads)

Compare to what Anneal proposes:

Anneal (the editor + the agent):
  VSCode fork (Electron/Chromium renderer)
    └─ Rust extension host (anneal-host binary)
         ├─ V8 isolates (JS extensions: ESLint, Prettier, GitLens...)
         ├─ Native Rust extensions (dylib trait impls)
         ├─ Intelligence stack (sqlite-vec, Kreuzberg, embeddings)
         └─ Agent runtime (native, in-process)

  anneal CLI (TUI client):
    └─ Shares sqlite-vec index with the editor
    └─ Same intelligence, different interface

OpenCode has the multi-interface vision right. But by not owning the editor, it's permanently a sidecar — a very good sidecar, but a sidecar. It can't provide inline completions as you type. It can't show diagnostics decorations in the gutter. It can't offer a "fix this" code action when you hover over an error. It can't do any of the things that make Cursor feel magical, because those features require being inside the editor, not beside it.

And because OpenCode doesn't have a local index, every query goes to a cloud LLM. The LLM has to read files into its context window to understand your codebase. That works — models are good at it — but it means every question costs tokens, takes network latency, and requires sending your code to someone else's servers. There's no persistent, local understanding of your project.

The lesson from OpenCode: the multi-interface architecture is correct. The client-server split is correct. Being editor-agnostic is wrong.

You can have multiple interfaces — TUI, web, desktop — but one of them needs to be a real editor. Not a sidecar. Not a subprocess. The editor itself. Because the editor is where the developer lives, and the agent needs to live there too, with shared state, shared index, and zero context switching.

Anneal takes OpenCode's insight (separate the intelligence from the interface) and combines it with Cursor's insight (the AI needs to be inside the editor) — while keeping it open source and local-first.

The Missing Layer

What's missing across all of these tools is a shared intelligence layer. Right now, your terminal AI tool and your editor AI tool have separate indexes, separate context, separate understanding of your codebase. You have to re-explain context when you switch between them.

Look at how Cursor handles this internally: the cursor-agent extension (38 MB, runs in the agent-exec host) and the cursor-retrieval extension (runs in the retrieval-always-local host) are in separate processes. They communicate through IPC. The agent asks the retrieval engine for context, and the results get serialized across a process boundary. That's overhead that exists purely because of the Node.js process-per-host architecture.

OpenCode's architecture is cleaner — the Bun backend is a single process — but it has no persistent local index. Every session starts from scratch. The LLM has to re-read files it already read in a previous session. There's no accumulated understanding.

Anneal's local intelligence stack — the sqlite-vec index, the Kreuzberg-powered extraction, the code-aware chunking — is a process-local library, not a service. It can serve the GUI (the VSCode fork's inline AI features) AND a TUI (an anneal CLI) from the same index. Switch between terminal and editor freely. The intelligence follows you. Same vectors. Same understanding. Zero re-indexing. And unlike OpenCode, it persists between sessions — the index is a SQLite file on disk, incrementally updated as your project changes.

The right answer to "GUI or TUI?" is "yes, and they share a brain."

The Competitive Landscape

VSCodeCursorOpenCodeClaude CodeZedLapceAnneal
Has an editorYesYesNoNoYesYesYes
Extension ecosystemMassiveMassiveN/AN/ATiny (WASM)Tiny (WASI)Massive + native
Extension performanceNode.jsNode.js + Rust addonN/AN/AWASM sandboxWASI sandboxRust native + V8
TUI interfaceNoNoYes (Go/Bubble Tea)YesNoNoYes (planned)
Web UINoNoYesNoNoNoPossible
Multi-interfaceNoNoYes (TUI+Web+Desktop+ACP)NoNoNoYes (Editor+TUI)
AI featuresCopilot (cloud)Deep (cloud)Deep (cloud, 75+ providers)Deep (cloud)BasicNoneLocal-first
Model agnosticNo (Copilot)PartialYes (75+ providers)No (Claude only)PartialN/AYes
Local indexNoPartial (Rust addon)NoNoNoNoYes (sqlite-vec)
Offline AINoNoNoNoNoNoYes
Open sourceYesNoYesNoYesYesYes
Non-code file indexingNoNoNoNoNoNoYes (Kreuzberg)
Shared index across interfacesNoNoNoNoNoNoYes

The Endgame: Removing the Protocol Entirely

There's a question I keep coming back to: is there a world where we don't have to speak the IPC language at all?

Right now, the 13-byte header framing, the binary RPC, the handshake sequence — all of it exists because VSCode's architecture draws a hard line between "the thing that renders pixels" (Chromium/Electron) and "the thing that understands code" (the extension host). Two processes. A pipe between them. A protocol over the pipe.

The IPC protocol is a compatibility tax. It's the price you pay for reusing Electron's renderer. Every completion request takes a round trip: keystroke in the renderer → serialize into binary RPC → write to pipe → context switch to extension host process → deserialize → call the provider → serialize the response → write to pipe → context switch back to renderer → deserialize → render the completion menu. Two context switches, two serialization boundaries, two pipe writes, for every single interaction.

That tax is worth paying — today. Because it gives you the entire VSCode UI for free. Monaco editor, sidebar, terminal, command palette, themes, keybindings. Years of Microsoft's design and engineering. You don't rewrite that on day one.

But what if you didn't have to pay it forever?

Phase 1: Speak the Protocol (Now)

Replace the extension host. Keep Electron. Speak the binary IPC protocol fluently. The renderer doesn't know or care that Rust is on the other end of the pipe. You get ecosystem compatibility on day one — every VSCode extension, every theme, every keybinding. This is the pragmatic move.

The cost: two process boundaries, serialization overhead, pipe latency. But the benefit is enormous: you ship something real that works with the existing ecosystem.

Phase 2: Thin the Protocol (Medium Term)

Once you control the extension host and have forked the renderer, you own both sides of the pipe. The protocol becomes something you can evolve.

Instead of the full binary RPC format with its 12 message types and $$ref$$ buffer encoding, you could:

  • Use shared memory for large data (completion lists, diagnostic arrays, document contents) instead of serializing across a pipe
  • Generate a typed protocol from the same Rust structs that define the trait system — no hand-written serialization, no version mismatches
  • Collapse the three extension host types that Cursor uses (user, retrieval, agent) back into one process that communicates with the renderer through a single, simplified channel
  • Move Tree-sitter parsing to the Rust side, sending the renderer pre-computed syntax tokens instead of raw text that Chromium re-parses with TextMate grammars

The protocol gets thinner as you own more of the stack. Each piece you move from the renderer to the host is one less thing that needs to cross the wire.

Phase 3: Remove the Protocol (The Endgame)

Here's the question that changes everything: what does the Electron renderer actually do?

It's Chromium. Drawing text in a <textarea>-like construct (Monaco). Rendering HTML/CSS for the sidebar, the terminal, the command palette. It's a web browser rendering a text editor. That's a staggering amount of machinery for the task of putting colored characters on a screen.

Zed proved the alternative works. Their GPUI framework renders the entire editor UI in Rust using GPU-accelerated paths — no Electron, no Chromium, no HTML, no CSS, no DOM. And the result is faster, uses less memory, and gives the developers more control over every pixel. The text rendering is sharper. The animations are smoother. The input latency is lower. Because there's no browser engine between the keystroke and the screen.

If the Anneal extension host already has:

  • All the intelligence (sqlite-vec, Kreuzberg, local embeddings)
  • All the extensions (V8 isolates for JS, native dylibs for Rust)
  • All the language understanding (Tree-sitter, LSP, diagnostics)
  • All the file system knowledge (watchers, git, indexing)
  • The editor core (text buffer, CRDT, selections, undo history)

...then the renderer is reduced to a single job: draw what the host tells it to draw. Colored text. Cursor blink. Gutter decorations. Completion popups. Scrollbars. It's a presentation layer, not a computation layer.

At that point, the architecture collapses into a single process:

Single Rust process:
  ├─ GPU renderer (wgpu / GPUI-style)
  │    └─ Text shaping (cosmic-text or swash)
  │    └─ Input handling (winit)
  │    └─ UI layout (Taffy or custom)
  ├─ Editor core
  │    └─ Text buffer (rope data structure)
  │    └─ Selections, cursors, undo
  │    └─ Tree-sitter incremental parsing
  ├─ Extension host
  │    └─ V8 isolates (JS extensions)
  │    └─ Native Rust extensions (dylib)
  │    └─ Provider registry
  ├─ Intelligence stack
  │    └─ sqlite-vec (vector search)
  │    └─ Kreuzberg (document extraction)
  │    └─ Local embedding model
  └─ Agent runtime
       └─ Tool execution (bash, file ops)
       └─ LLM interface (local + cloud)
       └─ Session management

No IPC. No pipe. No serialization. No context switches between processes. A completion request goes from keystroke → event handler → provider registry → Rust trait call (or V8 Fast API bridge) → response → GPU render. All function calls within one address space.

The latency floor drops from "two context switches and a pipe round-trip" to "a function call and a GPU draw command." We're talking microseconds instead of milliseconds.

And the memory story finally makes sense. One process. One allocator. V8 isolates get their own heaps (as they should), but the host controls the ceiling. No duplicated data structures across process boundaries. No serialized copies of completion items sitting in two different heaps. The 9.5 GB that Cursor uses for 4 windows could become 1-2 GB.

The Ladybird Parallel

This phased approach isn't novel. The most ambitious project in adjacent territory — Ladybird, the from-scratch web browser — is walking the same path right now, and their story is instructive.

Ladybird is building a completely independent browser engine. No Chromium. No WebKit. No forked code from any existing browser. Written from scratch, funded by donations and sponsorships from Shopify, Cloudflare, 37signals, and GitHub co-founder Chris Wanstrath. The philosophical position is sharp: "The web is too essential to have one primary source of funding, and too important to have that source of funding be advertising."

Sound familiar? Replace "web" with "developer tools" and "advertising" with "cloud AI subscriptions" and you have Anneal's thesis.

But here's the part that really resonates: Ladybird just adopted Rust. In February 2026, Andreas Kling announced the project was transitioning from C++ to Rust after a failed year-long detour through Swift (C++ interop never matured, platform support outside Apple's ecosystem was limited). The first target was LibJS, Ladybird's JavaScript engine. Kling used Claude Code and Codex for human-directed translation — hundreds of small prompts steering the process — and produced 25,000 lines of Rust in two weeks. Zero regressions across 52,898 test262 tests and 12,461 regression tests. Byte-for-byte identical output from both the C++ and Rust pipelines.

The strategy is deliberate: C++ development continues as the main focus. Rust porting runs as a parallel sidetrack with well-defined interop boundaries. The C++ code isn't thrown away — it's gradually replaced, subsystem by subsystem, with verified equivalence at each step.

This is exactly the phasing strategy Anneal uses:

Ladybird (Browser)Anneal (IDE)
Phase 1Build the engine in C++, pass web platform testsBuild the extension host in Rust, speak VSCode's IPC protocol
Phase 2Port subsystems to Rust with interop boundariesThin the protocol, move subsystems from renderer to host
Phase 3Full Rust engine, independent of all existing browsersFull Rust process, independent of Electron
Ecosystem compatMust pass web platform tests (the spec IS the ecosystem)Must run VSCode extensions (the VSIX format IS the ecosystem)
Language migrationC++ → Rust (gradual, verified, AI-assisted)Node.js → Rust (V8 embedded for compat, native for performance)

Both projects start by being compatible with the dominant ecosystem. Both migrate gradually toward a native Rust foundation. Both use well-defined interop boundaries to keep the old and new worlds coexisting. And both are driven by the same conviction: the thing the entire industry depends on shouldn't be controlled by one company's business model.

Ladybird is doing it for the browser. Anneal is doing it for the editor.

And there's a deeper connection: if Ladybird succeeds, Anneal's Phase 3 gets easier. Right now, dropping Electron means dropping the web rendering engine — which matters for VSCode's webview API, the settings UI, the extension marketplace, and other HTML-based features. But if Ladybird ships a lightweight, embeddable, independent web engine by 2028, Anneal could use it for the HTML rendering bits without carrying all of Chromium. A Rust IDE with a Rust browser engine embedded for the parts that need HTML. The dependencies align.

Why This Path Is Unique

This is what Zed does — single Rust process, GPU rendering, no Electron. But Zed started from scratch. No VSCode extensions. No ecosystem. They're building a beautiful empty room.

Anneal starts from the opposite end. Phase 1 gives you the ecosystem (VSCode extensions running on embedded V8). Phase 2 proves the Rust extension host in production. Phase 3, when it comes, brings the renderer in-house — but by then, the most important extensions have native Rust ports (through the dual interface system), the V8 compatibility layer is battle-tested, and the intelligence stack is mature.

You end up in the same place as Zed — a single Rust process with GPU rendering — but you got there while carrying the ecosystem with you. Every step of the way, the thing works. Every step, you ship something useful. The protocol is scaffolding. You build with it. Then you remove it.

And if the Ladybird comparison holds, the timeline isn't crazy. Ladybird went from hobby project to funded nonprofit to passing thousands of web platform tests in two years. Kling ported 25,000 lines of his JS engine from C++ to Rust in two weeks with AI assistance. The tools for this kind of migration exist now in a way they didn't even a year ago.

The IPC protocol is not the architecture. It's the migration path.

What Does This Mean?

If I'm being honest about what this could become:

In the near term (Phase 1), it's a faster VSCode. Your extensions load quicker. Your editor uses less memory. One extension host process per window instead of three. No language server child process explosion — the most common language servers could be compiled directly into the host. You get semantic search that works offline. That alone is worth the effort.

In the medium term (Phase 2), it's a platform shift. When you can write editor extensions in Rust with zero-cost abstractions and direct access to system APIs — no WASM sandbox, no Node.js overhead — you unlock a class of extensions that aren't possible today. Real-time audio visualization in the editor. GPU-accelerated rendering of custom views. Native integration with hardware debugging tools. Extensions that are as fast as the editor itself, because they're compiled with the same toolchain. The protocol thins. Shared memory replaces serialization. The two processes start to feel like one.

In the long term (Phase 3), it's a new kind of IDE. A single Rust process. GPU-rendered. Local-first AI. The full VSCode extension ecosystem running on embedded V8 alongside native Rust extensions. No Electron. No Chromium. No browser engine between the developer and the code. The protocol is gone because there's nothing left to separate.

And it's open source. Every layer. Every phase. The scaffolding and the building.

The AI coding tools market is at an inflection point. Cursor showed that developers will pay for AI-powered editing. OpenCode showed that a multi-interface, open-source agent can reach 100k stars. Zed showed that native Rust rendering is the future. But nobody has combined all three: ecosystem compatibility, open-source multi-interface intelligence, and native performance.

That's Anneal. Not today — today it's four Rust crates, a blog post, and a plan. But the architecture is right. The phasing is right. The tools exist. And the need is real.

What's Built So Far

The foundation exists:

  • anneal-v8 — V8 embedding with bytecode pre-compilation. Benchmarked at 83-94% faster cold starts. The JsRuntime abstraction handles compilation, caching, and execution.
  • anneal-host — A Rust binary that speaks VSCode's full IPC protocol. 13-byte header framing, binary RPC parsing, handshake sequence. Handles $activateByEvent, $startExtensionHost, and routes all 12 RPC message types. Phase 1 scaffolding — built to be removed.
  • anneal-traits — The dual interface system. Core types (Position, Range, Uri, TextEdit, CompletionItem, Diagnostic, etc.) and 10 provider traits (completion, hover, definition, formatting, code actions, code lenses, symbols, diagnostics, references, rename). Plus the Extension trait with manifest parsing and activation lifecycle. This survives all three phases — the traits are the stable API.
  • anneal-intel — Local intelligence stack. SQLite-backed vector storage with cosine similarity search. Code-aware chunking that respects function boundaries. Language detection for 20+ languages. All tested. This is the brain that follows you from GUI to TUI and back.

What's Next

  1. Wire the extension host into a VSCode dev build. Load the first real JS extension. (Phase 1 begins)
  2. Expand vscode.* API coverage until Prettier, ESLint, and GitLens work.
  3. Integrate a local embedding model into the intel pipeline.
  4. Build the anneal CLI for terminal-based AI interactions sharing the same index.
  5. Fork VSCode. Ship it. (Phase 1 complete)
  6. Move Tree-sitter parsing, syntax tokens, and shared memory into the host. (Phase 2)
  7. Prototype a native Rust renderer. (Phase 3 begins)

If you want to follow along or contribute, the code is at repos/fluid-compute and will move to its own org when the time is right. This is going to be a ride.


Anneal: making the brittle VSCode extension ecosystem resilient through controlled heat.

In metallurgy, annealing heats metal past its critical temperature and cools it slowly — relieving internal stresses, increasing ductility, making it workable without being fragile. That's what we're doing to the IDE.


References

VSCode Architecture

  1. VSCode Extension Host source code — The extension host process entry point and vscode.* API implementation.
  2. VSCode Extension API type definitions (vscode.d.ts) — The complete TypeScript definition file for the VSCode extension API surface.
  3. VSCode IPC binary framing (ipc.net.ts) — The binary wire protocol with header framing, socket layer, and WebSocket support used between the renderer and extension host.
  4. VSCode Extension Host protocol definitions (extHost.protocol.ts) — The RPC interface contract between the main process and extension host.
  5. progrium/vscode-protocol — Unofficial VSCode protocol decoder and documentation.

V8 and rusty_v8

  1. Deno's rusty_v8 bindings — Production-stable Rust bindings to V8's C++ API, synced to Chrome's release cadence. 3,800+ stars.
  2. Announcing Stable V8 Bindings for Rust — Deno's September 2024 announcement of rusty_v8 stabilization at v129.0.0 after 150 releases and 3.1 million downloads.
  3. V8 Blog: Pointer Compression in V8 — V8 team's original 2020 explanation of pointer compression, achieving up to 43% heap size reduction. Enabled in Chrome since v80.
  4. Platformatic — "We Cut Node.js Memory in Half" — Matteo Collina's February 2026 benchmarks showing ~50% memory savings with V8 pointer compression in Node.js, with only 2-4% average latency increase and 7% improved p99.
  5. Node.js Pointer Compression and Isolate Groups (Issue #55735) — James Snell's (Cloudflare) tracking issue for integrating pointer compression and IsolateGroups into Node.js.
  6. V8 IsolateGroups: Multi-Cage Mode — Dmitry Bezhetskov's (Igalia, sponsored by Cloudflare) technical write-up on the IsolateGroups feature enabling per-isolate pointer compression cages.
  7. V8 IsolateGroup API reference — The V8 C++ API for creating independent pointer compression cages.

Competing Editors and Tools

  1. Zed Editor — GPU-accelerated Rust editor with GPUI framework. WASM-based extensions.
  2. GPUI: Leveraging Rust and the GPU to Render UIs at 120 FPS — Zed's blog post on their GPU-accelerated UI framework that renders the entire editor without a browser engine.
  3. GPUI crate on crates.io — Zed's open-source GPU-accelerated UI framework for Rust.
  4. Lapce Editor — Rust-native editor with WASI-based extensions.
  5. Cursor — AI-powered VSCode fork by Anysphere. Closed source.
  6. OpenCode — Open-source AI coding agent with TUI (Go/Bubble Tea), web UI, and Tauri desktop app. 100k+ GitHub stars. Provider-agnostic (75+ LLMs).
  7. OpenCode GitHub repository — Source code for the open-source AI coding agent (Apache 2.0).
  8. How Coding Agents Actually Work: Inside OpenCode — Moncef Abboud's deep dive into OpenCode's client/server architecture, Bun backend, and tool system.
  9. Agent Client Protocol (ACP) — The open standard (JSON-RPC over stdio/HTTP) for editor-to-agent communication, adopted by OpenCode, Zed, JetBrains, and Neovim.
  10. Claude Code — Anthropic's terminal-native AI coding agent.

Ladybird Browser

  1. Ladybird Browser Initiative — Independent web browser building a from-scratch engine. Funded by donations from Shopify, Cloudflare, 37signals, and Chris Wanstrath. No Chromium. No WebKit.
  2. Why We Need Ladybird — Chris Wanstrath on browser engine monoculture and advertising-funded infrastructure.
  3. Ladybird Adopts Rust, with Help from AI — Andreas Kling's February 2026 announcement of the C++ → Rust transition. 25,000 lines of LibJS ported in two weeks using Claude Code and Codex, zero regressions across 65,000+ tests.

Local Intelligence Stack

  1. sqlite-vec — Embedded vector search extension for SQLite. Pure C, zero dependencies, runs anywhere SQLite runs. 7,000+ stars. Mozilla Builders project.
  2. Kreuzberg — Polyglot document intelligence framework with Rust core. Extracts text from 75+ file formats. SIMD-optimized, streaming parsers. 6,100+ stars.

Open Source Philosophy and Developer Tools Economics

  1. Cory Doctorow — "The 'Enshittification' of TikTok" — The original January 2023 WIRED essay naming the three-stage pattern of platform decay.
  2. Neovim — Community fork of Vim that modernized the architecture with Lua scripting, async I/O, and LSP support.
  3. Cursor Pricing — $20/month Pro tier for AI-powered editing.
  4. OpenCode vs Claude Code vs Cursor: 2026 Comparison — NxCode's detailed comparison of the three dominant AI coding tools.