Skip to content

v2: plugin Pi lifecycle hooks with in-process runtime closures #53

@hachej

Description

@hachej

Context

WorkspaceServerPlugin.extensionFactories was dropped in commit 02dc4b81 (PR feat/plugin-agent-layer-rebased-main) because no v1 plugin exercised the only capability it uniquely enabled. Plugins today register tools via WorkspaceServerPlugin.agentTools and ship Pi extensions as files via WorkspaceServerPlugin.extensionPaths.

What was dropped

A plugin could provide an in-process function (api) => void that Pi invoked with its extension API at agent boot. Unlike file-based extensionPaths (which Pi loads with no runtime context), the factory variant let plugins close over host runtime state — bridge instance, DB pool, fastify-scoped logger, etc.

The use case that justifies bringing it back

Pi's extension API exposes hooks beyond registerTool:

  • api.on('before_agent_start', (event) => { event.systemPrompt = ... }) — mutate system prompt with live runtime state (different from `pi.systemPrompt` which is static, or `WorkspaceServerPlugin.systemPrompt` which is host-aggregated at boot).
  • api.on('before_tool_call' | 'after_tool_call', ...) — observe/mutate tool calls (e.g. policy gating, redaction, structured audit logging).
  • api.registerSkill(...) / api.registerResource(...) — dynamic Pi skill/resource registration not expressible via static pi.skills directories.

A concrete scenario: an org plugin needs to gate `execute_sql` against per-user permissions held in the host's DB pool. The hook needs both Pi's tool-call event AND the host's runtime pool reference. File-based extensions can only reach the pool via a module-singleton hack; an in-process factory closes over it cleanly:

```ts
// Hypothetical v2 — NOT in v1 today
extensionFactories: [(api) => {
api.on('before_tool_call', async (tool) => {
if (tool.name === 'execute_sql') {
const ok = await dbPool.checkPermission(bridge.currentUser, tool.args.query)
if (!ok) throw new Error('blocked by policy')
}
})
}]
```

Acceptance for re-adding

Before bringing the field back, the new design should:

  1. Have at least one real plugin in this repo (or a documented downstream user) exercising a non-tool Pi hook (`api.on(...)`, `api.registerResource(...)`).
  2. Include tests that exercise the hook path end-to-end — not just "the array is passed through."
  3. Either clearly distinguish `agentTools` (boring-side) from `extensionFactories` (Pi-side hooks) in DESIGN.md, or unify them under one mechanism so plugin authors don't have to choose between two ways to register a tool.

Until those conditions are met, the field stays dropped. v1 plugins use `agentTools` for tools and `extensionPaths` for file-based Pi extensions.

Related

  • DESIGN.md §11 (Deferred post-v1)
  • Commit `02dc4b81`
  • Host-level `WorkspaceAgentPiOptions.pi.extensionFactories` is unaffected — hosts (e.g. `@hachej/boring-core`) can still inject their own.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions