#20802: feat(hooks): upgrade llm_input, llm_output, and after_tool_call to modifying hooks
agents
size: M
Cluster:
Plugin and Hook Enhancements
## Summary
- Upgrade `llm_input` hook from void (audit-only) to modifying: plugins can now **block** (abort the LLM call) or **mask** (modify prompt/systemPrompt) before content reaches the LLM
- Upgrade `llm_output` hook from void to modifying: plugins can now **mask** (modify assistantTexts) after the LLM responds — enables rehydrating masked tokens from `llm_input`
- Upgrade `after_tool_call` hook from void to modifying in the adapter path: plugins can now **mask** (modify/redact tool results) before the LLM sees them
- Subscribe handler path for `after_tool_call` remains audit-only since results are already committed to the session by that point
## Motivation
Three critical hooks — `llm_input`, `llm_output`, and `after_tool_call` — fire at exactly the right points for PII/data leakage prevention but were previously audit-only. This enables plugins to actively prevent sensitive data from reaching LLMs, and to rehydrate masked tokens in the response.
The mask/rehydrate round-trip: `llm_input` masks sensitive data (e.g. PII → `«PERSON_001»`) before it reaches the LLM, and `llm_output` rehydrates those tokens in the assistant response before it reaches the user.
## Changes
### Types (`src/plugins/types.ts`)
- Add `PluginHookLlmInputResult` (prompt, systemPrompt, block, blockReason)
- Add `PluginHookLlmOutputResult` (assistantTexts)
- Add `PluginHookAfterToolCallResult` (result)
- Update `PluginHookHandlerMap` return types for all three hooks
### Hook runner (`src/plugins/hooks.ts`)
- `runLlmInput`: switch from `runVoidHook` to `runModifyingHook` with merge semantics
- `runLlmOutput`: switch from `runVoidHook` to `runModifyingHook`
- `runAfterToolCall`: switch from `runVoidHook` to `runModifyingHook`
- Add new type imports and re-exports
### Consumers
- `attempt.ts`: await `llm_input` result, handle block and prompt/systemPrompt modification
- `attempt.ts`: await `llm_output` result, apply modified assistantTexts
- `pi-tool-definition-adapter.ts`: apply modified tool results from `after_tool_call` (both success and error paths)
### Tests
- 4 new `runLlmInput` runner tests (modified prompt, block, no hooks, multi-handler merge)
- 3 new `runLlmOutput` runner tests (modified assistantTexts, void handler, no hooks)
- 4 new `runAfterToolCall` runner tests (modified result, no hooks, void handler, last-wins)
- 3 new adapter e2e tests (modified result success/error path, undefined passthrough)
- All existing tests continue to pass
## Backward Compatibility
Fully backward compatible:
- Plugins returning `void` continue to work — `runModifyingHook` treats undefined/null returns as no-ops
- Handler signature change from `=> Promise<void>` to `=> Promise<Result | void>` is strictly additive
## Test plan
- [x] `pnpm test -- --run src/plugins/wired-hooks-llm.test.ts` (14 tests pass)
- [x] `pnpm vitest run --config vitest.e2e.config.ts src/agents/pi-tool-definition-adapter.after-tool-call.e2e.test.ts` (7 tests pass)
- [x] `pnpm tsc --noEmit` (clean, only pre-existing tui.ts error)
- [x] `pnpm lint` (0 warnings, 0 errors)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Most Similar PRs
#20426: feat: make llm_input/llm_output modifying hooks for middleware patt...
by chandika · 2026-02-18
87.4%
#11124: feat(plugins): add before_llm_request hook for custom LLM headers
by johnlanni · 2026-02-07
73.6%
#22624: feat(plugins): add before_context_send hook and model routing via b...
by davidrudduck · 2026-02-21
72.8%
#21035: feat: agent hardening — modifying after_tool_call hook, cron contex...
by roelven · 2026-02-19
72.3%
#16028: feat/before-tool-result
by ambushalgorithm · 2026-02-14
72.1%
#23559: feat(plugins): add before_context_send hook and model routing via b...
by davidrudduck · 2026-02-22
71.4%
#6405: feat(security): Add HTTP API security hooks for plugin scanning
by masterfung · 2026-02-01
69.9%
#20580: feat(hooks): bridge after_tool_call to internal hook handler system
by CryptoKrad · 2026-02-19
69.8%
#20067: feat(plugins): add before_agent_reply hook for message interception
by JoshuaLelon · 2026-02-18
69.7%
#18810: feat(hooks): wire before_tool_call/after_tool_call with veto support
by vjranagit · 2026-02-17
69.4%