Extension purpose
An extension is just a Lua file that teaches zi one new habit.
It can register a command, expose a model-visible tool, customize prompts, store small bits of state, react to lifecycle events, or publish simple UI. You do not need a framework, build step, package registry, or permission from the core project. Start with the smallest useful thing.
Good extensions feel boring in the best way: clear names, clear inputs, visible effects, and no surprise ownership of the user's workflow.
The concrete host functions are listed in extension api: zi table, and handler context is documented in context object.
Use extensions for things like:
- adding model-visible tools
- adding slash commands
- customizing prompts
- reacting to session, message, tool, and model events
- publishing message, status, progress, and report UI
- asking side-channel model questions
- registering provider/model claims
- storing per-session extension state
- attaching semantic notes/labels to durable session entries
- spawning delegated child zi tasks
Extensions describe what should happen. zi decides how to schedule work, update the terminal UI, store session data, call providers, and render transcript output.
Extension discovery
An extension root is a directory-like container. zi discovers extensions from the extensions/ folder:
<root>/
├─ extensions/
│ ├─ foo.lua
│ └─ bar/
│ └─ init.lua
├─ lua/
├─ prompts/
├─ skills/
├─ themes/
├─ agents/
└─ after/Supported extension shapes:
extensions/<id>.lua- A flat single-file extension.
extensions/<id>/init.lua- A bundled extension with extension-local modules next to
init.lua.
When multiple roots provide the same kind of capability, zi resolves precedence in this order:
explicit > user > project > builtinWithin a root, discovery is lexical and deterministic. Most duplicate registrations use first claimant wins. Commands are different: duplicate command names stay callable through resolved invocation names in commands.
Extension loading
The host installs a global zi table into the extension Lua state. Extensions may either register directly at top level or return a function that receives zi:
return function(zi)
zi.register_tool({
name = "greet",
description = "Generate a greeting.",
parameters = {
type = "object",
properties = {
name = { type = "string", description = "Name to greet" },
},
},
execute = function(params, ctx)
local name = params.name or "world"
return {
content = { { type = "text", text = "hello, " .. name } },
}
end,
})
endLoad/register is non-suspending. Keep it cheap and deterministic: register capabilities, initialize small local state, and defer real work to commands, tools, events, jobs, or host-owned prompts.
Extension lifecycle
An extension has two kinds of work:
- setup work, where it registers tools, commands, providers, and event handlers
- session work, where tools, commands, and events run with a live
ctx
Important rules:
- keep setup cheap and deterministic
- do not perform long-running work while the extension loads
- use tool bodies or command handlers for user-visible work
- use events for lifecycle policy and reaction
- recreate session-local UI surfaces, prompts, jobs, and provider handles after session changes
- store private extension state that should survive session changes in context state api
- store shared semantic session artifacts in context session api, such as notes, labels, and entry lookup
Common lifecycle events are session_start, session_shutdown, session_before_switch, and session_before_fork.