A native T3 Code-style desktop client for coding agents, built with GPUI and GPUI Kit. It drives Claude Code, Pi, jcode and Empryo.
Every thread runs its own agent process in the background. Claude Code threads talk to the
claude CLI over the stream-json protocol the Claude Agent SDKs use
(--input-format/--output-format stream-json plus the stdio control channel). Pi threads
talk to pi --mode rpc over its JSONL RPC protocol. jcode threads talk to
jcode api --stdio, a bridge to jcode's versioned harness API that starts jcode's shared
daemon if it isn't running, so jcode threads also show up in jcode's own session list.
Empryo threads talk to empryo --headless --chat --events, Empryo's headless chat: a message
per line on stdin, its events as JSONL on stdout, and a session the thread names
(--session-id) and resumes (--session).
The layout and workflow follow T3 Code. The look follows MetalUI, the "soft hardware" design system vectrum.cc is built on: the conversation sits on a raised sheet, buttons are caps that sink when pressed, code and tool output sit in inset wells, and labels are engraved in mono caps. It comes in MetalUI's two colorways, Bone and Graphite, plus popular color schemes rebuilt in the same style: Tokyo Night, Catppuccin, GitHub, One, Nord, Gruvbox, Rosé Pine, Solarized and Dracula.


> searches actions only.empryo --list-models).
Star models to keep them under Favorites, search across all of them, pick the first nine
with Ctrl+1–9, and switch providers with Ctrl+Shift+↑/↓. On a new thread, picking another
provider's model starts a thread of that kind in its place; once a thread has started,
other providers' models are greyed out, as in T3 Code. /model opens the picker too.--effort). All of it switches live; an Empryo thread
restarts Empryo with the new flags once its turn is done, resuming its session.--mode auto). With plan mode turned on (Settings → General → Legacy features), a
Build/Plan toggle and Shift+Tab switch Claude, or Empryo (--mode plan), into plan mode and
back.```mermaid
draws a diagram in the theme's colors. ```openui (OpenUI Lang)
draws native cards, stats, tables, charts, tabs and forms; their state stays put while
you scroll, and buttons and form submissions reach the agent as [UI] messages.
```svg draws the image. ```html gets a Preview button that opens the page in the
thread's browser panel. Every block keeps Copy and Show source. Agents are told
how to write them; Settings → General → Inline diagrams and UI turns it all off.@ lists the thread folder's
files and folders to mention. An optional ring (Settings → General → Legacy features)
shows how full the context window is, with the numbers and Compact context on hover.subagent extension
(a row per task, single, parallel or chained); and jcode's swarm, whose agents keep
working in sessions of their own after the turn ends: the app checks on them every few
seconds through jcode's session list, and reads what each last said.delegate_task,
task_status, task_cancel and orchestrator_capabilities, with instructions for
them. A delegated task runs in a subagent thread in the parent's folder, on the
parent's agent and model or any other agent and model the app has (Claude, Pi or
jcode). The subagents panel shows it working and opens its thread. The thread is
left out of the sidebar, and its header leads back to the parent. When the task
finishes, its answer goes to the call waiting on it (mode: "wait") or reaches the
parent's agent as a message the transcript doesn't show, which wakes it. Nothing to install: Claude reaches the
tools through an MCP server the app runs on loopback, with a token per thread.
Pi reaches them through an extension the app writes and loads with --extension;
before every run, that extension also switches off Pi's other subagent tools.
jcode gets them as session tools over its API, with its swarm and schedule
tools turned off. Subagents can't delegate tasks of their own..empryo/genome.db. A graph of the
48 highest ranked files, sized by rank and colored by folder, with a line for each import,
and the files ranked below it with how many files import each (→12) and what changed in
the last two days. A file opens to its symbols, exported ones first with their signatures,
and lights up with its imports in the graph. It follows the thread to its worktree and
reloads as Empryo updates the map.agent-browser CLI
while the panel shows it live. Click, scroll and type in the page yourself, or go to a
URL. When the agent opens a page while the panel is closed, a light on the globe says so.device_list,
device_open, device_screenshot and device_close, and drive devices with the
agent-device CLI through a launcher the app
puts on their PATH. A device an agent opens floats over the chat (or opens in the right
panel with Auto-show floating preview off), and its activity shows in the timeline.
Both tools are npm packages the app installs at pinned versions into its data folder and
runs with your Node; quitting stops them, while simulators and emulators keep running.
A thread on another device uses that device's simulators and emulators: its Device panel
lists, starts and controls them over the remote connection, and the video and touches go
through the other device's remote server, which passes only the stream routes on to its
hub, and only with its key.vectrum/<8 hex> branch checked out in a folder named after
it (under ~/.local/share/vectrum-code/worktrees/<project>, or the worktree location in
Settings → Storage), optionally from origin's copy of the base branch, with submodules
filled in. A setup card shows each stage (with Work locally and Cancel), and the
branch is then renamed after the first message (Settings → Source Control → Worktree
branch naming: a prefix plus a few words, a semantic prefix like feat/, or your own
instructions). Ctrl+Shift+L starts in the project's previous worktree instead./ menu with Claude's commands and skills ($ for skills alone), docked on the composer.SHA256SUMS
and whose sums carry the release key's Ed25519 signature. Run elsewhere, the button opens
the release page instead. With this app up to date, the same button shows Update N
servers for connected devices on an older version: each downloads and checks the release
itself, replaces its AppImage and restarts, and the connection comes back on the new version.android/), in Kotlin and Jetpack Compose:
a remote client that looks like the desktop, in the same themes. Pair it by scanning the QR
code under Settings → Connections; the sidebar, threads, composer and its pickers,
approvals and questions, diffs, settings, terminals and the Device panel work from the
phone, on the desktop's agents and files./ to search, ↑/↓ and Enter to jump to a setting,
which scrolls into view and pulses), a "Settings / Page" breadcrumb, and every setting as
a row with its description, its control and, once changed, a button that resets it.
Restore device defaults on General lists what it will reset before it does.
General, Integrations, Source Control and Storage open with T3 Code's "Applying settings
for All projects across All environments": the settings that shape threads (new
thread defaults, auto-settle, worktrees and their cleanup, branch naming, writing style,
text generation, browser access) belong to the machine the threads run on, so you can edit
this device's, one connected device's, or every connected device's at once. A Mixed tag
shows where they disagree (hover for each one's value), and picking a value applies it to
all of them; offline devices keep theirs. Pick a project to override settings for it on the
selected devices: overridden rows say Override, and their reset button returns them to
the environment's value. Device preferences, such as appearance and confirmations, ignore
the selection.
/
menu, the rich text composer, collapsing on scroll, the send shortcut (Enter, Ctrl+Enter
for multiline prompts, or Ctrl+Enter always), follow-up behavior, update checks,
continuing after restarts, background activity); Projects & threads; Confirmations
(unpin, archive, delete, and the quit shortcut: Direct, Hold or Double press, and a close
confirmation that Ctrl+Q, Alt+F4 and the window's close button go through); the text
generation model; the version; Diagnostics (CPU, memory and every process the app
started, with interrupt and kill buttons) and the open source licenses; and the folded
Legacy features (plan mode, the context window ring, the per-project sidebar).secrets.json only you can read), and its models: favorites, which ones the picker
shows, their order, and custom models. Add provider walks through kind, identity and
configuration. Below: usage hubs (CLIProxyAPI servers) and the health check interval.claude CLI, logged in (claude once interactively)npm install -g @earendil-works/pi-coding-agent, then log in to a provider with pi)curl -fsSL https://jcode.sh/install | bash, then jcode login)curl -fsSL https://empryo.com/install.sh | bash, then empryo --login claude or another
account, or empryo --set-key <provider> <key>). Add it in Settings → Providers → Add provider.npm install -g agent-browser && agent-browser install)ANDROID_HOME.vulkan-icd-loader plus your GPU's Vulkan driver, wayland,
libxkbcommon-x11, libxcb, fontconfig, freetype2)cargo run --release -- /path/to/project # defaults to the current directory
The shortcuts follow T3 Code's default keymap.
| Shortcut | Action |
|---|---|
| Enter / Shift+Enter | Send / new line |
| Esc | Interrupt Claude |
| Ctrl+K | Command palette |
| Ctrl+N (or Ctrl+Shift+O) | New thread (picks the project when there are several) |
| Ctrl+Shift+N | New thread in the current project |
| Ctrl+Alt+N | New thread without a project |
| Ctrl+Shift+[ / Ctrl+Shift+] | Previous / next thread |
| Ctrl+1–9 | Jump to a sidebar thread (hold Ctrl to see the numbers) |
| Ctrl+Shift+S | Settle the thread, or un-settle it |
| Ctrl+Shift+P | Pin or unpin the thread |
| Ctrl+Z | Undo the last settle, snooze, unpin or archive (while the notice shows) |
| Ctrl+Shift+C | Copy the thread's PR link, or its ID |
| Ctrl+U | Usage |
| Ctrl+Alt+A / Ctrl+Alt+Shift+A | Change the theme / cycle System, Light and Dark |
| Ctrl+Shift+M / E / A | Model picker / reasoning / access |
| Ctrl+Shift+G / X / H | Branch / workspace / machine (new thread) |
| Ctrl+Shift+L | Start in the previous worktree |
| Ctrl+S | Stash the prompt, or open the stash |
| Alt+↑ | Edit the last queued message |
| Ctrl+F | Find in thread |
| Ctrl+B | Show or hide the sidebar |
| Ctrl+J | Show or hide the terminal drawer |
| Ctrl+D / Ctrl+Shift+D | Split the terminal side by side / stacked (terminal focused) |
| Ctrl+N / Ctrl+W | New terminal / close the terminal (terminal focused) |
| Ctrl+Shift+C / Ctrl+Shift+V | Copy / paste in the terminal |
| Ctrl+Alt+B | Show or hide the right panel: Open a surface |
| Ctrl+Shift+B | Show or hide the thread details (project panel) |
| Ctrl+Alt+S | Show or hide the subagents panel |
| Ctrl+O | Add a project folder |
| Ctrl+, | Open or close settings |
| Ctrl+Q | Quit |
VECTRUM_CODE_CLAUDE_BIN: path to the claude binary. Otherwise it's looked up on PATH,
then ~/.local/bin and ~/.claude/local.VECTRUM_CODE_PI_BIN: path to the pi binary. Otherwise it's looked up on PATH, then in
~/.local/bin, ~/.npm-global/bin, ~/.bun/bin, ~/.volta/bin and nvm's Node versions.VECTRUM_CODE_JCODE_BIN: path to the jcode binary. Otherwise it's looked up on PATH,
then in ~/.local/bin and ~/.cargo/bin.VECTRUM_CODE_EMPRYO_BIN: path to the empryo binary. Otherwise it's looked up on PATH,
then in ~/.empryo/bin and ~/.local/bin.VECTRUM_UPDATE_URL: where to look for updates instead of https://vectrum.cc, such as a
test server. Releases from there still need the release key's signature.Favorite models and everything in the settings view are saved in
~/.config/vectrum-code/settings.json. The environment variables above win over the binary
paths set there for each agent's first instance. Threads and the project list live in
~/.local/share/vectrum-code (threads/, state.json); threads started without a project run
in its scratch folder.
./build.sh builds the release binary and an AppImage into build/. It builds in an Ubuntu
24.04 container, so the binary needs only glibc 2.39 and runs on Ubuntu 24.04, Fedora 40,
Debian 13 and newer; that needs Docker (directly, through sudo, or as DOCKER="…"), and
the image, target dir and cargo cache are kept in target/ubuntu-24.04. --native builds with
the host toolchain instead, for the host's glibc or newer. -v 0.1.2 first sets the
version (in Cargo.toml and Cargo.lock, committed as "Version 0.1.2"), and --release (or
-r) publishes the build on Vectrum: it tags the commit with the version, pushes the tag, and
uploads the AppImage, the binary, CHANGES.md (the commit subjects since the last release,
which the update button lists), SHA256SUMS and SHA256SUMS.sig, which publishes the
release. The signature is made with the Ed25519 key in VECTRUM_SIGNING_KEY or
~/.config/vectrum/release-signing-key.pem, whose public half must be in RELEASE_KEYS in
src/update.rs; installed apps only update to releases it signed, so keep it safe. So
./build.sh -v 0.1.2 -r makes and publishes 0.1.2. Releasing needs a clean working tree, a
version not tagged yet, and a read & write API token in VECTRUM_TOKEN or
~/.config/vectrum/token; the version commit stays on your branch until you push it.
VECTRUM_URL (https://vectrum.cc) and VECTRUM_REPO (from the origin remote) pick where
it goes.
cd android && ./gradlew :app:assembleDebug builds the companion app into
android/app/build/outputs/apk/debug/ (assembleRelease for a shrunk build, signed with the
debug key until there's an upload key). It needs JDK 21 and the Android SDK (platform 37) in
ANDROID_HOME or android/local.properties. The themes come from the desktop's palettes:
after changing them, cargo test export_android_themes -- --ignored rewrites
android/app/src/main/assets/themes.json.
src/claude.rs: spawns and owns the agent process, claude, pi, jcode api --stdio or
empryo --headless --chat
(stdin writer and stdout/stderr reader threads).src/session.rs: per-thread state. Folds Claude's stream-json, Pi's RPC, jcode's
harness API and Empryo's headless events into chat items, handles permission requests, control requests and
extension dialogs, and snapshots each turn's changes.src/models.rs: the model catalog across providers, and favorites.src/genome.rs: reads Empryo's Genome (.empryo/genome.db) and lays out its graph.src/git.rs: branch and change stats, working-tree snapshots, and diffs.src/settings.rs: the saved settings file, cached in memory.src/update.rs: the app's updater: release checks, signature and checksum checks, the
staged download beside the AppImage, and the restart.src/terminal.rs: a shell in a pseudo-terminal, emulated by alacritty_terminal, and the
bytes keys and pastes send to it.src/ui/terminal.rs: one terminal's view: the grid in the mono font, cursor, selection,
scrollback and its right-click menu.src/ui/chat.rs: start screen, transcript (with turn folding), tool cards, permission bar
and composer.src/ui/pickers.rs: the model, reasoning and access pickers.src/ui/settings/: the settings view (mod.rs: nav, search, top bar), the row and
section building blocks (widgets.rs), the search catalog (search.rs) and a file per
page.assets/licenses.tsv: the crates the app is built from, with their licenses, for the
Open source licenses page (regenerate it from cargo metadata after changing
dependencies).src/ui/changes.rs: the per-turn change overview and the diff view.src/ui/workspace.rs: window root: thread sidebar, top bar and project panel, plus the
git and editor helpers behind it. Its submodules hold the sidebar (sidebar.rs, and the
legacy layout in sidebar/legacy.rs), the thread menus, snoozing, confirmations and undo
(actions.rs), the chat header (header.rs), the command palette (palette.rs) and the
terminal drawer (terminal_drawer.rs).src/ui/icons.rs: the extra Lucide icons the app embeds.src/ui/metal.rs: the MetalUI layer. Colorway tokens and shadow recipes copied from
MetalUI's CSS, the theme mapping for the stock widgets, and the primitives (caps, wells,
keycaps, status LEDs, segmented switch).src/ui/themes.rs: the color schemes, the palette builder that turns each into a full
MetalUI palette, the light/dark/system selection, and the theme families.assets/fonts/: Geist, Geist Mono and Geist Pixel, embedded in the binary
(SIL Open Font License, see assets/fonts/OFL.txt).There is no UI for Pi extensions that ask for free text, code blocks have no syntax highlighting, and T3 Code's project scripts aren't here yet. The terminal drawer doesn't report mouse clicks to full-screen programs (only the wheel), open links, or show T3's popup after a selection. jcode models can't write thread titles (every jcode run is saved as a session), and jcode's side panel, applets and background-task progress aren't shown. Empryo threads don't get the app's subagent and device tools (Empryo loads MCP servers from its config files only), take images by path rather than inline, and can't be steered mid-turn: a message sent while Empryo works becomes its next turn.