Profiles
On this page
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, andcompatibleproviders. 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 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.
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-terminalinstalls 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.headlessinstalls 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-loopinstalls the same groups asinteractive-terminaland declares anapprovalseam — 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.autonomousinstalls the baseline and the model providers but not the terminal UI, and declaresplanner,scheduler,watchdog, andrecoveryseams. 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 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:
- The
--profile <name>command-line flag. - The
KLI_PROFILEenvironment variable. - The
profilekey in your merged settings, where a project's<repo>/.kli/settings.jsonwins over the global~/.config/kli/settings.json. - 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 noextends, it bottoms out atinteractive-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.