-
Notifications
You must be signed in to change notification settings - Fork 2.5k
feat(hooks/plugin): add lifecycle hooks + phase-1 plugin contract #473
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
f73fb27
f26d207
873ebd1
8d4cbd7
676f50f
50d6562
8aa0a30
274f0f7
cd0ea88
b213613
96097ff
2c144ff
cad5804
7be229d
76efd6c
e3094ec
23f909f
dae1228
116904c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,138 @@ | ||||||||||||||
| # Lifecycle Hooks: Plugin-Style Examples | ||||||||||||||
|
|
||||||||||||||
| This guide shows how to extend PicoClaw behavior with `pkg/hooks` without modifying core agent logic. | ||||||||||||||
|
|
||||||||||||||
| For future direction (beyond current hooks foundation), see [Plugin System Roadmap](plugin-system-roadmap.md). | ||||||||||||||
|
|
||||||||||||||
| 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. | ||||||||||||||
|
|
||||||||||||||
| ## How Plugin Works | ||||||||||||||
|
|
||||||||||||||
| PicoClaw's plugin model is a startup-time hook registry: | ||||||||||||||
|
|
||||||||||||||
| 1. Build a registry (`hooks.NewHookRegistry()`). | ||||||||||||||
| 2. Register one or more handlers per lifecycle hook with priority. | ||||||||||||||
| 3. Attach once with `agentLoop.SetHooks(registry)` before `agentLoop.Run(...)` (check error). | ||||||||||||||
| 4. Agent loop triggers hook handlers at specific lifecycle points. | ||||||||||||||
|
|
||||||||||||||
| Execution semantics: | ||||||||||||||
|
|
||||||||||||||
| - Observe-only hooks (`message_received`, `after_tool_call`, `llm_input`, `llm_output`, `session_start`, `session_end`) | ||||||||||||||
| - run concurrently | ||||||||||||||
| - cannot block core behavior | ||||||||||||||
|
||||||||||||||
| - cannot block core behavior | |
| - cannot cancel or modify core behavior (observe-only only) | |
| - may still add latency because handlers are awaited by the agent loop |
Copilot
AI
Feb 23, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The lifecycle map shows a linear flow, but in reality, llm_input, llm_output, before_tool_call, and after_tool_call can occur multiple times in an iteration loop (up to MaxToolIterations). Consider adding a note about iteration/looping behavior to avoid confusion for plugin developers who need to understand when their hooks will fire.
| Note: The map above is shown as a single linear pass for readability. In practice, the | |
| agent loop may iterate up to `MaxToolIterations`, and the following hooks can fire | |
| multiple times within a single overall lifecycle: | |
| `llm_input`, `llm_output`, `before_tool_call`, and `after_tool_call`. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,108 @@ | ||
| # Plugin System Roadmap | ||
|
|
||
| This document defines how PicoClaw evolves from hook-based extension points to a fuller plugin system in low-risk phases. | ||
|
|
||
| ## Current Status (Phase 0: Foundation) | ||
|
|
||
| Implemented in current hooks PR: | ||
|
|
||
| - Typed lifecycle hooks (`pkg/hooks`) | ||
| - Priority-based handler ordering | ||
| - Cancellation support for modifying hooks | ||
| - Panic recovery and error isolation | ||
| - Agent-loop integration via `agentLoop.SetHooks(...)` | ||
|
|
||
| Compatibility: | ||
|
|
||
| - If no hooks are registered, runtime behavior is unchanged. | ||
| - No config migration is required. | ||
|
|
||
| ## Non-Goals in Phase 0 | ||
|
|
||
| - No dynamic runtime plugin loading | ||
| - No remote plugin marketplace/distribution | ||
| - No plugin sandboxing model | ||
| - No stable external plugin ABI yet | ||
| - No Go `.so` plugin loading as default direction | ||
|
|
||
| ## Phase Plan | ||
|
|
||
| ## Phase 1: Static Plugin Contract (Compile-time) — Implemented | ||
|
|
||
| Goal: define a minimal public plugin contract for Go modules. | ||
|
|
||
| Implemented: | ||
|
|
||
| - Add `pkg/plugin` with a small interface: | ||
| - `Name() string` | ||
| - `APIVersion() string` | ||
| - `Register(*hooks.HookRegistry) error` | ||
| - Register plugins at startup in code. | ||
| - Add compatibility metadata (`plugin.APIVersion`) and registration-time checks. | ||
|
|
||
| Exit criteria (met): | ||
|
|
||
| - Example plugin module builds against the contract. | ||
| - Startup validation logs loaded plugins and registration errors clearly. | ||
|
|
||
| ## Phase 2: Config-driven Enable/Disable | ||
|
|
||
| Goal: operational control without code changes. | ||
|
|
||
| Proposed: | ||
|
|
||
| - Add plugin list/config in `config.json`: | ||
| - enabled/disabled flags | ||
| - optional plugin-specific settings | ||
| - Deterministic load order and conflict resolution rules. | ||
|
|
||
| Exit criteria: | ||
|
|
||
| - Users can toggle plugins without rebuilding. | ||
| - Clear startup diagnostics for invalid plugin config. | ||
|
|
||
| ## Phase 3: Developer Experience | ||
|
|
||
| Goal: make third-party plugin development straightforward. | ||
|
|
||
| Proposed: | ||
|
|
||
| - Provide `examples/plugins/*` reference implementations. | ||
| - Publish plugin authoring guide (lifecycle map, best practices, safety constraints). | ||
| - Add plugin-focused test harness pattern for hook behavior verification. | ||
|
|
||
| Exit criteria: | ||
|
|
||
| - New plugin can be built from template with minimal boilerplate. | ||
| - CI examples demonstrate expected behavior and regression checks. | ||
|
|
||
| ## Phase 4: Optional Dynamic Loading (Separate RFC) | ||
|
|
||
| Goal: support runtime-loaded plugins only if security and operability are acceptable. | ||
|
|
||
| Preferred direction: | ||
|
|
||
| - Runtime plugins run as subprocesses. | ||
| - Host and plugin communicate via RPC/gRPC. | ||
| - Host manages lifecycle (spawn/health/timeout/restart), not in-process dynamic loading. | ||
|
|
||
| Why this direction: | ||
|
|
||
| - Go native `.so` plugin loading has strict toolchain/ABI coupling with host binary. | ||
| - Subprocess RPC model reduces coupling and improves fault isolation. | ||
| - Process boundary provides a cleaner place for permissions and sandbox controls. | ||
|
|
||
| Preconditions: | ||
|
|
||
| - Threat model approved | ||
| - Signature/trust model defined | ||
| - Sandboxing and permission boundaries defined | ||
| - Rollback and safe-disable behavior validated | ||
| - Versioned RPC handshake and capability negotiation defined | ||
| - Process supervision policy defined (timeouts, retries, crash loop backoff) | ||
|
|
||
| Until then, compile-time registration remains the recommended model. | ||
|
|
||
| ## Maintainer Review Notes | ||
|
|
||
| The current hooks PR should be reviewed as Phase 0+1 only. It intentionally establishes extension points while avoiding high-risk runtime plugin mechanics. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing blank line between documentation links. For better readability and consistency with Markdown best practices, add a blank line between these two documentation references.