LFG Architecture map branch codex/deprecate-web-architecture
View
Self-hosted control plane · Bun + native Apple clients

One host, many agents, one journal.

LFG runs beside your AI coding agents, discovers their processes, normalizes their transcripts, and serves a single cursor-resumable event stream to native Apple clients. The browser product is gone. What remains is a smaller system with two files still carrying far more than their share.

Browser surface removed 4 surfaces 21 static + 16 session routes 2 oversized orchestrators

Use the Current / Both / Target control in the header to switch every comparison on this page between what ships today and the proposed boundary refactor. It is a refactor, not a rewrite: route behavior, journal semantics, transcript parsers and Swift reducers all stay characterized while composition moves into smaller units.


At a glance

What the deprecation already bought

Measured from the working tree against its parent commit. These are deletions that have landed, not projections.

−3,941 lines
Production TypeScript removed
14,212 → 10,271 (non-test src/)
−988 lines
serve.ts shrank by 41%
2,396 → 1,408
11 modules
Whole subsystems deleted
web control · automation · alternate harnesses
13 routes
Browser-only endpoints gone
terminal · voice · auto · reports

Still carrying too much

FileLinesOwns
ios/LFG/SessionStore.swift2,615Network, hosts, GRDB, outbox, projection, commands
src/sessions.ts1,721Discovery, identity, transcripts, pagination, projection
desktop/LFGSessions.swift1,766API, host state, iTerm automation, all SwiftUI
src/commands/serve.ts1,408Startup, routing, validation, orchestration
src/tmux.ts997Process inspection, pane I/O, prompt parsing
ios/LFG/SessionListView.swift1,054Grouping, search, rows, headers, toolbars

The one-line thesis

Removing the browser PTY path eliminated the strongest argument for a big-bang backend migration. The remaining problem is not the runtime — it is that six files hold most of the system’s decisions.

Recommendation
Preserve the Bun + native-client product boundary. Reduce the two oversized orchestration files, make contracts and state ownership explicit, and only then reconsider the runtime — on evidence, not instinct.
System context

Who talks to what

The control API is unauthenticated and can launch shell-capable agents. It binds to loopback and is reachable only over Tailscale. Every box below sits inside that high-trust boundary.

Current — native clients, direct runtimes

Consumers
iOS / iPadOS appPrimary monitor & control client
macOS launcherCross-host attach into iTerm2
WhatsApp sidecarOptional, separate CLI lifecycle
Control plane
Bun control APIserve.ts — routing, validation, orchestration
Domain
Session projectionDiscover, bind, normalize
Journal + pumpMonotonic event log
Send queueIdempotent delivery
Leases + managedOwnership
Push watcherAPNs + Live Activity
Runtimes & truth
tmux panesClaude Code CLI · Codex CLI
Transcript stores~/.claude · ~/.codex JSONL
~/.lfg stateSQLite + JSON records

Target — same boundary, explicit seams

Consumers
iOS / iPadOS appOne observable facade over actors
macOS launcherAPI/state split from iTerm automation
Transport
Thin HTTP controllersapp · router · schema · 8 controllers
Application
SessionApplicationServiceThe single orchestration seam
Domain services
SessionRepository+ transcript adapters
RuntimeDriverCapability interface
DeliveryServiceQueue + idempotency
EventServiceJournal + projection
OwnershipServiceManaged + leases
NotificationServicePush + activity
Runtimes & truth
Claude driverCLI + transcript adapter
Codex driverCLI + transcript adapter
Harness driverAt most one, if it earns it
Trust boundary — unchanged and non-negotiable
No authentication anywhere in the API. /api/file is read-only and traversal-hardened by realpath containment against a fixed root set (repos root, self repo, home, $TMPDIR/lfg-uploads), but it lives inside the same high-trust zone. No component may assume this API is safe on a public network.
Product boundary

Four surviving surfaces

Core

lfg serve

The control API plus background pumps: journal, send queue, APNs watcher. One Bun process, one event loop.

Primary client

LFG for iOS / iPadOS

Monitoring and control. Offline-durable GRDB cache, cursored SSE, durable outbox, push and Live Activity.

Secondary

LFG desktop for macOS

Lightweight cross-host launcher into iTerm2. No local database, no stream, no push, no editing.

Optional

CLI integrations

Markdown-defined insight agents and a WhatsApp sidecar. Both candidates for a Phase 0 product decision.

Components · backend

The Bun service, by responsibility

Colocated *.test.ts files characterize state projection, transcript normalization, session binding, leases, event pumping, send delivery, tmux parsing, APNs and push transitions.

AreaModulesResponsibilityStatus
Entry & config cli.ts · config.ts · commands/setup.ts Command dispatch, environment paths and defaults, setup wrapper Stable
HTTP composition commands/serve.ts 1,408 Starts pumps and the APNs watcher lazily; validates and routes every REST and SSE request; maps API operations to domain functions Split
Session projection sessions.ts 1,721 · session-state.ts · turn-state.ts · hook-state.ts · activity.ts Discovers direct Claude and Codex processes and transcripts; normalizes message formats; resolves live/resumable state, prompt/activity/status, fork lineage, titles, pagination Split
Process adapter tmux.ts 997 · procinfo.ts · closing.ts Cross-platform process inspection; tmux spawn/attach/input/interrupt/close; prompt and busy-chrome parsing; close tombstones Async
Ownership managed.ts · leases.ts Records LFG-managed panes and lineage; prevents two synced hosts owning one transcript Stable
Durable delivery sendq.ts 831 · sendq-store.ts Idempotent client-id sends, SQLite state, background insertion and confirmation, queue actions, retry and reconciliation Stable
Durable events journal.ts · journal-pump.ts · transcript.ts SQLite event log, one global transcript/pane/queue pump, cursor replay and retention, bounded file scanning Stable
User & directory metadata users.ts · dirs.ts · hostinfo.ts Owner tags, repository and inbox creation and trust, stable host identity Unify I/O
Push push/apns.ts · store.ts · watcher.ts 609 · liveactivity*.ts · fleet-active-store.ts APNs credentials, JWT and transport; device and token stores; transition detection; notification payloads; fleet Live Activity lifecycle Stable
Insight agents commands/agents.ts · agents/registry.ts · runner.ts · collectors/* Loads Markdown agent definitions, collects git/GitHub/security/files/model data, runs reports through claude -p, writes report and action continuity files Optional
Optional messaging commands/whatsapp.ts 543 WhatsApp authentication, group routing, session listing and control Optional
Runtime product line simplified to two direct drivers
claude and codex now share the same managed-tmux lifecycle. The alternate harness registry, command files, provider/MCP packages, OpenCode binary, and native model-picker branches are gone. WhatsApp remains but routes into these same direct sessions; insight reports always use the installed Claude CLI.
Components · clients

iOS, iPadOS and macOS layers

iOS / iPadOS app target

  • App & lifecycleLFGApp · RootView · SettingsView. Scene entry, host setup, adaptive navigation, foreground/background and deep-link lifecycle.
  • Application stateSessionStore (2,615) · HostLink · BackgroundSender. Multi-host orchestration, REST snapshots plus journal reduction, commands, optimistic state and outbox, stream health, background-safe sends.
  • ScreensSessionListView (1,054) · SessionDetailView · NewSessionView · NewSession/*. Group, filter, search; transcript and control surface; create, resume and fork flows.
  • PresentationComponents · MessageComposer · RichContent · Theme · UnreadBadges. Reusable UI, attachments, Markdown/tool/media rendering, read affordances.
  • NotificationsPushManager · LiveActivityManager · FleetActivityController · RetiredSessionActivity. APNs lifecycle and navigation, push-to-start tokens, fleet activity projection.

LFGCore — the shared, simulator-free core

  • Contract & transportModels · LFGClient · SSEParser · HostEvents. Codable contract, REST/SSE client, lenient decoding.
  • Identity & healthMultiHost · HostConfig · HostState · HostHealth. Multi-host identity and one host-health state machine.
  • PersistenceLFGStore · LFGStoreRecords. GRDB store and records; the durable local authority.
  • ReducersOptimisticSendReconciliation · ReadState · RecentDirs. Transcript and display reduction, optimistic-send reconciliation, read/unread.
  • ActivityPush · FleetActivitySnapshot · LFGFleetAttributes. Push and ActivityKit data shared with the widget target.

LFGCore/Tests covers these seams without a simulator. ios/project.yml is the XcodeGen source of truth; ios/LFGWidgets supplies the ActivityKit widget.

macOS launcher & operations

desktop/LFGSessions.swift 1,766Fetches all configured hosts, dedupes by host id, groups and searches sessions, reports offline hosts, attaches/resumes/transfers through iTerm2
desktop/build.shCompiles and signs the single-file SwiftUI app and builds its icon
scripts/*setup · serve-forever · release, plus service templates: dependencies, environment, systemd/launchd, Tailscale Serve, release packaging
Runtime data

Storage and ownership

Agent CLIs own transcript truth. LFG projects it. SQLite owns durable server state and in-memory caches are only accelerators.

StoreOwnerContents & behavior
~/.claude/projectsClaude CodeJSONL conversation history, session ids, cwd and tool records; syncable across hosts
~/.codex/sessionsCodexRollout JSONL and thread metadata
Agent hook stateAgent hooksStrong running/idle/ended signal when present
~/.lfg/journal.dbBun serviceJournal sequence and events plus send-queue rows; survives process restart
~/.lfg/managed.jsonBun servicetmux name, cwd, agent kind, creation time, parent/fork lineage
~/.lfg/leases + transcript-adjacentBun serviceHost/process/start identity and expiry; a synced lease prevents two machines claiming one conversation
~/.lfg/session-users.json, titles, host idBun serviceOwner tags, title overrides, stable host identity
~/.lfg/push-*Bun serviceAPNs devices, Live Activity tokens, active fleet activity snapshot
~/.lfg/reports, ~/.lfg/agentsInsight-agent CLIGenerated reports, action sidecars and runlogs, local agent definitions
iOS application databaseLFGStore / GRDBHost, session, message and queue snapshots; cursors; read state; durable outbound work
iOS preferencesAppSettingsConfigured hosts, owner/filter/group and presentation preferences
~/.config/lfg-desktop/hosts.jsonmacOS appHost URL list, initially localhost
Ownership rules that must survive any refactor
  • Agent CLIs own transcript truth; LFG only projects it into a normalized session API.
  • SQLite owns durable server event and delivery state; in-memory caches are accelerators.
  • The iOS database owns locally renderable state and cursors; network state is reconciled into it, never treated as durable by itself.
  • A fresh lease identifies the one host allowed to present or control a synced transcript as live. Resume and fork return conflicts when another host owns it.
Derived, not stored
Client-facing status — needsInput, blocked, working, idle — is a projection of hook, transcript, pane and process facts. It is never an independently mutable flag. “Closed / resumable” means a transcript exists without a locally owned live process; a fresh foreign lease can redirect or reject work.
Contract

The native client HTTP/SSE API

One Bun server, JSON unless noted. This is the API the iOS and macOS clients consume — it is not the removed browser product, and none of its routes served a web bundle.

Host, events and environment

GET /api/infoStable hostId and display hostname
GET /api/pingLiveness, journal head, timestamp; also keeps carrier-NAT mappings warm
GET /api/events?since=Host-wide cursor-resumable SSE
GET /api/events/pageBounded journal page for background wake
GET /api/usersConfigured owner roster and avatar URLs
GET /api/repos · /api/dirsAvailable working directories
POST /api/dirs/new · /inboxCreate a trusted directory · set inbox
GET /api/file?path=Scoped agent-produced file read with byte ranges
GET /api/claude/usageCached Claude OAuth usage, when credentials exist

Sessions and lifecycle

GET /api/sessionsCurrent normalized live session snapshot
GET /api/sessions/resumablePaginated closed transcript catalog
POST /api/sessions/newStart a runtime and bind its session id
POST /api/sessions/resumeResume a closed transcript
POST /api/sessions/forkFork a Claude transcript
GET /api/sessions/:id/messagesBounded, backward or full transcript page
POST /api/sessions/:id/sendIdempotent queued, immediate or wake-up send
POST /api/sessions/:id/uploadStore attachment, return host path
POST /api/sessions/:id/modelChange or recover Claude model
PUT /api/sessions/:id/titlePersist title override
POST /api/sessions/:id/userAssign or clear configured owner

Control and queue

POST /api/sessions/:id/answerResolve a pending pane prompt by index
POST /api/sessions/:id/dismissSend Escape to the pending prompt
POST /api/sessions/:id/interruptStop the current turn, preserve queued steering
POST /api/sessions/:id/closeEnd the live process, release the lease
GET|DELETE /api/sessions/:id/queueReconcile and list · clear resolved rows
POST …/queue/:mid/retryRetry a failed row
DELETE …/queue/:midRemove an undelivered row
POST …/queue/:mid/send-nowInterrupt and prioritize a row

Push

POST /api/push/registerRegister an APNs device token
POST /api/push/unregisterRemove a device token
GET /api/push/healthPush configuration and device count
POST …/live-activity/start-tokenRegister a push-to-start token
POST …/live-activity/update-tokenRegister or update an activity token

21 static path literals plus 16 session-scoped UUID-matched routes, all matched by hand-rolled string and regex comparison inside a single fetch handler.

Removed browser surface — not current architecture, not supported contract

The deprecated browser product added a React/Vite static bundle, browser-only live streams, a terminal PTY WebSocket, speech routes, a runtime proxy, an Auto-agent editor/scheduler/controllers and report/action HTTP controllers. None of it was used by the native clients. Historical engineering notes may still describe these as failure analysis — they are history, not contract.

GET / · /_extStatic React/Vite bundle and extension host
/api/term · /api/term/scanTerminal PTY WebSocket — src/pty.ts, 204 lines
/api/voice/stt · tts · identifyBrowser speech flow
/api/auto/agents · findingsAuto-agent editor and scheduler — src/auto/*, 578 lines
/api/actions/execute · execute-combinedsrc/actions/index.ts, 515 lines
/api/agents · /api/reportsBrowser report and action controllers

Also deleted: src/links.ts (77), docs/mockups/auto-agents.html, the .claude/web-screen-captures/ set, and the web feature doc. The PTY removal is what eliminated the strongest reason to consider a big-bang backend migration.

Capability

Features and platform requirements

FeatureStatusRequirements & behavior
Multi-host ownershipCoreSynced transcripts plus fresh leases; the native client aggregates hosts and dedupes by hostId
Cursored event streamCoreOne global journal pump; 14-day retention; SSE heartbeats carry the current head
Durable sendsCoreClient-id idempotency, SQLite queue, composer-safe insertion, transcript-confirmed delivery
Direct agent runtimesCoreManaged tmux sessions for Claude and Codex only; omitted agent defaults to Claude and unsupported kinds return 400
Insight agentsOptionalMarkdown/YAML definitions; collectors read repo files, git, GitHub, security and OpenRouter; report generation uses claude -p
WhatsApp sidecarOptionalBaileys authentication and a configured group allowlist; routes to direct Claude or Codex managed sessions
APNs alertsOptionalApple team, key and bundle configuration; sandbox vs production token awareness
Fleet Live ActivityOptionalActivityKit tokens and the APNs live-activity topic; bounded to three active fleet rows
Setup & release automationImplementedUbuntu/Debian or macOS, systemd or launchd, Bun bundle, optional Tailscale Serve

iOS / iPadOS requirements

  • iOS / iPadOS 17.2+, Swift 6 strict concurrency
  • GRDB for the durable local store, MarkdownUI for GFM rendering
  • Network reachability to every configured host, typically over Tailscale
  • Optional APNs credentials on those hosts for push and Live Activity
  • XcodeGen (ios/project.yml) is the project source of truth

Host and macOS requirements

  • Bun ≥ 1.3.14, single long-lived serve process
  • tmux for CLI runtimes; Claude Code and/or Codex CLI installed
  • Linux for full process enumeration — /proc is used for cwd resolution
  • macOS launcher targets macOS 26, needs iTerm2 automation permission
  • Tailscale or an equivalently private network — mandatory, not optional
Hard-won

Quirks, invariants and failure behavior

The invariants are what the refactor must not break. The quirks are what previous sessions burned real time rediscovering.

Core invariants

  • One global producer writes live deltas. Clients never cause the server to create per-session transcript-tail pumps.
  • Journal sequence is monotonic within a database lifetime. A cursor gap triggers resync rather than guessing.
  • A client-id send is idempotent, and a durable send is never owned by a SwiftUI view lifecycle.
  • Normalized transcript identity is the client-facing key — not the tmux name, which is only used for process ownership and owner tags.
  • Foreign fresh leases are respected before resume or control. Close releases the local lease but never deletes transcript history.
  • UI status is a projection of hook, transcript, pane and process facts.
  • Clients decode leniently, servers validate strictly — native clients tolerate missing or older fields; server inputs are validated at each command boundary.
  • Nothing assumes the API is safe on a public network.

Architecture hazards

  • Single-process Bun server. One event loop serves all HTTP and both pumps. Synchronous subprocess fan-out on a hot path stalls everything.
  • No bare setInterval fan-out over a session collection — a growing collection turns the tick into a spawn storm. Batch or stagger.
  • No /proc on macOS. listSessions enriches via /proc/<pid>/cwd. On macOS, CLI and tmux sessions will not enumerate — expected, not a bug.
  • Bun auto-loads .env. ps eww does not show a Bun process’s effective environment.
  • Restart by port, not by pattern. Kill via lsof -ti :8766; stale processes silently keep the port. Then probe a changed endpoint before calling the deploy done.

The tmux send path — two traps

  • Newlines are Enter. send-keys -l transmits byte-for-byte, so an embedded \n submits early and fragments a multi-line message. Insert via bracketed paste (load-buffer + paste-buffer -p) so Claude collapses it to a single pasted chip and submits it whole.
  • Do not gate Enter on re-finding your text. A busy Claude swallows input into its own queue and clears the composer, so a composer scrape misfires. Confirm via transcript growth when idle, or composer-cleared → queued when busy.
  • codex inside a claude pane breaks that session’s send path. Two session rows claim one pane; the collision guard nulls tmuxTarget and sends 409. Fingerprint: tmuxName set but tmuxTarget: null.

Failure behavior the clients guarantee

  • Offline — durable cached state stays renderable; the list and transcripts still work with no host reachable.
  • Stream loss — reconnect with the persisted per-host cursor; a watchdog catches a silent stream that never errors.
  • Unserviceable cursor — the server emits event: resync with its head; the client full-refreshes over REST and resets.
  • Send failure — surfaced as a failed queue row with retry, delete and send-now, never silently dropped.
  • Host unreachable — reported explicitly rather than presented as an empty session list.
  • Lease conflict — resume and fork return a conflict instead of two hosts driving one transcript.
Operational quirk that reads as a code bug
Bun has no hot reload and the serve process is long-lived, so the running code routinely lags source. A “previously-fixed” bug that recurs is usually a deploy gap. Compare the process start time against the fixed file’s mtime before re-debugging correct, unit-tested code.
Behavior

Major user flows

Six flows carry the product. Each one crosses the client, the API, the domain and a real agent process — which is exactly why the seams matter.

Monitor the fleet

Cold launch renders from GRDBThe local database is the durable authority, so the list and last-read transcripts appear before any network call resolves.LFGStore → SessionProjection
Fan out to every configured hostSnapshot /api/sessions and /api/info per host; two URLs reporting one hostId collapse into a single host.HostLink · MultiHost
Open one SSE stream per host from the stored cursorOne stream covers all sessions, so nothing rebuilds when sessions open, close or transfer. The server subscribes first, then replays, then flushes its buffer — an event appended mid-replay is neither lost nor duplicated.GET /api/events?since=N
Server derives status from structured signalsHook state when present, transcript and pane chrome as fallback, reduced to needs-input, blocked, working or idle.hook-state · turn-state · session-state
Client reduces events idempotently and advances its cursorHeartbeats carry the head so a gap is detected without waiting for traffic. A stale cursor gets event: resync and a REST refresh.HostEvents → LFGStore
Group, filter, search and mark readGroup by status or directory, filter by owner, maintain unread state across launches.SessionListView · ReadState

The comparison

Current architecture vs simplified target

Same product boundary, same runtime, same routes. What changes is where decisions live. Switch the header control to isolate either side.

Current — composition inside two hot files

Native appsLenient Codable models, duplicated route strings, partial decoder tests
serve.ts — 1,408 linesStartup, persistence wiring, route matching, validation, session orchestration, file ranges, usage fetching and APNs registration, all in one fetch handler
sessions.ts — 1,721 linesOS discovery, Claude/Codex identity heuristics, transcript normalization, title and model extraction, resumable scans, message pagination, prompts and display projection
tmux.tsSync subprocesses on the event loop
5 bespoke JSON storesEach with its own read/write pattern
journal · sendqSQLite, well-factored
Every feature touches a shared hot file. The contract exists only as ad hoc casts. Identity heuristics silently choose between ambiguous bindings with no evidence trail.

Target — one decision per seam

Native appsGenerated DTOs and route constants, fixture contract tests against real Swift decoders
Thin HTTP controllersA handler contains validation and one application-service call — nothing else
SessionApplicationServiceThe only place orchestration decisions live
RuntimeDriver per runtimeOwns its id binding and transcript adapter
One atomic JSON record helperSchema, temp-file + rename, version, corruption reporting
journal · sendqUnchanged — already the good part
Metrics before any runtime decisionp50/p95 session enumeration, pump duration, resync count, send confirmation latency
Adding a runtime means implementing one driver and one transcript adapter, without editing an unrelated controller. Unsupported operations become a consistent 409 with a machine-readable code.

Current — iOS state

SessionStore.swift — 2,615 linesNetwork clients, host links, database synchronization, outbox, aggregation, optimistic creation, transfer, commands, read state, lifecycle and view-facing projection
Transient dictionariesIn-memory only
UserDefaultsPreferences and some state
GRDBDurable, but not the only source

A high-conflict file across concurrent agent sessions, and expensive to reason about for actor isolation and source of truth.

Target — iOS state

SessionStore — the sole @Observable facadeEnvironment-facing surface only; no mechanics
HostCoordinatorHost identity, HostLink lifecycle, reachability, cursor
SessionRepositoryGRDB queries, transactions, cache hydration
OutboxDurable create/send/upload state machine and background transport
SessionProjectionPure host aggregation, display status, id remapping
SessionCommandsCalls the API, records intent, applies success and failure effects
GRDB is the durable local authorityViews never choose between a dictionary, UserDefaults and the database
Proposal

Proposed module boundaries

Concrete file layout, so the refactor is reviewable as a diff rather than argued as a principle.

Backend — from one fetch handler to typed controllers

src/server/app.tsBun startup, error policy, dependency construction
src/server/router.tsMethod and path matching, 404 and 405 behavior
src/server/schema.tsRequest and response validators
src/server/controllers/host.tsinfo, ping, users
src/server/controllers/events.tsSSE stream and bounded pages
src/server/controllers/directories.tsrepos, dirs, new, inbox
src/server/controllers/files.tsScoped reads with byte ranges
src/server/controllers/sessions.tsSnapshot, resumable, new, resume, fork, messages, control
src/server/controllers/queue.tsQueue listing and row actions
src/server/controllers/push.tsDevice and activity tokens, health
src/server/controllers/usage.tsCached Claude OAuth usage
src/application/session-service.tsThe single orchestration seam

Session domain — from one file to five seams

runtime-catalog.tsProcesses, panes, registry and managed binding
transcripts/claude.tsResolve, decode, extract metadata
transcripts/codex.tsResolve, decode, extract metadata
session-projection.tsMerge facts into the external Session DTO
resumable-repository.tsIndexed, paginated closed history
message-repository.tsRecent and backward pages, normalization
Keep parsers pure and fixture-heavy. Make identity heuristics return evidence or confidence so an ambiguous binding is diagnosable rather than silently chosen.

RuntimeDriver — the capability interface that replaces agent-kind branching

discoverFind live processes and bind identity
create · resumeStart a runtime, own its id binding
sendDeliver text, report confirmation
interrupt · closeStop a turn, end a process
changeModelDeclared capability, not a special case
promptControlAnswer and dismiss semantics
identity behaviorWhether resume is id-stable
capabilities recordUnsupported → consistent 409 with a machine-readable code

Avoid a universal base class. A small capability record plus functions keeps the distinct Claude and Codex semantics visible instead of flattening them.

Work

Changes to make

Contract

Make the backend/Swift contract executable

TypeScript handlers and lenient Swift Codable models currently evolve independently; route strings are duplicated and only selected builders and decoders have tests.

  • Define compact JSON Schema / OpenAPI for external request, response and event envelopes.
  • Generate only DTOs and route constants if generation is stable; otherwise validate bodies with Zod and run fixture contract tests against the real Swift decoders.
  • Keep lenient response decoding for rolling upgrades; make required command inputs strict.
  • Adopt a compatibility rule: servers may add fields; removals and renames require a migration window.
Backend

Extract HTTP controllers

  • First extract pure validators and controller functions, snapshot-testing status, body and headers for every current route.
  • Inject Journal, queue, session catalog, runtime and push dependencies rather than reaching for module singletons.
  • Avoid adopting a framework unless the small router itself becomes a burden — the win is separation and testability, not routing features.
iOS

Split the store behind one facade

  • Keep SessionStore as the only environment-facing @Observable surface.
  • Move mechanics into HostCoordinator, SessionRepository, Outbox, SessionProjection, SessionCommands.
  • Views read published query projections and invoke commands only.
  • Characterize cold launch, offline list, optimistic send and create, id remap, resync, deep link and transfer before moving each seam.
Domain

Capabilities, persistence and the hot path

  • Replace agent-kind branching with the RuntimeDriver capability interface.
  • Add a tiny atomic JSON record helper — schema validation, temp-file plus rename, version and corruption reporting — for managed, titles, users, host identity, registry and push tokens. Keep high-churn queue and journal state in SQLite.
  • Do not migrate every small file into one database. Inspectability is genuinely valuable for a self-hosted tool.
  • Cache one discovery snapshot per pump tick, use async subprocesses for request work, cap collector and runtime concurrency.
Verification

Fill the verification gaps

  • Start the Bun app with fake dependencies and contract-test every route — including method mismatch, malformed JSON, ranges, stale cursors and conflicts.
  • Add one end-to-end test for create → send → queue event → transcript reconciliation against a fake runtime.
  • Add macOS model and API tests independent of iTerm automation.
  • Add shell syntax and release-bundle assertions to CI so deprecated artifacts cannot re-enter packaging.
  • Track p50/p95 session enumeration, event-pump duration, cursor resync count and send confirmation latency in structured logs.
Housekeeping

Adopt an active-document policy

The repository holds far more historical .claude, .codex, delegation, verification and improvement-log material than current product documentation.

  • Keep one current system map, current feature docs and decision records.
  • Archive or remove superseded plans and evidence on a cadence.
  • Mark historical documents with status and replacement links so search results stop masquerading as the current API.
Sequence

Phased roadmap

The phases are ordered because each one removes ambiguity the next one would otherwise have to preserve.

Phase 0 — direct-runtime boundary Complete Claude CLI and Codex CLI are the complete runtime set. The alternate harness family, OpenCode integration, provider dependencies and stale native choices have been removed. WhatsApp and insight agents remain thin adapters over the direct runtimes and can be evaluated independently. Strict contract: claude | codex
Phase 1 — highest-value structural refactors Split serve.ts into typed controllers. Split SessionStore.swift behind one observable facade. Establish the executable backend/Swift contract. These three are where the leverage is. Guarded by golden route tests and iOS characterization tests
Phase 2 — domain boundaries and state consistency Replace agent-kind branching with runtime capabilities. Decompose sessions.ts into catalog, transcript and projection modules. Unify small server records behind one atomic persistence helper. Move blocking process work off the request and event hot path. Recorded transcript and process fixtures are the safety net
Phase 3 — frontend and operational cleanup Decompose SessionListView.swift and desktop/LFGSessions.swift; on macOS, separate API and host state, iTerm automation and SwiftUI presentation even if the build stays swiftc-based. Fill the verification gaps. Only then make the runtime-language choice evidence-driven. Do this after SessionStore query boundaries settle, or view extraction preserves accidental coupling
Stay on Bun during these extractions
Reconsider a Rust daemon only if measured event-loop stalls, memory, deployment or process-supervision failures remain after the async-subprocess and caching boundaries land. If it is ever needed, move only the watcher, journal and process core behind the existing API contract — do not rewrite routes, native clients and transcript semantics simultaneously.
Sequencing

Priority, risk and guardrails

Ordered by execution sequence, not by size. Each row names the specific way it can go wrong and the specific thing that catches it.

#ChangeValueMain riskGuardrail
1Optional-family product decisionVery highRemoving latent useUsage inventory and one explicit decision
2Route/contract tests and schemasHighFixture driftRun against real Swift decoders
3Extract HTTP controllersHighRoute behavior changesGolden status, body and header tests
4Split iOS store servicesVery highOffline and outbox regressionsCharacterization tests plus real lifecycle verification
5Runtime driver capabilitiesHighClaude/Codex semantic flatteningExplicit capability matrix and per-driver fixtures
6Split session catalog and transcriptsHighIdentity binding regressionsRecorded transcript and process fixtures
7Persistence and process cleanupMediumMigration and corruptionVersioned atomic reads and fallback backups
8View and desktop decompositionMediumUI regressionsScreenshot or UI evidence after each screen
9Re-evaluate backend runtimeConditionalBig-bang rewriteMetrics and an API-preserving strangler migration
Outcome

Expected complexity reduction

Measured values are counted from the working tree. Targets are proposed budgets, not predictions — they are the thresholds that make the refactor reviewable.

Measured — already landed

MetricBeforeAfterΔ
Production TS lines14,21210,271−28%
serve.ts2,3961,408−41%
Browser-only routes130−100%
Deleted production modules110−100%
Frontend build pipelines21−50%
Live-delivery pipelines21−50%

Total working-tree diff: 396 insertions, 5,901 deletions across 76 files.

Target — proposed budgets

MetricTodayBudget
Largest backend file1,721≤ 400
Largest Swift state file2,615≤ 400
Files touched to add a runtimemany2
Files touched to add a route1 hot file1 controller + schema
Sources of truth in a view31
Routes under contract testpartialall
Sync subprocesses on hot pathseveral0
Runtime variants52–3

The runtime-variant budget is contingent on the Phase 0 usage inventory, not assumed.

Done means

Definition of simpler

  • A route handler contains validation and one application-service call — not process, transcript and persistence mechanics.
  • Adding a runtime implements one driver and one transcript adapter, without editing unrelated controllers.
  • SwiftUI views have one query source and one command surface.
  • Restart, offline, resync and duplicate-send behavior remain deterministic.
  • Current architecture and API can be found in one document and verified by an executable contract suite.
  • Optional features incur dependencies and branches only when they are an intentional, supported product line.
Sources: .codex/architecture/lfg-system-map.md · .codex/brainstorm/lfg-simplification-plan.md Line counts and route inventory verified against working tree branch codex/deprecate-web-architecture