# Capabilities and Fault Barriers


Out of the box, kli reads, runs, and edits your code without stopping to ask. There is no "allow this action?" prompt to clear, no per-command gate to babysit. You start kli in a project and it works on your code the way you would: it opens files, runs the shell, and applies edits directly. When you do want a session held back from some of that, you say so once, in writing, before the session starts. And when a piece of kli misbehaves, the failure is confined to that piece instead of taking down the program you are working in.

Two mechanisms produce that behavior: a capability subject that decides what a caller is allowed to do, and fault barriers that decide what happens when a caller breaks.

## Why there is no approval prompt

Every gated operation in kli runs under a *subject* — the capability-bearing identity of whoever is calling. Before a tool reads a file, runs a process, edits the context, or installs an extension, it asks the current subject whether it holds the matching capability. If it does, the call proceeds. If it does not, the call is denied.

The default subject is the *system subject*, and the system subject passes every check. It is the value in force at startup and for a normal boot. So under the default, every capability question gets the same answer — yes — which is why a fresh session does its work without ever interrupting you. There is no approval workflow because there is nothing to approve against: the default identity is already trusted with everything.

This is a deliberate choice, not a missing feature. An interactive prompt trains you to click "yes" without reading it, and a tool that asks before every file write is a tool you stop trusting to do real work. kli's position is that the decision about what an agent may touch belongs to you, made once, ahead of time, rather than to a dialog box in the middle of a task.

## Restricting a session with capabilities

You narrow what a session can do through the `capabilities` key in settings — `~/.config/kli/settings.json` for your global default, or `<repo>/.kli/settings.json` for one project. The key is an array of capability names, and its presence is what switches a session from "trusted with everything" to "trusted with exactly this list."

The three states are distinct:

- **Key absent.** The session keeps the system subject. Every gated tool is allowed. This is the default.
- **Key present, non-empty.** The session runs under a restricted subject that holds exactly the capabilities you named, and nothing else. A tool whose capability is not on the list is denied when it runs.
- **Key present, empty array (`[]`).** The session holds no capabilities at all. Every gated tool is denied. This is the most restrictive setting and the one to reach for when you want a read-nothing, run-nothing session.

A capability name implies the finer-grained capabilities it depends on. Granting `tools/standard`, for instance, grants the file read, file write, file edit, and process-execution capabilities that the standard toolset is built from — you do not have to enumerate each one. The set you write is closed under these implications before the session uses it, so naming a coarse capability is enough to admit everything it covers.

A malformed value — anything that is not an array of strings — is not treated as "deny all." kli warns that the setting was ignored and falls back to the system subject. The restriction has to be a well-formed list to take effect; a typo does not silently lock you out, and it does not silently grant you nothing either. (The warning surfaces in the transcript as a boot diagnostic, since the terminal takeover would otherwise wipe it.)

For the capability names you can put in the array and the tools each one gates, see [Capabilities](/kli/config/capabilities).

## How a fault stays contained

kli is one running program with everything else installed as a retractable extension — the model providers, the tools, the commands, the terminal UI. That design lets you change kli while it runs, and it also means a fault in one extension is, structurally, a fault inside the program you are using. Without containment, an extension that threw an error mid-render could crash the whole session and lose your work.

Crash barriers are what keep that from happening. A barrier wraps a *seam* — a place where extension or hot-patched code runs — and catches errors that escape it. (It catches ordinary errors only; it never swallows the serious conditions that signal the process itself is unsound.) When a fault crosses a barrier, three things can happen, and each barrier picks its policy for the seam it guards:

- **continue** — unwind the failed unit and return a fallback value. The fault is recorded; the session carries on as if that unit produced nothing.
- **reify** — do everything `continue` does, and additionally surface the fault to you, typically as an event in the transcript, so you see that something failed and where.
- **escalate** — decline to contain. The error keeps propagating outward, toward the next barrier or the process boundary. This is how a fault that should be loud stays loud.

Every contained fault is written to a per-seam log under the cache directory regardless of policy, so a failure is never silently lost even when the session continues. The diagnostic goes to a file, never to the terminal, because writing to the terminal would corrupt the live UI that the barrier is trying to protect — and a failure in the logging path itself loses the line and nothing else. Containment is the one job a barrier may not break, so reporting a fault can never cause one.

The result you feel is graceful degradation. A widget in the status bar that errors contributes no line that frame instead of breaking the bar. A render that faults skips the frame and tells you in the transcript. A misbehaving extension fails in place and the rest of the session keeps going.

Degradation is not unconditional, though. A seam that keeps faulting is a seam that is genuinely broken, and continuing to paper over it just hides the problem. The render barrier, for example, runs `continue` until its faults pile into a streak, then switches to `escalate` so a persistently broken renderer surfaces as a real failure rather than an endless run of skipped frames. The point of a barrier is to keep one fault from killing the app, not to pretend a fault never happened.

## How the two relate

Capabilities decide what a caller is *allowed* to do; barriers decide what happens when a caller *fails*. They are independent, and together they set the shape of a kli session: trusted by default and held back only on your explicit instruction, and able to survive the failure of any single part because no single part can take the whole down.

To put a restriction in place, see [Restrict what kli can do](/kli/guides/restrict-what-kli-can-do). For the live-kernel design that makes extensions — and therefore seams — the unit of both trust and containment, see [The live image](/kli/concepts/the-live-image). Capabilities bound what an agent is *allowed* to do, not what the kli process can *reach* on the host; for that boundary, see [Security model and sandboxing](/kli/concepts/security-model-and-sandboxing).
