Architecture
This page covers Kangentic’s internal architecture for contributors and advanced users who want to understand how the system works under the hood.
High-Level Overview
Section titled “High-Level Overview”Kangentic is an Electron application with a React frontend and a Node.js backend. The architecture follows Electron’s process model:
┌─────────────────────────────────┐│ Main Process ││ ┌───────────┐ ┌─────────────┐ ││ │ Session │ │ Worktree │ ││ │ Manager │ │ Manager │ ││ ├───────────┤ ├─────────────┤ ││ │ Transition│ │ Database │ ││ │ Engine │ │ (SQLite) │ ││ └───────────┘ └─────────────┘ │└──────────┬──────────────────────┘ │ IPC (contextBridge)┌──────────┴──────────────────────┐│ Renderer Process ││ ┌───────────┐ ┌─────────────┐ ││ │ Kanban │ │ Terminal │ ││ │ Board │ │ (xterm.js) │ ││ └───────────┘ └─────────────┘ │└─────────────────────────────────┘Process Model
Section titled “Process Model”Main Process
Section titled “Main Process”The main process handles all system-level operations:
| Component | Role |
|---|---|
| Session Manager | Tracks agent lifecycle (spawn, suspend, resume, kill). Manages PTY instances via node-pty. Maintains session state and scrollback buffers. |
| Transition Engine | Executes actions when tasks move between columns. Resolves permission modes. Handles spawn, kill, command injection, and worktree actions. |
| Worktree Manager | Creates and removes git worktrees. Manages branch naming ({slug}-{id}, optionally prefixed with the base branch) and cleanup. |
| Database | SQLite with WAL mode for concurrent reads and crash-safe writes. Two databases: global (projects, config) and per-project (swimlanes, tasks, sessions). |
| Config Manager | Loads, saves, and merges configuration across global, project, and per-swimlane levels, including the named per-column strategy ladders behind Board Profiles. |
| Agent Adapters | Per-agent subfolders under src/main/agent/adapters/ (claude/, codex/, gemini/, cursor/, copilot/, grok/, antigravity/, opencode/, aider/, qwen-code/, kimi/, droid/, warp/, ollama/). Each adapter handles its CLI’s detection, command building, hooks, session resume, folder trust, and activity strategy behind the shared AgentAdapter interface. (ollama/ drives local LLMs as single-turn chat, so it opts out of hooks and resume.) |
| Hook Manager | Injects agent-specific hooks (e.g., Claude Code’s PreToolUse/PostToolUse/Stop, Gemini’s BeforeAgent/AfterAgent, Copilot’s inline hooks) for activity detection. Hook files are reference-counted so concurrent sessions don’t clobber each other. |
| Retrieval Engine | Indexes agent conversation transcripts into a per-project search index (SQLite FTS5 keyword plus sqlite-vec vectors) for Conversation Memory. Embedding inference runs in an isolated Electron utilityProcess (onnxruntime-node) so it never blocks the main process, and degrades to keyword-only when the model or vector extension is unavailable. |
| Pop-Out Window Manager | The detachable-surface engine (src/main/pop-out/): opens registered UI surfaces (usage stats, Changes, a single changed file’s diff, Browser, Agent Monitor) as real second OS windows, persists their bounds per monitor, and fans out the live IPC pushes each surface subscribes to. |
Renderer Process
Section titled “Renderer Process”The renderer is a React application that provides the UI:
| Component | Role |
|---|---|
| Kanban Board | Drag-and-drop board with swimlane columns. Built with @dnd-kit for smooth interactions. |
| Terminal | xterm.js instances for each active session. Receives data from the main process via IPC. |
| Window Manager | The in-app window layer (src/renderer/window-manager/): floating, tiled, snapped, and maximized window states over an n-ary tiling tree. Hosts three window content kinds - task detail, command terminal, and conversation - as isolated layers over the board and the Agent Monitor, including when the monitor is detached into a pop-out. |
IPC Communication
Section titled “IPC Communication”The main and renderer processes communicate via Electron’s contextBridge API. IPC channels are organized by domain:
| Domain | Channels | Description |
|---|---|---|
| Projects | project:list, project:create, project:delete, project:open, project:getCurrent, project:openByPath, project:probePath, project:ensureGit, project:searchEntries, project:reorder, project:setGroup, project:rename, project:setDefaultAgent, project:setDefaultModel, project:setDefaultEffort, project:autoOpened, project:relocate, project:moveProgress, project:pathMissing | Multi-project management, including relocating a project folder |
| Project Groups | projectGroup:list, projectGroup:create, projectGroup:update, projectGroup:delete, projectGroup:reorder, projectGroup:setCollapsed | Sidebar group management |
| Tasks | task:list, task:create, task:update, task:delete, task:move, task:cancelSpawn, task:list-archived, task:list-archived-preview, task:unarchive, task:bulk-delete, task:bulk-delete-progress, task:bulk-unarchive, task:switchBranch, task:autoMoved, task:createdByAgent, task:updatedByAgent, task:deletedByAgent, task:sessionResync, task:prLinkChanged, task:movedByMobile, task:spawnBlocked, task:spawnProgress, task:getSpawnProgress, task:spawnWarning, task:updateFromBase, task:setRuntimeOverride, task:resolvePr, task:setDetailViewState, task:autoCommandResult | Task CRUD and movement |
| Attachments | attachment:list, attachment:add, attachment:remove, attachment:getDataUrl, attachment:open | Task image attachments |
| Swimlanes | swimlane:list, swimlane:create, swimlane:update, swimlane:delete, swimlane:reorder, swimlane:updatedByAgent | Column management |
| Actions/Transitions | action:list, action:create, action:update, action:delete, transition:list, transition:set, transition:getFor | Workflow automation |
| Sessions | session:spawn, session:kill, session:write, session:resize, session:list, session:getScrollback, session:suspend, session:resume, session:reconcile, session:reset, session:idleTimeout, session:getSummary, session:listSummaries, session:getToolBreakdown, session:spawnTransient, session:killTransient, session:setFocused, session:setMounted, session:notifyUserInterrupt, session:injectSettings | Agent lifecycle |
| Session Events | session:data, session:drainAck, session:ptyResized, session:firstOutput, session:getFirstOutput, session:exit, session:usage, session:getUsage, session:activity, session:getActivity, session:getActivityReason, session:getActivityReasons, session:getActivityStats, session:event, session:getEvents, session:getEventsCache, session:status | Real-time streaming |
| Usage Stats | usage:getDashboardStats | Aggregated per-period stats for the usage dashboard |
| Config | config:get, config:getGlobal, config:set, config:setSync, config:getProject, config:setProject, config:getProjectByPath, config:setProjectByPath, config:syncDefaultToProjects, config:changed | Settings management; config:changed is a bare signal fanned to every window after a write so live settings stay in sync |
| Keybindings | keybindings:probeGlobal | Detects OS-level shortcut conflicts for the global hotkeys |
| Agent | agent:listCommands, agent:summarize, agent:list, agent:probeExecutionServer | Agent listing, slash-command discovery, prompt summarization, and execution-server probing |
| Handoffs | handoff:list | Cross-agent handoff history |
| Shell | shell:getAvailable, shell:getDefault, shell:openPath, shell:openExternal, shell:showItemInFolder, shell:exec | Shell detection and utilities |
| Fonts | font:getAvailable | System font enumeration for the terminal font picker |
| Git | git:detect, git:listBranches, git:diffFiles, git:fileContent, git:branchSummary, git:diffSubscribe, git:diffUnsubscribe, git:diffChanged, git:checkPendingChanges, git:commitGraph, git:fileHistory, git:blame | Git operations, commit-graph, per-file history and blame, and diff streaming for the Changes panel |
| Dialog | dialog:selectFolder | Native file dialogs |
| Window | window:minimize, window:maximize, window:close, window:flashFrame, window:isFocused | Window controls |
| Pop-Out Windows | popOut:open, popOut:close, popOut:focus, popOut:isOpen, popOut:listOpen, popOut:changed | Detachable OS-level windows for the stats, Changes, single-file diff, Browser, and Agent Monitor surfaces |
| Analytics | analytics:trackRendererError, analytics:trackFeatureUsed | Error telemetry, plus one-per-day adoption counts for a curated feature list that main re-validates |
| App | app:getVersion | App metadata |
| Notifications | notification:show, notification:clicked | Desktop notifications |
| Announcements | announcements:get, announcements:getHistory, announcements:markRead, announcements:changed | The remote announcements feed, already filtered in the main process for this client’s version and platform, plus the local archive the megaphone’s history list reads and its per-entry read state |
| Board Config | boardConfig:exists, boardConfig:export, boardConfig:apply, boardConfig:changed, boardConfig:getBoardProfiles, boardConfig:setBoardProfiles, boardConfig:boardProfilesChanged, boardConfig:getShortcuts, boardConfig:setShortcuts, boardConfig:shortcutsChanged, boardConfig:setDefaultBaseBranch | kangentic.json import/export, board profiles, and shared shortcuts |
| Mobile Bridge | mobile:getStatus, mobile:startPairing, mobile:cancelPairing, mobile:listDevices, mobile:revokeDevice, mobile:renameDevice, mobile:setDeviceCapabilities, mobile:testRelay, mobile:pairingSas, mobile:pairingConfirmed, mobile:pairingEnded, mobile:stateChanged, mobile:getTerminalStreams, mobile:terminalStreamsChanged | Mobile companion pairing, device management, and relay status. Machine-global like config; the bridge ships in production builds and is off by default |
| Agent Monitor | monitor:getSnapshot, monitor:changed, monitor:subscribe, monitor:unsubscribe, monitor:revealTask, monitor:getTaskDetail, monitor:peek, monitor:setPeekSubscribed | Machine-global monitor aggregating live sessions across every registered project. Snapshot pushes and the recent-output peek are subscription-gated, so a closed monitor costs nothing |
| Task-Detail Ownership | detail:requestOpen, detail:openHere, detail:closeHere, detail:syncOwned, detail:remoteOwners | Cross-window arbiter that keeps a task’s detail open in only one renderer at a time; ownership is derived from what each surface reports as mounted |
| Backlog | backlog:list, backlog:create, backlog:update, backlog:delete, backlog:reorder, backlog:bulk-delete, backlog:promote, backlog:demote, backlog:renameLabel, backlog:deleteLabel, backlog:remapPriorities, backlog:changedByAgent, backlog:labelColorsChanged | Backlog CRUD and labels |
| Backlog Import | backlog:importCheckCli, backlog:importFetch, backlog:importExecute, backlog:importSourcesList, backlog:importSourcesAdd, backlog:importSourcesRemove | External issue import |
| Backlog Attachments | backlogAttachment:list, backlogAttachment:add, backlogAttachment:remove, backlogAttachment:getDataUrl, backlogAttachment:open | Backlog item attachments |
| Clipboard | clipboard:readImage, clipboard:writeText | Read a pasted image; write text to the clipboard |
| Browser | browser:captureSend, browser:urlGet, browser:urlSetTask, browser:urlClearTask, browser:clearStorage, browser:jarEnsure, browser:zoomChanged, browser:paneRegister, browser:paneUnregister, browser:paneUserClose, browser:paneVisibility, browser:paneOpenRequest, browser:paneCloseRequest, browser:agentInput, browser:downloadDone, browser:userKeyDuringDrive, browser:guestMouseButton | Embedded browser pane (capture-and-send, per-task URLs, zoom sync, pane registration, the user’s Close browser sent before the pane unmounts so no hand-off lane re-spends the memory, where a pane sits on screen so agents can read its visibility, agent-driven open/close for the kangentic_browser_* MCP tools, agent-drive announcements so the pane can restore your focus, download completion, keystrokes intercepted mid-drive, and guest mouse back/forward the page would otherwise swallow) |
| Search | search:everything | Unified search across tasks, backlog, events, projects, and past conversations |
| Conversation Memory | transcript:get, transcript:listSessions, memory:status, memory:rebuildIndex | Structured conversation transcripts for the viewer, semantic-layer status, and index rebuild |
| Board Auth - Asana | boards:asana:authStatus, boards:asana:setPat, boards:asana:clearCredential | Asana personal-access-token credential management |
| Diagnostics | diagnostics:logAppend, diagnostics:crashReport | Renderer console and crash forwarding to .kangentic/logs/ |
| Dictation | transcribe:start, transcribe:stop, transcribe:cancel, transcribe:commit, transcribe:submit, transcribe:getInfo, transcribe:partial, transcribe:final, transcribe:audioChunk, transcribe:requestMic, transcribe:modelProgress, transcribe:downloadModel, transcribe:liveWrite, transcribe:prewarm | Local push-to-talk voice-to-text into the focused text field or terminal |
| Updater | updater:check, updater:install, updater:downloaded | Auto-update lifecycle |
All handle calls are async. Real-time updates (terminal data, session status, activity state) use event-based callbacks via ipcMain.on / webContents.send.
Database Schema
Section titled “Database Schema”Kangentic uses two SQLite databases:
Global Database
Section titled “Global Database”-- Projects across the workspaceCREATE TABLE projects ( id TEXT PRIMARY KEY, name TEXT NOT NULL, path TEXT NOT NULL, github_url TEXT, default_agent TEXT NOT NULL DEFAULT 'claude', position INTEGER NOT NULL DEFAULT 0, last_opened TEXT NOT NULL, created_at TEXT NOT NULL);
CREATE TABLE global_config ( key TEXT PRIMARY KEY, value TEXT NOT NULL);
-- project_groups sidebar groups projects can be filed underPer-Project Database
Section titled “Per-Project Database”-- Board columns (swimlanes)CREATE TABLE swimlanes ( id TEXT PRIMARY KEY, name TEXT NOT NULL, role TEXT, -- 'todo', 'done', or NULL position INTEGER NOT NULL, color TEXT NOT NULL DEFAULT '#3b82f6', icon TEXT DEFAULT NULL, -- Lucide icon name is_archived INTEGER NOT NULL DEFAULT 0, is_ghost INTEGER NOT NULL DEFAULT 0, permission_mode TEXT DEFAULT NULL, auto_spawn INTEGER NOT NULL DEFAULT 1, auto_command TEXT DEFAULT NULL, auto_command_mode TEXT NOT NULL DEFAULT 'immediate', plan_exit_target_id TEXT DEFAULT NULL, created_at TEXT NOT NULL);
-- Kanban tasksCREATE TABLE tasks ( id TEXT PRIMARY KEY, display_id INTEGER, -- per-project human ticket number; kept monotonic via the -- display_id_high_water row in project_meta (never recycled) title TEXT NOT NULL, description TEXT NOT NULL DEFAULT '', swimlane_id TEXT NOT NULL REFERENCES swimlanes(id), position INTEGER NOT NULL, agent TEXT, session_id TEXT, worktree_path TEXT, worktree_folder TEXT, -- write-once worktree directory name branch_name TEXT, base_branch TEXT DEFAULT NULL, use_worktree INTEGER DEFAULT NULL, pr_number INTEGER, pr_url TEXT, pr_state TEXT, -- open, draft, merged, or closed head_sha TEXT, -- captured worktree HEAD commit; anchors PR -- resolution after worktree deletion or branch rename archived_at TEXT DEFAULT NULL, created_at TEXT NOT NULL, updated_at TEXT NOT NULL);
-- Workflow actions (spawn_agent, kill_session, etc.)CREATE TABLE actions ( id TEXT PRIMARY KEY, name TEXT NOT NULL, type TEXT NOT NULL, config_json TEXT NOT NULL DEFAULT '{}', created_at TEXT NOT NULL);
-- Column transition rules (which actions fire on move)CREATE TABLE swimlane_transitions ( id TEXT PRIMARY KEY, from_swimlane_id TEXT NOT NULL, -- swimlane ID or '*' for any source to_swimlane_id TEXT NOT NULL REFERENCES swimlanes(id), action_id TEXT NOT NULL REFERENCES actions(id), execution_order INTEGER NOT NULL DEFAULT 0);
-- Agent sessionsCREATE TABLE sessions ( id TEXT PRIMARY KEY, task_id TEXT NOT NULL REFERENCES tasks(id), session_type TEXT NOT NULL, agent_session_id TEXT, -- native session ID from the agent CLI (Claude/Codex/Gemini) command TEXT NOT NULL, cwd TEXT NOT NULL, permission_mode TEXT, prompt TEXT, status TEXT NOT NULL DEFAULT 'running', -- running, queued, suspended, exited, orphaned exit_code INTEGER, suspended_by TEXT DEFAULT NULL, -- 'user' or 'system' started_at TEXT NOT NULL, suspended_at TEXT, exited_at TEXT);
-- Task image/file attachmentsCREATE TABLE task_attachments ( id TEXT PRIMARY KEY, task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE, filename TEXT NOT NULL, file_path TEXT NOT NULL, media_type TEXT NOT NULL, size_bytes INTEGER NOT NULL, created_at TEXT NOT NULL);
-- Conversation Memory index (see the Conversation Memory feature)-- memory_chunks chunked conversation turns - the searchable corpus-- memory_chunks_fts FTS5 keyword index over memory_chunks.text-- memory_chunks_vec sqlite-vec (vec0) embedding vectors, created when semantic search is enabled-- memory_index_state per-session indexing and embedding progress-- memory_meta index metadata (embedding model, dimensions)-- conversation_turn_usage durable per-turn token/cost ledger, retained even after a session is deleted
-- Remaining tables, same database-- usage_history per-session token/cost samples behind the Usage Stats Dashboard-- session_activity_intervals resolved activity spans per session, for the activity timeline-- session_transcripts captured terminal transcripts kept after a session ends-- session_messages_sent durable log of messages steered into a running session-- handoffs cross-agent handoff records-- backlog_tasks staged items not yet promoted to the board-- backlog_attachments files attached to backlog items-- project_meta per-project counters, including the display-ID high-water markThe blocks above are the shape of the schema, not a column-complete dump: several tables have grown columns through migrations (per-task agent, model, and effort overrides on tasks, applied model and effort plus cost and token metrics on sessions, and per-column session-target and handoff settings on swimlanes). src/main/db/migrations/ is authoritative.
Both databases use WAL (Write-Ahead Logging) mode for:
- Concurrent read access from both main and renderer processes
- Crash safety - committed data survives unexpected termination
- Better write performance for high-frequency updates
Data Flow
Section titled “Data Flow”Starting an Agent
Section titled “Starting an Agent”User drags card → Renderer sends task:move → Transition Engine looks up actions for * → target swimlane → Actions execute in order (e.g., kill_session → spawn_agent) → Worktree Manager creates worktree + branch (if enabled) → agent-resolver picks the effective agent (column override or project default) → Session Manager asks the agent adapter to build a spawn command → Session Manager spawns the CLI via node-pty → Agent adapter writes any required hook files (reference-counted) → Session record created in DB (status: running) → Terminal data flows: agent stdout → IPC session:data → xterm.jsSuspending an Agent
Section titled “Suspending an Agent”User drags card to Done → Renderer sends task:move → Transition Engine fires kill_session action → Session Manager sends the agent's configured exit sequence → Session Manager updates status to "suspended" → Worktree is deleted to reclaim disk; branch name and session records are preserved so a move back out of Done restores both → Main sends session:status update to RendererResuming an Agent
Section titled “Resuming an Agent”User drags card from Done → Renderer sends task:unarchive → Handler rebuilds the worktree the move into Done deleted, on the preserved branch (or checks that branch out, for a non-worktree task) → A failure here notifies and stops: the task is unarchived, no agent starts → Handler runs the Done → target transition through the shared spawn chokepoint → Agent adapter builds a resume command with the stored native session ID → Session Manager launches the new process → Session Manager updates status to "running" → Terminal reconnects to new process → Agent continues with full context (where resume is supported)Technology Stack
Section titled “Technology Stack”| Component | Technology | Purpose |
|---|---|---|
| Shell | Electron | Cross-platform desktop container |
| UI Framework | React | Component-based UI |
| State Management | Zustand + React hooks | UI state |
| Board | @dnd-kit | Kanban drag-and-drop interactions |
| Terminal | xterm.js + node-pty | Terminal emulation and PTY management |
| Database | better-sqlite3 | Persistent storage (WAL mode) |
| Conversation Memory | sqlite-vec + @huggingface/transformers + onnxruntime-node | Local keyword + semantic search over conversations; embedding inference in an isolated utilityProcess |
| Agent Runtime | Claude Code, Codex, Gemini, Cursor CLI, GitHub Copilot CLI, Grok Build, Antigravity CLI, OpenCode, Aider, Qwen Code, Kimi Code, Factory Droid, Warp (Oz CLI), Ollama CLIs | Pluggable adapters, one per CLI (fourteen total; thirteen coding agents plus Ollama for local LLMs) |
| Version Control | git worktree | Isolated environments |
| Styling | Tailwind CSS 4 | Utility-first styling |
| Diff Viewer | Monaco Editor | The Changes panel’s split/inline diffs |
| Charts | Recharts | Usage Stats Dashboard visualizations |
| Build Tooling | Vite + esbuild | Renderer and main-process bundling |
| Packaging | electron-builder | Platform installers (NSIS, DMG, deb/rpm) |
| Language | TypeScript | Type safety throughout |
Next steps
Section titled “Next steps”See also:
- Configuration Reference - the config layer in concrete keys
- Contributing - get the codebase running locally
- Shell Support - PTY layer and per-platform shell detection
Kangentic is free and open source. A star helps other people find it.
Star on GitHub