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) 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 EngineRuns 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 ManagerCreates and removes git worktrees. Manages branch naming ({slug}-{id}) 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/, 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 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, 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 ManagerThe 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.

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:

WorkerRole
kangentic-pty-hostEvery 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-retrievalEvery 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-embeddingsONNX inference for the local embedding model, driven by one engine on main
kangentic-dictationThe dictation engine
kangentic-line-countLine 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.

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:pathMissing, project:listChangedMulti-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 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:autoCommandResult, task:movedByAutomation, task:conversationExpired, task:takeConversationExpiredTask 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
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
Automationsautomation:list, automation:replaceForColumn, automation:runsForTask, automation:runAgain, automation:runFailed, automation:runsInterruptedColumn automations and their run log; the last two are main-to-renderer pushes behind the failure toast and the project-open interrupted summary
Workflowsworkflow: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:completedThe 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
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:getTaskReport, session:getTaskLastMessage, session:getToolBreakdown, session:getToolResultTokens, session:spawnTransient, session:killTransient, session:setTransientBranch, session:setFocused, session:setMounted, session:notifyUserInterrupt, session:injectSettingsAgent lifecycle, including the Command Terminal’s durable slot titles and branch picks
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:messageTrail, session:getMessageTrails, session:status, session:removedReal-time streaming, including the agent message trail the board cards print
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:changed, config:writeFailedSettings 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
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:prefetchRemotes, git:worktreeHead, git:commitGraph, git:fileHistory, git:blameGit 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
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:setDefaultBaseBranch, boardConfig:warnings, boardConfig:getLastWarningskangentic.json import/export, board profiles, shared shortcuts, and the warnings an apply raises (shown in the board’s banner on project open)
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:importGetCached, backlog:importReconcile, 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:saveImage, clipboard:writeTextRead a pasted image, save a dropped image as a PNG copy, 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:guestMouseButton, browser:viewportOverride, browser:viewportClear, browser:viewportGet, browser:paneWidgetSize, browser:offscreenSurfaces, browser:offscreenSurfacesGet, browser:offscreenCloseEmbedded 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)
Searchsearch:everythingUnified search across tasks, backlog, events, projects, and past conversations
Conversation Memorytranscript:get, transcript:listSessionsStructured conversation transcripts for the viewer
Knowledge GraphknowledgeGraph: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:relatedToTaskThe 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 processesleftoverProcesses:report, leftoverProcesses:stop, leftoverProcesses:stopSettled, leftoverProcesses:checkThe 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 - 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:downloaded, updater:blockedAuto-update lifecycle, plus the one failure the user is told about: an install that cannot update itself where it sits
Host MemoryhostMemory:pressure, hostMemory:recoveredMain-to-renderer pushes that raise the low-memory toast and clear it again once headroom recovers
Graphics healthgpuHealth:status, gpuHealth:setAccelerationAndRestartHow 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.

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
-- dev_ports dev-server port leases, keyed by task; global because ports are a machine-wide resource
-- 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 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,
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 history
CREATE 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 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
);
-- 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
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.js
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 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 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)
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, the PTYs in their own worker process
Databasebetter-sqlite3Persistent storage (WAL mode)
Conversation Memorysqlite-vec + @huggingface/transformers + onnxruntime-nodeLocal keyword + semantic search over conversations, tasks, and commits; the index and the embedding inference each in their own worker process
Knowledge Graph mapthree.js + camera-controlsThe 3D map, drawn on demand so an idle graph renders no frames
Process cleanupkoffiReads other processes’ environments and working directories on Windows and macOS, to find what a finished task left running
Agent RuntimeClaude 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 CLIsPluggable adapters, one per CLI (fifteen total; fourteen 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

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

Star on GitHub