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) and keeps the session registry. Since v0.44.0 the PTYs themselves run in the pty host worker (below), and Session Manager is the main-process facade over it. |
| Transition Engine | Runs a column’s automations when tasks move between columns: the source’s On task exit group first, then the destination’s On task enter group. Resolves permission modes. Handles spawn, kill, and message delivery; each automation type is an adapter under src/main/automations/adapters/. |
| Worktree Manager | Creates and removes git worktrees. Manages branch naming ({slug}-{id}) 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/, goose/, 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, task records, and default-branch commits into a per-project search index (SQLite FTS5 keyword plus sqlite-vec vectors) for Conversation memory and the Knowledge Graph. Since v0.44.0 every read and write of that index runs in the retrieval worker (below); embedding inference runs in its own worker, and search 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, Knowledge Graph) as real second OS windows, persists their bounds per monitor, and fans out the live IPC pushes each surface subscribes to. |
Worker processes
Section titled “Worker processes”Work that would block the main process runs in Electron utilityProcess workers, each with its own crash policy and captured error output. There are five:
| Worker | Role |
|---|---|
kangentic-pty-host | Every PTY and the work on its output since v0.44.0: node-pty, the headless terminal and scrollback ring, backpressure, the raw transcript, and the adapters’ output detectors. It also runs the main process’s one-shot child processes and the agent CLIs’ headless runs (Ask, task summaries, auto-naming), because on Windows each process spawn blocks the thread that calls it. Output reaches main only for sessions a window shows or a phone streams. Under a 15-second terminal flood, main went from 40.6% busy to under 1%. It restarts on a crash, the lost agent sessions resume, and after five crashes the same code runs inside main instead |
kangentic-retrieval | Every read and write of the Knowledge Graph index since v0.44.0, plus the map pass, searches, Ask preparation, and parsing agent transcripts for the conversation viewer, the phone, and MCP. It opens the project database on its own connection after main has migrated it |
kangentic-embeddings | ONNX inference for the local embedding model, driven by one engine on main |
kangentic-dictation | The dictation engine |
kangentic-line-count | Line counts for diffs with many untracked files |
Main still writes project data (tasks, sessions, the app tables) inside an immediate transaction that waits for a worker’s write instead of failing with SQLITE_BUSY.
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, project:listChanged | Multi-project management, including relocating a project folder; project:listChanged tells the sidebar to refetch when it clicked a project main could no longer resolve |
| 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:movedByAutomation, task:conversationExpired, task:takeConversationExpired | Task CRUD and movement, including the quiet reload after a Move to column row or a workflow moves a card, and the queued notice for a conversation the agent deleted, which the renderer takes on the push and whenever a project becomes current |
| 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 |
| Automations | automation:list, automation:replaceForColumn, automation:runsForTask, automation:runAgain, automation:runFailed, automation:runsInterrupted | Column automations and their run log; the last two are main-to-renderer pushes behind the failure toast and the project-open interrupted summary |
| Workflows | workflow:list, workflow:listSupersessions, workflow:get, workflow:create, workflow:rename, workflow:delete, workflow:start, workflow:pause, workflow:resume, workflow:addTask, workflow:updateTask, workflow:removeTask, workflow:setLink, workflow:setRoute, workflow:setPositions, workflow:agentOpen, workflow:agentResume, workflow:agentPause, workflow:changed, workflow:completed | The Workflows view and its agent pane. Every call names the workflow’s home project; the MCP workflow tools reach the same operations through workflow-service.ts |
| 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:getTaskReport, session:getTaskLastMessage, session:getToolBreakdown, session:getToolResultTokens, session:spawnTransient, session:killTransient, session:setTransientBranch, session:setFocused, session:setMounted, session:notifyUserInterrupt, session:injectSettings | Agent lifecycle, including the Command Terminal’s durable slot titles and branch picks |
| 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:messageTrail, session:getMessageTrails, session:status, session:removed | Real-time streaming, including the agent message trail the board cards print |
| 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, config:writeFailed | Settings management; config:changed is a bare signal fanned to every window after a write so live settings stay in sync, and config:writeFailed carries the one toast a failed settings write raises |
| 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:prefetchRemotes, git:worktreeHead, git:commitGraph, git:fileHistory, git:blame | Git operations, commit-graph, per-file history and blame, the worktree-head read the Command Terminal’s branch pill re-derives from, 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, boardConfig:warnings, boardConfig:getLastWarnings | kangentic.json import/export, board profiles, shared shortcuts, and the warnings an apply raises (shown in the board’s banner on project open) |
| 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:importGetCached, backlog:importReconcile, 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:saveImage, clipboard:writeText | Read a pasted image, save a dropped image as a PNG copy, 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, browser:viewportOverride, browser:viewportClear, browser:viewportGet, browser:paneWidgetSize, browser:offscreenSurfaces, browser:offscreenSurfacesGet, browser:offscreenClose | Embedded browser pane (capture-and-send, per-task URLs, zoom sync, pane registration, the user’s Close browser sent before the pane unmounts so nothing re-spends the memory offscreen, 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, guest mouse back/forward the page would otherwise swallow, the viewport an agent imposed and the pane measurement it is fitted against, and which tasks hold their one surface offscreen, so the card globe and the Browser pill light up for it and Close browser can reach it) |
| Search | search:everything | Unified search across tasks, backlog, events, projects, and past conversations |
| Conversation Memory | transcript:get, transcript:listSessions | Structured conversation transcripts for the viewer |
| Knowledge Graph | knowledgeGraph:status, knowledgeGraph:prewarm, knowledgeGraph:graphEndChat, knowledgeGraph:rebuildIndex, knowledgeGraph:rebuildPlan, knowledgeGraph:taskSummary, knowledgeGraph:graphSnapshot, knowledgeGraph:graphProjects, knowledgeGraph:graphRefresh, knowledgeGraph:graphChanged, knowledgeGraph:graphBuildProgress, knowledgeGraph:graphAnswer, knowledgeGraph:graphAnswerStream, knowledgeGraph:relatedToTask | The Knowledge Graph: index status for the settings tab, a prewarm that starts the embedding worker (and, with a chat, the answering agent) when the graph opens, the map’s snapshot, projects, refresh, and first-build progress, Ask and its streamed answer, the Rebuild and what it would spend, and the prior-work recall for task detail. Replaced the memory:* channels in v0.44.0 |
| Leftover processes | leftoverProcesses:report, leftoverProcesses:stop, leftoverProcesses:stopSettled, leftoverProcesses:check | The report behind the leftover-process toast, and the Stop button that names one reported process by its report id, never a pid. stopSettled pushes the real outcome of a Stop main gave up waiting on, and check lets a row still shown as running catch up with a process that ended on its own |
| 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, updater:blocked | Auto-update lifecycle, plus the one failure the user is told about: an install that cannot update itself where it sits |
| Host Memory | hostMemory:pressure, hostMemory:recovered | Main-to-renderer pushes that raise the low-memory toast and clear it again once headroom recovers |
| Graphics health | gpuHealth:status, gpuHealth:setAccelerationAndRestart | How this launch is rendering, and whether the graphics downgrade still needs reporting. A pull rather than a push, because it is decided during boot, before a renderer could be listening. The second saves Graphics acceleration and restarts in one call, since the graphics mode is chosen before the app is ready |
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 under-- dev_ports dev-server port leases, keyed by task; global because ports are a machine-wide resourcePer-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, -- retired in v0.42.0: migrated into a send_message -- automation, then nulled; nothing reads it auto_command_mode TEXT NOT NULL DEFAULT 'immediate', -- retired with it 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, pushed_branch TEXT DEFAULT NULL, -- the branch the work was actually pushed to, when the -- agent pushed under a name of its own base_branch TEXT DEFAULT NULL, -- the base the user explicitly chose resolved_base_branch TEXT DEFAULT NULL, -- the base the worktree was observed to be cut from use_worktree INTEGER DEFAULT NULL, pr_number INTEGER, pr_url TEXT, pr_state TEXT, -- open, draft, merged, or closed pr_merge_readiness TEXT, -- would a merge succeed right now: ready, blocked, -- conflicting, queued, running, or unknown 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);
-- Column automations: what a column does when a task enters it, when its-- agent finishes a turn there, or when the task leaves it. One ordered list-- per column, split by trigger; replaced the actions and swimlane_transitions-- tables in v0.42.0 (those are migrated once, then left intact and unread)CREATE TABLE column_automations ( id TEXT PRIMARY KEY, swimlane_id TEXT NOT NULL REFERENCES swimlanes(id) ON DELETE CASCADE, name TEXT NOT NULL, -- unique per column, ignoring case type TEXT NOT NULL, -- send_message, move_to_column, run_script, webhook, -- notify, or the legacy spawn_agent trigger TEXT NOT NULL DEFAULT 'enter', -- 'enter', 'turn_end', or 'exit' position INTEGER NOT NULL, -- run order within its trigger group enabled INTEGER NOT NULL DEFAULT 1, config_json TEXT NOT NULL, -- the type's own fields condition TEXT DEFAULT NULL, -- v0.46.0: prose the Automation agent judges; NULL = always runs final_line TEXT DEFAULT NULL, -- v0.46.0: a Text match pattern for the agent's last line max_runs INTEGER DEFAULT NULL, -- v0.46.0: Frequency, runs per task since it last left Done created_at TEXT NOT NULL, updated_at TEXT NOT NULL);
-- The run log: one row per execution, whatever happened. Name and type are-- denormalized and there is deliberately no foreign key to the automation,-- so renaming or deleting one does not empty its historyCREATE TABLE automation_runs ( id TEXT PRIMARY KEY, automation_id TEXT NOT NULL, automation_name TEXT NOT NULL, type TEXT NOT NULL, task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE, swimlane_id TEXT NOT NULL, trigger TEXT NOT NULL, status TEXT NOT NULL, -- running, succeeded, failed, skipped, interrupted detail TEXT, -- the failure or skip reason, or a one-line success attempts INTEGER NOT NULL DEFAULT 0, condition_met INTEGER DEFAULT NULL, -- v0.46.0: the Automation agent's answer, with condition_reason TEXT DEFAULT NULL, -- its one-line reason final_line_met INTEGER DEFAULT NULL, -- v0.46.0: a Text match's result, kept apart final_line_reason TEXT DEFAULT NULL, -- because no agent was asked background_call_id TEXT DEFAULT NULL, -- the background call that answered the Condition started_at TEXT NOT NULL, finished_at TEXT);
-- 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);
-- Knowledge Graph index (see the Conversation Memory and Knowledge Graph features)-- memory_chunks the searchable corpus; a corpus column says what each row indexes:-- conversation turns, task records, session changes, default-branch-- commits, and (with Source code on) code passages-- memory_chunks_fts FTS5 keyword index over memory_chunks.text (code excluded)-- memory_vec_conversation sqlite-vec (vec0) embedding vectors, one table per embedded corpus,-- memory_vec_task created by the retrieval worker when the Knowledge Graph is on-- memory_vec_code (replaced the single memory_chunks_vec in v0.44.0)-- memory_index_state per-document indexing and embedding progress-- memory_task_summaries one agent-written summary per finished task (v0.44.0)-- memory_doc_sums per-document vector sums behind the map, so a rebuild reads only-- the documents that changed (v0.44.0)-- memory_meta index metadata (embedding model, dimensions, the cached map,-- the commit and code index heads)-- conversation_turn_usage durable per-turn token/cost ledger, retained even after a session is deleted-- turn_spawn_links maps a subagent-spawning tool call to the turn that made it, for the fan-out rollup
-- Remaining tables, same database-- usage_history per-session token/cost samples behind the Usage Stats Dashboard; since-- v0.43.0 each row carries its conversation id and the agent's running-- totals beside a per-leg delta, so summing a resumed conversation's-- legs no longer counts the earlier ones again-- session_activity_intervals resolved activity spans per session, behind the activity timeline and-- the dashboard's Avg Active tile-- 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-- remote_item_cache the Import dialog's per-source cache of remote items, reconciled incrementally-- task_moves one row per column a task entered (v0.46.0): the visit boundary for-- automations, a workflow task's step history, and the completed-- task view's Route-- workflows a workflow's plan, state, and its agent's conversation (v0.46.0); its-- tasks stay on their own boards and carry its id-- task_links a workflow's links, either end possibly in another project (v0.46.0)-- background_calls one row per headless agent call Kangentic makes for you: Condition-- checks, workflow steps, task summaries, titles, terminal names-- (v0.46.0), kept apart from usage_history so no session figure counts it-- transcript_archive_state Kangentic's own Brotli-compressed copy of each parsed conversation,-- transcript_archive_rows written by the retrieval worker so a conversation stays readable-- after the agent CLI deletes its file (v0.46.0)-- project_meta per-project counters, including the display-ID high-water mark-- schema_meta key/value flags for one-way data migrations (the automations migration records itself here)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 and, since v0.46.0, a workflow task’s workflow, route, and canvas position on tasks, applied model and effort plus cost and token metrics and the agent-reported transcript path 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 runs the source column's On task exit automations while the session is still attached (capped at 60s in aggregate) → 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) → The destination's On task enter automations run: a send_message row is typed at the new agent, and each run is recorded in automation_runs → 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 runs the source column's On task exit automations → 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 move out of Done through the shared spawn chokepoint; the target's On task enter automations run, minus any message to the agent (a restore never messages it) → 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, the PTYs in their own worker process |
| Database | better-sqlite3 | Persistent storage (WAL mode) |
| Conversation Memory | sqlite-vec + @huggingface/transformers + onnxruntime-node | Local keyword + semantic search over conversations, tasks, and commits; the index and the embedding inference each in their own worker process |
| Knowledge Graph map | three.js + camera-controls | The 3D map, drawn on demand so an idle graph renders no frames |
| Process cleanup | koffi | Reads other processes’ environments and working directories on Windows and macOS, to find what a finished task left running |
| Agent Runtime | Claude Code, Codex, Gemini, Cursor CLI, GitHub Copilot CLI, Grok Build, Antigravity CLI, Goose CLI, OpenCode, Aider, Qwen Code, Kimi Code, Factory Droid, Warp (Oz CLI), Ollama CLIs | Pluggable adapters, one per CLI (fifteen total; fourteen 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 |
See also
Section titled “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