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.system command 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.