# ClosedClaw Plugin System Hooks: Complete Guide to Agent Runtime Integration

> Explore the ClosedClaw plugin system hooks. Discover 17 lifecycle hooks for seamless agent runtime integration and modification of agent behavior.

- Repository: [aSafeLobotomy/closedclaw](https://github.com/asafelobotomy/closedclaw)
- Tags: deep-dive
- Published: 2026-02-25

---

**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`](https://github.com/asafelobotomy/closedclaw/blob/main/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`](https://github.com/asafelobotomy/closedclaw/blob/main/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`](https://github.com/asafelobotomy/closedclaw/blob/main/src/plugins/hooks.ts) wraps the internal hook engine ([`src/hooks/internal-hooks.ts`](https://github.com/asafelobotomy/closedclaw/blob/main/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`](https://github.com/asafelobotomy/closedclaw/blob/main/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`](https://github.com/asafelobotomy/closedclaw/blob/main/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`](https://github.com/asafelobotomy/closedclaw/blob/main/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`](https://github.com/asafelobotomy/closedclaw/blob/main/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

```typescript
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

```typescript
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

```typescript
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`](https://github.com/asafelobotomy/closedclaw/blob/main/src/plugins/types.ts).

## Key Source Files and Implementation Details

| File | Purpose |
|------|---------|
| **[`src/plugins/types.ts`](https://github.com/asafelobotomy/closedclaw/blob/main/src/plugins/types.ts)** | Defines `PluginHookName`, event payload types, and the `ClosedClawPluginApi` interface. [View on GitHub](https://github.com/asafelobotomy/closedclaw/blob/main/src/plugins/types.ts) |
| **[`src/plugins/hooks.ts`](https://github.com/asafelobotomy/closedclaw/blob/main/src/plugins/hooks.ts)** | Implements `HookRunner` with methods like `runBeforeAgentStart` and `runMessageSending` that orchestrate handler execution. [View on GitHub](https://github.com/asafelobotomy/closedclaw/blob/main/src/plugins/hooks.ts) |
| **[`src/hooks/internal-hooks.ts`](https://github.com/asafelobotomy/closedclaw/blob/main/src/hooks/internal-hooks.ts)** | Low-level registration (`registerInternalHook`) and dispatch (`triggerInternalHook`) for legacy compatibility. [View on GitHub](https://github.com/asafelobotomy/closedclaw/blob/main/src/hooks/internal-hooks.ts) |
| **[`src/plugins/loader.ts`](https://github.com/asafelobotomy/closedclaw/blob/main/src/plugins/loader.ts)** | Loads plugins, registers built-in hooks (ClawTalk, Kernel Shield), and creates the global HookRunner. [View on GitHub](https://github.com/asafelobotomy/closedclaw/blob/main/src/plugins/loader.ts) |
| **[`src/agents/pi-embedded-runner/run/attempt.ts`](https://github.com/asafelobotomy/closedclaw/blob/main/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](https://github.com/asafelobotomy/closedclaw/blob/main/src/agents/pi-embedded-runner/run/attempt.ts) |
| **[`src/auto-reply/reply/dispatch-from-config.ts`](https://github.com/asafelobotomy/closedclaw/blob/main/src/auto-reply/reply/dispatch-from-config.ts)** | Dispatches `message_received`, `message_sending`, and `message_sent` hooks during message processing. [View on GitHub](https://github.com/asafelobotomy/closedclaw/blob/main/src/auto-reply/reply/dispatch-from-config.ts) |
| **[`src/agents/pi-tools.before-tool-call.ts`](https://github.com/asafelobotomy/closedclaw/blob/main/src/agents/pi-tools.before-tool-call.ts)** | Wrapper invoking `before_tool_call` and `after_tool_call` hooks around tool execution. [View on GitHub](https://github.com/asafelobotomy/closedclaw/blob/main/src/agents/pi-tools.before-tool-call.ts) |
| **[`src/agents/session-tool-result-guard-wrapper.ts`](https://github.com/asafelobotomy/closedclaw/blob/main/src/agents/session-tool-result-guard-wrapper.ts)** | Synchronously runs `tool_result_persist` hooks during transcript building. [View on GitHub](https://github.com/asafelobotomy/closedclaw/blob/main/src/agents/session-tool-result-guard-wrapper.ts) |

## 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`](https://github.com/asafelobotomy/closedclaw/blob/main/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`](https://github.com/asafelobotomy/closedclaw/blob/main/src/agents/pi-tools.before-tool-call.ts) at lines 25-39, running before the actual tool implementation receives the call.