From ac905d478b86622931cc997b01d47623d08a5632 Mon Sep 17 00:00:00 2001 From: xj Date: Sun, 22 Feb 2026 02:09:59 -0800 Subject: [PATCH] docs(hooks): add plugin-style lifecycle hook examples --- README.md | 10 ++++ docs/hooks-plugin-examples.md | 88 +++++++++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+) create mode 100644 docs/hooks-plugin-examples.md diff --git a/README.md b/README.md index 6f5cce4d0..af1d9f793 100644 --- a/README.md +++ b/README.md @@ -1003,6 +1003,16 @@ PicoClaw routes providers by protocol family: This keeps the runtime lightweight while making new OpenAI-compatible backends mostly a config operation (`api_base` + `api_key`). +### Lifecycle Hooks (Plugin-style Extensions) + +PicoClaw provides typed lifecycle hooks for observability, outbound filtering, and tool guardrails. + +- Register hooks in Go at startup with `hooks.NewHookRegistry()`. +- Attach once via `agentLoop.SetHooks(registry)` before `Run()`. +- If hooks are not set, default behavior is unchanged. + +See runnable examples: [docs/hooks-plugin-examples.md](docs/hooks-plugin-examples.md) +
Zhipu diff --git a/docs/hooks-plugin-examples.md b/docs/hooks-plugin-examples.md new file mode 100644 index 000000000..d243e424d --- /dev/null +++ b/docs/hooks-plugin-examples.md @@ -0,0 +1,88 @@ +# Lifecycle Hooks: Plugin-Style Examples + +This guide shows how to extend PicoClaw behavior with `pkg/hooks` without modifying core agent logic. + +Current model: +- "Plugin-style" means registering Go handlers at startup. +- Hooks are in-process (no dynamic `.so` loading). +- If no hooks are registered, the runtime follows the normal zero-cost path. + +## Available Hooks + +| Hook | Type | Typical use | +|---|---|---| +| `message_received` | observe-only | inbound telemetry | +| `message_sending` | modifying + cancel | content filtering, safety policy | +| `before_tool_call` | modifying + cancel | tool allow/deny, arg rewriting | +| `after_tool_call` | observe-only | latency/error metrics | +| `llm_input` | observe-only | prompt size monitoring | +| `llm_output` | observe-only | response/tool-call telemetry | +| `session_start` | observe-only | session audit | +| `session_end` | observe-only | session cleanup metrics | + +## Quick Start + +```go +package main + +import ( + "context" + "strings" + + "github.com/sipeed/picoclaw/pkg/hooks" +) + +func buildHooks() *hooks.HookRegistry { + reg := hooks.NewHookRegistry() + + // 1) Guardrail: block shell tool globally. + reg.OnBeforeToolCall("block-shell", 100, func(_ context.Context, e *hooks.BeforeToolCallEvent) error { + if e.ToolName == "shell" { + e.Cancel = true + e.CancelReason = "shell tool is disabled by local policy" + } + return nil + }) + + // 2) Outbound filter: redact obvious API key patterns. + reg.OnMessageSending("redact-secrets", 50, func(_ context.Context, e *hooks.MessageSendingEvent) error { + e.Content = strings.ReplaceAll(e.Content, "sk-", "[redacted]-") + return nil + }) + + // 3) Telemetry: record tool latency or errors. + reg.OnAfterToolCall("tool-telemetry", 0, func(_ context.Context, e *hooks.AfterToolCallEvent) error { + // Send to metrics backend / logs as needed. + _ = e.ToolName + _ = e.Duration + _ = e.Result + return nil + }) + + return reg +} +``` + +Attach once during startup: + +```go +agentLoop := agent.NewAgentLoop(cfg, msgBus, provider) +agentLoop.SetHooks(buildHooks()) // Must be called before Run() +``` + +## Priority and Cancellation + +- Lower `priority` runs first. +- `message_sending` and `before_tool_call` are sequential and can cancel. +- Other hooks are observe-only and run concurrently. + +Recommended ordering: +- `0-49`: telemetry and logging +- `50-89`: transforms (redaction, normalization) +- `90+`: hard guardrails (block/cancel) + +## Safety Notes + +- Hook panics are recovered internally; one bad hook does not crash the loop. +- Hook errors are logged and execution continues unless `Cancel` is set. +- Keep hook handlers fast and non-blocking to avoid latency impact.