Lisp Extensions
On this page
A Lisp extension lets you give the model a tool it can call, back a slash command with a function, bind a key, change the theme, change how a tool's output is drawn, or run code when something happens in the session. This is where you stop describing what kli should do and add behavior it did not have, and where you can change that behavior and try it in the same session, without restarting.
The zero-code altitudes are text. A prompt template is a message you send; a skill is a procedure the model reads. Neither can run code, call out to a system, or touch the interface. When you need any of those, you write an extension. This is not a fallback for when Markdown runs out: it is the native floor, the same kind of object kli itself is built from.
What an extension adds
An extension is a list of contributions. Each contribution is one named thing the extension installs into the running program, and each kind of contribution is a different thing the model, the session, or the interface gains. The kinds you reach for:
- Tool. A function the model can call by name, with a description, a parameter schema, and a runner that does the work. This is how the model hits an API you have, queries your database, runs a computation, or drives a system kli does not know about. The runner returns content the model reads back, and the tool can carry a renderer that controls how its call shows up in the terminal.
- Command. A slash command backed by a function rather than a Markdown body. Where a prompt template expands to text, a command runs code when you type it — inspect the session, write a file, call into the program, print a result.
- Keybinding. A key bound to an action in the terminal interface.
- Theme. A palette the interface draws with.
- Renderer. Code that decides how a message or a tool call is drawn in the terminal, so you can change what you see without changing what the model does.
- Status-bar slot and widget. A piece of the interface that shows your own state.
- Event handler. A function that fires when something happens — a tool call, a turn boundary, an event your own code emits — so behavior can be triggered by the session rather than by you or the model.
- Method. A new method on an existing generic function, which is how you adjust behavior kli already has instead of adding something beside it.
You are not working with a separate plugin API bolted onto the side of kli. kli itself is built out of these same contributions: the model providers, the tools that read and edit your files, the slash commands, and the terminal interface are all extensions, installed at startup the same way yours installs. An extension you write is the same kind of object as the program's own parts, which is why it can reach the same places they do.
Where extensions live and how they load
Extensions are plain Lisp files. kli reads them from ~/.config/kli/extensions/ for every session and from <project>/.kli/extensions/ for one project. A file declares an extension with a name, what it requires, and what it provides, and kli installs it on startup.
/reload re-reads those files into the running program. It retracts every user extension currently installed, re-indexes the files on disk, and installs them again — so the loop is edit the file, run /reload, use the change, with no rebuild and no restart. A file that fails to load is reported and skipped; the rest keep working, and you fix it and reload again.
Recoding while it runs
The thing this tier gives you beyond capability is that the capability is editable in place. kli runs as one live program rather than a fixed binary, so an extension installs into the session while it is running and can be replaced or removed without ending it.
Replacing an extension is a single operation. kli deactivates the running version — draining the tools, commands, keybindings, and methods it had installed — then activates the new source. If the new version fails to come up, kli brings the old one back and reports the error, so a broken edit leaves you where you started instead of in a half-applied state. This is why you can change a tool's behavior, reload, and call it again in the same conversation, watching the difference turn by turn.
Removal is the same symmetry from the other side. Every contribution an extension adds has a matching way to remove it, and deactivating an extension takes out exactly the pieces it installed and nothing else. Your context, your session log, and your model connection are untouched by adding or removing an extension. The Live Image covers the mechanism behind this; you do not need it to write an extension, but it is the reason the edit-reload-try loop exists.
What it costs
Writing an extension is programming, in Common Lisp, against kli's extension vocabulary. A command that replies with a fixed string is a few lines; a tool that does real work is as much code as the work takes. The altitudes compose rather than rank: a project commonly carries a few prompt templates, a couple of skills, and one extension that adds the tool those skills tell the model to use. Choosing an altitude lays out what each can and cannot do.
You do not have to write the Lisp yourself. The built-in creating-extensions skill walks the model through authoring one, so you can describe the tool or command you want and have the model write, reload, and test it in the session with you.
Adding a tool means the model can now call it, which is also something you may want to restrict. A tool can declare the capabilities it needs, and kli refuses the call if the current session does not hold them. See Capabilities and fault barriers for how that gating works.
Related
- Choosing an altitude — what a Lisp extension can do that a template or skill cannot, and when each is enough.
- The Live Image — how extensions install, retract, and recode without a restart.
- Sharing extensions — handing an extension to someone else, including the
/install <url> <git-tree-sha1>consent flow. - Tools, Slash commands, Themes, Keymap — the built-in contributions an extension extends.