Safety model
ka is designed so that speed never comes from deleting guardrails. The safety floor is layered, and the most dangerous parts are unbypassable: they apply even in free mode, and headless runs auto-deny them.
Clearances
Section titled “Clearances”Every tool sits in one of three tiers:
- read (observation):
read,glob,grep,pathfinder,web_fetch - write (mutation):
edit,write,todo,snapshots - exec (arbitrary side effects):
bash,delegate, MCP tools
| Mode | Behavior |
|---|---|
guarded |
Exec-tier calls and non-allowlisted writes ask first; “always allow” persists as a rule for the session |
accept_edits |
File writes proceed; exec still asks |
free |
Everything runs automatically; hardstops still ask |
plan |
Read-only except .ka/plans/; ka can investigate and plan, not touch |
The TUI picker names them guarded / accept edits / full access / plan. Mode is part of the strand state: /mode persists it to the session like any other change.
Rules as data
Section titled “Rules as data”[[rules]] are evaluated before mode logic, first match wins:
[[rules]]tool = "bash"pattern = "cargo *" # glob on the call's primary argumentverdict = "allow" # allow | ask | deny
[[rules]]tool = "write"verdict = "deny" # no pattern = every call of the toolAnswering “always” on an approval writes the equivalent entry into the project layer’s [permissions] allow.
Hardstops
Section titled “Hardstops”A flat, unbypassable list checked on the decomposed command set (see below): recursive root/home deletion, fork bombs, fetch-and-execute pipes, writes to /etc, dd to devices. PATH aliases are resolved to the real executable before matching, so rm via a symlink is still rm. In the TUI, a hardstop asks even in free mode; headless runs deny outright.
Bash decomposition
Section titled “Bash decomposition”Every bash call is decomposed in pure Rust before verdicts apply: compounds split on && ; || | &, wrappers (timeout, nice, nohup, stdbuf, env, bare xargs) are stripped, and redirections count as writes. A readonly allowlist (ls, cat, git status/diff/log, …) auto-passes, but only when there are no compound operators. The point: bash -c "curl … | sh" can’t dress up as a read.
The read ledger
Section titled “The read ledger”ka keeps a per-strand map of file → (mtime, hash) at last read. edit and write refuse files that are unread or changed since the last read; the model can never blind-edit a file that drifted underneath it. The ledger syncs during settling after each turn.
Secret redaction
Section titled “Secret redaction”Redaction is one-way and applied to every tool result before it reaches the model or the transcript. Keys entered via /key are stored in the OS keyring or a 0600 env file, never in strands.
Project trust
Section titled “Project trust”.ka/ka.toml is config from a directory, and directories can be hostile. The first time ka sees one, it asks; the decision is stored in ~/.local/state/ka/trust.json. --trust accepts up front. Untrusted project layers are simply not loaded.
Filesystem sandbox
Section titled “Filesystem sandbox”[sandbox]mode = "fs" # default "off"allow_write = ["/opt/cache"] # extra writable dirsWith mode = "fs", bash children can read everything but write only the cwd, /tmp, XDG state/cache, and any allow_write entries, enforced by bubblewrap, firejail, or in-kernel landlock (whichever is available). The sandbox fails closed: if no mechanism can be applied, the run is refused.
Sandbox expansion: when a sandboxed command deterministically needs more than the policy allows (a redirection outside the write allowlist, a network-touching program under a network-denying backend, env assignments an env-clearing backend would strip), ka computes the exact missing grants before running and asks once: allow (this run), always (write paths persist to [sandbox] allow_write; network and env grants are session-scoped), or deny (the sandbox stays unchanged; the command fails on its own). Grants come from command analysis, never from failure output, and writes beneath broad directories (/, $HOME, /etc) are never offered. Nothing expands without an explicit ask.
Guards
Section titled “Guards”[guards]spend_usd = 2.0 # ask before the session's total cost crosses $2.00context_pct = 90 # ask when the context meter first passes 90%Each guard latches once per session; answering stop aborts the current turn cleanly.
Runtime feature toggles
Section titled “Runtime feature toggles”One grammar decides what the model can use: set at startup (flags, [features]) or flipped mid-session (/features, /sandbox). Every change is recorded on the strand and restored on resume, exactly like model and mode.
agents | skills | mcp | hooks | web | lsp | debug # whole featuresmcp:<server> · skill:<name> · tool:<hand> · agent:<name> # single itemsDisabling is fail-closed: the capability vanishes from the model’s tool list and a stray call is rejected naming the toggle, so it cannot be reached even by guessing the name. Enabling restores it: an MCP server disabled at startup spawns on the spot, and so do the LSP and debug tiers when configured. Toggles land between turns: sent mid-turn, the hide applies to the next model round-trip inside that turn, and the side effects (spawn, strand record, inventory) settle with it. Disabling a tier never weakens the permission tiers; it only removes capabilities. Precedence: flags → resumed strand → [features] → defaults.
ka --disable agents,mcp:github # no subagents, one server offka --disable tool:bash # a session that cannot run commandska --enable mcp:github # re-enable over a [features] disableIn the TUI, /features opens the panel (↑↓ choose, Enter toggles, s saves the current set as your user-layer default), /features on|off <spec> toggles directly, and /sandbox off|fs switches the bash sandbox live: the fs policy is recomputed from [sandbox] allow_write, so unpersisted session grants drop on off→fs. The status bar shows ⊘ N while N specs are disabled.
The troubleshooting floor
Section titled “The troubleshooting floor”ka --safe-mode (or KA_SAFEMODE=1) disables all customizations (AGENTS.md, MEMORY.md, skills, agents, commands, hooks, MCP, LSP), keeping built-ins, config, and auth. It outranks every feature toggle: nothing it disables can be re-enabled until restart. If something misbehaves, safe mode answers whether the culprit is your extensions or ka itself.