231 lines
12 KiB
Markdown
231 lines
12 KiB
Markdown
# Architecture
|
||
|
||
## Recommended shape
|
||
|
||
Lumbridge should be two cooperating Rust processes:
|
||
|
||
```text
|
||
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:
|
||
|
||
1. GPUI for a Zed-like native model and excellent text-heavy interaction.
|
||
2. 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:
|
||
|
||
```text
|
||
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.
|