Skip to content

Architecture

Nerve is a web interface for OpenClaw — chat, voice input, TTS, and agent monitoring in the browser. It connects to the OpenClaw gateway over WebSocket and provides a rich UI for interacting with AI agents.

System Diagram

┌──────────────────────────────────────────────────────────────────┐
│  Browser (React SPA)                                             │
│                                                                  │
│  ┌──────────┐  ┌──────────┐  ┌───────────┐  ┌────────────────┐  │
│  │ ChatPanel│  │ Sessions │  │ Workspace │  │ Command Palette│  │
│  └────┬─────┘  └────┬─────┘  └─────┬─────┘  └────────────────┘  │
│       │              │              │                             │
│  ┌────┴──────────────┴──────────────┴────────────────────────┐   │
│  │           React Contexts (Gateway, Session, Chat, Settings)│   │
│  └────────────────────────────┬──────────────────────────────┘   │
│                               │ WebSocket (/ws proxy)            │
└───────────────────────────────┼──────────────────────────────────┘

┌───────────────────────────────┼──────────────────────────────────┐
│  Nerve Server (Hono + Node)   │                                  │
│                               │                                  │
│  ┌────────────────────────────┴─────────────┐                    │
│  │         WebSocket Proxy (ws-proxy.ts)     │                   │
│  │  - Intercepts connect.challenge           │                   │
│  │  - Injects device identity (Ed25519)      │                   │
│  └────────────────────────────┬──────────────┘                   │
│                               │                                  │
│  ┌───────────────┐  ┌────────┴─────┐  ┌───────────────────────┐ │
│  │ REST API      │  │ SSE Stream   │  │ Static File Server    │ │
│  │ /api/*        │  │ /api/events  │  │ Vite build → dist/    │ │
│  └───────┬───────┘  └──────────────┘  └───────────────────────┘ │
│          │                                                       │
│  ┌───────┴──────────────────────────────────────────────────┐    │
│  │  Services: TTS (OpenAI, Replicate, Edge), Whisper,       │    │
│  │  Claude Usage, TTS Cache, Usage Tracker                  │    │
│  └──────────────────────────────────────────────────────────┘    │
└──────────────────────────────┬───────────────────────────────────┘
                               │ HTTP / WS
                    ┌──────────┴──────────┐
                    │  OpenClaw Gateway    │
                    │  (ws://127.0.0.1:    │
                    │       18789)         │
                    └─────────────────────┘

Frontend Structure

Built with React 19, TypeScript, Vite, and Tailwind CSS v4.

Entry Point

FilePurpose
src/main.tsxMounts the React tree: ErrorBoundary → StrictMode → AuthGate. The auth gate checks session status before rendering the app
src/App.tsxRoot layout — wires contexts to lazy-loaded panels, manages keyboard shortcuts and command palette

Context Providers (State Management)

All global state flows through four React contexts, nested in dependency order:

ContextFileResponsibilities
GatewayContextsrc/contexts/GatewayContext.tsxWebSocket connection lifecycle, RPC method calls, event fan-out via pub/sub pattern, model/thinking status polling, activity sparkline
SettingsContextsrc/contexts/SettingsContext.tsxSound, TTS provider/model, wake word, panel ratio, theme, font, telemetry/events visibility. Persists to localStorage
SessionContextsrc/contexts/SessionContext.tsxSession list (via gateway RPC), granular agent status tracking (IDLE/THINKING/STREAMING/DONE/ERROR), busy state derivation, unread session tracking, agent log, event log, session CRUD (delete, spawn, rename, abort)
ChatContextsrc/contexts/ChatContext.tsxThin orchestrator composing 4 hooks: useChatMessages (CRUD, history, scroll), useChatStreaming (deltas, processing stage, activity log), useChatRecovery (reconnect, retry, gap detection), useChatTTS (playback, voice fallback, sound feedback)

Data flow pattern: Contexts subscribe to gateway events via GatewayContext.subscribe(). The SessionContext listens for agent and chat events to update granular status. The ChatContext listens for streaming deltas and lifecycle events to render real-time responses.

Feature Modules

Each feature lives in src/features/<name>/ with its own components, hooks, types, and operations.

features/auth/

Authentication gate and login UI.

FilePurpose
AuthGate.tsxTop-level component — shows loading spinner, login page, or the full app depending on auth state
LoginPage.tsxFull-screen password form matching Nerve's dark theme. Auto-focus, Enter-to-submit, gateway token hint
useAuth.tsAuth state via useSyncExternalStore. Module-level fetch checks /api/auth/status once on load. Exposes login/logout callbacks
index.tsBarrel export

features/chat/

The main chat interface.

FilePurpose
ChatPanel.tsxFull chat view — message list with infinite scroll, input bar, streaming indicator, search
InputBar.tsxText input with voice recording, image attachment, tab completion, input history
MessageBubble.tsxRenders individual messages (user, assistant, tool, system) with markdown
ToolCallBlock.tsxRenders tool call blocks with name, arguments, and results
DiffView.tsxSide-by-side diff rendering for file edits
FileContentView.tsxSyntax-highlighted file content display
ImageLightbox.tsxFull-screen image viewer
SearchBar.tsxIn-chat message search (Cmd+F)
MemoriesSection.tsxInline memory display within chat
edit-blocks.tsParses edit/diff blocks from tool output
extractImages.tsExtracts image content blocks from messages
image-compress.tsClient-side image compression before upload
types.tsChat-specific types (ChatMsg, ImageAttachment)
utils.tsChat utility functions
useMessageSearch.tsHook for message search filtering
operations/Pure business logic (no React): loadHistory.ts, sendMessage.ts, streamEventHandler.ts
components/Sub-components: ActivityLog, ChatHeader, HeartbeatPulse, ProcessingIndicator, ScrollToBottomButton, StreamingMessage, ThinkingDots, ToolGroupBlock, useModelEffort

features/sessions/

Session management sidebar.

FilePurpose
SessionList.tsxHierarchical session tree with parent-child relationships
SessionNode.tsxIndividual session row with status indicator, context menu
SessionInfoPanel.tsxSession detail panel (model, tokens, thinking level)
SpawnAgentDialog.tsxDialog for spawning sub-agents with task/model/thinking config
sessionTree.tsBuilds tree structure from flat session list using parentId
statusUtils.tsMaps agent status to icons and labels

features/file-browser/

Full workspace file browser with tabbed CodeMirror editor.

FilePurpose
FileTreePanel.tsxCollapsible file tree sidebar with directory expand/collapse
FileTreeNode.tsxIndividual file/directory row with icon and indent
EditorTabBar.tsxTab bar for open files with close buttons
EditorTab.tsxSingle editor tab with modified indicator
FileEditor.tsxCodeMirror 6 editor — syntax highlighting, line numbers, search, Cmd+S save
TabbedContentArea.tsxManages chat/editor tab switching (chat never unmounts)
editorTheme.tsOne Dark-inspired CodeMirror theme matching Nerve's dark aesthetic
hooks/useFileTree.tsFile tree data fetching and directory toggle state
hooks/useOpenFiles.tsOpen file tab management, save with mtime conflict detection
utils/fileIcons.tsxFile extension → icon mapping
utils/languageMap.tsFile extension → CodeMirror language extension mapping
types.tsShared types (FileNode, OpenFile, etc.)

features/workspace/

Workspace file editor and management tabs.

FilePurpose
WorkspacePanel.tsxContainer for workspace tabs
WorkspaceTabs.tsxTab switcher (Memory, Config, Crons, Skills)
tabs/MemoryTab.tsxView/edit MEMORY.md and daily files
tabs/ConfigTab.tsxEdit workspace files (SOUL.md, TOOLS.md, USER.md, etc.)
tabs/CronsTab.tsxCron job management (list, create, toggle, run)
tabs/CronDialog.tsxCron creation/edit dialog
tabs/SkillsTab.tsxView installed skills with eligibility status
hooks/useWorkspaceFile.tsFetch/save workspace files via REST API
hooks/useCrons.tsCron CRUD operations via REST API
hooks/useSkills.tsFetch skills list

features/settings/

Settings drawer with tabbed sections.

FilePurpose
SettingsDrawer.tsxSlide-out drawer container. Includes logout button when auth is enabled
ConnectionSettings.tsxGateway URL/token, reconnect
AudioSettings.tsxTTS provider, model, voice, wake word
AppearanceSettings.tsxTheme, font selection

features/tts/

Text-to-speech integration.

FilePurpose
useTTS.tsCore TTS hook — speaks text via server /api/tts endpoint. Supports OpenAI, Replicate, Edge (default) providers
useTTSConfig.tsServer-side TTS voice configuration management

features/voice/

Voice input and audio feedback.

FilePurpose
useVoiceInput.tsWeb Speech API integration for voice-to-text with Whisper fallback
audio-feedback.tsNotification sounds (ping on response complete)

features/markdown/

Markdown rendering pipeline.

FilePurpose
MarkdownRenderer.tsxreact-markdown with remark-gfm, syntax highlighting via highlight.js
CodeBlockActions.tsxCopy/run buttons on code blocks

features/charts/

Inline chart rendering with three renderers: TradingView widgets for live financial data, Lightweight Charts for custom time-series and candlestick data, and Recharts for bar/pie charts.

FilePurpose
InlineChart.tsxChart router — dispatches [chart:{...}] markers to the correct renderer based on type
extractCharts.tsBracket-balanced parser for [chart:{...}] markers. Validates chart data by type (bar, line, pie, area, candle, tv)
LightweightChart.tsxRenders line, area, and candlestick charts using lightweight-charts (TradingView). Dark theme, gradient fills, crosshair, percentage change badges
TradingViewWidget.tsxEmbeds TradingView Advanced Chart widget via official script injection for real financial tickers (e.g. TVC:GOLD, BITSTAMP:BTCUSD)

Chart type routing:

TypeRendererUse case
tvTradingView WidgetLive financial tickers (stocks, crypto, forex, commodities)
line, areaLightweight ChartsCustom time-series data
candleLightweight ChartsCustom OHLC candlestick data
bar, pieRechartsCategory comparisons, proportions

features/command-palette/

Cmd+K command palette.

FilePurpose
CommandPalette.tsxFuzzy-search command list
commands.tsCommand definitions (new session, reset, theme, TTS, etc.)

features/connect/

FilePurpose
ConnectDialog.tsxInitial gateway connection dialog. Uses official gateway URL + serverSideAuth metadata from /api/connect-defaults to decide whether the token field is needed

features/activity/

FilePurpose
AgentLog.tsxScrolling agent activity log (tool calls, lifecycle events)
EventLog.tsxRaw gateway event stream display

features/dashboard/

FilePurpose
TokenUsage.tsxToken usage and cost display
MemoryList.tsxMemory listing component
useLimits.tsClaude Code / Codex rate limit polling

features/memory/

FilePurpose
MemoryEditor.tsxInline memory editing
MemoryItem.tsxIndividual memory display with edit/delete
AddMemoryDialog.tsxDialog for adding new memories
ConfirmDeleteDialog.tsxDelete confirmation
useMemories.tsMemory CRUD operations

Shared Components

PathPurpose
components/TopBar.tsxHeader with agent log, token data, event indicators
components/StatusBar.tsxFooter with connection state, session count, sparkline, context meter
components/ResizablePanels.tsxDraggable split layout (chat left, panels right)
components/ContextMeter.tsxVisual context window usage bar
components/ConfirmDialog.tsxReusable confirmation modal
components/ErrorBoundary.tsxTop-level error boundary
components/PanelErrorBoundary.tsxPer-panel error boundary (isolates failures)
components/NerveLogo.tsxSVG logo component
components/skeletons/Loading skeleton components (Message, Session, Memory)
components/ui/Primitives: button, card, dialog, input, switch, scroll-area, collapsible, AnimatedNumber, InlineSelect

Hooks

HookFilePurpose
useWebSockethooks/useWebSocket.tsCore WebSocket management — connect, RPC, auto-reconnect with exponential backoff
useConnectionManagerhooks/useConnectionManager.tsAuto-connect logic, official gateway resolution, serverSideAuth gating, and credential persistence in localStorage
useDashboardDatahooks/useDashboardData.tsFetches memories and token data via REST + SSE
useServerEventshooks/useServerEvents.tsSSE client for /api/events
useInputHistoryhooks/useInputHistory.tsUp/down arrow input history
useTabCompletionhooks/useTabCompletion.tsTab completion for slash commands
useKeyboardShortcutshooks/useKeyboardShortcuts.tsGlobal keyboard shortcut registration
useGitInfohooks/useGitInfo.tsGit branch/status display

Libraries

FilePurpose
lib/constants.tsApp constants: context window limits (with dynamic getContextLimit() fallback), wake/stop/cancel phrase builders, attachment limits
lib/themes.tsTheme definitions and CSS variable application
lib/fonts.tsFont configuration
lib/formatting.tsMessage formatting utilities
lib/sanitize.tsHTML sanitization via DOMPurify
lib/highlight.tsSyntax highlighting configuration
lib/utils.tscn() classname merge utility (clsx + tailwind-merge)
lib/progress-colors.tsColor scales for progress indicators
lib/text/isStructuredMarkdown.tsDetects structured markdown for rendering decisions

Backend Structure

Built with Hono (lightweight web framework), TypeScript, running on Node.js ≥22.

Entry Point

FilePurpose
server/index.tsStarts HTTP + HTTPS servers, sets up WebSocket proxy, file watchers, graceful shutdown
server/app.tsHono app definition — middleware stack, route mounting, static file serving with SPA fallback

Middleware Stack

Applied in order in app.ts:

MiddlewareFilePurpose
Error handlermiddleware/error-handler.tsCatches unhandled errors, returns consistent JSON. Shows stack in dev
LoggerHono built-inRequest logging
CORSHono built-in + customWhitelist of localhost origins + ALLOWED_ORIGINS env var. Validates via URL constructor. Methods: GET, POST, PUT, PATCH, DELETE, OPTIONS
Security headersmiddleware/security-headers.tsStandard security headers (CSP, X-Frame-Options, etc.)
Body limitHono built-inConfigurable max body size (from config.limits.maxBodyBytes)
Authmiddleware/auth.tsWhen NERVE_AUTH=true, requires a valid signed session cookie on /api/* routes (except auth endpoints and health). WebSocket upgrades checked separately in ws-proxy.ts
CompressionHono built-ingzip/brotli on all routes except SSE (/api/events)
Cache headersmiddleware/cache-headers.tsHashed assets → immutable, API → no-cache, non-hashed static → must-revalidate
Rate limitingmiddleware/rate-limit.tsPer-IP sliding window. Separate limits for general API vs TTS/transcribe. Client ID from socket or custom header

API Routes

RouteFileMethodsPurpose
/healthroutes/health.tsGETHealth check with gateway connectivity probe
/api/auth/statusroutes/auth.tsGETCheck whether auth is enabled and current session validity
/api/auth/loginroutes/auth.tsPOSTAuthenticate with password, set signed session cookie
/api/auth/logoutroutes/auth.tsPOSTClear session cookie
/api/connect-defaultsroutes/connect-defaults.tsGETReturns the official gateway WS URL plus authEnabled / serverSideAuth metadata. token is always null; trusted flows use server-side token injection
/api/eventsroutes/events.tsGET, POSTSSE stream for real-time push (memory.changed, tokens.updated, status.changed, ping). POST for test events
/api/ttsroutes/tts.tsPOSTText-to-speech with provider auto-selection (OpenAI → Replicate → Edge). LRU cache with TTL
/api/tts/configroutes/tts.tsGET, PUTTTS voice configuration per provider (read / partial update)
/api/transcriberoutes/transcribe.tsPOSTAudio transcription via OpenAI Whisper or local whisper.cpp (STT_PROVIDER). Multipart file upload, MIME validation
/api/transcribe/configroutes/transcribe.tsGET, PUTSTT runtime config (provider/model/language), model readiness/download status, hot-reload updates
/api/languageroutes/transcribe.tsGET, PUTLanguage preference + Edge voice gender management (NERVE_LANGUAGE, EDGE_VOICE_GENDER)
/api/language/supportroutes/transcribe.tsGETFull provider × language compatibility matrix + current local model multilingual state
/api/voice-phrasesroutes/voice-phrases.tsGETMerged recognition phrase set (selected language + English fallback)
/api/voice-phrases/statusroutes/voice-phrases.tsGETPer-language custom phrase configuration status
/api/voice-phrases/:langroutes/voice-phrases.tsGET, PUTRead/save language-specific stop/cancel/wake phrase overrides
/api/agentlogroutes/agent-log.tsGET, POSTAgent activity log persistence. Zod-validated entries. Mutex-protected file I/O
/api/tokensroutes/tokens.tsGETToken usage statistics — scans session transcripts, persists high water mark
/api/memoriesroutes/memories.tsGET, POST, DELETEMemory management — reads MEMORY.md + daily files, stores/deletes via gateway tool invocation
/api/memories/sectionroutes/memories.tsGET, PUTRead/replace a specific memory section by title
/api/gateway/modelsroutes/gateway.tsGETAvailable models via openclaw models list. Allowlist support
/api/gateway/session-inforoutes/gateway.tsGETCurrent session model/thinking level
/api/gateway/session-patchroutes/gateway.tsPOSTChange model/effort for a session
/api/server-inforoutes/server-info.tsGETServer time, gateway uptime, agent name
/api/versionroutes/version.tsGETPackage version from package.json
/api/git-inforoutes/git-info.tsGET, POST, DELETEGit branch/status. Session workdir registration
/api/workspace/:keyroutes/workspace.tsGET, PUTRead/write workspace files (strict key→file allowlist: soul, tools, identity, user, agents, heartbeat)
/api/cronsroutes/crons.tsGET, POST, PATCH, DELETECron job CRUD via gateway tool invocation
/api/crons/:id/toggleroutes/crons.tsPOSTToggle cron enabled/disabled
/api/crons/:id/runroutes/crons.tsPOSTRun cron job immediately
/api/crons/:id/runsroutes/crons.tsGETCron run history
/api/skillsroutes/skills.tsGETList skills via openclaw skills list --json
/api/filesroutes/files.tsGETServe local image files (MIME-type restricted, directory traversal blocked)
/api/files/treeroutes/file-browser.tsGETWorkspace directory tree (excludes node_modules, .git, etc.)
/api/files/readroutes/file-browser.tsGETRead file contents with mtime for conflict detection
/api/files/writeroutes/file-browser.tsPOSTWrite file with mtime-based optimistic concurrency (409 on conflict)
/api/claude-code-limitsroutes/claude-code-limits.tsGETClaude Code rate limits via PTY + CLI parsing
/api/codex-limitsroutes/codex-limits.tsGETCodex rate limits via OpenAI API with local file fallback
/api/kanban/tasksroutes/kanban.tsGET, POSTTask CRUD -- list (with filters/pagination) and create
/api/kanban/tasks/:idroutes/kanban.tsPATCH, DELETEUpdate (CAS-versioned) and delete tasks
/api/kanban/tasks/:id/reorderroutes/kanban.tsPOSTReorder/move tasks across columns
/api/kanban/tasks/:id/executeroutes/kanban.tsPOSTSpawn agent session for task
/api/kanban/tasks/:id/completeroutes/kanban.tsPOSTComplete a running task (auto-called by poller)
/api/kanban/tasks/:id/approveroutes/kanban.tsPOSTApprove task in review -> done
/api/kanban/tasks/:id/rejectroutes/kanban.tsPOSTReject task in review -> todo
/api/kanban/tasks/:id/abortroutes/kanban.tsPOSTAbort running task -> todo
/api/kanban/proposalsroutes/kanban.tsGET, POSTList and create proposals
/api/kanban/proposals/:id/approveroutes/kanban.tsPOSTApprove pending proposal
/api/kanban/proposals/:id/rejectroutes/kanban.tsPOSTReject pending proposal
/api/kanban/configroutes/kanban.tsGET, PUTBoard configuration

Server Libraries

FilePurpose
lib/config.tsCentralized configuration from env vars — ports, keys, paths, limits, auth settings. Validated at startup
lib/session.tsSession token creation/verification (HMAC-SHA256), password hashing (scrypt), cookie parsing for WS upgrade requests
lib/ws-proxy.tsWebSocket proxy — client→gateway with session cookie auth on upgrade and Ed25519 device identity injection
lib/device-identity.tsEd25519 keypair generation/persistence (~/.nerve/device-identity.json). Builds signed connect blocks for gateway auth
lib/gateway-client.tsHTTP client for gateway tool invocation API (/tools/invoke)
lib/file-watcher.tsWatches MEMORY.md, memory/, and workspace directory (recursive). Broadcasts file.changed SSE events for real-time sync
lib/file-utils.tsFile browser utilities — path validation, directory exclusions, binary file detection
lib/files.tsAsync file helpers (readJSON, writeJSON, readText)
lib/mutex.tsAsync mutex for serializing file read-modify-write. Includes keyed mutex variant
lib/env-file.tsMutex-protected .env key upserts for hot-reload settings writes (writeEnvKey)
lib/language.tsLanguage/provider compatibility helpers (isLanguageSupported, fallback metadata)
lib/voice-phrases.tsRuntime per-language phrase storage/merge (custom overrides + English fallback)
lib/cached-fetch.tsGeneric TTL cache with in-flight request deduplication
lib/usage-tracker.tsPersistent token usage high water mark tracking
lib/tts-config.tsTTS voice configuration file management
lib/openclaw-bin.tsResolves openclaw binary path (env → sibling of node → common paths → PATH)

Services

FilePurpose
services/openai-tts.tsOpenAI TTS API client (gpt-4o-mini-tts, tts-1, tts-1-hd)
services/replicate-tts.tsReplicate API client for hosted TTS models (Qwen3-TTS). WAV→MP3 via ffmpeg
services/edge-tts.tsMicrosoft Edge Read-Aloud TTS via WebSocket protocol. Free, zero-config. Includes Sec-MS-GEC token generation
services/tts-cache.tsLRU in-memory TTS cache with TTL expiry (100 MB budget)
services/openai-whisper.tsOpenAI Whisper transcription client
services/whisper-local.tsLocal whisper.cpp STT via @fugood/whisper.node. Singleton model context, auto-download from HuggingFace, GPU detection
services/claude-usage.tsClaude Code CLI usage/limits parser via node-pty

Updater (server/lib/updater/)

Self-update system invoked via npm run update (entrypoint: bin/nerve-update.tsbin-dist/bin/nerve-update.js).

FilePurpose
orchestrator.tsState machine: lock → preflight → resolve → snapshot → update → build → restart → health → rollback
preflight.tsValidates git, Node.js, npm versions and git repo state
release-resolver.tsFinds latest semver tag via git ls-remote --tags, falls back to local tags
snapshot.tsSaves current git ref, version, and .env backup to ~/.nerve/updater/
installer.tsgit fetch + checkout --force, npm install, npm run build + build:server
service-manager.tsAuto-detects systemd or launchd, provides restart/status/logs
health.tsPolls /health and /api/version with exponential backoff (60s deadline)
rollback.tsRestores snapshot ref, clean rebuilds, restarts service
lock.tsPID-based exclusive lock file (wx flag) with stale detection
reporter.tsFormatted terminal output with stage progress, colors, and dry-run markers
types.tsShared types, exit codes, UpdateError class

Compiled separately via config/tsconfig.bin.json to avoid changing the server's rootDir.


Data Flow

WebSocket Proxy

Browser WS → /ws?target=ws://gateway:18789/ws → ws-proxy.ts → OpenClaw Gateway
  1. Client connects to /ws endpoint on Nerve server
  2. When auth is enabled, the session cookie is verified on the HTTP upgrade request (rejects with 401 if invalid)
  3. Proxy validates target URL against WS_ALLOWED_HOSTS allowlist
  4. Proxy opens upstream WebSocket to the gateway
  5. On connect.challenge event, proxy intercepts the client's connect request and injects Ed25519 device identity (device block with signed nonce)
  6. If the gateway rejects the device (close code 1008), proxy retries without device identity (reduced scopes)
  7. After handshake, all messages are transparently forwarded bidirectionally
  8. Pending messages are buffered (capped at 100 messages / 1 MB) while upstream connects

Server-Sent Events (SSE)

Browser → GET /api/events → SSE stream (text/event-stream)

Events pushed by the server:

  • memory.changed — File watcher detects MEMORY.md or daily file changes
  • tokens.updated — Token usage data changed
  • status.changed — Gateway status changed
  • ping — Keep-alive every 30 seconds

SSE is excluded from compression middleware to avoid buffering.

REST API

REST endpoints serve two purposes:

  1. Proxy to gateway — Routes like /api/crons, /api/memories (POST/DELETE), /api/gateway/* invoke gateway tools via invokeGatewayTool()
  2. Local server data — Routes like /api/tokens, /api/agentlog, /api/server-info read from local files or process info

Gateway RPC (via WebSocket)

The frontend calls gateway methods via GatewayContext.rpc():

MethodPurpose
statusGet current agent model, thinking level
sessions.listList active sessions
sessions.deleteDelete a session
sessions.resetClear session context
sessions.patchRename a session
chat.sendSend a message (with idempotency key)
chat.historyLoad message history
chat.abortAbort current generation
connectInitial handshake with auth/device identity

Event Types (Gateway → Client)

EventPayloadPurpose
connect.challenge{ nonce }Auth handshake initiation
chat{ sessionKey, state, message?, content? }Chat state changes: started, delta, final, error, aborted
agent{ sessionKey, state, stream, data? }Agent lifecycle: lifecycle.start/end/error, tool.start/result, assistant stream
cron{ name }Cron job triggered
exec.approval.requestExec approval requested
exec.approval.resolvedExec approval granted
presencePresence updates

Kanban Subsystem

The kanban board provides task management with agent execution, drag-and-drop reordering, and a proposal system for agent-initiated changes.

Store Design

server/data/kanban/tasks.json   -- single JSON file (tasks + proposals + config)
server/data/kanban/audit.log    -- append-only audit log (JSONL)

All data lives in one JSON file (StoreData). Every mutation acquires an async mutex, reads the file, applies the change, and writes back atomically via temp-file rename. This guarantees consistency under concurrent requests without a database.

FilePurpose
server/lib/kanban-store.tsKanbanStore class -- mutex-protected CRUD, workflow transitions, CAS versioning, proposal management
server/routes/kanban.tsHono routes, Zod validation, gateway session spawning, poll loop
server/lib/parseMarkers.tsRegex-based [kanban:create]/[kanban:update] marker extraction

The store schema is versioned (meta.schemaVersion). A migrate() function runs on every read to backfill new fields transparently.

State Machine

                    execute          complete (success)        approve
  backlog --------+                  +---- review ------------ done
                  v                  |       |
  todo --------- in-progress -------+       | reject
                  |                          v
                  | abort / error         todo (run cleared)
                  +---------------------> todo
TransitionFromToTrigger
Executebacklog, todoin-progressPOST .../execute
Complete (success)in-progressreviewPoller or POST .../complete
Complete (error)in-progresstodoPoller or POST .../complete with error
ApprovereviewdonePOST .../approve
RejectreviewtodoPOST .../reject (clears run + result)
Abortin-progresstodoPOST .../abort

The cancelled status exists in the schema but has no automatic transitions -- tasks are moved there manually via PATCH.

CAS Versioning

Every task has a version field (starts at 1, incremented on every mutation). Mutating endpoints (PATCH, reorder, workflow actions) require the client to send the current version. If it doesn't match, the server returns 409 with the latest task so the client can retry:

json
{ "error": "version_conflict", "serverVersion": 5, "latest": { "..." } }

This prevents stale overwrites from concurrent editors (drag-and-drop, API clients, agent completions).

Agent Execution Flow

1. POST /api/kanban/tasks/:id/execute
   +-- store.executeTask()    -> status = in-progress, run.status = running
   +-- invokeGatewayTool('sessions_spawn', { label: 'kanban-<id>', ... })
       +-- fire-and-forget

2. pollSessionCompletion()    -> polls gateway subagents every 5s (max 360 attempts / 30 min)
   +-- invokeGatewayTool('subagents', { action: 'list' })
   +-- match by label
   +-- if status=done:
       |   fetch session history (last 3 messages)
       |   parseKanbanMarkers(resultText) -> create proposals
       |   stripKanbanMarkers(resultText) -> clean result
       +-- store.completeRun(taskId, cleanResult)
   +-- if status=error/failed:
       +-- store.completeRun(taskId, undefined, errorMsg)
   +-- if status=running:
       +-- schedule next poll

3. store.completeRun()
   |-- success -> run.status = done, task.status = review
   +-- error   -> run.status = error, task.status = todo

The model cascade is: task's model -> board config defaultModel -> anthropic/claude-sonnet-4-5.

Marker Parsing

When agent output arrives (via poller or POST .../complete), it's scanned for kanban markers:

[kanban:create]{"title":"Fix login bug","priority":"high"}[/kanban:create]
[kanban:update]{"id":"abc","status":"done"}[/kanban:update]

Each valid marker creates a proposal in the store. The markers are then stripped from the result text before it's saved on the task. See Agent Markers for the full format.

Proposal System

Proposals let agents suggest task changes without directly modifying the board:

  1. Agent emits [kanban:create] or [kanban:update] markers in its output
  2. Backend parses markers -> creates proposals with status: "pending"
  3. Frontend polls proposals every 5 seconds -> shows inbox notification
  4. Operator approves or rejects each proposal

The proposalPolicy config controls behavior:

  • "confirm" (default) -- proposals stay pending until the operator acts
  • "auto" -- proposals are applied immediately on creation

Polling Architecture

WhatWhoIntervalPurpose
Task listFrontend5sSync board state across tabs/users
ProposalsFrontend5sShow new proposals in inbox
Gateway subagentsBackend5sDetect when agent runs complete

Backend polling for each running task is independent -- each executeTask call starts its own poll loop (capped at 360 attempts = 30 minutes). Stale runs are reconciled by reconcileStaleRuns().


Build System

Development

bash
npm run dev          # Vite dev server (frontend) — port 3080
npm run dev:server   # tsx watch (backend) — port 3081

Vite proxies /api and /ws to the backend dev server.

Production

bash
npm run prod         # Builds frontend + backend, then starts
# Equivalent to:
npm run build        # tsc -b && vite build → dist/
npm run build:server # tsc -p config/tsconfig.server.json → server-dist/
npm start            # node server-dist/index.js

Vite Configuration

  • Plugins: @vitejs/plugin-react, @tailwindcss/vite
  • Path alias: @/./src/
  • Manual chunks: react-vendor, markdown (react-markdown + highlight.js), ui-vendor (lucide-react), utils (clsx, tailwind-merge, dompurify)
  • HTTPS: Auto-enabled if certs/cert.pem and certs/key.pem exist

TypeScript Configuration

Project references with four configs:

  • config/tsconfig.app.json — Frontend (src/)
  • config/tsconfig.node.json — Vite/build tooling
  • config/tsconfig.server.json — Backend (server/) → compiled to server-dist/
  • config/tsconfig.scripts.json — Setup scripts
  • config/tsconfig.bin.json — CLI tools (bin/) → compiled to bin-dist/

Testing

Framework: Vitest with jsdom environment for React tests.

bash
npm test              # Run all tests
npm run test:coverage # With V8 coverage

Test Files

48 test files, 692 tests. Key areas:

AreaFilesCoverage
Server libconfig, device-identity, env-file, gateway-client, mutex, voice-language-coverage, ws-proxyAuth, config resolution, WS proxy relay, device identity signing
Server routesauth, events, files, gateway, health, sessions, skillsAPI endpoints, error handling, gateway proxy
Server middlewareauth, error-handler, rate-limit, security-headersRequest pipeline
Chat operationssendMessage, streamEventHandler, loadHistory, mergeRecoveredTailMessage lifecycle, streaming, history recovery
Client featuresextractCharts, extractImages, edit-blocks, MarkdownRenderer, ContextMeter, ErrorBoundary, sessionTreeRendering, parsing, UI components
HooksuseAuth, useInputHistory, useKeyboardShortcuts, useServerEvents, useTTSClient-side state and interaction
Voice/audioaudio-feedback, useVoiceInput, voice-prefixVoice input, TTS, wake/stop phrase parsing
Utilitiesconstants, formatting, sanitize, unreadSessionsShared logic

Configuration

  • Environment: jsdom (browser APIs mocked)
  • Setup: src/test/setup.ts
  • Exclusions: node_modules/, server-dist/ (avoids duplicate compiled test files)
  • Coverage: V8 provider, text + HTML + lcov reporters

Released under the MIT License.