extension design rules
Prefer documented host APIs over host details.
Do not depend on TUI components, terminal input streams, mailbox payloads, provider runtime handles, transcript row objects, or render caches.
Keep load/register cheap and deterministic.
Use tools for model-visible capabilities.
Use commands for direct user actions.
Use events for lifecycle policy, prompt/context rewriting, message watching, and tool/provider interception.
Use `ctx.ui` to publish UI intent.
Use `ctx.state` for durable per-extension session state.
Use `ctx.ai.complete` for side-channel model calls that should not mutate the transcript.
Use `zi.register_provider` for provider/model visibility, not for request rewriting. Use `before_provider_request` for request rewriting.
capability guide
- Need: add an action the model can call
- Use tools.
- Need: add a slash command for the user
- Use commands.
- Need: change the system prompt
- Use `before_agent_start`.
- Need: rewrite submitted user input
- Use `input`.
- Need: rewrite messages before a provider call
- Use `context`.
- Need: rewrite the provider request
- Use `before_provider_request`.
- Need: show feedback, status, progress, a report, or a prompt
- Use context ui api.
- Need: remember a per-session choice
- Use context state api.
- Need: inspect the transcript, attach notes, label important entries, or query labeled entries
- Use context session api.
- Need: ask a side-channel model question
- Use `ctx.ai.complete`.
- Need: expose a model/provider
- Use providers.
- Need: run a bounded OS command from extension code
- Use `zi.system`.
- Need: delegate work to a child zi run
- Use spawn helper.
canonical patterns
A model-visible tool has one job: accept structured parameters, perform the action, and return content.
return function(zi)
zi.register_tool({
name = "project_status",
description = "Summarize project status.",
parameters = { type = "object", properties = {} },
execute = function(params, ctx)
return { content = { { type = "text", text = "No status provider configured." } } }
end,
})
end
A slash command is for direct user intent.
return function(zi)
zi.register_command({
name = "note",
description = "Save a session note.",
handler = function(args, ctx)
local ok = ctx.session.append_note({ kind = "manual", body = tostring(args or "") })
if ctx.ui then ctx.ui.message(ok and "note saved" or "note failed") end
end,
})
end
A semantic message observer can attach durable session metadata without touching raw jsonl or UI rows.
zi.on("message", function(event, ctx)
local message = event.message or {}
if message.role == "user" and message.text and message.text:match("decision") then
ctx.session.label(message.entry_id, "decision")
ctx.session.append_note({
kind = "observation",
body = "possible decision",
source_entry_id = message.entry_id,
})
end
end)
An event is for policy or reaction.
zi.on("message", function(event, ctx)
local message = event.message or {}
if message.role == "assistant" and ctx.ui then
ctx.ui.message("assistant replied")
end
end)
extension examples
hello.lua- Minimal tool registration.
commands.lua- Slash command with a host-owned report.
dynamic_tools.lua- Dynamic tool registration from an event or command.
prompt_customizer.lua- System prompt customization with
before_agent_start.
status_line.lua- Turn lifecycle status publication.
message_watch.lua- Semantic message observer.
model_completion.lua- Model catalog inspection and
ctx.ai.complete.
session_lifecycle.lua- Session lifecycle observation and cancellable pre-hooks.
session_notes.lua- Session note storage and retrieval.
auto_label.lua- Durable message labels, label queries, and entry lookup.
git_status.lua- Yieldable
zi.systemcommand execution with host-owned report output.
message.lua- Short feedback through
ctx.ui.message.
question.lua,questionnaire.lua,timed_confirm.lua- Host-owned prompts.
custom_header.lua,widget_placement.lua,hidden_thinking_label.lua,titlebar.lua- Pi-mono parity examples rewritten onto host-owned semantic UI primitives.
input_transform.lua,permission_gate.lua- Input and permission-style interception patterns.
summarize.lua,handoff.lua,qna.lua- Workflow-shaped commands/tools built from the same primitives.