In the previous chapter, Input State Management, we learned how the system handles what the user types. But what if the user wants the agent to do something it wasn't originally programmed to do?
This chapter introduces Extensibility.
Think about your web browser. Out of the box, it can browse the web. But if you want to block ads, manage passwords, or check your grammar, you install Extensions.
We apply the same logic here:
If you wanted to build a feature that says, "Stop the agent if it tries to commit code without running tests first," you wouldn't rewrite the agent's source code. You would write a Plugin with a Hook.
The system separates the definition of a plugin from the logic that runs when events happen.
Just like a shipping manifest lists what is inside a container, a Plugin Manifest tells the system what is inside the plugin. Does it have new commands? Does it have specialized tools (MCP Servers)?
// From plugin.ts
export type LoadedPlugin = {
name: string // e.g., "git-safety-check"
manifest: PluginManifest
enabled?: boolean // Is it turned on?
// What does this plugin provide?
hooksConfig?: HooksSettings
mcpServers?: Record<string, McpServerConfig>
}
LoadedPlugin as the installation record. It tells the system: "The 'git-safety-check' plugin is installed, enabled, and located at this folder path."A Hook is an event listener. It waits for a specific moment in the agent's lifecycle.
Common events (defined in hooks.ts) include:
SessionStart: The user just opened the app.PreToolUse: The agent is about to run a command (e.g., write_file).PostToolUse: The agent just finished running a command.UserPromptSubmit: The user just hit "Enter".When a Hook triggers, it must give an answer back to the main system. It can say:
Let's imagine a plugin that prevents the agent from editing .env files (which contain secrets) without explicit permission, even if the global safety settings usually allow edits.
The plugin registers a hook for PreToolUse. This event fires before the tool runs.
The hook receives the input:
Agent is trying to use
write_fileon.env
The hook logic checks the filename. It sees .env.
The hook returns a specific object to block the action.
// Conceptual response object
{
hookEventName: 'PreToolUse',
permissionDecision: 'deny', // Block it!
permissionDecisionReason: 'Plugins are not allowed to touch .env files'
}
The agent receives this "Deny" signal. Instead of writing the file, it sees an error: "Action blocked by plugin: Plugins are not allowed to touch .env files."
How does the system ensure plugins don't freeze the application? It uses a strict schema for communication.
Let's look at the actual types in hooks.ts.
Because plugins might be written in other languages or run in different processes, we use Zod (a validation library) to ensure the data they send back is perfect.
// From hooks.ts
export const syncHookResponseSchema = lazySchema(() =>
z.object({
// Can we proceed?
decision: z.enum(['approve', 'block']).optional(),
// If we block, why?
reason: z.string().optional(),
// Updates to permissions
permissionDecision: permissionBehaviorSchema().optional(),
})
)
dicision instead of decision), the system rejects it immediately to prevent crashes.
Since you might have 10 plugins installed, the system has to collect answers from all of them. This is the AggregatedHookResult.
export type AggregatedHookResult = {
// Did any plugin say "Stop"?
preventContinuation?: boolean
stopReason?: string
// Did any plugin add new data to the prompt?
additionalContexts?: string[]
// Did any plugin change the input arguments?
updatedInput?: Record<string, unknown>
}
preventContinuation flag will be set to true. Safety always wins.
Hooks aren't just for blocking. They can be helpful! A SessionStart hook might check your project folder and say:
{
hookEventName: 'SessionStart',
additionalContext: 'NOTICE: This project uses Python 2.7. Be careful.'
}
The system takes this string and secretly whispers it to the Agent so it knows how to behave, without the user having to type it manually.
Extensibility turns our agent from a static tool into a platform.
LoadedPlugin).PreToolUse, SessionStart).hookJSONOutputSchema).By using this architecture, developers can build tools that inspect, modify, and improve the agent's workflow without ever touching the core source code.
This concludes the core logic of the agent. But how do we know if the agent is working correctly? How do we track errors across all these plugins and sessions?
Next Chapter: Telemetry & Event Contracts
Generated by Code IQ