Skip to main content
Kova’s agent does not live in the render process. It runs in a separate local sidecar process (apps/sidecar/pi-agent, Bun + TypeScript) and talks to the frontend over an NDJSON protocol on stdin/stdout. The direct benefit: long jobs never block the interface, and a frontend crash never kills the work in progress.

The life of one turn

You will understand the agent’s behaviour faster from this loop than from any single module:
1

Assemble context

Pull the session history, inject long-term memory and loaded skills, attach the content of any @-mentioned files and any attachments. Whether compaction is needed depends on how much of the window is already used.
2

Call the model

Hand the assembled context to the currently selected model and receive incremental text or a set of tool calls. Switching models affects only this step.
3

Adjudicate each tool call

Every call passes the session mode first (may this tier write?), then the approval level (must this tier ask?). Both must allow it before it proceeds.
4

If approval is needed, suspend

Suspending is not discarding: the sidecar holds the turn state, pushes an approval event to the frontend, and resumes from exactly that step when your answer arrives. A denied call comes back to the model as a blocked result so it can take another route instead of deadlocking.
5

Execute and feed results back

The tool runs inside the sidecar, its result is appended to the context as a tool message, and the loop returns to step 2. The turn ends when the model stops asking for tools.
6

Persist

Transcript, tool calls, and checkpoints are written to session storage; a long session triggers compaction, folding early turns into a summary.
Only step 2 belongs to the model. Everything else is deterministic local logic. That is why switching models never changes permission behaviour — approval, compaction, and subagent scheduling all live outside the model.
How many iterations this loop ran, what each was trying to do, and why it stopped are invisible by default. To see inside it, see the Loop view.

Built-in tools

The sidecar registers roughly these categories: Question deserves a mention: it is the channel through which the agent asks the user something, suspending the turn when it needs to clarify a requirement or confirm a decision, instead of guessing and continuing.

Session mode: whether it may write

agent

The full toolset. The default — reads, writes, and runs commands.

plan

Structurally read-only. It can read and plan, then uses plan_exit to get approval before handing back to agent to implement.

ask

A read-only subset with no bash. For “just tell me, don’t touch anything”.

goal

Full toolset plus cross-turn autonomy, for “finish this objective” work.
The point of plan is separating planning from implementation by permission: the reading phase gets read-only rights, and write rights only come back at the implementation phase.

Approval level: whether it must ask

ask

write / edit / bash / config tools — every single one asks.

workspace-write

Inside the workspace and listed writable roots needs no approval; bash still asks, with “remember this class of command” support.

auto-edit

Writes and edits are fully auto-approved, including outside the workspace; bash still asks.

auto

Everything auto-approved.
Those four are the complete set (the level is an enum, not a tunable; Shift+Tab cycles through them).

The approval card’s three buttons

“Allow and remember” generates a prefix rule, not an exact match: (Exact-match rules without * are still supported — that is the form you hand-write into the list; the UI only ever produces prefix rules.) Interpreter and destructive commands (node / bash / npx / pnpm dlx / sudo / rm / cp / find) are not remembered — everything after the first word is the code to execute or an arbitrary path, so one click would mean a permanent free pass. Their card has no third button and simply says the approval is for this one time.
These rules must carry three guards. Segment-by-segment checking (the second segment of git log && rm -rf ~ has to match a rule itself), no hidden writes (redirects, command substitution, backticks and $'…' never match; redirect detection follows shell lexing, so echo a${IFS}>f counts as a write), and escape/quote parsing done the way the shell does it (anything unparseable is neither allowed nor remembered). On top of those, the writable-root list restricts where writes may land, and project files can only be proposed, never self-authorised. So “remember this class of command” never grants write access to a directory you did not approve manually.To be straightforward about one thing: workspace-write does not contain bash. A command can reach any path outside the workspace through the shell. This has its own section in docs/permission-modes.md — it is a deliberate trade-off, not an oversight.

The writable-root list (directories that need no approval outside the workspace)

Inside the workspace writes need no approval and outside it they ask — but real work spans projects (a frontend and a backend directory, a shared library outside the monorepo), and asking every time is pure noise. The list fixes that: directories you put on it are treated as “inside the workspace”. Three files, unioned: “Allow and remember” on the approval card writes into the machine-local file (local). In other words: every file that can authorize anything lives on your own machine, so there is no trust state a repository could forge — a root declared by the project layer only shows up on the card as “this project requests X”.

The two dimensions are separate

Mode decides whether it may change things; approval level decides whether it must ask. They are independent dropdowns, and the combination is the actual behaviour. Collapsing them is the most common misunderstanding about Kova.

Context compaction

Long conversations always hit the window limit. Compaction folds older turns into a summary while preserving key decisions, files already produced, and unfinished objectives, so a task keeps going instead of losing its thread.

Long-term memory

Beyond in-session compaction, Kova keeps a layer of cross-session memory. Entries are viewable and editable under Settings → Memory. Memory lives in two layers: a global one at ~/.kova/memory (shared across sessions and workspaces) and a workspace one at <workspace>/.kova/memory. The sessions themselves (transcript and traces) are not there — they live under sessions/ in the app data directory.

Subagents

Subagents let the agent split large work and process it in parallel instead of grinding through it serially in one context.
1

Task

Dispatch a subagent with its own context and toolset.
2

TaskWait

Wait for a subagent to finish and collect its result.
3

TaskList

List live subagents and their status.
4

TaskStop

Terminate a running subagent.
Definitions live in ~/.kova/subagents (machine-wide) or <workspace>/.kova/subagents (committable, shared with your team), both managed under Settings → Subagents. Discovery has three layers: built-in, user-defined, and plugin-provided. Subagent activity is persisted, so a long job survives a restart (see docs/subagent-activity-persistence-design.md).

Scheduled tasks and unattended runs

Automations are scheduled with croner. Scheduled runs use a dedicated automation approval level — clearly, the most dangerous levels cannot be the default when nobody is watching. Results can be pushed to any service over webhooks.

Next

Beyond tools, there are workspaces you can actually open.