14. The Extension System
Once your agent is used by different teams, customization requests never stop: add an internal search tool, intercept dangerous commands, change the system prompt, show token cost in the status bar, save a report after a task completes. Merging all of these into the core would bloat the kernel. The goal of an extension system is to productize the points of customization.
An extension system is not a scripts directory
An extension system must provide at least three kinds of capability:
- Lifecycle hooks: listen to session, turn, model request, tool call, and shutdown events.
- Registration: register tools, commands, keyboard shortcuts, status displays, and renderers.
- Runtime context: access to cwd, the session, events, the UI, configuration, and safety APIs.
Extensions should influence the agent through controlled APIs, not by mutating internal objects directly. Otherwise a single extension can corrupt the log, the events, and the permission model.
Lifecycle hooks
A practical set of hooks might include:
session_start
before_agent_start
turn_start
before_model_request
after_model_response
tool_call
tool_result
session_before_compact
session_shutdown
Some hooks are purely notifications; others can block or rewrite. The semantics of each hook must be explicit. For example, tool_call may return deny to prevent tool execution, while turn_start is observation-only and may not rewrite messages. Permission-related hooks should fail closed by default: if an extension throws, it is better to block a dangerous operation than to proceed.
Registering tools
When an extension registers a tool, it must provide a definition of the same quality as a built-in tool: name, description, schema, execute function, output truncation, error semantics, and an optional UI renderer. A custom tool that writes files must participate in the same file-write queue; otherwise it will clobber the built-in edit/write tools and vice versa.
Tool descriptions must also enter the "available tools" section of the system prompt. Otherwise the model won't know when to use the extension tool. A description should name the tool explicitly — don't write "use this tool," because in a flattened prompt the model may not know what "this" refers to.
Extensions and safety
An extension is generally arbitrary code. Installing an extension means trusting it with every resource within the process's permissions. The product must tell users this fact and distinguish project extensions, user-global extensions, and built-in extensions. Untrusted projects should not auto-execute project extensions.
The extension API must also prevent bypassing the permission gate. If an extension registers a dangerous_shell tool, that tool must still pass through the tool call permission check. Otherwise a user who has disabled built-in bash gets bypassed by an extension tool.
The self-hosting test
The way to judge whether the extension API is good enough is to implement part of the product's own capabilities with it. For example:
- The status bar is registered through the extension API.
- Permission confirmation is implemented through the
tool_callhook. - Custom compaction is implemented through
session_before_compact. - Automatic checkpoints are implemented through
turn_startandsession_shutdown.
If any of these requirements forces a change to core code, the extension surface isn't complete enough yet.
Exercises
Implement a minimal extension system.
Acceptance criteria:
- An extension can register a read-only tool.
- An extension can deny a bash command before the tool call runs.
- An extension can append a tool guide to the system prompt.
- Extension errors become explicit events and are never silently swallowed.
- In an untrusted project, project extensions do not run automatically.