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 PluginHookBeforeAgentStartEvent and can return PluginHookBeforeAgentStartResult to modify the system prompt, tool allow-list, model selection, or prepend context. Defined at src/plugins/types.ts#L89-L100.

  • agent_end: Triggers after the run finishes, regardless of success, error, or abort status. Receives PluginHookAgentEndEvent containing messages, success flag, error details, and duration. Defined at src/plugins/types.ts#L102-L108.

Memory Management Hooks

  • before_compaction / after_compaction: Surround the transcript compaction process (token pruning). Receive PluginHookBeforeCompactionEvent and PluginHookAfterCompactionEvent respectively. Defined at src/plugins/types.ts#L110-L124.

Message Flow Hooks

  • message_received: Fires when an inbound user message is handed to the reply dispatcher. Receives PluginHookMessageReceivedEvent with sender, content, and metadata. Defined at src/plugins/types.ts#L126-L133.

  • message_sending: Executes just before the bot sends a reply, allowing modification or cancellation. Receives PluginHookMessageSendingEvent and can return PluginHookMessageSendingResult with new content or a cancel flag. Defined at src/plugins/types.ts#L135-L144.

  • message_sent: Triggers after the reply has been sent or failed. Receives PluginHookMessageSentEvent with recipient, content, success status, and error details. Defined at src/plugins/types.ts#L146-L152.

Tool Execution Hooks

  • before_tool_call: Fires immediately before a tool is invoked by the LLM. Receives PluginHookBeforeToolCallEvent and can return PluginHookBeforeToolCallResult to modify parameters or block execution. Defined at src/plugins/types.ts#L154-L162.

  • after_tool_call: Executes after a tool finishes or errors. Receives PluginHookAfterToolCallEvent. Defined at src/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 PluginHookToolResultPersistEvent and may return a new AgentMessage to replace the persisted entry. Defined at src/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 PluginHookSessionStartEvent and PluginHookSessionEndEvent. Defined at src/plugins/types.ts#L190-L203.

  • gateway_start / gateway_stop: Trigger when the HTTP gateway process starts or stops. Receive PluginHookGatewayStartEvent and PluginHookGatewayStopEvent. Defined at src/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

  1. Plugin Registration: Plugins register hooks via api.registerHook or the newer api.on method. The generic signature for api.on is defined in ClosedClawPluginApi at src/plugins/types.ts#L268-L274.

  2. Registry Storage: All registrations are stored in the global PluginRegistry within the typedHooks array. The plugin loader (src/plugins/loader.ts) builds this registry and instantiates the global HookRunner at src/plugins/loader.ts#L74-L77.

  3. HookRunner Creation: The HookRunner class in src/plugins/hooks.ts wraps the internal hook engine (src/hooks/internal-hooks.ts). It exposes async methods like runBeforeAgentStart, runMessageSending, and runBeforeToolCall, 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, the runEmbeddedAttempt function retrieves the global HookRunner and invokes runBeforeAgentStart at 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 runAgentEnd at 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.ts invokes message_received at lines 150-170, message_sending at lines 236-250, and message_sent after transmission.

  • Tool Execution: The wrapper in src/agents/pi-tools.before-tool-call.ts calls runBeforeToolCall at lines 25-39, allowing parameter modification or blocking, followed by runAfterToolCall after completion.

  • Transcript Persistence: src/agents/session-tool-result-guard-wrapper.ts runs runToolResultPersist synchronously at lines 26-33 during transcript building.

Execution Semantics

The HookRunner handles execution semantics internally:

  • Parallel vs. Sequential: Handlers for events like agent_end run in parallel (fire-and-forget), while hooks like before_agent_start run sequentially to allow result merging.

  • Priority System: The api.on method accepts a priority option (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 in src/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_start through agent_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 PluginRegistry and exposing typed methods like runBeforeAgentStart that 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.on method 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 (like agent_end), and critical paths like tool_result_persist run 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →