Tempest Architecture
Tempest is a Tauri + React desktop app designed to run multiple AI coding agents in parallel within isolated git worktrees. This document describes how the system is organized: the Rust backend, React frontend, IPC layer, state management, worktree isolation model, and the semantic code intelligence (Atlas) that powers agent context.1. High-Level Overview
Tempest uses Tauri’s framework to split responsibilities between a native backend and a web-based frontend:2. Process Model
Main Process (Rust / Tauri)
The Tauri main process runs once per app instance and manages:- PTY Sessions Registry: A thread-safe
DashMap<String, Arc<PtySession>>tracking all active terminal/agent sessions. - Webview Windows: Main window and ephemeral “zen” windows (full-screen worktree views).
- Git Operations: Subprocess invocation for
git worktree,git status,git push, etc. - File I/O: Reading/writing config, runtime state, and project files.
- Atlas Spawning: Starting the Node.js Atlas MCP server process on demand.
src-tauri/src/lib.rs:
Renderer Process (React / Vite)
The frontend runs in a Tauri webview and orchestrates the UI:- Component tree: React 19 with hooks for state management.
- Xterm.js integration: Renders PTY output in terminal tabs.
- CodeMirror 6: Syntax-highlighted file editing with language support.
- Theme system: Custom CSS variables (
--tempest-*) with Geist fonts. - Markdown rendering: GitHub-flavored markdown for diffs, logs, and previews.
Sidecar Processes
Atlas Node Process:- One per indexed project (not per worktree).
- Spawned on-demand by
start_atlas_index(). - Runs
node server-entry.js --path <project>as a daemon. - Handles semantic indexing, code graph queries, and MCP protocol.
- Survives app restarts (managed by process registry on-disk).
- Spawned inside each worktree via
create_pty_session(). - Runs inside an interactive shell (PowerShell/bash) with
-NoExit/execflags. - Receives MCP config pointing to the Atlas server.
- Killed on session close via
taskkill /F /T(Windows) orkill -9(Unix).
3. IPC Communication
Command Invocation (Request-Response)
React components call Tauri commands via@tauri-apps/api/core:
#[tauri::command] in lib.rs):
- Worktree Management:
create_terminal_worktree,git_worktree_remove,close_and_remove_worktree - Git Operations:
git_init,git_status,git_push_branch,git_switch_branch,git_diff - PTY Sessions:
create_pty_session,write_to_pty,resize_pty,close_pty_session - File I/O:
read_file,write_file,list_directory - State Persistence:
read_runtime_state,write_runtime_state - Atlas:
start_atlas_index,check_atlas_db,write_atlas_mcp_config
serde_json.
Event Streaming
The Tauri backend emits events for long-lived outputs (PTY data, Atlas indexing logs):4. State Management
Tempest uses a layered state architecture to balance persistence, consistency, and performance:Layer 1: Tauri Backend State (Per-Process)
In-Memory:PtyState: Active PTY sessions keyed by session ID.ZenState: Ephemeral zen window config.
.tempest-pid sidecar files) but their React bindings disappear until restoration.
Layer 2: Persistent JSON (Tauri App Data Dir)
runtime-state.json (written by write_runtime_state):
loadRuntimeState() before rendering.
WorktreeSession stores metadata needed to restore a session:
Layer 3: React Store (In-Memory, Ephemeral)
Stores ephemeral state not persisted to disk:workState: Per-session “idle” | “working” | “done” badges (agent activity).messageQueue: Queued Tauri API calls awaiting rate-limit slots.sessions: Proxy overruntime-state.jsonwith lifecycle hooks.appSettings: Cached user preferences (theme, font size).attribution: Co-author toggle state.
useSyncExternalStore for fine-grained subscriptions (e.g., one session’s badge doesn’t re-render every other session).
Layer 4: localStorage (Legacy, Deprecated)
Older code read fromlocalStorage:
loadRuntimeState() imports localStorage into the JSON file, then migrates it out. New code should use only the JSON file and React stores.
5. The Worktree Model
Tempest achieves agent parallelization via git worktrees — a Git feature that creates lightweight, isolated checkouts of the same repository.Git Worktree Isolation
- Each worktree has its own working-tree state and checked-out branch.
- Agents can edit files, run
git status, commit, and push on their own branches without conflicting. - The main
.git/directory is shared, avoiding storage duplication. - Lightweight: no full checkout, just hard links to objects.
Worktree Creation Flow (create_terminal_worktree)
-
Ensure commits exist: If the project has no commits, create an empty initial commit (gives
git worktree adda HEAD to branch off). -
Prune stale metadata:
git worktree pruneremoves dangling.git/worktrees/<name>entries left from crashes. -
Create the worktree:
git worktree add .tempest/<name> -b <name>. -
Copy small files:
.env,.env.local,.env.development,.env.productionare copied (not committed, but needed by agents). -
Link large directories:
node_modulesand.venvare linked (not copied) using:- Windows:
junction::create()creates a directory junction (efficient hardlink for directories). - Unix:
symlink()creates a symlink.
- Windows:
-
Write metadata: Persist the worktree’s PID to
.tempest/<name>/.tempest-pidfor force-killing on restart. -
Detect empty trees: If the tree has only
.git/, error out (user needs to commit project files).
Worktree Cleanup
When deleting a worktree:- Kill the PTY child:
child.kill()on the portable-pty child (kills shell + agent + descendants). - Wait for exit: Poll
try_wait()in a loop (up to ~3s) so the OS releases the directory handle. - Force-kill by PID: Read
.tempest-pidand invoketaskkill /F /T <pid>(Windows) orkill -9 <pid>(Unix) to handle orphaned processes after app restart. - Remove junction/symlinks first: Call
std::fs::remove_dir()(Windows) orstd::fs::remove_file()(Unix) to strip junctions/symlinks beforeremove_dir_all(). Without this,remove_dir_all()follows the junction into the realnode_modulesand freezes or deletes it. - Retry loop: On Windows, the PTY process may release its CWD handle asynchronously, so retry
remove_dir_all()up to 6 times with 500ms gaps (~3s total). - Last resort: Shell out to
cmd /c rmdir /s /q(Windows only). - Prune git metadata:
git worktree prunecleans up any dangling refs.
6. The Atlas Package
Atlas is Tempest’s semantic code intelligence engine — a Node.js package that builds a code graph and exposes it via MCP (Model Context Protocol) so agents get surgical context.What Atlas Does
- User enables “Token Intelligence” for a project.
- Tempest calls
start_atlas_index(), which spawnsnode server-entry.js --init --path <project>. - Atlas scans the project recursively:
- Uses tree-sitter (WASM grammars) to parse source code.
- Extracts function/class/type definitions, calls, imports.
- Stores in SQLite database:
atlas.db.
- Exits when done; database persists.
node server-entry.js --path <project> (no --init):
Atlas Architecture
- Extraction: Language-specific parsers (tree-sitter + language handlers for JS, Python, Rust, Go, etc.). Outputs symbols (definitions + refs).
- Database: SQLite schema mapping files -> symbols, call graphs, import chains.
- Graph: Traversal utilities for reaching related code (callers, callees, dependencies).
- MCP Server: Exposes query tools to agents (Claude, Cursor, etc.) over stdio.
MCP Server Lifecycle
The Atlas server runs in daemon mode (single per project):7. Data Flow: User Action to Agent Execution
Scenario: Launch a Claude Agent in a Worktree
Frontend:- User clicks “New Agent” button in the WorkspaceView.
- React component calls
invoke("create_terminal_worktree", { projectPath, name }). - Waits for worktree path to return.
- Spawns blocking thread to create worktree (git commands, file copies, junctions).
- Returns worktree path to React.
WorktreeSession { agent: "claude", projectId, name, ... }.
4. Persists to runtime state via setRuntimeState() (calls write_runtime_state).
5. Renders a new terminal tab.
Frontend:
6. User enters a prompt or clicks “Resume Conversation”.
7. Component calls invoke("create_pty_session", { session_id, cwd: worktreePath, command: "claude", args: ["--path", worktreePath, "--session", sessionId, ...], on_event: channel }).
Tauri Backend:
- Runs on blocking thread: opens a PTY via
portable_pty::PtySystem::openpty(). - Spawns shell:
powershell.exe -NoExit -Command "claude --path ... ; exec $SHELL -i". - Writes the shell’s PID to
.tempest-pidsidecar (for cleanup on restart). - Spawns a reader thread that pumps PTY output and sends it via
on_event.send(). - Inserts the
PtySessioninto theDashMapregistry. - Returns Ok(()).
- Receives
pty-outputevents via listener. - Updates terminal state (xterm.js) with each data chunk.
- Renders live output as the agent runs.
- Reads MCP config from
.mcp.json, connects to Atlas daemon. - Runs iteration loop: read user prompt, query Atlas for context, call LLM, run tools, write output.
- Writes to stdout/stderr (captured by PTY reader thread, streamed to frontend).
- On exit, the PTY session closes; React marks session as closed.
- User closes the tab or clicks “End Workspace”.
- Component calls
invoke("close_and_remove_worktree", { session_id, repo_path, worktree_path }).
- Removes session from
DashMapregistry. - Kills the PTY child (kills shell + agent + descendants).
- Waits for process to exit (~3s).
- Kills by PID via
.tempest-pidsidecar (redundant, handles restart case). - Removes junctions/symlinks.
- Removes worktree directory.
- Prunes git metadata.
- Returns Ok(()).
- Receives Ok(), removes the session from React store and UI.
8. Key Dependencies and Design Rationale
Tauri (@tauri-apps/api, @tauri-apps/cli)
Why: Cross-platform desktop app with native backend. Smaller than Electron, Rust-backed for performance, built-in IPC.
Trade-offs: Learning curve for Rust + Tauri conventions, but payoff is a ~15 MB app vs. ~150 MB for Electron.
portable-pty (Rust, 0.8)
Why: Cross-platform PTY spawning. Abstracts Windows conpty + Unix PTYs behind one API. Essential for terminal apps.
Key challenge: Windows PTY process lifecycle — must kill the entire job object (shell + descendants) atomically, handle handle lag on directory cleanup.
Solution: Track PID in sidecar, force-kill by PID after app restart, retry loop for handle release.
dashmap (Rust, 6)
Why: Lock-free concurrent hashmap. Stores active PTY sessions keyed by session ID.
Trade-off: Ergonomic API (e.g., map.insert()) vs. raw Mutex<HashMap> — lock contention on many sessions is avoided.
xterm.js (@xterm/xterm, 6.0.0 + addons)
Why: Mature, feature-rich web terminal emulator. Supports ANSI color, mouse, search, Unicode.
Addons used:
@xterm/addon-fit: Auto-fit to container size.@xterm/addon-search: Find in terminal.@xterm/addon-unicode11: Full Unicode support.@xterm/addon-web-links: Clickable URLs.@xterm/addon-webgl: GPU-accelerated rendering for large scrollback.
CodeMirror 6 (various @codemirror/* packages)
Why: Modern code editor framework. Extensible, tree-sitter integration ready, minimal bundle size (~50 KB gzipped core).
Language packages (installed separately):
@codemirror/lang-javascript,lang-python,lang-rust,lang-json, etc.- Each ~10-20 KB, loaded on demand.
React 19 + React DOM 19
Why: Modern hooks, JSX, ecosystem maturity. Setup: Vite for dev speed, no StrictMode (double-invokes effects, would spawn PTY twice on mount — avoided per Termic’s design).React Markdown + Rehype + Remark
Why: Render git diffs, agent logs, LLM responses as formatted Markdown.react-markdown: JSX-based markdown rendering.remark-gfm: GitHub-flavored markdown (tables, strikethrough, tasklists).rehype-raw: Embed raw HTML (diffs, code blocks).rehype-highlight: Syntax highlighting (withhighlight.jsbackend).
Geist Fonts + github-markdown-css
Why: Consistent, modern design. Geist (Vercel’s font) is licensed for use, CSS normalizes markdown rendering.Vite (7.0.4)
Why: Lightning-fast dev server. Native ES modules, < 1s rebuild on file save.
Config: Port 1420 (fixed for Tauri), watch ignores src-tauri/ (Rust changes don’t rebuild frontend).
9. File Layout
Directory Purpose Summary
| Directory | Purpose | Persisted? |
|---|---|---|
src/ | React frontend logic and UI | No (rebuilt) |
src-tauri/src/ | Tauri backend; all OS-level ops | No (compiled) |
.tempest/ (in project) | Worktree directories (symlinks/junctions + copies) | Yes (git-ignored) |
.atlas/ (in project) | Atlas database and index metadata | Yes (git-ignored) |
.mcp.json, .cursor/mcp.json | MCP server config (agent-readable) | Yes (git-ignored) |
~/.local/share/tempest/ (Linux) or %APPDATA%\tempest\ (Windows) | App data dir (runtime-state.json) | Yes |
10. Threading and Concurrency Model
Main Thread (Tauri App Thread)
- Runs the Tauri event loop.
- Executes async commands (but command bodies run on worker threads).
- Emits events to connected clients.
Worker Thread Pool (Tauri)
- Bounded pool (default: 8 threads).
- Blocks on git subprocesses, file I/O (slow).
- Commands decorated with
asyncrun here viaspawn_blocking().
PTY Reader Threads
- One per active PTY session.
- Reads from the PTY master, sends chunks via
on_event. - Runs indefinitely until EOF (process exit).
React Task Scheduling
- React 19’s auto-batching groups state updates in a microtask.
- Tauri commands return via JS microtask scheduler.
- No explicit thread management needed in React (all on web worker/main thread).
Concurrency Guarantees
- PtyState: Lock-free (DashMap). No contention on session adds/removes.
- WorktreeSession: Copied and re-saved to JSON atomically (no partial updates).
- Git operations: Subprocess-based (git’s own locking prevents conflicts).
- Database: SQLite serializes writes (WAL mode optional, not currently used).
11. Worktree Persistence and Recovery
On Graceful Shutdown
- React store saves
sessions: Record<string, WorktreeSession>toruntime-state.json. - PTY sessions are killed (no graceful shutdown of agent CLI).
- Worktree directories remain (git-ignored).
On App Restart
- Load Runtime State:
loadRuntimeState()restores allWorktreeSessionrecords from JSON. - Restore UI: For each session, create a tab + PTY connection.
- Restore PTY Processes:
- Read
.tempest/<name>/.tempest-pidfile. - If agent process is still running, reattach its PTY (not yet implemented; currently treated as new).
- If process is dead, the PID file persists but references a non-existent process (safe).
- Read
- Cleanup: Periodically prune orphaned sessions (missing on-disk paths).
Orphaned Processes
If the app crashes (or user force-kills it):- PTY processes continue running in the background (still holding worktree dir lock on Windows).
- User relaunches app, runtime state restores.
- On worktree deletion,
kill_persisted_pid()reads.tempest-pidand force-kills the orphaned process.
12. Error Handling Patterns
Tauri Commands
Commands returnResult<T, String>. Errors serialize as JSON strings:
PTY Session Errors
PTY creation failures (e.g., shell not found) are returned to React via the command’sResult<(), String>.
PTY runtime errors (read failures, write failures) are silent — the reader thread closes and the session is marked closed.
Atlas Indexing
Index failures are fire-and-forget in the backend (logged to stderr). The UI shows “indexing in progress…” and if it hangs, the user can retry or skip.13. Future Extensibility
Tempest’s architecture is designed for expansion:Adding New Tauri Commands
- Add a
#[tauri::command]function insrc-tauri/src/lib.rs. - Register in
generate_handler![...]at the bottom. - Call from React via
invoke().
Adding New React Components
- Create in
src/components/. - Use existing stores (sessions, appSettings, etc.) or create new via
useSyncExternalStore. - Subscribe to Tauri events via
listen()if needed.
Extending Atlas
- Add new language parsers in
packages/atlas/src/extraction/languages/. - Add new query types in
packages/atlas/src/mcp/tools.ts. - Rebuild and redeploy.
Multi-Window Support
Tauri supports multiple windows. Tempest currently uses “zen” windows (full-screen worktree view) and could expand to split panes, floating panels, etc.Summary
Tempest is a tightly-integrated Tauri + React system where:- Rust backend handles OS complexity (PTY, git, filesystem, process cleanup).
- React frontend orchestrates the UI and calls Rust commands.
- Git worktrees isolate agent work with shared objects and linked dependencies.
- Atlas provides semantic code intelligence via MCP.
- Persistent JSON survives app restarts; in-memory stores handle ephemeral state.
- Blocking thread pool prevents UI freezes on slow operations.
- Force-kill by PID ensures clean worktree cleanup even after crashes.