✍️ Blog Post

Hook Patterns: Quality Validation, Context Injection, and More

16 min read

Hooks are OpenClaw's event system. They run when commands fire, sessions start, or the gateway boots. Here are the patterns I use in production.

What Are Hooks?

Hooks are small TypeScript functions that run in response to events. They let you customize OpenClaw behavior without modifying core code.

Event Types:

  • Command events: /new, /reset, /stop
  • Agent events: agent:bootstrap
  • Gateway events: gateway:startup

Pattern 1: Session Memory

Save conversation context when you reset a session. This is my most-used hook.

Use case: I issue /new to start fresh. But I want to search past conversations later.

How it works:

  1. User sends /new
  2. command:new event fires
  3. session-memory hook extracts last 15 lines of conversation
  4. Uses LLM to generate descriptive filename slug
  5. Saves to ~/.openclaw/workspace/memory/YYYY-MM-DD-slug.md

Example output:

# Session: 2026-01-16 14:30:00 UTC

- **Session Key**: agent:main:main
- **Session ID**: abc123
- **Source**: telegram

## Last 15 lines
[conversation excerpt]

Configuration:

"hooks": {
  "internal": {
    "enabled": true,
    "entries": {
      "session-memory": {
        "enabled": true
      }
    }
  }
}

Pattern 2: Command Auditing

Log every command to a JSONL file for debugging and compliance.

Use case: When something goes wrong, I need to know what commands were issued and when.

Implementation:

const handler: HookHandler = async (event) => {
  if (event.type !== 'command') return;
  
  const logEntry = {
    timestamp: event.timestamp.toISOString(),
    action: event.action,
    sessionKey: event.sessionKey,
    senderId: event.context.senderId,
    source: event.context.commandSource
  };
  
  await fs.appendFile(
    '~/.openclaw/logs/commands.log',
    JSON.stringify(logEntry) + '\n'
  );
};

Log output:

{"timestamp":"2026-02-08T14:30:00.000Z","action":"new","sessionKey":"agent:main:main","senderId":"+1234567890","source":"telegram"}
{"timestamp":"2026-02-08T15:45:22.000Z","action":"stop","sessionKey":"agent:main:main","senderId":"user@example.com","source":"whatsapp"}

Query the log:

# View recent commands
tail -n 20 ~/.openclaw/logs/commands.log | jq .

# Filter by action
grep '"action":"new"' ~/.openclaw/logs/commands.log | jq .

Pattern 3: Bootstrap File Injection

Modify which files get injected into the agent's context at session start.

Use case: During a "purge window" (9pm-9:15pm), swap SOUL.md with SOUL_EVIL.md for personality changes.

Implementation:

const handler: HookHandler = async (event) => {
  if (event.type !== 'agent' || event.action !== 'bootstrap') return;
  
  const now = new Date();
  const hour = now.getHours();
  const minute = now.getMinutes();
  
  // Purge window: 21:00-21:15
  const inPurgeWindow = hour === 21 && minute < 15;
  
  if (inPurgeWindow) {
    const soulIndex = event.context.bootstrapFiles?.findIndex(
      f => f.name === 'SOUL.md'
    );
    
    if (soulIndex !== -1) {
      event.context.bootstrapFiles[soulIndex] = {
        name: 'SOUL.md',
        content: await fs.readFile('SOUL_EVIL.md', 'utf-8')
      };
    }
  }
};

Configuration:

"hooks": {
  "internal": {
    "enabled": true,
    "entries": {
      "soul-evil": {
        "enabled": true,
        "chance": 0.1,
        "purge": {
          "at": "21:00",
          "duration": "15m"
        }
      }
    }
  }
}

Pattern 4: Startup Automation

Run tasks when the gateway starts.

Use case: Send myself a morning briefing when the gateway boots.

BOOT.md example:

# Morning Boot Routine

1. Check weather for San Francisco
2. List unread emails from past 12 hours
3. Check GitHub notifications
4. Send summary to the operator via Telegram

If any critical issues (failed CI, urgent emails), send alert.

How it works:

  1. Gateway starts
  2. gateway:startup event fires
  3. boot-md hook reads BOOT.md
  4. Runs instructions via agent runner
  5. Sends messages via message tool

Pattern 5: Quality Validation

Validate agent output before it's sent to the user.

Use case: Prevent sending email drafts that don't include required fields.

Implementation sketch:

const handler: HookHandler = async (event) => {
  if (event.type !== 'tool_result') return;
  
  // Check if this is an email send
  if (event.toolName === 'gog' && event.params.includes('gmail send')) {
    // Validate email has subject and body
    if (!event.params.includes('--subject') || 
        !event.params.includes('--body')) {
      event.messages.push(
        '⚠️ Email validation failed: Missing subject or body'
      );
      return; // Don't send
    }
  }
};

Pattern 6: Context Injection

Add dynamic context to every session based on external state.

Use case: Inject current time, weather, and calendar events at session start.

Implementation:

const handler: HookHandler = async (event) => {
  if (event.type !== 'agent' || event.action !== 'bootstrap') return;
  
  const weather = await getWeather('San Francisco');
  const events = await getTodayCalendarEvents();
  
  const contextFile = {
    name: '_DYNAMIC_CONTEXT.md',
    content: `# Dynamic Context

**Current Time**: ${new Date().toISOString()}
**Weather**: ${weather}
**Today's Events**: ${events.length} events
`
  };
  
  event.context.bootstrapFiles?.push(contextFile);
};

Creating Custom Hooks

Step 1: Create hook directory

mkdir -p ~/.openclaw/hooks/my-hook

Step 2: Create HOOK.md

---
name: my-hook
description: "Does something useful"
metadata: { "openclaw": { "emoji": "🎯", "events": ["command:new"] } }
---

# My Custom Hook

This hook does something useful when you issue `/new`.

Step 3: Create handler.ts

import type { HookHandler } from "../../src/hooks/hooks.js";
import ProductCTA from "@/components/ProductCTA";
import EmailCapture from "@/components/EmailCapture";

const handler: HookHandler = async (event) => {
  if (event.type !== 'command' || event.action !== 'new') {
    return;
  }

  console.log('[my-hook] Running!');
  // Your logic here
};

export default handler;

Step 4: Enable hook

openclaw hooks enable my-hook

Step 5: Restart gateway

Best Practices

1. Keep Handlers Fast

Hooks run during command processing. Don't block:

// ✓ Good - fire and forget
const handler: HookHandler = async (event) => {
  void processInBackground(event);
};

// ✗ Bad - blocks command processing
const handler: HookHandler = async (event) => {
  await slowDatabaseQuery(event);
};

2. Handle Errors Gracefully

const handler: HookHandler = async (event) => {
  try {
    await riskyOperation(event);
  } catch (err) {
    console.error('[my-hook] Failed:', err);
    // Don't throw - let other handlers run
  }
};

3. Filter Events Early

const handler: HookHandler = async (event) => {
  // Return early if not relevant
  if (event.type !== 'command' || event.action !== 'new') {
    return;
  }
  
  // Your logic here
};

The Bottom Line

Hooks are how you make OpenClaw yours. The bundled hooks (session-memory, command-logger, boot-md) cover common cases. But custom hooks let you build exactly what you need.

Start with the bundled hooks. Enable session-memory and command-logger. Then build custom hooks as needs arise. And remember: hooks are event-driven. They react. They don't initiate.

Continue Learning

Ready to build?

Get the OpenClaw Starter Kit — config templates, 5 production-ready skills, deployment checklist. Go from zero to running in under an hour.

$14 $6.99

Get the Starter Kit →

Also in the OpenClaw store

🗂️
Executive Assistant Config
Buy
Calendar, email, daily briefings on autopilot.
$6.99
🔍
Business Research Pack
Buy
Competitor tracking and market intelligence.
$5.99
Content Factory Workflow
Buy
Turn 1 post into 30 pieces of content.
$6.99
📬
Sales Outreach Skills
Buy
Automated lead research and personalized outreach.
$5.99

Get the free OpenClaw quickstart guide

Step-by-step setup. Plain English. No jargon.