## Extension design rules

Good zi extensions are small, inspectable, and kind to the user.

They do one useful thing, say what they are doing, and leave the user in control. They avoid hidden policy, surprising network calls, and clever abstractions that only the author can debug.

This page is guidance for humans and for zi when generating extensions.

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. Register capabilities at load time; do real work later, when a tool, command, or event is running with a live context.

## Choose the smallest surface

Use [tools](api.html#tools) for model-visible capabilities.

Use [commands](api.html#commands) for direct user actions.

Use [events](api.html#events) for lifecycle policy, prompt/context rewriting, message watching, and tool/provider interception.

Use [`ctx.ui`](context.html#context-ui-api) to publish UI intent.

Use [`ctx.state`](context.html#context-state-api) for durable per-extension session state.

Use [`ctx.ai.complete`](context.html#context-model-and-ai-api) for side-channel model calls that should not mutate the transcript.

Use [`zi.register_provider`](api.html#providers) for provider/model visibility, not for request rewriting. Use [`before_provider_request`](api.html#events) for request rewriting.

## Capability guide

Need: add an action the model can call
: Use [tools](api.html#tools).

Need: add a slash command for the user
: Use [commands](api.html#commands).

Need: change the system prompt
: Use [`before_agent_start`](api.html#events).

Need: rewrite submitted user input
: Use [`input`](api.html#events).

Need: rewrite messages before a provider call
: Use [`context`](api.html#events).

Need: rewrite the provider request
: Use [`before_provider_request`](api.html#events).

Need: show feedback, status, progress, a report, or a prompt
: Use [context ui api](context.html#context-ui-api).

Need: remember a per-session choice
: Use [context state api](context.html#context-state-api).

Need: inspect the transcript, attach notes, label important entries, or query labeled entries
: Use [context session api](context.html#context-session-api).

Need: ask a side-channel model question
: Use [`ctx.ai.complete`](context.html#context-model-and-ai-api).

Need: expose a model/provider
: Use [providers](api.html#providers).

Need: run a bounded OS command from extension code
: Use [`zi.system`](context.html#system-command-helper).

Need: delegate work to a child zi run
: Use [spawn helper](context.html#spawn-helper).

## Canonical patterns

A model-visible tool has one job: accept structured parameters, perform the action, and return content. If the behavior cannot be described in one sentence, split it.

```lua
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. It should be safe to run because a person asked for it.

```lua
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. Prefer this kind of visible memory over private state when the information belongs to the session.

```lua
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. Keep event behavior easy to explain when someone reads the session later.

```lua
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)
```

## Kindness rules

Ask before destructive actions.

Show paths before changing files outside the current project.

Name models and providers when an extension changes them.

Prefer local files, local state, and project-relative paths when possible.

Do not hide network calls inside unrelated commands.

Return readable errors. A person should know what failed and what they can try next.

Keep generated extensions short enough to review.

## 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.
