docs(hooks): add plugin-style lifecycle hook examples
This commit is contained in:
parent
ebb28f5201
commit
ac905d478b
2 changed files with 98 additions and 0 deletions
10
README.md
10
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`).
|
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)
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary><b>Zhipu</b></summary>
|
<summary><b>Zhipu</b></summary>
|
||||||
|
|
||||||
|
|
|
||||||
88
docs/hooks-plugin-examples.md
Normal file
88
docs/hooks-plugin-examples.md
Normal file
|
|
@ -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.
|
||||||
Loading…
Add table
Reference in a new issue