ClosedClaw Plugin System Hooks: Complete Guide to Agent Runtime Integration
ClosedClaw provides a typed, event-driven hook system with 17 distinct lifecycle hooks that allow plugins to intercept and modify agent behavior at specific points in the runtime execution.
The ClosedClaw repository implements a comprehensive plugin architecture that decouples custom logic from core agent operations. By leveraging the plugin system hooks, developers can intercept session initialization, message flows, tool executions, and gateway lifecycle events without modifying the underlying runtime code.
Complete List of ClosedClaw Plugin Hooks
The authoritative enumeration of available hooks is defined in src/plugins/types.ts within the PluginHookName type. These hooks cover the full spectrum of agent operations:
Agent Lifecycle Hooks
-
before_agent_start: Fires immediately after the embedder creates a session but before the LLM receives its first prompt. Receives
PluginHookBeforeAgentStartEventand can returnPluginHookBeforeAgentStartResultto modify the system prompt, tool allow-list, model selection, or prepend context. Defined atsrc/plugins/types.ts#L89-L100. -
agent_end: Triggers after the run finishes, regardless of success, error, or abort status. Receives
PluginHookAgentEndEventcontaining messages, success flag, error details, and duration. Defined atsrc/plugins/types.ts#L102-L108.
Memory Management Hooks
- before_compaction / after_compaction: Surround the transcript compaction process (token pruning). Receive
PluginHookBeforeCompactionEventandPluginHookAfterCompactionEventrespectively. Defined atsrc/plugins/types.ts#L110-L124.
Message Flow Hooks
-
message_received: Fires when an inbound user message is handed to the reply dispatcher. Receives
PluginHookMessageReceivedEventwith sender, content, and metadata. Defined atsrc/plugins/types.ts#L126-L133. -
message_sending: Executes just before the bot sends a reply, allowing modification or cancellation. Receives
PluginHookMessageSendingEventand can returnPluginHookMessageSendingResultwith new content or a cancel flag. Defined atsrc/plugins/types.ts#L135-L144. -
message_sent: Triggers after the reply has been sent or failed. Receives
PluginHookMessageSentEventwith recipient, content, success status, and error details. Defined atsrc/plugins/types.ts#L146-L152.
Tool Execution Hooks
-
before_tool_call: Fires immediately before a tool is invoked by the LLM. Receives
PluginHookBeforeToolCallEventand can returnPluginHookBeforeToolCallResultto modify parameters or block execution. Defined atsrc/plugins/types.ts#L154-L162. -
after_tool_call: Executes after a tool finishes or errors. Receives
PluginHookAfterToolCallEvent. Defined atsrc/plugins/types.ts#L164-L171. -
tool_result_persist: Runs synchronously when a tool result is about to be written into the transcript (hot path). Receives
PluginHookToolResultPersistEventand may return a newAgentMessageto replace the persisted entry. Defined atsrc/plugins/types.ts#L173-L188.
Infrastructure Hooks
-
session_start / session_end: Fire when a session is created or closed across gateway, UI, or other interfaces. Receive
PluginHookSessionStartEventandPluginHookSessionEndEvent. Defined atsrc/plugins/types.ts#L190-L203. -
gateway_start / gateway_stop: Trigger when the HTTP gateway process starts or stops. Receive
PluginHookGatewayStartEventandPluginHookGatewayStopEvent. Defined atsrc/plugins/types.ts#L205-L218.
How Hooks Interface with the Agent Runtime
The plugin system and agent runtime maintain loose coupling through a centralized HookRunner architecture. Plugins register callbacks through the API, while the runtime invokes these hooks at specific execution points without direct knowledge of individual plugins.
The Hook Registration Flow
-
Plugin Registration: Plugins register hooks via
api.registerHookor the newerapi.onmethod. The generic signature forapi.onis defined inClosedClawPluginApiatsrc/plugins/types.ts#L268-L274. -
Registry Storage: All registrations are stored in the global
PluginRegistrywithin thetypedHooksarray. The plugin loader (src/plugins/loader.ts) builds this registry and instantiates the global HookRunner atsrc/plugins/loader.ts#L74-L77. -
HookRunner Creation: The
HookRunnerclass insrc/plugins/hooks.tswraps the internal hook engine (src/hooks/internal-hooks.ts). It exposes async methods likerunBeforeAgentStart,runMessageSending, andrunBeforeToolCall, each gathering type-specific handlers from the registry.
Runtime Integration Points
The agent runtime invokes the HookRunner at critical execution moments:
-
Agent Initialization: In
src/agents/pi-embedded-runner/run/attempt.ts, therunEmbeddedAttemptfunction retrieves the global HookRunner and invokesrunBeforeAgentStartat lines 209-229. The result can modify the tool allow-list, override the model, or prepend context to the prompt. -
Agent Termination: The same file calls
runAgentEndat lines 831-848 after the session finishes, regardless of success or failure. -
Message Dispatch: The reply dispatcher in
src/auto-reply/reply/dispatch-from-config.tsinvokesmessage_receivedat lines 150-170,message_sendingat lines 236-250, andmessage_sentafter transmission. -
Tool Execution: The wrapper in
src/agents/pi-tools.before-tool-call.tscallsrunBeforeToolCallat lines 25-39, allowing parameter modification or blocking, followed byrunAfterToolCallafter completion. -
Transcript Persistence:
src/agents/session-tool-result-guard-wrapper.tsrunsrunToolResultPersistsynchronously at lines 26-33 during transcript building.
Execution Semantics
The HookRunner handles execution semantics internally:
-
Parallel vs. Sequential: Handlers for events like
agent_endrun in parallel (fire-and-forget), while hooks likebefore_agent_startrun sequentially to allow result merging. -
Priority System: The
api.onmethod accepts apriorityoption (default 1000). Higher priority values execute first, allowing plugins to control execution order relative to built-in hooks like ClawTalk (priority 1000). -
Result Merging: For hooks that return values (like
before_agent_start), the HookRunner merges results from multiple handlers, with later results potentially overriding earlier ones depending on the specific merge strategy implemented insrc/plugins/hooks.ts#L183-L255.
Registering Hooks in Your Plugin
Plugin authors interact with the hook system through the ClosedClawPluginApi interface. Here are practical implementations:
Classic Registration with registerHook
export const myPlugin: ClosedClawPluginDefinition = {
id: "my-plugin",
name: "My Demo Plugin",
version: "0.1.0",
register: (api) => {
// Listen to every command the LLM issues
api.registerHook("message_sending", async (event, ctx) => {
// Rewrite outgoing messages that contain a secret token
if (event.content.includes("SUPER_SECRET")) {
return { content: "[redacted]" };
}
});
// Run after a tool finishes
api.registerHook("after_tool_call", async (event) => {
api.logger.info?.(`Tool ${event.toolName} completed`);
});
},
};
The registerHook method signature is defined at src/plugins/types.ts#L254-L263.
Modern Registration with api.on
export const myPlugin = {
id: "my-plugin",
register: (api) => {
// High-priority before_agent_start hook to prepend a disclaimer
api.on("before_agent_start", async (ev, ctx) => {
return { prependContext: "[Disclaimer] This session is monitored." };
}, { priority: 1200 }); // runs before the built-in ClawTalk hook (1000)
},
};
The generic signature for api.on is api.on<K extends PluginHookName>(hookName: K, handler: PluginHookHandlerMap[K], opts?) at src/plugins/types.ts#L268-L274.
Accessing Hook Context
api.on("session_start", (event, ctx) => {
api.logger.info?.(
`Session ${event.sessionId} started in workspace ${ctx.workspaceDir}`
);
});
The ctx argument provides typed context (PluginHookSessionContext, PluginHookGatewayContext, etc.) defined under PluginHook*Context in src/plugins/types.ts.
Key Source Files and Implementation Details
| File | Purpose |
|---|---|
src/plugins/types.ts |
Defines PluginHookName, event payload types, and the ClosedClawPluginApi interface. View on GitHub |
src/plugins/hooks.ts |
Implements HookRunner with methods like runBeforeAgentStart and runMessageSending that orchestrate handler execution. View on GitHub |
src/hooks/internal-hooks.ts |
Low-level registration (registerInternalHook) and dispatch (triggerInternalHook) for legacy compatibility. View on GitHub |
src/plugins/loader.ts |
Loads plugins, registers built-in hooks (ClawTalk, Kernel Shield), and creates the global HookRunner. View on GitHub |
src/agents/pi-embedded-runner/run/attempt.ts |
Runtime entry point invoking before_agent_start and agent_end hooks during agent execution. View on GitHub |
src/auto-reply/reply/dispatch-from-config.ts |
Dispatches message_received, message_sending, and message_sent hooks during message processing. View on GitHub |
src/agents/pi-tools.before-tool-call.ts |
Wrapper invoking before_tool_call and after_tool_call hooks around tool execution. View on GitHub |
src/agents/session-tool-result-guard-wrapper.ts |
Synchronously runs tool_result_persist hooks during transcript building. View on GitHub |
Summary
- ClosedClaw exposes 17 typed plugin hooks covering the complete agent lifecycle from
before_agent_startthroughagent_end, including message flows, tool execution, compaction events, and gateway lifecycle. - The HookRunner architecture decouples plugins from runtime logic by storing registrations in a global
PluginRegistryand exposing typed methods likerunBeforeAgentStartthat the agent calls at specific execution points. - Hooks support priority-based execution allowing plugins to control ordering relative to built-in hooks like ClawTalk (priority 1000) using the
api.onmethod with priority options. - Execution semantics vary by hook type: some run sequentially with result merging (like
before_agent_start), others fire-and-forget in parallel (likeagent_end), and critical paths liketool_result_persistrun synchronously on the hot path.
Frequently Asked Questions
What is the difference between registerHook and api.on in ClosedClaw?
Both methods register plugin hooks, but api.on provides modern priority support while registerHook uses the classic callback style. The api.on method accepts a generic hook name and an options object with a priority field (default 1000), allowing plugins to execute before or after built-in hooks like ClawTalk. The registerHook method is defined at src/plugins/types.ts#L254-L263, while api.on appears at src/plugins/types.ts#L268-L274.
Can multiple plugins modify the same hook result, and how are conflicts resolved?
Yes, multiple plugins can register handlers for the same hook, and ClosedClaw handles this through result merging strategies implemented in the HookRunner. For hooks like before_agent_start that return values, handlers run sequentially and results are merged, with later results potentially overriding earlier ones depending on the specific merge logic in src/plugins/hooks.ts#L183-L255. For fire-and-forget hooks like agent_end, handlers execute in parallel without result merging.
Which hooks run synchronously versus asynchronously in the ClosedClaw runtime?
Most hooks run asynchronously, but tool_result_persist is a critical exception that executes synchronously on the hot path during transcript building. This hook fires in src/agents/session-tool-result-guard-wrapper.ts and must complete before the tool result is written to the transcript. Asynchronous hooks like message_sending or before_tool_call allow for network requests or file I/O without blocking the main execution thread.
How do I block or modify a tool call using the ClosedClaw plugin system?
Use the before_tool_call hook to intercept tool invocations before they execute. This hook receives a PluginHookBeforeToolCallEvent and can return a PluginHookBeforeToolCallResult containing modified parameters or a block flag to prevent execution entirely. The hook is invoked from src/agents/pi-tools.before-tool-call.ts at lines 25-39, running before the actual tool implementation receives the call.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →