"Approximately 71 columns by 42 rows per panel" was derived from a hardcoded 8.4 px advance that nothing had measured. The measured advance is about 7.3 px, so the figure was roughly 13% short — a full-width pane on the reference display measures 140 columns, not 122. The paragraph now says the number is a consequence of the measurement rather than a property of the design. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
342 lines
19 KiB
Markdown
342 lines
19 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-theme`: derives the interface palette from a syntax theme's anchors.
|
|
- `lumbridge-acp`: ACP client, capability negotiation, transcript normalization.
|
|
- `lumbridge-harness`: manifests, launch profiles, hooks, PTY fallback adapters,
|
|
and documented status/usage probes.
|
|
- `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`,
|
|
`lumbridge-harness`, `lumbridge-theme`, `lumbridge-ui-fixture`, and the `lumbridge` application
|
|
itself. The GPUI shell was a spike in an excluded workspace until it graduated
|
|
into `apps/lumbridge`; `scripts/ci.sh` now takes `--headless` and `--ui` so the
|
|
non-UI crates still build in seconds, and `cargo deny check licenses` gates the
|
|
dependency closure that graduation made the product's. See decision 0017. `lumbridge-core` also contains the
|
|
first typed workspace command reducer and the usage ledger. 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.
|
|
|
|
Terminal cell size is measured from the text system rather than assumed, and the
|
|
viewport's pixel extent is reported to the PTY. Both were wrong before: the
|
|
advance was a hardcoded guess 13% wider than the face actually painted, and
|
|
`ws_xpixel`/`ws_ypixel` were zero, so every program doing pixel arithmetic was
|
|
told the window had no size.
|
|
|
|
Each `lumbridge-runtime` actor owns one `PtySession` on a dedicated thread.
|
|
Bounded command and event queues serialize input, resize, close, and shutdown
|
|
against ordered raw-byte output. A generic pane-indexed registry now owns and
|
|
routes multiple actors without knowing whether their panels are attached. The
|
|
GPUI slice feeds three independent actor sessions through three terminal engines
|
|
while three non-terminal 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. The engine
|
|
now exposes retained-history offsets and page/top/bottom viewport movement
|
|
without writing scroll keys to the PTY. Text selection, mouse reporting, and a
|
|
lower-level terminal canvas remain. These actors still run 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 responsive horizontal row of self-contained
|
|
vertical panels: one panel in compact windows, three at normal desktop widths,
|
|
and five on a 3440 px ultrawide. Inside every panel, the top 20% holds that pane's
|
|
agent, tool, context, goal, target, and status; the middle 60% holds its terminal
|
|
or work surface; and the bottom 20% holds that pane's answer, choices, chat, and
|
|
approval boundary. The selected panel alone owns keyboard input. Six surfaces
|
|
continue updating so performance comparisons remain meaningful, with a sliding
|
|
visible window keeping the selected pane onscreen. See decisions 0008 and 0009.
|
|
|
|
Panel attachment is independent from session lifetime. Detaching removes a pane
|
|
from the layout tree but retains its definition in the workspace's detached
|
|
registry and leaves its runtime-owned process alive. Reattaching restores that
|
|
identity without a launch. Terminating an attached or detached session is a
|
|
separate Execute-capability operation. The GPUI slice now layers an unbounded
|
|
panel registry above the six-surface comparison fixture. New Terminal, Browser,
|
|
Markdown, and Review panels receive monotonic local IDs, are inserted beside
|
|
the selection, and persist their identity, order, selection, and attachment in
|
|
a credential-free SQLite snapshot. Each Terminal panel is keyed into the
|
|
runtime registry by that durable panel ID. See decisions 0010 and 0011.
|
|
|
|
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.
|
|
|
|
Floem is frozen: the scorecard records it failing the accessibility hard gate
|
|
with no AccessKit at its pinned revision, and comparability on the footer strip
|
|
was already lost when the usage ledger landed. It is retained as evidence at the
|
|
revision where the comparison was made, not maintained in parity. The product
|
|
ships on published `gpui 0.2.2`, which has no AccessKit either, behind a narrow
|
|
adapter that no-ops today and calls real roles on a Zed revision later — so that
|
|
gate is knowingly unmet and staged rather than quietly dropped. See decision
|
|
0017.
|
|
|
|
The original comparison bar was that 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.
|
|
|
|
## Theme model
|
|
|
|
Every colour in the interface is a named semantic role derived from a syntax
|
|
theme's five anchors, not a constant. `lumbridge-theme` holds the derivation and
|
|
no framework types, so the arithmetic is tested headless; the shell converts a
|
|
derived palette into GPUI colours once per theme change and owns the result.
|
|
|
|
The frame is the editor background pushed one logarithmic contrast step away
|
|
from the content, so the work surface is the brightest thing on screen; a theme
|
|
already at black lifts its surface instead. Provenance colours are separate
|
|
roles from state colours, with a test holding them pairwise distinct, because
|
|
decision 0012 colours a usage reading by its source and never by its value.
|
|
|
|
The default theme pins its roles to the eleven constants that preceded it, so
|
|
introducing the engine changed no pixels. The terminal ANSI palette and the
|
|
theme catalog are still fixed tables. See decision 0018.
|
|
|
|
## 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.
|
|
|
|
`lumbridge-core` now implements that stream. A bounded per-profile `UsageLedger`
|
|
accepts observations in time order, and `project` derives the footer view:
|
|
window fraction, reset, burn rate, and exhaustion. Facts keep their reported
|
|
provenance; every derived value is labelled estimated. The projection withholds
|
|
a burn rate from a single sample, withholds a window fraction without a reported
|
|
ceiling, withholds an exhaustion estimate that falls after the reset, treats an
|
|
unavailable observation as invalidating older facts, and reports an expired
|
|
window as rolled over rather than freezing its last percentage. `FooterUsage`
|
|
renders the five product-spec questions and returns explicit unavailable phrases
|
|
instead of placeholder numbers. See decision 0012.
|
|
|
|
`lumbridge-harness` is the other side of that boundary: it runs processes,
|
|
reads a clock, and parses untrusted wire text, then hands back observations.
|
|
Its first adapter probes Codex's documented `account/rateLimits/read` surface
|
|
over the app-server's JSON-RPC stdio protocol. The client cannot express a
|
|
request outside a two-variant enum and answers every server-to-client request
|
|
with `-32601`, so a harness asking Lumbridge for a credential is refused by
|
|
construction. All protocol decisions live in a pure state machine driven by
|
|
`&str` lines and an explicit timestamp, with a `replay_transcript` entry point
|
|
that verifies the whole path against a fixture without a process, a clock, or a
|
|
network. See decision 0013.
|
|
|
|
Its second adapter reads Claude Code's session transcripts, the only local
|
|
surface that reports what that harness spent. It is a tail follower with a
|
|
per-file byte offset, so its total is monotonic, and it reports nothing until
|
|
it has read the existing backlog to the end — a partially-read backlog is
|
|
indistinguishable from a burst of spend, and the ledger would derive a rate
|
|
from it. The transcript carries no window or ceiling, so that profile reports
|
|
consumption against none. The parser models four token counters and nothing
|
|
else, so the conversations in those files are not representable in a Lumbridge
|
|
value. See decision 0014.
|
|
|
|
Claude Code's subscription windows come from two further surfaces, because
|
|
neither answers the whole question. The CLI pipes a `rate_limits` object
|
|
carrying the five-hour and seven-day windows to whatever `statusLine` command
|
|
the user has configured, on every turn; a small installed bridge writes those
|
|
fields — and only those — to a local feed the probe tails, for free and without
|
|
a credential (decision 0015). The account usage endpoint supplies what the
|
|
status line cannot: the per-model `weekly_scoped` limits a Max plan meters
|
|
separately, and an answer on a cold start before any session has taken a turn.
|
|
Reaching it means reading Claude Code's stored access token, which `AGENTS.md`
|
|
now permits under a narrow named allowance — one documented question about the
|
|
user's own account, never persisted, never logged, never in argv, no refresh
|
|
token, identified as Lumbridge rather than as the harness, and switchable off
|
|
(decision 0016).
|
|
|
|
The endpoint is the authority whenever it answers; the status line covers the
|
|
interval between its deliberately slow refreshes. Both are `ProviderReported`
|
|
and share the window arithmetic, so one quota read two ways cannot produce two
|
|
numbers. Per-model limits arrive as profiles the probe announces at runtime,
|
|
since their names come from the response.
|
|
|
|
The GPUI shell now runs both probes; a probe contributes its own profiles on
|
|
top of the declared ones, so the Claude windows appear once they report. Its
|
|
footer strip
|
|
shows every declared harness at once, and a harness with no adapter renders
|
|
the honest gap.
|
|
|
|
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.
|