CodeDocs Vault

Design Patterns, Tradeoffs & The V1→V2 Story

What patterns recur, what they cost, what's clever, and what to look out for.

This doc is the editorial layer over the previous four. Read after 01-architecture.md.


1. Recurring Patterns

1.1 Provider injection (host-service)

packages/host-service/src/app.ts's createApp({ config, providers }) is a clean example of dependency inversion. The host-service doesn't know:

Same code runs on a laptop with a KeychainCredentialProvider and on a Linux container with a FileBasedCredentialProvider. The boundaries doc (HOST_SERVICE_BOUNDARIES.md) is itself an artifact of this pattern — a contract written down so future work doesn't accidentally re-couple.

1.2 tRPC everywhere

Same protocol over IPC, HTTP, and WebSocket. The renderer can do electronTrpc.foo.bar.useQuery() and localHostService.foo.bar.useQuery() with identical ergonomics. End-to-end types from a single source of truth (packages/trpc/).

The cost: subscriptions over IPC must be observables (per apps/desktop/AGENTS.md) — async generators don't work; this is a recurring footgun for new contributors.

1.3 MCP and tRPC share business logic

defineTool(server, {
  name: "automations_create",
  inputSchema: { … },
  handler: async (input, ctx) => caller.automation.create(input),
});

The MCP handler is a thin shim over a tRPC caller. One implementation, two surfaces (developer-facing tRPC API + agent-facing MCP). Deepens the language of the system rather than maintaining parallel logic.

1.4 Manifest-based process adoption

~/.superset/host/{org}/manifest.json is the rendezvous file. Electron quits, host-service stays alive (release mode), Electron restarts and adopts. PTY daemon does the same trick. The manifest is small ({ pid, endpoint, authToken, startedAt, organizationId }) and the discovery logic re-validates each one before adopting — defensive against dead pids.

1.5 PATH rewriting as instrumentation

Rather than fork the agent CLIs to add hooks, Superset prepends ~/.superset/bin/ to PATH and drops shim scripts there. Combined with rewriting the agent's own hook config files (~/.claude/settings.json, ~/.codex/...), it gets observation without modification. Idempotent rewrite blocks are marker-fenced so manual edits to the same files don't break either side.

1.6 Discriminated unions + exhaustiveness

Patterns like ResolvedRef (in the v2 git-ref work) define template-literal fullRef types and force switch(kind) blocks to handle every case via a never default. The repo includes a lint script (scripts/check-git-ref-strings.sh) that bans .startsWith("origin/") outside refs.ts so contributors cannot regress to string-typed refs.

1.7 Event-sourced log + pure reducer

The v2 chat plan (plans/v2-chat-greenfield-architecture.md) commits to a single event log per session with monotonic seq, a pure applyEvent(state, event) reducer on the client, gap detection on receive, and replay endpoints. This is the standard Redux/CQRS playbook applied to a chat surface — the win is multi-device convergence (every device replays the same log to identical state).

1.8 Authority by ownership

Recurring decision: who is the source of truth?

Question Authority
"Does this branch have a workspace?" Cloud DB (host-stale caches lose)
"What's the fork-point for diffs?" Branch's tracked upstream (not hardcoded origin/main)
"Who owns chat events?" Event log (one writer per session)
"When two refresh-token attempts collide?" Postgres advisory lock (single-flight)
"What's a workspace's port range?" Local SQLite port_base
"Is the host online?" Heartbeat-driven v2_hosts.online

Writing these down (rather than hoping consistency emerges) is the lesson.

1.9 Cloud-synced collections as primary state

The pendingWorkspaces Electric collection is read in the renderer as if it were a local table — but writes propagate through the cloud and back. This unwinds the "is the workspace ready yet?" question across processes and devices.

1.10 Per-agent dialect templates

contextPromptTemplate.system / .user per agent (Claude → XML, Codex → markdown). Same LaunchSource[] composition; different surface form. Adding Gemini/Pi/etc. = adding a template + a preset, not branching if (agent === "claude") everywhere.

1.11 Bytes through IPC, encode at boundaries

Attachments travel as Uint8Array between renderer/main/host-service; the AI SDK's Anthropic provider does the base64 conversion exactly once at the API boundary. Avoids both performance hits (re-encoding) and bugs (string truncation).

1.12 Heredoc with random delimiter for prompt-as-shell-arg

buildPromptCommandString (packages/shared/src/agent-prompt-launch.ts:26-68). The defensive coding standard for moving arbitrary text into a child process's argv when the binary's flag schema is out of your control.


2. Notable Tradeoffs

2.1 Worktrees over containers

Pro: native git, cheap, plays well with local IDE/dev servers, no Docker dependency. Con: no security isolation between worktrees (they share ~), no resource quotas, no portability to remote machines (which is why the v2 host-service is being designed to run remotely separately).

The container/VM path is left to the user via setup scripts.

2.2 Bun everywhere except node-pty

Pro: speed, single runtime, single PM (bun). Con: the PTY daemon runs Node, complicating the build (compile:app checks the bundle, validate:native-runtime checks runtime modules, etc.). If anything else needs node-pty's tty.ReadStream, it must live in the daemon too.

2.3 Two databases

Cloud Postgres + local SQLite, with Electric SQL bridging only the queue table. Most data is single-sided:

Pro: less sync surface area, simpler invariants. Con: mental model overhead — readers must know which table is authoritative for which question.

2.4 V1 + V2 in parallel

Two of every entity (projects/v2_projects, workspaces/v2_workspaces, device_presence/v2_hosts). New code goes to v2; old code stays.

Pro: zero cutover risk; ship v2 incrementally. Con: doubled vocabulary, doubled tests, deferred consolidation cost. Per plans/, the team is shipping V2 of every flow first, then will sunset V1 — a pragmatic but expensive strategy.

2.5 Polling vs events

V1 chat polls getDisplayState() and listMessages() independently at 4 fps from two harness sources, racing on the client. V2 replaces it with an event log. The pivot exposes the cost of "easy" polling — subtle race bugs that are invisible until multiple devices observe the same session.

2.6 Better Auth instead of Clerk

Self-hosted auth = more control + lower cost + custom plugins (organization, apiKey, stripe, expo); fewer hosted batteries. Trusted-origin lists, refresh-token discipline, OAuth flows — all in tree.

2.7 Mastracode + Vercel AI SDK

Mastra owns the agent loop (system prompt, tool-use, hooks, approvals). Vercel AI SDK is used at the edges (small-model one-shots, UI streaming hooks). Two LLM frameworks to maintain, but each is best at its layer; combining them avoids forcing one to do the other's job.


3. The V1 → V2 Transition (Strategic Narrative)

Theme V1 (Electron-monolithic) V2 (distributed, event-driven)
Process model Electron main + terminal-host daemon Electron + standalone host-service + supervised PTY daemon
Workspace creation Tightly coupled to renderer pendingWorkspaces Electric collection + dedicated page
Branch model Hardcoded origin/main BaseBranchSource carried through chain
Diffs Latest-vs-latest (creeping numbers) Fork-point (merge-base) — stable
Chat 4 fps polling, two sources, races on client Event-sourced log, monotonic seq, pure reducer, gap replay
Devices Single-device assumption v2_hosts (machines) × v2_users_hosts (access)
Notifications Played in Electron main Client-side, enabling remote hosts
Agent launch Flat-string prompt Composed LaunchSource[]LaunchSpec with cache hints
Workspace state Defensive auto-delete on local stale cache Cloud-authoritative; local cache is hint-only

Active work-in-progress (from plans/):

Done (in plans/done/):


4. Conventions Worth Adopting

The repo's AGENTS.md and per-app AGENTS.md files codify some specific rules; the underlying philosophy is more general. A few worth borrowing:

  1. Plans go in plans/ (cross-cutting) or apps/<app>/plans/ (app-scoped); shipped plans move to plans/done/. Never *_PLAN.md at app root.
  2. Architecture/reference docs go in <app>/docs/, not src/. Code is for code; docs sit beside it.
  3. One source of truth for shared resources — slash commands live in .agents/commands/; .claude/commands and .cursor/commands are symlinks. MCP servers configured once in .mcp.json; clones are symlinks. Codex uses .codex/config.toml. OpenCode mirrors the set in opencode.json.
  4. Biome at root, not per-package. Speed first.
  5. gh over git for PR/issue ops. Saves a layer of translation.
  6. No fork tarball overrides for upstream packages unless explicitly requested. Prefer published mastracode/@mastra/*.
  7. Type safety over any. Discriminated unions, exhaustiveness checks.
  8. Co-locate tests/utils/hooks/components. Folder-per-component (MyComp/MyComp.tsx + MyComp.test.tsx + index.ts); promote to components/ only when used 2+ places.
  9. Drizzle migrations are auto-generated. Never edit packages/db/drizzle/*.sql or meta/_journal.json.
  10. Use ask_user (Superset MCP) for interactive Qs in agent runs, not plain text.

5. Things That Are Unusual / Clever


6. Pitfalls / Lessons Already Learned


7. Open Questions / Where The Engineering Bets Lie


8. The Editorial Take

Superset is one of the most architecturally disciplined developer-tool codebases I've seen at this stage of a startup. Several things stand out:

The principal risks are the ones flagged above: V1 sunset cost, multi-device chat untested at scale, and the (already-mitigated) trap of believing local cached state about something the cloud actually owns.

If you're picking up this codebase for the first time, the order I'd recommend is:

  1. README.md — 5 min
  2. AGENTS.md (root) — 10 min
  3. apps/desktop/HOST_SERVICE_ARCHITECTURE.md and HOST_SERVICE_BOUNDARIES.md — 20 min
  4. apps/desktop/V2_WORKSPACE_CREATION.md — 15 min
  5. packages/shared/src/agent-prompt-template.ts — 10 min (small, dense)
  6. apps/api/MCP_TOOLS.md — 10 min
  7. plans/v2-chat-greenfield-architecture.md — 30 min if you want the spice