Agents animate in the terminal title while they work, and they do not agree on an alphabet: Claude Code cycles the quadrant circles and rests on an asterisk, others step through the braille frames, some write nothing at all. Rendered as they arrive, a column of tabs carries a mark in front of some rows and not others, in three vocabularies — while the row already says what the agent is doing, in one, with its status dot. So the mark comes off, for everyone, with no setting. A switch would not settle this: nobody opens settings to decide how a spinner is drawn, and a default-off toggle buys two render paths to maintain forever in order to answer a question that has one right answer per person and no way for the app to know which. **A known alphabet, not a shape.** The obvious rule — a leading character that is non-ASCII and above some code point, followed by a space — matches by shape, and `🔥 build`, or `📁 ~/repo` written by somebody's shell integration, fits it exactly and quietly loses its first character with no way to ask for it back and no clue as to what took it. Matching marks we have actually seen costs the same and cannot do that: the braille block, the four quadrant circles, and Claude Code's resting asterisk. When an agent invents a mark that is not on the list, the failure is today's behaviour — the mark stays — which is the safe direction to fail in, and adding it is a line in the table. Two things the rule insists on, both to keep it from reaching past what it is for. A mark only counts with whitespace behind it, so `✳fixing` is a word that starts with a character rather than a mark in front of one. And a title that is *only* a mark keeps it: taking it would leave an empty string, and an empty title is not a tab called nothing, it is a tab that falls back to its number — less than the mark was saying. It happens in `TabView::label`, which is where a title becomes a label, so the strip, the sidebar, the switcher and the rename box's prefill all agree without being told separately — and, because `label` reaches a given name before it reaches the title, a tab somebody deliberately called `✳ release` keeps what they called it. That ordering is the only thing standing between a user's name and a rename behind their back, so there is a test on it rather than a comment. Three existing tests carried `✳` in their fixtures and now expect it gone. The one in `switcher.rs` was asserting that a tab in another window is named the way a local one would be, which is still exactly what it asserts; the one in `tty7-cli` is the table getting this for free, since `tab_label` reads `label` and so `tty7 ls` says what the tab strip says without either being told about the other. The daemon's fixtures keep their marks on purpose: a title is stored as the terminal wrote it, and only what turns one into a label takes anything off. This leaves the row with nothing moving in it, which is a real loss and is answered separately: `AgentStatus::dot_rgb` returns three flat colours, and a `Working` dot that breathes says the same thing in the vocabulary the row already speaks.
tty7
A terminal workbench: persistent sessions, remote work, agents.
Pure Rust · GPU rendering on Zed's gpui · VT core from Alacritty
English · 简体中文
Why
A background server owns your shells and panes — not the window. Everything below follows from that.
- Performance — ~2× the throughput of Alacritty, Ghostty, or Kitty (benchmarks)
- Persistent sessions — quit or reboot; your shells and supported agent sessions keep running, no tmux
- Agent-aware — Claude Code, Codex & co.: status, notifications, and git context for every repo at once
- Scriptable by agents — one agent opens a pane for another, hands off a task, waits, and reads the result, with or without the GUI running
- Editor-grade input — suggestions, completion, highlighting, history search, with no plugin to install
- Remote development — files, repos, panes, and git data stay on the remote machine, over a native SSH stack
- Git beside the terminal — source control, diffs, and worktrees without leaving the window
Install
Native builds for macOS, Windows, and Linux on Releases:
| macOS | …-macos-arm64.dmg · …-x86_64.dmg |
drag into Applications |
| Windows | …-setup.exe · portable ….zip |
|
| Linux | …-x86_64.AppImage |
chmod +x and run — X11/Wayland libraries bundled |
What's inside
| Agent-aware | per-pane detection (20 CLIs) · status dot · notifications · branch + diff · tray icon when input is needed · resume after reboot · tab sidebar grouped by repository |
| CLI + Skills | bundled tty7 CLI · agent skill · run streams a command and exits with its code · split · send · wait --until free · capture |
| Editor-grade input | ghost suggestions from history · explained tab completion · syntax highlighting · multi-line editing · click places the caret · ⌃ R fuzzy history |
| Window | tabs & splits · ⌘ P palette · ⌘ F scrollback search · ⌘ J panel with process tree and listening ports · 13 themes, your own YAML, iTerm2 import · IME |
| Shell integration | injected when a pane starts, nothing to install · prompt marks · working directory · exit codes · command-finished notifications · zsh, bash, fish, PowerShell, WSL, remote panes |
| Remote workspaces | remote files, repos, changes, diffs, worktrees, tabs, and panes · reconnect from any client and continue where you left off |
| SSH | native russh stack: profiles with keychain secrets · SFTP panel · port forwarding · jump hosts · one-time, unprivileged tty7-server install |
| Git | panel follows the focused pane · stage, commit, amend, branch, push, stash · side-by-side or unified diffs · commit graph with cherry-pick, revert, and reset · a new worktree opens its own tab |
Supported agents
Detection is free: brand avatar, branch + diff, tab title.
Status takes one click under Settings → Agents to install that agent's hook,
and brings the status dot, notifications, the tray icon, tty7 wait, and resume
after a reboot. Fork needs both — the agent's own fork command, and the hook
that tells tty7 which session to fork.
The full support matrix, all twenty
| Agent | Detected | Status · resume | Fork |
|---|---|---|---|
| Claude Code | ✓ | ✓ | ✓ |
| Codex | ✓ | ✓ | ✓ |
| TraeCode | ✓ | ✓ | ✓ |
| Grok | ✓ | ✓ | ✓ |
| OpenCode | ✓ | ✓ | ✓ |
| Oh My Pi | ✓ | ✓ | ✓ |
| Droid | ✓ | ✓ | ✓ |
| Qwen Code | ✓ | ✓ | ✓ |
| Goose | ✓ | ✓ | ✓ |
| Gemini | ✓ | ✓ | |
| Copilot | ✓ | ✓ | |
| Kimi Code | ✓ | ✓ | |
| Pi | ✓ | ✓ | |
| Aider | ✓ | ||
| Amp | ✓ | ||
| Cursor | ✓ | ||
| Auggie | ✓ | ||
| Hermes | ✓ | ||
| Vibe | ✓ | ||
| Antigravity | ✓ |
None of them are wrapped or proxied — the agent you start is the agent you get,
in a normal PTY, with its own interface. An agent launched through a wrapper
script can be mapped to one by name with agent_commands in config.json.
Documentation
Full documentation lives in docs/ —
keyboard shortcuts ·
config.json ·
CLI reference. The agent-facing CLI interface is also
documented in skills/tty7/SKILL.md.
Install the skill with:
npx skills add l0ng-ai/tty7 # install
npx skills update tty7 # update later
Benchmarks
Same machine, same day, same 155×40 grid — Apple M1 Pro, macOS 26.3.1, five-run averages (2026-07-04):
| tty7 | Alacritty | Ghostty | Kitty | |
|---|---|---|---|---|
Plaintext I/O — 11 MB cat (lower = better) |
95 ms | 239 ms | 179 ms | 185 ms |
| DOOM-fire frame rate (higher = better) | 888 fps | 485 fps | 552 fps | 617 fps |
| Cold-launch memory | 116 MB¹ | 105 MB | 128 MB | 130 MB |
¹ GUI 105 MB + the persistent server 11 MB.
Methodology and one-command reproduction: scripts/bench/.
Built on gpui and alacritty_terminal · Apache-2.0 · Discord · Changelog