- Persistent terminal sessions that survive client disconnects, SSH
drops, and laptop suspends. PTY ownership lives in
pyred, not in any UI process. - Process-first model: every command run is a structured
Blockwith start/end/exit-code/duration, not just a stream of bytes. - Single binary set replaces the
tmux + alacrittycombination for a Linux daily driver — multiplexing, reattach, and rendering converge. - First-class agent integration: panes are MCP resources that any MCP client can read, search, and act on without screen-scraping.
- Scriptable via Lua (mlua 5.4) at well-defined hook points.
- Searchable history across all sessions via Tantivy.
- Windows / macOS support in S0–S6. Linux-first, no portability tax.
- Electron, web UI, or cloud sync by default.
- Telemetry of any kind.
- Reimplementing an ANSI parser — reuse
alacritty_terminal. - Replacing the user's shell.
pyreruns$SHELL; it does not embed one.
| Term | Definition |
|---|---|
| Session | A named collection of panes owned by pyred, persisted across client lifetimes. |
| Pane | A single PTY + parsed grid + scrollback + block log. Belongs to one session. |
| Block | One command execution: prompt boundary → user input → command output → exit. Keyed by OSC 133 markers. |
| Command | The text the user submitted that started a Block. |
pyrec (CLI) and pyre (renderer) are clients of pyred over a
local Unix domain socket. pyred owns every PTY via portable-pty,
runs the ANSI parser (alacritty_terminal), emits Block events to the
SQLite store, and streams updated grid frames + block deltas back to
attached clients. The wire format is defined in the pyre-proto crate
and versioned from day one (proto_version: u32).
pyred ships two process models, selectable via
pyred.process_model = "single" | "hybrid" (default "single").
- Single (Option A in ADR-002): one
pyredprocess owns the public socket, theSessionRegistry, all PTYs, the SQLite pool, and the Tantivy writer. Simplest, lowest memory, no crash isolation. - Hybrid (Option C in ADR-002, accepted 2026-05-19): a thin
supervisor binds the public
pyre.sockand owns the aggregated Tantivy index. Each session runs as a worker process, owning its own PTYs and a per-session UDS at$XDG_RUNTIME_DIR/pyre/session-<id>.sock. Stream-mode (0x02) attaches are proxied transparently from the supervisor to the worker. Worker crashes evict only that session; the supervisor survives.
See docs/adr/0002-daemon-process-architecture.md for the full decision record and migration triggers.
Blocks are demarcated by the standard OSC 133 prompt sequences emitted by a cooperating shell (zsh/bash with a small rc snippet, fish native):
| Marker | Meaning |
|---|---|
| OSC 133 ; A | Prompt start. Closes any open Block. |
| OSC 133 ; B | Prompt end / command start. Captures the command text. |
| OSC 133 ; C | Command output start. |
| OSC 133 ; D ; | Command end with exit code. Block finalized. |
Each finalized Block carries {id, pane_id, session_id, command, started_at, ended_at, exit_code, cwd, stdout_offset, stdout_len}.
Defined in pyre-proto. Request/response + a streaming output channel.
Transport TBD in ADR-001 (tonic over UDS vs tarpc over UDS).
| Method | Purpose |
|---|---|
Spawn |
Create a new pane in a session, run $SHELL or given argv. |
Attach |
Subscribe to a pane's output stream and current grid snapshot. |
Detach |
Drop a client subscription without killing the pane. |
Resize |
Change cols/rows for a pane. |
Kill |
Terminate a pane (SIGTERM, then SIGKILL after grace). |
ListSessions |
Enumerate sessions + panes + last-active timestamps. |
OutputFrame (stream) |
Server-push grid delta or raw bytes + block events. |
BlockEvent (stream) |
Push on Block start/end with full Block record. |
Every message carries proto_version. Mismatched clients are rejected
with a versioned error envelope; no silent fallback.
Single file at $XDG_DATA_HOME/pyre/state.db. WAL mode. Three core
tables:
| Table | Columns (essentials) |
|---|---|
sessions |
id, name, created_at, last_active_at |
panes |
id, session_id, argv, cwd, cols, rows, created_at, closed_at |
blocks |
id, pane_id, command, started_at, ended_at, exit_code, cwd, stdout_blob_path |
Stdout is stored as compressed blobs on disk (zstd) referenced from
blocks.stdout_blob_path; the SQLite row carries metadata only.
One index per user at $XDG_DATA_HOME/pyre/index/. Documents are
Blocks. Fields: command, stdout (tokenized), cwd (raw),
exit_code (i64), started_at (date). Queries from pyrec search "<query>" or via MCP.
- A pane survives every client disconnect.
pyredkeeps reading the PTY and updating the parsed grid + block log. - On
Attach, the daemon sends the current grid snapshot + the last N (configurable, default 1000) Blocks for that pane, then begins streaming live deltas. - Multiple clients may attach to the same pane simultaneously
(collaboration / mirror mode). Input from any client is serialized
by
pyredbefore being written to the PTY.
User config at $XDG_CONFIG_HOME/pyre/init.lua. Hooks:
| Hook | Fires on | Receives |
|---|---|---|
on_pane_spawn |
New pane created | pane table |
on_block_end |
Block finalized (OSC 133 ; D) | block table |
on_attach |
Client attaches to a pane | pane, client |
on_keybind |
User-defined chord matched in TUI | event |
Hooks run in a sandboxed Lua VM owned by pyred (no io, no os.execute
unless explicitly enabled in config).
pyred exposes an MCP server on the same UDS (or a sibling socket).
Resources:
pane://<session>/<pane_id>— read current grid + last K Blocks.block://<block_id>— read one Block (command + stdout).- Tools:
search_blocks(query),spawn(argv, session),send_input(pane, text).
This lets any MCP client consume pane state without screen-scraping. See docs/AGENTS.md for the agent multiplexer playbook.
- UDS at
$XDG_RUNTIME_DIR/pyre.sockwith mode0700, owned by the invoking user. No network listener by default. - Lua sandbox: no filesystem or process access unless
[lua].unsafe = truein config. - MCP tool calls that mutate state (
spawn,send_input) require an explicit[mcp].allow_mutations = truein config; read-only by default. - No telemetry, no auto-update, no cloud calls. Ever.
- Windows and macOS support.
- Built-in shell, multiplexer protocol compatibility with tmux.
- Cloud sync, account systems, network-exposed daemon.
- Reimplementing the ANSI parser, a cron scheduler, or an external experiential memory layer — pyre exposes blocks and hooks locally.