12 KiB
Architecture
Recommended shape
Lumbridge should be two cooperating Rust processes:
native desktop UI
| local authenticated IPC
Lumbridge session runtime
|-- local PTYs and process trees
|-- SSH/Tailscale transport to remote Lumbridge runtimes
|-- ACP clients and adapters
|-- repositories and worktrees
|-- usage/event ledger
`-- secret-store handles
Separating the UI from the session runtime lets terminals survive renderer restarts, permits a future headless/SSH mode, and gives persistence one owner. The first development build may run both in one process, but boundaries and IPC messages should be real from the beginning.
Planned crates
lumbridge-core: IDs, commands, events, errors, capability and usage types.lumbridge-runtime: session ownership, supervision, recovery, IPC server.lumbridge-pty: portable PTY and process-tree adapters.lumbridge-terminal: VT parsing, scrollback, selection, search, render model.lumbridge-acp: ACP client, capability negotiation, transcript normalization.lumbridge-harness: manifests, launch profiles, hooks, PTY fallback adapters.lumbridge-provider: BYOK providers and provider-neutral usage records.lumbridge-storage: SQLite migrations, event log, snapshots, retention.lumbridge-remote: OpenSSH/Tailscale command construction, framed stdio, handshake, reconnect, heartbeat, and remote-runtime discovery.lumbridge-secrets: Keychain/libsecret adapters and redaction.lumbridge-git: repositories, worktrees, diffs, status, conflict state.lumbridge-buzz: credential-free Buzz event/broker preparation and pane-share safety gates; platform adapters own signing and transport.lumbridge-ui: native desktop state and rendering.lumbridge: installable application entry point.
The scaffold currently contains lumbridge-core, lumbridge-storage,
lumbridge-buzz, lumbridge-pty, lumbridge-runtime, lumbridge-terminal, and
the entry point. lumbridge-core also contains the first typed workspace
command reducer. Larger runtime crates are added after their architecture spikes
pass.
Terminal path
The runtime owns PTYs, child process groups, resize signals, input ordering, and
raw output. The first lumbridge-pty boundary now uses portable-pty behind a
single-owner session, a fixed credential-free child environment, validated
sizes, an 8 KiB bounded output queue, resize/input/wait operations, and Unix
process-group cleanup. It deliberately does not emulate a terminal or persist
scrollback. The terminal engine turns output into immutable render snapshots and
bounded deltas for the UI. Scrollback is chunked and persisted separately from
the live screen to prevent large agent transcripts from blocking input.
The first lumbridge-terminal implementation wraps alacritty_terminal behind
a Lumbridge-owned input and immutable snapshot contract. It parses styled cells,
cursor state, alternate screen, terminal modes, title events, keyboard input,
bracketed paste, resize, and terminal-generated protocol replies. OSC 52
clipboard writes are disabled by default. The renderer never imports the
upstream terminal type, and protocol replies return through the same actor input
queue as human input. See decision 0005.
The first lumbridge-runtime actor now owns one PtySession on a dedicated
thread. Bounded command and event queues serialize input, resize, close, and
shutdown against ordered raw-byte output. The GPUI slice feeds one actor session
through the terminal engine while five surfaces retain deterministic comparison
output. Its adapter groups adjacent cells into native GPUI paint runs and renders
ANSI/indexed/RGB colors, emphasis, hyperlinks, and cursor shapes. Window geometry
drives terminal rows and columns and resizes both the engine and PTY. Selection,
scrollback navigation, mouse reporting, and a lower-level terminal canvas remain.
This actor still runs in-process; moving the same framework-neutral
contract behind local authenticated IPC is the next durability step. See
decisions 0004 and 0005.
Default workspace composition
The default desktop workspace is a vertical 3×1 composition: the top 20% holds pane, agent, tool, context, and goal state; the middle 60% is the active terminal or work surface; the bottom 20% is the answer, choice, chat, and approval shelf. Six deterministic surfaces still update in the spike so performance comparisons remain meaningful, but only the selected surface owns the large work region. Users may deliberately split that region later; a dashboard grid is not the calm default. See decision 0008.
We should evaluate, not blindly copy, WezTerm, Zellij, RMUX, tmux, and cmux. The first spike must compare a reusable terminal crate with a small first-party layer. Remaining correctness cases include OSC 8 links, Kitty keyboard/graphics negotiation, Unicode width, IME, mouse modes, shell integration, and simultaneous automation plus human input.
Workspace command plane
Human UI, local automation, remote clients, and agents reduce the same typed workspace commands. Stable request IDs make retries idempotent; validated pane IDs, split ratios, launch intents, and close dispositions prevent untyped JSON from becoming the product API. A multi-command setup plan is reduced atomically. Observe, Configure, and Execute capabilities are checked before mutation, and a layout-only agent cannot smuggle an executable or arbitrary arguments into a split request. Current code is an in-process reducer; serialization, durable events, and authenticated IPC are subsequent layers. See decision 0006.
UI decision gate
Do not lock the project to a webview or to Zed's application implementation details. The measured spike compares:
- GPUI for a Zed-like native model and excellent text-heavy interaction.
- Floem for an independent native Rust model with existing editor primitives.
The winner must render six busy panes smoothly, keep input latency low, support IME/accessibility, package on macOS and both Linux targets, and avoid a license or upstream-stability trap. Current Zed GPUI is the provisional winner because its AccessKit semantics compile while the pinned Floem revision has no semantic accessibility integration. This remains behind a narrow adapter until platform assistive-technology, IME, packaging, licensing, and release measurements pass.
Local-first and remote session path
SQLite is device-local. It stores workspace metadata, pane layouts, event and usage history, remote routing profiles, and small snapshots. It never stores SSH private keys, Tailscale credentials, provider API keys, or subscription tokens. Secrets remain in the OS credential store or in the user's existing SSH agent.
A remote pane is not a local PTY wrapped around a long-lived ssh process. Its
durable owner is a per-user lumbridge-runtime on the destination:
MacBook Lumbridge UI/runtime
|
| ssh host lumbridge remote connect --stdio
| or: tailscale ssh host lumbridge remote connect --stdio
v
remote per-user Lumbridge runtime -- Unix socket -- PTYs, agents, worktrees
|
`-- remote SQLite + chunked scrollback on that machine
The SSH child carries a versioned framed protocol over stdio. Normal OpenSSH
remains the default because it honors the user's config, agent, host keys,
ProxyJump, and Tailscale addresses. tailscale ssh is an explicit transport for
users who want Tailscale's SSH proxy and host-key path. Lumbridge does not
configure a tailnet, weaken ACLs, copy SSH keys, or require a listening TCP port.
The remote runtime assigns a stable session ID before acknowledging a launch. On network loss the local pane becomes disconnected, the remote PTY continues, and reconnect resumes from the last acknowledged output sequence. A second authorized Lumbridge installation can attach to the same remote session after the remote runtime arbitrates input ownership. Collaborative simultaneous input is not part of the first release.
Harness integration
Each harness is described by a versioned manifest: executable discovery, launch arguments, environment allowlist, resume semantics, status/usage probes, ACP command when available, and hook installation rules.
ACP is preferred because it provides structured prompts, plans, tool calls, permissions, content blocks, and session lifecycle. A supervised PTY is always available because terminal fidelity and arbitrary CLI compatibility are product requirements. Harness-specific adapters translate both paths into the same event model without pretending a PTY has capabilities it cannot prove.
Lumbridge Harness is a separate optional orchestrator, not a UI framework. The DeepSeek Harness snapshot is a useful MIT-licensed reference for plugin seams, durable session events, approvals, ACP, and agent lifecycle, but its developer- preview Node/TypeScript/web composition is not embedded into the Rust desktop process. If we maintain a fork, it lives in its own repository and communicates through a versioned, capability-scoped protocol. A first-party Rust service may later replace that implementation without changing the desktop contract.
The Harness can consume redacted workspace projections and emit suggestions or typed command plans. Suggestions have no authority. Plan execution is routed through the same command plane as human actions, and trace export to a hosted model requires explicit scope and destination consent. See decision 0007.
Usage model
Usage is an append-only observation stream, not a mutable percentage field. Observations include account profile, provider, harness, model, units, time window, reset time, provenance, confidence, and source timestamp. Projections are derived views that can be recomputed as forecasting improves.
Subscription balance is provider-specific and sometimes unavailable. BYOK calls usually expose token counts but cost still depends on cached tokens, reasoning, tool calls, and current pricing. Adapters normalize facts without erasing their source or uncertainty.
Buzz collaboration
Buzz channel messages, replies, agents, and attachments use the upstream Rust
SDK's signed Nostr semantics. SQLite stores a public identity and opaque
credential-store handle, never the private identity key. Pane images cross the
network only after local capture, redaction preview, explicit destination, and
confirmation. See BUZZ_INTEGRATION.md for the contract and test plan.
Persistence
SQLite in WAL mode stores metadata, commands/events, normalized usage, and small snapshots. Large scrollback chunks and binary attachments use content-addressed files. A write-ahead event is committed before an external mutation is reported as accepted. Startup replays incomplete operations and reconciles live children.
Default data roots are ~/Library/Application Support/ai.karti.lumbridge/ on
macOS and ${XDG_DATA_HOME:-~/.local/share}/lumbridge/ on Linux. Backups and
exports are explicit; Lumbridge does not synchronize the database through a
hidden hosted account.
Security
- macOS secrets: Keychain; Linux secrets: Secret Service/libsecret, with an explicit encrypted-file fallback only if the user enables it.
- Never pass keys in process arguments. Prefer inherited file descriptors or a minimal child environment when a harness requires environment variables.
- IPC is local, authenticated, permission-restricted, and version-negotiated.
- Transcript and crash-report redaction happens before persistence or export.
- Repository trust, harness approval mode, and sandbox mode are visible per pane.
- Remote control and plugins are out of scope until a capability/permission model exists.
Platform adapters
Shared contracts cover PTY, process tree, notifications, secret store, paths,
autostart, updater, and packaging. macOS uses forkpty/process groups and native
Keychain. Ubuntu and Omarchy use Unix PTYs, cgroups/systemd scopes when available,
and Secret Service. Omarchy is treated as Arch Linux, not as a separate kernel.