Skip to content

Architecture

This page covers Kangentic’s internal architecture for contributors and advanced users who want to understand how the system works under the hood.

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) │ │
│ └───────────┘ └─────────────┘ │
└─────────────────────────────────┘

The main process handles all system-level operations:

ComponentRole
Session ManagerTracks agent lifecycle (spawn, suspend, resume, kill). Manages PTY instances via node-pty. Maintains session state and scrollback buffers.
Transition EngineExecutes actions when tasks move between columns. Resolves permission modes. Handles spawn, kill, command injection, and worktree actions.
Worktree ManagerCreates and removes git worktrees. Manages branch naming ({slug}-{id}, optionally prefixed with the base branch) and cleanup.
DatabaseSQLite with WAL mode for concurrent reads and crash-safe writes. Two databases: global (projects, config) and per-project (swimlanes, tasks, sessions).
Config ManagerLoads, saves, and merges configuration across global, project, and per-swimlane levels, including the named per-column strategy ladders behind Board Profiles.
Agent AdaptersPer-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 ManagerInjects 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 EngineIndexes 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 ManagerThe 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.

The renderer is a React application that provides the UI:

ComponentRole
Kanban BoardDrag-and-drop board with swimlane columns. Built with @dnd-kit for smooth interactions.
Terminalxterm.js instances for each active session. Receives data from the main process via IPC.
Window ManagerThe 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.

The main and renderer processes communicate via Electron’s contextBridge API. IPC channels are organized by domain:

DomainChannelsDescription
Projectsproject: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:pathMissingMulti-project management, including relocating a project folder
Project GroupsprojectGroup:list, projectGroup:create, projectGroup:update, projectGroup:delete, projectGroup:reorder, projectGroup:setCollapsedSidebar group management
Taskstask: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:autoCommandResultTask CRUD and movement
Attachmentsattachment:list, attachment:add, attachment:remove, attachment:getDataUrl, attachment:openTask image attachments
Swimlanesswimlane:list, swimlane:create, swimlane:update, swimlane:delete, swimlane:reorder, swimlane:updatedByAgentColumn management
Actions/Transitionsaction:list, action:create, action:update, action:delete, transition:list, transition:set, transition:getForWorkflow automation
Sessionssession: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:injectSettingsAgent lifecycle
Session Eventssession: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:statusReal-time streaming
Usage Statsusage:getDashboardStatsAggregated per-period stats for the usage dashboard
Configconfig:get, config:getGlobal, config:set, config:setSync, config:getProject, config:setProject, config:getProjectByPath, config:setProjectByPath, config:syncDefaultToProjects, config:changedSettings management; config:changed is a bare signal fanned to every window after a write so live settings stay in sync
Keybindingskeybindings:probeGlobalDetects OS-level shortcut conflicts for the global hotkeys
Agentagent:listCommands, agent:summarize, agent:list, agent:probeExecutionServerAgent listing, slash-command discovery, prompt summarization, and execution-server probing
Handoffshandoff:listCross-agent handoff history
Shellshell:getAvailable, shell:getDefault, shell:openPath, shell:openExternal, shell:showItemInFolder, shell:execShell detection and utilities
Fontsfont:getAvailableSystem font enumeration for the terminal font picker
Gitgit:detect, git:listBranches, git:diffFiles, git:fileContent, git:branchSummary, git:diffSubscribe, git:diffUnsubscribe, git:diffChanged, git:checkPendingChanges, git:commitGraph, git:fileHistory, git:blameGit operations, commit-graph, per-file history and blame, and diff streaming for the Changes panel
Dialogdialog:selectFolderNative file dialogs
Windowwindow:minimize, window:maximize, window:close, window:flashFrame, window:isFocusedWindow controls
Pop-Out WindowspopOut:open, popOut:close, popOut:focus, popOut:isOpen, popOut:listOpen, popOut:changedDetachable OS-level windows for the stats, Changes, single-file diff, Browser, and Agent Monitor surfaces
Analyticsanalytics:trackRendererError, analytics:trackFeatureUsedError telemetry, plus one-per-day adoption counts for a curated feature list that main re-validates
Appapp:getVersionApp metadata
Notificationsnotification:show, notification:clickedDesktop notifications
Announcementsannouncements:get, announcements:getHistory, announcements:markRead, announcements:changedThe 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 ConfigboardConfig:exists, boardConfig:export, boardConfig:apply, boardConfig:changed, boardConfig:getBoardProfiles, boardConfig:setBoardProfiles, boardConfig:boardProfilesChanged, boardConfig:getShortcuts, boardConfig:setShortcuts, boardConfig:shortcutsChanged, boardConfig:setDefaultBaseBranchkangentic.json import/export, board profiles, and shared shortcuts
Mobile Bridgemobile: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:terminalStreamsChangedMobile companion pairing, device management, and relay status. Machine-global like config; the bridge ships in production builds and is off by default
Agent Monitormonitor:getSnapshot, monitor:changed, monitor:subscribe, monitor:unsubscribe, monitor:revealTask, monitor:getTaskDetail, monitor:peek, monitor:setPeekSubscribedMachine-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 Ownershipdetail:requestOpen, detail:openHere, detail:closeHere, detail:syncOwned, detail:remoteOwnersCross-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
Backlogbacklog: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:labelColorsChangedBacklog CRUD and labels
Backlog Importbacklog:importCheckCli, backlog:importFetch, backlog:importExecute, backlog:importSourcesList, backlog:importSourcesAdd, backlog:importSourcesRemoveExternal issue import
Backlog AttachmentsbacklogAttachment:list, backlogAttachment:add, backlogAttachment:remove, backlogAttachment:getDataUrl, backlogAttachment:openBacklog item attachments
Clipboardclipboard:readImage, clipboard:writeTextRead a pasted image; write text to the clipboard
Browserbrowser: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:guestMouseButtonEmbedded 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)
Searchsearch:everythingUnified search across tasks, backlog, events, projects, and past conversations
Conversation Memorytranscript:get, transcript:listSessions, memory:status, memory:rebuildIndexStructured conversation transcripts for the viewer, semantic-layer status, and index rebuild
Board Auth - Asanaboards:asana:authStatus, boards:asana:setPat, boards:asana:clearCredentialAsana personal-access-token credential management
Diagnosticsdiagnostics:logAppend, diagnostics:crashReportRenderer console and crash forwarding to .kangentic/logs/
Dictationtranscribe: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:prewarmLocal push-to-talk voice-to-text into the focused text field or terminal
Updaterupdater:check, updater:install, updater:downloadedAuto-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.

Kangentic uses two SQLite databases:

-- Projects across the workspace
CREATE 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 under
-- 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 tasks
CREATE 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 sessions
CREATE 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 attachments
CREATE 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 mark

The 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
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.js
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 Renderer
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)
ComponentTechnologyPurpose
ShellElectronCross-platform desktop container
UI FrameworkReactComponent-based UI
State ManagementZustand + React hooksUI state
Board@dnd-kitKanban drag-and-drop interactions
Terminalxterm.js + node-ptyTerminal emulation and PTY management
Databasebetter-sqlite3Persistent storage (WAL mode)
Conversation Memorysqlite-vec + @huggingface/transformers + onnxruntime-nodeLocal keyword + semantic search over conversations; embedding inference in an isolated utilityProcess
Agent RuntimeClaude Code, Codex, Gemini, Cursor CLI, GitHub Copilot CLI, Grok Build, Antigravity CLI, OpenCode, Aider, Qwen Code, Kimi Code, Factory Droid, Warp (Oz CLI), Ollama CLIsPluggable adapters, one per CLI (fourteen total; thirteen coding agents plus Ollama for local LLMs)
Version Controlgit worktreeIsolated environments
StylingTailwind CSS 4Utility-first styling
Diff ViewerMonaco EditorThe Changes panel’s split/inline diffs
ChartsRechartsUsage Stats Dashboard visualizations
Build ToolingVite + esbuildRenderer and main-process bundling
Packagingelectron-builderPlatform installers (NSIS, DMG, deb/rpm)
LanguageTypeScriptType safety throughout

See also:

Kangentic is free and open source. A star helps other people find it.

Star on GitHub