---
title: Capabilities
description: All extension capabilities — lifecycle events, tools, commands, widgets, and more.
---

# Extension Capabilities

## Lifecycle events

Extensions can hook into 23 lifecycle events:

| Event | Description |
|-------|-------------|
| `OnSessionStart` | Session initialized |
| `OnSessionShutdown` | Session ending |
| `OnBeforeAgentStart` | Before the agent loop begins |
| `OnAgentStart` | Agent loop started |
| `OnAgentEnd` | Agent loop completed |
| `OnToolCall` | Tool call requested by the model |
| `OnToolExecutionStart` | Tool execution beginning |
| `OnToolOutput` | Streaming tool output chunk (for long-running tools) |
| `OnToolExecutionEnd` | Tool execution completed |
| `OnToolResult` | Tool result returned |
| `OnInput` | User input received |
| `OnMessageStart` | Assistant message started |
| `OnMessageUpdate` | Streaming text chunk received |
| `OnMessageEnd` | Assistant message completed |
| `OnModelChange` | Model switched |
| `OnContextPrepare` | Context being assembled for the model |
| `OnBeforeFork` | Before forking a conversation branch |
| `OnBeforeSessionSwitch` | Before switching sessions |
| `OnBeforeCompact` | Before conversation compaction |
| `OnCustomEvent` | Custom inter-extension event received |
| `OnSubagentStart` | Subagent spawned by the main agent |
| `OnSubagentChunk` | Real-time output from subagent (text, tool calls, results) |
| `OnSubagentEnd` | Subagent completed with final response/error |

### Example

```go
api.OnToolCall(func(event ext.ToolCallEvent, ctx ext.Context) {
    ctx.PrintInfo("Calling tool: " + event.Name)
})

api.OnAgentEnd(func(_ ext.AgentEndEvent, ctx ext.Context) {
    ctx.PrintInfo("Agent finished")
})
```

## Tools

Register custom tools that the LLM can invoke:

```go
api.RegisterTool(ext.ToolDef{
    Name:        "weather",
    Description: "Get current weather for a location",
    Parameters: map[string]ext.ParameterDef{
        "city": {Type: "string", Description: "City name", Required: true},
    },
    Handler: func(ctx ext.Context, params map[string]any) (string, error) {
        city := params["city"].(string)
        return "Sunny, 72°F in " + city, nil
    },
})
```

## Commands

Register slash commands that users can invoke directly:

```go
api.RegisterCommand(ext.CommandDef{
    Name:        "stats",
    Description: "Show context statistics",
    Handler: func(ctx ext.Context, args string) {
        stats := ctx.GetContextStats()
        ctx.PrintInfo(fmt.Sprintf("Tokens: %d", stats.TotalTokens))
    },
})
```

## Widgets

Add persistent status displays above or below the input area:

```go
ctx.SetWidget(ext.WidgetConfig{
    ID:       "token-count",
    Position: "bottom",
    Content:  ext.WidgetContent{Text: "Tokens: 1,234"},
})

// Update later
ctx.SetWidget(ext.WidgetConfig{
    ID:       "token-count",
    Position: "bottom",
    Content:  ext.WidgetContent{Text: "Tokens: 2,456"},
})

// Remove
ctx.RemoveWidget("token-count")
```

## Headers and footers

Persistent content above and below the conversation:

```go
ctx.SetHeader(ext.HeaderFooterConfig{
    Content: ext.WidgetContent{Text: "Project: my-app | Branch: main"},
})

ctx.SetFooter(ext.HeaderFooterConfig{
    Content: ext.WidgetContent{Text: "Plan Mode (read-only)"},
})
```

## Status bar

Custom status bar entries:

```go
ctx.SetStatus("mode", "Planning")
ctx.RemoveStatus("mode")
```

## Shortcuts

Global keyboard shortcuts:

```go
api.RegisterShortcut(ext.ShortcutDef{
    Key:         "ctrl+t",
    Description: "Toggle plan mode",
}, func(ctx ext.Context) {
    // handle shortcut
})
```

## Overlays

Modal dialogs with markdown content:

```go
ctx.ShowOverlay(ext.OverlayConfig{
    Title:   "Help",
    Content: "# Keyboard Shortcuts\n\n- **ctrl+t** — Toggle plan mode\n- **ctrl+s** — Save session",
})
```

## Tool renderers

Customize how specific tool calls are displayed in the TUI:

```go
api.RegisterToolRenderer(ext.ToolRenderConfig{
    ToolName: "bash",
    Render: func(name, args, result string, isError bool) string {
        return "$ " + args + "\n" + result
    },
})
```

## Message renderers

Custom rendering for assistant messages:

```go
api.RegisterMessageRenderer(ext.MessageRendererConfig{
    Name: "custom",
    Render: func(content string) string {
        return ">> " + content
    },
})
```

## Editor interceptors

Handle key events and wrap the editor's rendering:

```go
ctx.SetEditor(ext.EditorConfig{
    HandleKey: func(key, text string) ext.EditorKeyAction {
        if key == "escape" {
            return ext.EditorKeyAction{Handled: true}
        }
        return ext.EditorKeyAction{Handled: false}
    },
})
```

## Interactive prompts

Select, confirm, input, and multi-select dialogs:

```go
// Single select
response := ctx.PromptSelect(ext.PromptSelectConfig{
    Title:   "Choose a model",
    Options: []string{"claude-sonnet", "gpt-4o", "llama3"},
})

// Confirm
confirmed := ctx.PromptConfirm(ext.PromptConfirmConfig{
    Title: "Delete this file?",
})

// Text input
name := ctx.PromptInput(ext.PromptInputConfig{
    Title:       "Enter project name",
    Placeholder: "my-project",
})
```

## Options

Register configurable extension options:

```go
api.RegisterOption(ext.OptionDef{
    Name:         "auto-commit",
    Description:  "Automatically commit on shutdown",
    DefaultValue: "false",
})
```

## Subagents

Spawn in-process child Kit instances:

```go
result := ctx.SpawnSubagent(ext.SubagentConfig{
    Task:         "Analyze the test files and summarize coverage",
    Model:        "anthropic/claude-haiku-latest",
    SystemPrompt: "You are a test analysis expert.",
})
```

### Monitoring subagents spawned by the main agent

When the LLM uses the built-in `subagent` tool, extensions can monitor the subagent's activity in real-time using three lifecycle events:

```go
// Subagent started
api.OnSubagentStart(func(e ext.SubagentStartEvent, ctx ext.Context) {
    // e.ToolCallID — unique ID for this subagent invocation
    // e.Task — the task/prompt sent to the subagent
    ctx.PrintInfo(fmt.Sprintf("Subagent started: %s", e.Task))
})

// Real-time streaming output from subagent
api.OnSubagentChunk(func(e ext.SubagentChunkEvent, ctx ext.Context) {
    // e.ToolCallID — matches the start event
    // e.Task — task description
    // e.ChunkType — "text", "tool_call", "tool_execution_start", "tool_result"
    // e.Content — text content (for text chunks)
    // e.ToolName — tool name (for tool-related chunks)
    // e.IsError — true if tool result is an error
    switch e.ChunkType {
    case "text":
        // Streaming text output
    case "tool_call":
        // Subagent is calling a tool
    case "tool_execution_start":
        // Tool execution started
    case "tool_result":
        // Tool execution completed (check e.IsError)
    }
})

// Subagent completed
api.OnSubagentEnd(func(e ext.SubagentEndEvent, ctx ext.Context) {
    // e.ToolCallID — matches start event
    // e.Task — task description
    // e.Response — final response from subagent
    // e.ErrorMsg — error message if subagent failed
    if e.ErrorMsg != "" {
        ctx.PrintError(fmt.Sprintf("Subagent failed: %s", e.ErrorMsg))
    } else {
        ctx.PrintInfo(fmt.Sprintf("Subagent completed: %s", e.Response))
    }
})
```

This enables building widgets that display real-time subagent activity.

## LLM completion

Make direct model calls without going through the agent loop:

```go
response := ctx.Complete(ext.CompleteRequest{
    Prompt: "Summarize this in one sentence: " + content,
})
```

## Themes

Register and switch color themes at runtime:

```go
// Register a custom theme
ctx.RegisterTheme("neon", ext.ThemeColorConfig{
    Primary:    ext.ThemeColor{Light: "#CC00FF", Dark: "#FF00FF"},
    Secondary:  ext.ThemeColor{Light: "#0088CC", Dark: "#00FFFF"},
    Success:    ext.ThemeColor{Light: "#00CC44", Dark: "#00FF66"},
    Warning:    ext.ThemeColor{Light: "#CCAA00", Dark: "#FFFF00"},
    Error:      ext.ThemeColor{Light: "#CC0033", Dark: "#FF0055"},
    Info:       ext.ThemeColor{Light: "#0088CC", Dark: "#00CCFF"},
    Text:       ext.ThemeColor{Light: "#111111", Dark: "#F0F0F0"},
    Background: ext.ThemeColor{Light: "#F0F0F0", Dark: "#0A0A14"},
})

// Switch to it
ctx.SetTheme("neon")

// List all available themes
names := ctx.ListThemes()
```

See [Themes](/themes) for the full theme file format, built-in themes, and color reference.

## Custom events

Inter-extension communication:

```go
// Emit
ctx.EmitCustomEvent("my-extension:data-ready", payload)

// Listen
api.OnCustomEvent("my-extension:data-ready", func(data any, ctx ext.Context) {
    // handle event
})
```

## Bridged SDK APIs

Extensions can access powerful internal SDK capabilities that enable advanced features like conversation tree navigation, dynamic skill loading, template parsing, and model resolution.

### Tree Navigation

Navigate the conversation tree, summarize branches, and implement "fresh context" loops:

```go
// Get a specific node by ID with full metadata and children
node := ctx.GetTreeNode("entry-id")
// node.ID, node.ParentID, node.Type ("message"/"branch_summary"/etc)
// node.Role, node.Content, node.Model, node.Children ([]string)

// Get the current branch from root to leaf
branch := ctx.GetCurrentBranch()  // []ext.TreeNode

// Get child entry IDs of a node
children := ctx.GetChildren("entry-id")  // []string

// Navigate/fork to a different entry in the tree
result := ctx.NavigateTo("entry-id")  // ext.TreeNavigationResult{Success, Error}

// Summarize a range of the branch using LLM
summary := ctx.SummarizeBranch("from-id", "to-id")  // string

// Collapse a branch range into a summary entry (fresh context primitive)
result := ctx.CollapseBranch("from-id", "to-id", "summary text")
```

### Skill Loading

Load and inject skills dynamically at runtime:

```go
// Discover skills from standard locations
result := ctx.DiscoverSkills()  // ext.SkillLoadResult{Skills, Error}
// Standard locations: ~/.config/kit/skills/, .kit/skills/, .agents/skills/

// Load a specific skill file
skill, err := ctx.LoadSkill("/path/to/skill.md")  // (*ext.Skill, error string)
// skill.Name, skill.Description, skill.Content, skill.Tags, skill.When

// Load all skills from a directory
result := ctx.LoadSkillsFromDir("/path/to/skills")  // ext.SkillLoadResult

// Inject a skill as context (pre-loads for next turn)
err := ctx.InjectSkillAsContext("skill-name")  // error string

// Inject a skill file directly
err := ctx.InjectRawSkillAsContext("/path/to/skill.md")  // error string

// Get all discovered skills
skills := ctx.GetAvailableSkills()  // []ext.Skill
```

### Template Parsing

Parse and render templates with variable substitution:

```go
// Parse a template to extract {{variables}}
tpl := ctx.ParseTemplate("name", "Hello {{name}}, welcome to {{place}}!")
// tpl.Name, tpl.Content, tpl.Variables ([]string)

// Render a template with variable values
vars := map[string]string{"name": "Alice", "place": "Kit"}
rendered := ctx.RenderTemplate(tpl, vars)  // "Hello Alice, welcome to Kit!"

// Parse command-line style arguments
pattern := ext.ArgumentPattern{
    Positional: []string{"command", "target"},  // $1, $2
    Rest:       "args",                         // $@
    Flags:      map[string]string{"--loop": "loop", "-f": "force"},
}
result := ctx.ParseArguments("deploy staging --loop 5", pattern)
// result.Vars["command"] = "deploy"
// result.Vars["target"] = "staging"
// result.Flags["--loop"] = "5"

// Simple positional argument parsing ($1, $2, $@)
args := ctx.SimpleParseArguments("deploy staging --force", 2)
// args[0] = "deploy staging --force" (full input)
// args[1] = "deploy" ($1)
// args[2] = "staging" ($2)
// args[3] = "--force" ($@)

// Evaluate model conditionals with wildcards
matches := ctx.EvaluateModelConditional("claude-*")  // bool
// Patterns: * matches any, ? matches single char, comma = OR

// Render content with <if-model> conditionals
content := `<if-model is="claude-*">Hi Claude<else>Hi there</if-model>`
rendered := ctx.RenderWithModelConditionals(content)  // based on current model
```

### Model Resolution

Resolve model fallback chains and query capabilities:

```go
// Resolve a chain of model preferences (tries each until available)
result := ctx.ResolveModelChain([]string{
    "anthropic/claude-opus-4",
    "anthropic/claude-sonnet-4",
    "openai/gpt-4o",
})
// result.Model (selected), result.Capabilities, result.Attempted, result.Error

// Get capabilities for a specific model
caps, err := ctx.GetModelCapabilities("anthropic/claude-sonnet-4")
// caps.Provider, caps.ModelID, caps.ContextLimit, caps.Reasoning, caps.Streaming

// Check if a model is available (provider exists)
available := ctx.CheckModelAvailable("anthropic/claude-sonnet-4")  // bool

// Get current provider/model ID
provider := ctx.GetCurrentProvider()  // "anthropic"
modelID := ctx.GetCurrentModelID()    // "claude-sonnet-4"
```
