Context
WordPress core now ships nine execution lifecycle hooks on `WP_Ability` (all `@since 7.1.0`), landed across three PRs by @gziolo:
- PR #11731 — original four: `wp_pre_execute_ability`, `wp_ability_normalize_input`, `wp_ability_permission_result`, `wp_ability_execute_result`. Changeset 62397.
- PR #11852 — new `wp_ability_invoked` action + enriched `$ability` parameter on `wp_before_execute_ability` / `wp_after_execute_ability`. Trac #65248.
- PR #10557 — input/output validation filters: `wp_ability_validate_input`, `wp_ability_validate_output`. Trac #64311.
The substrate adopts these through `WP_Agent_Ability_Lifecycle_Bridge` (in `src/Abilities/`). Each hook is wired as a separate slice / PR.
Hook surface and substrate mapping
| Hook |
Type |
When |
Substrate use |
Slice |
| `wp_ability_invoked` |
action |
Top of `execute()`, every call |
universal telemetry — captures all attempts including the failure modes `wp_ability_execute_result` misses |
TBD |
| `wp_pre_execute_ability` |
filter |
Pre-normalize, sentinel |
approval boundary returning `approval_required` envelope with `WP_Agent_Pending_Action` payload |
TBD — @ibrahimhajjaj |
| `wp_ability_normalize_input` |
filter |
Post-normalize |
inject `WP_Agent_Execution_Principal` / `WP_Agent_Caller_Context` into ability input |
TBD |
| `wp_ability_validate_input` |
filter |
Post-schema validation |
agent-aware input validation layering |
TBD |
| `wp_ability_permission_result` |
filter |
Post-`permission_callback` |
route through `WP_Agent_Tool_Access_Policy` + capability ceiling |
TBD |
| `wp_before_execute_ability` |
action |
Pre-`do_execute` (now with `$ability`) |
substrate-level pre-execute audit |
TBD |
| `wp_ability_execute_result` |
filter |
Post-`execute_callback` |
result observation — emits `agents_api_ability_executed` action |
#199 (in flight) |
| `wp_ability_validate_output` |
filter |
Post-output validation |
output-shape audits |
TBD |
| `wp_after_execute_ability` |
action |
Success path only (now with `$ability`) |
success-only commit hooks (transcript, telemetry) |
TBD |
Design notes carried into slices
These came out of @ibrahimhajjaj's AbilityGuard work (https://github.com/ibrahimhajjaj/abilityguard) and should be observed by every slice that hooks one of the filters:
- Capability detection cannot key off `$wp_version`. The Abilities API also ships as a standalone repo (`WordPress/abilities-api`) and a Composer package on its own release cadence. A WP 7.0 host can have a recent standalone plugin with the filters; a WP 7.1 host can lack them. Slices that need runtime capability detection should reflect on `WP_Ability::execute()` source for the literal filter name. (Most slices don't need detection because the filter just stays idle when not applied.)
- Sentinel pass-through is mandatory on `wp_pre_execute_ability`. Core seeds `$pre` with a fresh `WP_Filter_Sentinel` per call. Any handler must `if ( ! ( $pre instanceof WP_Filter_Sentinel ) ) return $pre;` before doing anything, or it clobbers a prior consumer's short-circuit. Smokes should include a multi-consumer coexistence case.
- `wp_pre_execute_ability` fires before `wp_before_execute_ability`. Anything that opens a pending row or audit record in a before-action never runs on a short-circuit. The pre-execute handler has to do that work inline.
- `wp_ability_execute_result` only sees the post-callback path — it fires inside `do_execute()` after `execute_callback` returned and before `validate_output()`. It does not fire for pre-execute short-circuit, normalize-input failure, validate-input failure, permission denial, or validate-output failure. A complete failure-aware observer needs `wp_ability_invoked` plus the validation filters.
- No `duration_ms` at the `wp_ability_execute_result` seam since the outer `execute()` has not returned. Timing belongs in a `wp_after_execute_ability` hook (success path) or a paired `wp_ability_invoked` / `wp_after_execute_ability` measurement.
- Approval-required envelope inside the loop is the typed `WP_Agent_Pending_Action` returned in a `WP_Agent_Message::approvalRequired(...)` envelope. The `WP_Error` with `status => 202` pattern (used by AbilityGuard) is appropriate at the REST run-controller boundary but is swallowed by `mediate_tool_calls` and cannot carry the typed payload anyway.
Cross-cutting integration: loop-level approval recognition
`mediate_tool_calls` does not currently recognize `approval_required` envelopes returned from ability execution — it treats every executor return as a normal `tool_result`. The `wp_pre_execute_ability` slice depends on the loop learning to detect the envelope and surface it as an approval pause (`status: 'approval_required'`). This is a paired piece of work with the bridge-side approval handler.
Acceptance criteria
- README adoption section documenting `WP_Agent_Ability_Lifecycle_Bridge` and the substrate's observable actions
- Each of the nine hooks wired through a separate PR, sharing the same bridge class
- Smokes per slice; multi-consumer coexistence smoke for `wp_pre_execute_ability`
- `mediate_tool_calls` recognizes `approval_required` envelopes returned from tool execution
- Capability-detection helper that reflects on `WP_Ability::execute()` rather than gating on `$wp_version`
Timing
Hooks are in WP core trunk; `@since 7.1.0`. On WordPress < 7.1 the filters never fire, so registered handlers stay idle — no version gate needed.
AI assistance
- AI assistance: Yes
- Tool(s): Claude Code (Opus 4.7)
- Used for: Mapping the now-nine core filters against this substrate's existing primitives and drafting the adoption plan.
Context
WordPress core now ships nine execution lifecycle hooks on `WP_Ability` (all `@since 7.1.0`), landed across three PRs by @gziolo:
The substrate adopts these through `WP_Agent_Ability_Lifecycle_Bridge` (in `src/Abilities/`). Each hook is wired as a separate slice / PR.
Hook surface and substrate mapping
Design notes carried into slices
These came out of @ibrahimhajjaj's AbilityGuard work (https://github.com/ibrahimhajjaj/abilityguard) and should be observed by every slice that hooks one of the filters:
Cross-cutting integration: loop-level approval recognition
`mediate_tool_calls` does not currently recognize `approval_required` envelopes returned from ability execution — it treats every executor return as a normal `tool_result`. The `wp_pre_execute_ability` slice depends on the loop learning to detect the envelope and surface it as an approval pause (`status: 'approval_required'`). This is a paired piece of work with the bridge-side approval handler.
Acceptance criteria
Timing
Hooks are in WP core trunk; `@since 7.1.0`. On WordPress < 7.1 the filters never fire, so registered handlers stay idle — no version gate needed.
AI assistance