Skip to content

Hook Lifecycle (v2.1.0)

Overview

4 hooks, each registering a single event via api.on() in index.ts. All scanning is live against AIRS — no caching. Each hook is boolean-gated by its config toggle.

Hook Events

Event Handler Async? Can Block? Config Toggle
before_prompt_build prompt-guard Yes Via context injection prompt_scanning
message_sending response-guard Yes Yes — replaces content response_scanning
before_tool_call tool-input-guard Yes Yes — { block: true } tool_protection
after_tool_call tool-output-audit Yes No (fire-and-forget) tool_protection

Hook Details

prompt-guard (before_prompt_build)

Scans the latest user message via scan({ prompt }). On action === "allow", returns void. On any other action, returns { prependSystemContext: warning } with a category-specific refusal directive. On scan error + fail_closed, injects a generic refusal. On scan error + fail_open, returns void.

response-guard (message_sending)

Scans the assistant response via scan({ response }). On action === "allow", returns void. For DLP-only violations with dlp_mask_only=true, masks content via maskSensitiveData() regex patterns (SSN, credit cards, emails, API keys, AWS keys, phone numbers, private IPs). For all other non-allow actions, returns { content: blockMessage }. On scan error + fail_closed, blocks. On scan error + fail_open, returns void.

tool-input-guard (before_tool_call)

Scans tool inputs via scan({ toolEvents: [{ metadata, input }] }) using the SDK's toolEvent content type. Metadata includes ecosystem: "mcp", method: "tool_call", serverName, and toolInvoked. On action === "allow", returns void. On any other action, returns { block: true, blockReason }. On scan error + fail_closed, blocks. On scan error + fail_open, returns void.

tool-output-audit (after_tool_call)

Fire-and-forget scan of tool outputs via scan({ response, toolEvents: [{ metadata, output }] }). Logs structured JSON audit events. Cannot block — after_tool_call is async void. Tool outputs are indirectly covered by response-guard when they flow into the assistant response. Errors are caught and logged, never thrown.

Execution Order

User types message
  → before_prompt_build          ← prompt-guard (inject refusal if not allow)
  → LLM processes
  → before_tool_call             ← tool-input-guard (block if not allow)
  → tool executes
  → after_tool_call              ← tool-output-audit (fire-and-forget)
  → message_sending              ← response-guard (replace content if not allow)
  → User sees response

Registration

const hookCtx = (ctx: any) => ({ ...ctx, cfg: api.config });
let hookCount = 0;

if (config.prompt_scanning) {
  hookCount += registerPromptGuardHooks(api, hookCtx);
}
if (config.response_scanning) {
  hookCount += registerResponseGuardHooks(api, hookCtx);
}
if (config.tool_protection) {
  hookCount += registerToolInputGuardHooks(api, hookCtx);
  hookCount += registerToolOutputAuditHooks(api, hookCtx);
}

All handlers receive hookCtx and must call it on ctx before reading config: getConfig(hookCtx(ctx)). This wraps the OpenClaw context with { cfg: api.config } so handlers can access plugin config.

Config

Key Type Default Controls
api_key string AIRS API key
profile_name string AIRS security profile
app_name string "openclaw" App identifier in scans
fail_closed boolean true Block on scan errors
dlp_mask_only boolean true Mask DLP violations instead of blocking
prompt_scanning boolean true Enable prompt-guard
response_scanning boolean true Enable response-guard
tool_protection boolean true Enable tool-input-guard + tool-output-audit