Skip to content

Architecture Overview

nick3 edited this page May 28, 2026 · 1 revision

Architecture Overview

How the codebase is laid out and how the major subsystems fit together. Useful before you start hacking on ClusterSpace.


Process model

Standard Electron 3-process model:

┌──────────────────────────────────────────────────────────────────┐
│  Main process (Node.js)                                          │
│  - src/main/index.ts                — entry, IPC handlers        │
│  - src/main/pty-manager.ts          — node-pty pool              │
│  - src/main/workspace-store.ts      — electron-store backed      │
│  - src/main/ai-manager.ts           — LLM streaming, tool exec   │
│  - src/main/goal-runner.ts          — autonomous loop            │
│  - src/main/browser-pane-registry.ts— WebContents inventory      │
│  - src/main/ai-tools/...            — tool registry + impls      │
└─────────────────┬────────────────────────────────────────────────┘
                  │ IPC (contextBridge)
┌─────────────────┴────────────────────────────────────────────────┐
│  Preload (sandboxed Node bridge)                                 │
│  - src/preload/index.ts             — typed window.electronAPI   │
└─────────────────┬────────────────────────────────────────────────┘
                  │ window.electronAPI.*
┌─────────────────┴────────────────────────────────────────────────┐
│  Renderer (Chromium + React)                                     │
│  - src/renderer/App.tsx             — root                       │
│  - src/renderer/context/*           — global state via React     │
│  - src/renderer/components/*        — UI surface                 │
│  - src/renderer/hooks/*             — useTerminal, shortcuts     │
└──────────────────────────────────────────────────────────────────┘

Main process responsibilities

File Responsibility
index.ts App lifecycle, IPC handler registration, store instantiation
pty-manager.ts Spawn / kill / read / write PTYs; per-pane scrollback buffer; background/foreground for workspace switches
workspace-store.ts Workspaces, panes, grid config, settings, window bounds — all via electron-store
credentials-store.ts SSH server passwords (safeStorage)
browser-pane-registry.ts Map<paneId, WebContents> for routing browser tool calls
browser-store.ts Browser tabs, bookmarks, history, downloads, find-in-page state
browser-credentials-store.ts Saved logins (safeStorage)
browser-recipes.ts Stored recipes + recipe runner
browser-action-log.ts 500-entry ring buffer of browser tool calls
browser-approval.ts Regex-based legacy approval gate for risky browser actions
cdp-helpers.ts Chrome DevTools Protocol helpers for trusted input events
ai-store.ts AI providers (API keys via safeStorage), active provider
ai-memory-store.ts Conversation history, per (provider, workspace, pane) keying
ai-manager.ts Stream messages, build tool context, dispatch tools, build vision helpers
agent-store.ts Per-pane agent state
orchestration-store.ts Multi-pane orchestration goals + event log
goal-policy.ts Risk-level evaluator + per-tool permissions table
goal-store.ts Persistent goal checkpoints + step log (50-goal cap)
goal-runner.ts The autonomous loop, transient tools, critic, verification
config-loader.ts Loads personas/skills/tasks from resources/defaults/ and <userData>/clusterspace-data/config/
legacy-rename.ts One-time migration from fleet-term-data/ → clusterspace-data/
ai-tools/*.ts Tool implementations grouped by category, registered into toolRegistry
ai-tools/plugin-loader.ts Hot-reload user plugin tools from <userData>/.../config/tools/*.js

Renderer responsibilities

File Responsibility
App.tsx Root component; mounts contexts and dashboards
context/WorkspaceContext.tsx Active workspace + workspaces array + grid/pane mutations
context/AIContext.tsx Conversation state, streaming, tool auto-loop (chat panel)
context/AgentContext.tsx Agent states map, active orchestration goal, event log
hooks/useTerminal.ts xterm.js lifecycle wired to PTY IPC
hooks/useKeyboardShortcuts.ts Global hotkey dispatcher
components/PaneGrid.tsx Grid layout + drag-resize + drag-swap
components/TerminalPane.tsx (+ TerminalTabContent.tsx) Terminal pane (per-pane tabs)
components/BrowserPane.tsx Browser pane (webview wrapper, chrome controls, tabs)
components/AIChatPanel.tsx Floating AI chat panel
components/GoalDashboard.tsx (+ GoalCreateDialog.tsx) Goal runner UI
components/FleetDashboard.tsx Multi-pane orchestration dashboard
components/PaneLabel.tsx / PaneLabelWithAgent.tsx Per-pane label with agent status
components/CommandPalette.tsx Ctrl+P searchable actions
components/StatusBar.tsx Bottom bar with workspace, broadcast, Fleet, Goals, settings, memory chips

Shared types

src/shared/types.ts — single file with every cross-process type:

  • GridConfig, PaneConfig, WorkspaceConfig, WindowState, AppSettings
  • AIProviderConfig, AISettings, AIMessage, AIToolCall, AIToolResult, AIToolDefinition, PagedTextResult
  • PaneAgentState, AgentTask, AgentStatus, OrchestrationGoal, OrchestrationEvent
  • GoalStatus, GoalRisk, SuccessCriterion, GoalPolicy, GoalStep, GoalCheckpoint, GoalRunnerEvent
  • BrowserContentResult, BrowserActionResult, Bookmark, HistoryEntry, DownloadInfo, BrowserCredential[Meta]
  • IPC_CHANNELS (all string constants)
  • DEFAULT_AI_SYSTEM_PROMPT, DEFAULT_AI_SETTINGS, DEFAULT_TERMINAL_THEME, DEFAULT_TEMPLATES

If you change a cross-process shape, change it here once and everything that imports it stays in sync.


IPC contract

All renderer↔main communication goes through window.electronAPI, exposed by src/preload/index.ts via Electron's contextBridge. Channel names are constants in IPC_CHANNELS to keep typos out.

Full reference: IPC-Channels-Reference.


Data storage

All persistent state uses electron-store — JSON files under <userData>/clusterspace-data/. See Data-Storage-and-Migration for the full file inventory.

Personas, skills, task templates, and plugin tools live under <userData>/clusterspace-data/config/ (user-authored) with fallback to bundled resources/defaults/ (read-only).


How a tool call flows

1. User asks AI: "list panes in workspace 1"
2. Renderer → AI_CHAT_STREAM IPC → AIManager.streamMessage
3. AIManager POSTs OpenAI-compat request → streams SSE chunks back
4. Assistant message arrives with toolCalls: [{name: 'list_panes', arguments: {}}]
5. Renderer dispatches AI_EXECUTE_TOOL for each toolCall
6. AIManager.executeTool:
     a. Build ToolContext (window, ptyManager, stores, vision helpers)
     b. If active goal: evaluate(name, getPermissions(name, args), policy)
        - If denied: return error to model
        - If needsApproval: prompt user via BROWSER_APPROVAL_REQUEST
     c. Fall back to legacy approval gate (regex-based) if no policy
     d. toolRegistry.dispatch(name, args, ctx)
     e. Log to action log (for browser_*)
     f. Truncate result if > 3000 chars
7. Tool result → AI message with role: 'tool', tool_call_id
8. Renderer re-streams with the appended messages
9. Loop until model emits no more tool_calls or 20 turns (chat panel auto-loop guard)

How a goal runs

1. User starts goal → GoalRunner.start(input)
2. Runner creates GoalCheckpoint in goal-store
3. Runner sets policy on AIManager (gates tool dispatch)
4. Runner builds the goal prompt (instruction + criterion + contract)
5. Runner enters runLoop:
   - streamMessage → assistant
   - for each tool_call: dispatch (with policy enforced) + log step
   - if pendingClaim: verifySuccessCriterion → completed | continue
   - if pendingAbort: aborted
   - every N steps: runCritic → maybe inject STUCK/MISLED or pendingClaim
   - check wall-clock cap
6. Loop ends → endGoal updates status, clears policy, emits ended event

See Goal-Runner-Internals for the deep dive.


Build & dev

npm install          # deps + native rebuild via electron-builder postinstall
npm run dev          # Vite for renderer + Electron with auto-restart on main/preload changes
npm run build        # tsc --noEmit + Vite production build
npm run dist         # electron-builder packaged installer for current platform
npm run rebuild      # force native rebuild (if node-pty fails)

Vite serves the renderer at localhost:5173 in dev. The main process restarts on changes to src/main/** and src/preload/**; the renderer hot-reloads on changes to src/renderer/** and src/shared/**.


See also

Clone this wiki locally