Event-Driven Hooks

+15 Mana ✨

Introduction

OpenClaw provides a powerful event-driven hook system that lets you extend and automate agent behavior at key lifecycle points. Hooks respond to specific events such as commands, agent bootstrap, gateway startup, and message flow, giving you fine-grained control over your automation workflows.

Key Concepts

  • Event Types: Categories of events that hooks can subscribe to, including command, agent, gateway, message, and plugin.
  • Hook Discovery: The ordered process by which OpenClaw locates and loads hooks from workspace, managed, and bundled directories.
  • HookHandler: The async function signature that every hook must export to process events.
  • Event Object: The data structure passed to each handler containing type, action, session context, and messages.

Real World Context

In production environments, event-driven architectures are foundational to tools like GitHub Actions, Git hooks, and CI/CD pipelines. OpenClaw hooks follow the same philosophy: rather than polling for changes, your code reacts to specific lifecycle events. This pattern keeps automation composable, testable, and easy to reason about.

Deep Dive

OpenClaw defines five categories of events, each with specific actions:

typescript
// Command events fire when the user issues CLI commands
// Actions: new, reset, stop
{ type: 'command', action: 'new' }
{ type: 'command', action: 'reset' }
{ type: 'command', action: 'stop' }

// Agent events fire during agent lifecycle
// Action: bootstrap
{ type: 'agent', action: 'bootstrap' }

// Gateway events fire when the server starts
// Action: startup
{ type: 'gateway', action: 'startup' }

// Message events fire during conversation flow
// Actions: received, sent
{ type: 'message', action: 'received' }
{ type: 'message', action: 'sent' }

// Plugin events fire for tool interactions
// Action: tool_result_persist
{ type: 'plugin', action: 'tool_result_persist' }

The code above shows every event type and its available actions. The command type covers CLI interactions, agent and gateway handle lifecycle moments, message tracks conversation flow, and plugin captures tool results.

When OpenClaw needs to run hooks, it searches three locations in a specific priority order:

text
1. Workspace hooks:   ~workspace/hooks/
2. Managed hooks:     ~/.openclaw/hooks/
3. Bundled hooks:     dist/hooks/bundled/

This discovery order means workspace-level hooks take highest priority, allowing project-specific overrides. Managed hooks apply across all your projects, and bundled hooks ship with OpenClaw as defaults.

Every hook must export a HookHandler function. Here is the type signature and an example:

typescript
// The HookHandler type defines the contract for all hooks
type HookHandler = (event: HookEvent) => Promise<void>;

// The event object carries all context your hook needs
interface HookEvent {
  type: string;        // e.g. 'command', 'agent', 'message'
  action: string;      // e.g. 'new', 'bootstrap', 'received'
  sessionKey: string;  // unique identifier for the current session
  timestamp: number;   // Unix timestamp of the event
  messages: Message[]; // conversation messages at time of event
  context: Record<string, unknown>; // additional metadata
}

// Example: a hook that logs every new command
const handler: HookHandler = async (event) => {
  if (event.type === 'command' && event.action === 'new') {
    console.log(`New session started: ${event.sessionKey}`);
    console.log(`Timestamp: ${new Date(event.timestamp).toISOString()}`);
  }
};

export default handler;

The handler receives the full event object, checks the type and action to decide whether to act, and then performs its logic asynchronously. The sessionKey identifies which session triggered the event, while messages gives access to the conversation history and context provides any extra metadata.

Common Pitfalls

  • Ignoring discovery order: Placing a hook in ~/.openclaw/hooks/ when a workspace-level hook with the same name exists will cause the managed hook to be silently skipped.
  • Blocking the event loop: Since handlers are async, performing heavy synchronous operations inside them will block other hooks and degrade performance.
  • Not filtering by action: Subscribing to an event type without checking the action field can cause your hook to fire on unintended triggers.

Best Practices

  • Always filter on both event.type and event.action to ensure your hook only runs when intended.
  • Keep hook handlers focused on a single responsibility; compose multiple hooks rather than building monolithic ones.
  • Use the context field to pass data between hooks in the same event cycle rather than relying on global state.

Summary

  • OpenClaw hooks respond to five event types: command, agent, gateway, message, and plugin.
  • Hook discovery follows a priority chain: workspace, then managed, then bundled.
  • Every hook exports an async HookHandler function that receives a structured event object.
  • The event object provides type, action, sessionKey, timestamp, messages, and context.
  • Filtering on both type and action ensures hooks only fire for the intended events.
✓ Completed