# Profiles


A profile is a named group of extensions kli boots with.

## A profile is a named group of extensions

kli is a small kernel with everything else installed on top as an extension: the
model providers, the tools, the commands, the terminal UI. A profile is the list
that says which of those to install at boot, under one name.

The list is built from groups of extension manifests. Three carry the substance:

- A **baseline** group every profile installs. It holds the parts kli needs to
  be an agent at all: the event system, the session log, the config layer, the
  agent loop, and the file, search, and shell tools.
- A **model-provider** group: the `anthropic`, `openai`, `openai-codex`, and
  `compatible` providers. A profile that talks to a model installs this; one that
  does not, leaves it out.
- A **terminal-UI** group: the chat view, the input editor, markdown rendering,
  the slash commands, completion. Only a profile meant to be driven by a person
  at a terminal installs this.

Every profile also carries a **nix-declared** group, spliced in right after the
baseline. It is empty in plain kli and holds whatever a Nix-configured image
declares at boot, so those extensions boot as baseline children. The
[profiles reference](/kli/config/profiles) lists exactly what each group installs.

A profile names the groups it wants and they install together as a unit. A
profile is nothing more than that explicit list, which is why the built-ins
differ only in which groups they include. The kernel itself never learns what a
profile is; it sees the install requests and nothing more. For why installing
and retracting extensions on a running kernel works at all, see
[The Live Image](/kli/concepts/the-live-image).

## The four built-in profiles

Each built-in is a fixed combination of those groups, plus a declaration of any
capabilities the profile expects you to supply.

- **`interactive-terminal`** installs the baseline, model-provider, and
  terminal-UI groups. This is the default — what you get when
  you run kli with no profile selected. It is the profile for sitting at a
  terminal and working with the agent.
- **`headless`** installs the baseline group only. No model providers, no
  terminal UI. It is the minimal agent core, the starting point for a profile or
  an embedding that wires its own providers and front end.
- **`human-in-loop`** installs the same groups as `interactive-terminal` and
  declares an `approval` seam — a named point a human-approval extension is meant
  to fill. The profile itself does not provide approval; it states that the slot
  exists so an extension can complete it.
- **`autonomous`** installs the baseline and the model providers but not the
  terminal UI, and declares `planner`, `scheduler`, `watchdog`, and `recovery`
  seams. It is the shape for an agent that runs without a person watching: it can
  talk to a model and use tools, with the supervision pieces left as seams for
  you to fill.

A seam is a capability a profile declares but does not provide. It is how a
profile names its own extension points without pretending to satisfy them. What
fills a seam is a separate extension, governed by the
[capabilities array](/kli/config/capabilities) and your installed extensions.

## How precedence picks the active profile

At boot kli resolves one profile name from the first of these that is set:

1. The `--profile <name>` command-line flag.
2. The `KLI_PROFILE` environment variable.
3. The `profile` key in your merged settings, where a project's
   `<repo>/.kli/settings.json` wins over the global `~/.config/kli/settings.json`.
4. The default, `interactive-terminal`, when none of the above names a profile.

The flag wins over the environment variable, which wins over settings, which
wins over the default. If the resolved name is neither a built-in nor a profile
declared in settings, kli warns and boots the default rather than failing. The
warning surfaces in the session, so a typo in a profile name does not leave you
guessing why you got the terminal you did not ask for.

## Profiles you define in settings

Beyond the four built-ins you can declare your own profile under the `profiles`
object in `settings.json`. A declared profile is a delta on top of a built-in,
with four fields:

- `extends` — the profile it builds on. With no `extends`, it bottoms out at
  `interactive-terminal`.
- `enable` — extension ids to add to the active set.
- `disable` — extension ids to remove from it.
- `settings` — a settings overlay that rides along while the profile is active.

Resolution walks the `extends` chain down to a built-in base, then folds the
deltas from the base up: a later profile in the chain has the last word on any
given extension id, and its settings merge over the earlier ones. A declared
profile cannot reuse a built-in name; that is reserved, and an entry that tries
to shadow one is ignored with a warning. A malformed entry is skipped the same
way, so one bad profile does not stop the rest from loading.

The `enable` and `disable` deltas gate your user extensions, the optional ones
you install yourself. They do not gate the built-in groups, which arrive with
the base. Whether a given user extension is installed at boot comes down to its
own configuration and the active profile's deltas together.

## Switching profile while kli runs

The `/profile` command lists the available profiles and live-switches between
them. A switch re-bases your user extensions onto the target profile's set —
installing the ones it wants that are absent, retracting the ones it does not —
and swaps in the target's settings overlay. Because the switch operates on a
running kernel, your context and session log carry across it.

One thing does not switch live: the built-in base. The base group is installed
once at boot, so switching to a profile with a different base (from
`interactive-terminal` to `headless`, say) cannot take effect in place. kli tells
you so and points you at restarting with `--profile <name>`. Everything that is
a delta on the same base switches in the running session.
