Daeseon Yoo
Back to project
·Tech retro·5 min

Talkak's core architecture: the eight technical decisions that make a terminal into a startup OS

A standing reference for what Talkak is actually made of: Tauri (Rust + React) hosting xterm.js panes whose processes live in per-pane tmux sessions; terminal state kept outside the React lifecycle; a BYO-Claude wrapper that injects the platform directive into every agent; in-band channels read from transcripts, never the TUI stream; an append-only provenance-checked graph fed by both git (PULL) and the agents themselves (PUSH); hooks as the enforcement layer; and a test gate every release must pass. Each decision exists because something broke without it.

What Talkak is, in one layer-cake

┌────────────────────────────────────────────────────────────┐
│ PLATFORM  — the connective layer (the actual product)      │
│   living per-startup graph: structure · issues · flows ·    │
│   changes · metrics, with provenance                        │
├────────────────────────────────────────────────────────────┤
│ AUGMENTATION — cards, summaries, status, phrases/terms,    │
│   usage pulse, honesty gates                                │
├────────────────────────────────────────────────────────────┤
│ SUBSTRATE — multi-pane terminal where the user's own        │
│   Claude/Codex do the real work (BYO, we never call AI)     │
└────────────────────────────────────────────────────────────┘

The terminal is not the product; it's where the data is born. Everything above it exists to turn "a solo founder running several startups through AI agents" into one legible operating picture.

The eight decisions

1. Tauri 2.x — Rust backend + web renderer, not Electron

One binary, small footprint, and the backend is real Rust: portable-pty for terminals, tokio for async workers, tracing for runtime logs. The renderer is React + TypeScript + xterm.js. IPC is Tauri commands (renderer→Rust) and events (Rust→renderer, e.g. the PTY output stream).

2. Every pane is a tmux session — work survives the app

Each pane spawns tmux -L dalkkak new-session -A -s dalkkak-<paneId>. The tmux server is a separate OS process, so quitting (or crashing, or updating) Talkak kills nothing: relaunch re-attaches and the agents are still running mid-task. This is the single most load-bearing decision in the product — sessions have survived ~20 reinstalls in two days of heavy dogfooding.

3. Terminal state lives OUTSIDE the React lifecycle

xterm Terminal objects, PTY subscriptions, and buffers live in a module-scoped registry (terminalRegistry.ts), not in components. React remounts freely (layout changes, re-renders); the terminal never loses a byte. React displays; Rust owns the process; the registry bridges.

4. BYO AI, injected — the wrapper is the platform's lever

Talkak never calls an LLM itself. Instead it prepends a claude wrapper to each pane's PATH that adds --append-system-prompt with the platform DIRECTIVE: honesty rules, the dk-summary card contract, English refinement, and graph-push instructions. One file changes → every agent in every startup behaves differently. AI features that need a one-shot call (phrase correction, summaries) shell out to the user's own claude -p — their auth, their account, our zero keys.

5. In-band channels are read from TRANSCRIPTS, never the TUI stream

Agents emit <dk-summary> (session card) and <dk-node> (graph record) blocks. Parsing them out of the PTY stream fails — a TUI redraws, wraps, and mangles text. So capture reads Claude Code's own transcript files (JSONL on disk), incrementally, with persisted byte offsets. The stream is for humans; the transcript is for machines.

6. The connective graph — append-only, provenance-checked, two feeds

Per-startup JSONL stores with a locked schema (schema_version, deterministic node_id, provenance: confirmed|inferred|hypothesis). Two write paths, one validator: PULL (a 20s worker turns git commits in the granted project folder into confirmed change nodes — evidence is the commit hash) and PUSH (agents file structure/issue/flow facts as they work; confirmed requires evidence and evidence.source is force-set to agent, so provenance can't be spoofed). Security model: per-startup folder grants through a single Rust allowlist chokepoint — never blanket disk access.

7. Hooks are the enforcement layer — the harness is the law

Claude Code hooks feed Talkak twice over. Status: every agent event appends to a JSONL stream that powers per-pane state (thinking/tool/idle) and usage. Enforcement: a Stop-hook runs after every reply and blocks unverified done-claims and missing summary blocks — prompt rules decay, process-level gates don't. Scoped per-pane via the DALKKAK_PANE_ID env var that pty.rs injects.

8. The regression gate — a red test means no release

build = vitest run && tsc && vite build, and the Tauri release pipeline calls exactly that. The tests are the product's actual scar tissue: the pane-sharing corruption (with the real corrupted tree as a fixture), the copy-banner bug on both copy paths, the Ctrl+3=ESC chord contract, dk-summary capture across every chunk boundary, the honesty gate's fail-open. Rust has its own cargo test suite for the graph-push validator. Every regression that reaches the user becomes a test the same day.

Cross-cutting laws (learned, not designed)

Stack summary

LayerTech
Desktop shellTauri 2.x (Rust)
Terminalxterm.js + portable-pty + tmux (-L dalkkak)
UIReact 19 + TypeScript (strict) + react-mosaic + Vite
Async/runtimetokio workers, tracing + daily-rolling runtime logs
Data spineappend-only JSONL graph, locked schema v1, dual validators (TS + Rust)
AIBYO Claude Code/Codex only — wrapper-injected directive, user's own claude -p
Qualityvitest (48) + cargo test (7) gating every build; weekly regression trend
Releaseminisign-signed tar.gz + latest.json, Tauri updater, Vercel landing