feat(plugins): add before agent finalize hook (#71765)

This commit is contained in:
Vincent Koc
2026-04-25 17:21:17 -07:00
committed by GitHub
parent 727e0e013e
commit f3accc753c
24 changed files with 522 additions and 27 deletions

View File

@@ -1,4 +1,4 @@
f5236ba3f34837485d1e319262d4d73ecd46ea8890d3f4c26a069834f376b796 config-baseline.json
484b36513ecb4a13cc945c3916fbe5ac712b5e0ab2c4ffa2dc811758da4ec7a6 config-baseline.core.json
15a3740b57d0c95f0c0963c1d0eff6d85ecdb8cb03960b4763e847f8a24551c0 config-baseline.json
3c39a3a2008ce938886b600e9429a71921c1f9b00c64a16801f47d6d8d2ad7a8 config-baseline.core.json
7cd9c908f066c143eab2a201efbc9640f483ab28bba92ddeca1d18cc2b528bc3 config-baseline.channel.json
17eb3f8887193579ff32e35f9bd520ba2bd6049e52ab18855c5d41fcbf195d83 config-baseline.plugin.json
9e131d7734f8b9cc9e7f8af6cc6b6dc81c9971dc551fadbe66fb0d682173f32d config-baseline.plugin.json

View File

@@ -126,6 +126,11 @@ Each event includes: `type`, `action`, `sessionKey`, `timestamp`, `messages` (pu
**Compaction events**: `session:compact:before` includes `messageCount`, `tokenCount`. `session:compact:after` adds `compactedCount`, `summaryLength`, `tokensBefore`, `tokensAfter`.
`command:stop` observes the user issuing `/stop`; it is cancellation/command
lifecycle, not an agent-finalization gate. Plugins that need to inspect a
natural final answer and ask the agent for one more pass should use the typed
plugin hook `before_agent_finalize` instead. See [Plugin hooks](/plugins/hooks).
## Hook discovery
Hooks are discovered from these directories, in order of increasing override precedence:

View File

@@ -219,7 +219,8 @@ For runtime hook debugging:
from a module-loaded inspection pass.
- `openclaw gateway status --deep --require-rpc` confirms the reachable Gateway,
service/process hints, config path, and RPC health.
- Non-bundled conversation hooks (`llm_input`, `llm_output`, `agent_end`) require
- Non-bundled conversation hooks (`llm_input`, `llm_output`,
`before_agent_finalize`, `agent_end`) require
`plugins.entries.<id>.hooks.allowConversationAccess=true`.
Use `--link` to avoid copying a local directory (adds to `plugins.load.paths`):

View File

@@ -160,7 +160,7 @@ See [MCP](/cli/mcp#openclaw-as-an-mcp-client-registry) and
- `plugins.entries.<id>.apiKey`: plugin-level API key convenience field (when supported by the plugin).
- `plugins.entries.<id>.env`: plugin-scoped env var map.
- `plugins.entries.<id>.hooks.allowPromptInjection`: when `false`, core blocks `before_prompt_build` and ignores prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride`. Applies to native plugin hooks and supported bundle-provided hook directories.
- `plugins.entries.<id>.hooks.allowConversationAccess`: when `true`, trusted non-bundled plugins may read raw conversation content from typed hooks such as `llm_input`, `llm_output`, and `agent_end`.
- `plugins.entries.<id>.hooks.allowConversationAccess`: when `true`, trusted non-bundled plugins may read raw conversation content from typed hooks such as `llm_input`, `llm_output`, `before_agent_finalize`, and `agent_end`.
- `plugins.entries.<id>.subagent.allowModelOverride`: explicitly trust this plugin to request per-run `provider` and `model` overrides for background subagent runs.
- `plugins.entries.<id>.subagent.allowedModels`: optional allowlist of canonical `provider/model` targets for trusted subagent overrides. Use `"*"` only when you intentionally want to allow any model.
- `plugins.entries.<id>.config`: plugin-defined config object (validated by native OpenClaw plugin schema when available).

View File

@@ -34,6 +34,7 @@ These are in-process OpenClaw hooks, not Codex `hooks.json` command hooks:
- `llm_input`, `llm_output`
- `before_tool_call`, `after_tool_call`
- `before_message_write` for mirrored transcript records
- `before_agent_finalize` through Codex `Stop` relay
- `agent_end`
Plugins can also register runtime-neutral tool-result middleware to rewrite
@@ -583,10 +584,10 @@ The Codex harness has three hook layers:
OpenClaw does not use project or global Codex `hooks.json` files to route
OpenClaw plugin behavior. For the supported native tool and permission bridge,
OpenClaw injects per-thread Codex config for `PreToolUse`, `PostToolUse`, and
`PermissionRequest`. Other Codex hooks such as `SessionStart`,
`UserPromptSubmit`, and `Stop` remain Codex-level controls; they are not exposed
as OpenClaw plugin hooks in the v1 contract.
OpenClaw injects per-thread Codex config for `PreToolUse`, `PostToolUse`,
`PermissionRequest`, and `Stop`. Other Codex hooks such as `SessionStart` and
`UserPromptSubmit` remain Codex-level controls; they are not exposed as
OpenClaw plugin hooks in the v1 contract.
For OpenClaw dynamic tools, OpenClaw executes the tool after Codex asks for the
call, so OpenClaw fires the plugin and middleware behavior it owns in the
@@ -622,6 +623,7 @@ Supported in Codex runtime v1:
| Context engine lifecycle | Supported | Assemble, ingest or after-turn maintenance, and context-engine compaction coordination run for Codex turns. |
| Dynamic tool hooks | Supported | `before_tool_call`, `after_tool_call`, and tool-result middleware run around OpenClaw-owned dynamic tools. |
| Lifecycle hooks | Supported as adapter observations | `llm_input`, `llm_output`, `agent_end`, `before_compaction`, and `after_compaction` fire with honest Codex-mode payloads. |
| Final-answer revision gate | Supported through the native hook relay | Codex `Stop` is relayed to `before_agent_finalize`; `revise` asks Codex for one more model pass before finalization. |
| Native shell, patch, and MCP block or observe | Supported through the native hook relay | Codex `PreToolUse` and `PostToolUse` are relayed for committed native tool surfaces, including MCP payloads on Codex app-server `0.125.0` or newer. Blocking is supported; argument rewriting is not. |
| Native permission policy | Supported through the native hook relay | Codex `PermissionRequest` can be routed through OpenClaw policy where the runtime exposes it. If OpenClaw returns no decision, Codex continues through its normal guardian or user approval path. |
| App-server trajectory capture | Supported | OpenClaw records the request it sent to app-server and the app-server notifications it receives. |
@@ -635,7 +637,6 @@ Not supported in Codex runtime v1:
| `tool_result_persist` for Codex-native tool records | That hook transforms OpenClaw-owned transcript writes, not Codex-native tool records. | Could mirror transformed records, but canonical rewrite needs Codex support. |
| Rich native compaction metadata | OpenClaw observes compaction start and completion, but does not receive a stable kept/dropped list, token delta, or summary payload. | Needs richer Codex compaction events. |
| Compaction intervention | Current OpenClaw compaction hooks are notification-level in Codex mode. | Add Codex pre/post compaction hooks if plugins need to veto or rewrite native compaction. |
| Stop or final-answer gating | Codex has native stop hooks, but OpenClaw does not expose final-answer gating as a v1 plugin contract. | Future opt-in primitive with loop and timeout safeguards. |
| Byte-for-byte model API request capture | OpenClaw can capture app-server requests and notifications, but Codex core builds the final OpenAI API request internally. | Needs a Codex model-request tracing event or debug API. |
## Tools, media, and compaction

View File

@@ -64,6 +64,7 @@ observation-only.
- `before_prompt_build` — add dynamic context or system-prompt text before the model call
- `before_agent_start` — compatibility-only combined phase; prefer the two hooks above
- **`before_agent_reply`** — short-circuit the model turn with a synthetic reply or silence
- **`before_agent_finalize`** — inspect the natural final answer and request one more model pass
- `agent_end` — observe final messages, success state, and run duration
**Conversation observation**
@@ -185,7 +186,16 @@ bodies, or provider request IDs. These hooks include stable metadata such as
`durationMs`/`outcome`, and `upstreamRequestIdHash` when OpenClaw can derive a
bounded provider request-id hash.
Non-bundled plugins that need `llm_input`, `llm_output`, or `agent_end` must set:
`before_agent_finalize` runs only when a harness is about to accept a natural
final assistant answer. It is not the `/stop` cancellation path and does not
run when the user aborts a turn. Return `{ action: "revise", reason }` to ask
the harness for one more model pass before finalization, `{ action:
"finalize", reason? }` to force finalization, or omit a result to continue.
Codex native `Stop` hooks are relayed into this hook as OpenClaw
`before_agent_finalize` decisions.
Non-bundled plugins that need `llm_input`, `llm_output`,
`before_agent_finalize`, or `agent_end` must set:
```json
{

View File

@@ -65,11 +65,12 @@ dynamic tools still execute through OpenClaw, while Codex-native tools such as
shell/apply-patch execute inside Codex. For Codex-native tool events, OpenClaw
injects a per-turn native hook relay so plugin hooks can block
`before_tool_call`, observe `after_tool_call`, and route Codex
`PermissionRequest` events through OpenClaw approvals. The v1 relay is
deliberately conservative: it does not mutate Codex-native tool arguments,
rewrite Codex thread records, or gate final answers/Stop hooks. Use explicit
ACP only when you want the ACP runtime/session model. The embedded Codex support
boundary is documented in the
`PermissionRequest` events through OpenClaw approvals. Codex `Stop` hooks are
relayed to OpenClaw `before_agent_finalize`, where plugins can request one more
model pass before Codex finalizes its answer. The relay remains deliberately
conservative: it does not mutate Codex-native tool arguments or rewrite Codex
thread records. Use explicit ACP only when you want the ACP runtime/session
model. The embedded Codex support boundary is documented in the
[Codex harness v1 support contract](/plugins/codex-harness#v1-support-contract).
Natural-language triggers that should route to the ACP runtime:

View File

@@ -223,7 +223,7 @@ do not run in live chat traffic, check these first:
`openclaw gateway run` process.
- Use `openclaw plugins inspect <id> --json` to confirm hook registrations and
diagnostics. Non-bundled conversation hooks such as `llm_input`,
`llm_output`, and `agent_end` need
`llm_output`, `before_agent_finalize`, and `agent_end` need
`plugins.entries.<id>.hooks.allowConversationAccess=true`.
- For model switching, prefer `before_model_resolve`. It runs before model
resolution for agent turns; `llm_output` only runs after a model attempt