[wp-trac] [WordPress Trac] #64989: Abilities API: Add execution lifecycle filters to WP_Ability methods
WordPress Trac
noreply at wordpress.org
Mon Mar 30 07:21:32 UTC 2026
#64989: Abilities API: Add execution lifecycle filters to WP_Ability methods
-------------------------+-----------------------
Reporter: gziolo | Owner: (none)
Type: enhancement | Status: new
Priority: normal | Milestone: 7.1
Component: AI | Version: 6.9
Severity: normal | Keywords: abilities
Focuses: |
-------------------------+-----------------------
The Abilities API (#64098, WordPress 6.9) provides two registration-phase
filters (`wp_register_ability_args`, `wp_register_ability_category_args`)
and two execution-phase actions (`wp_before_execute_ability`,
`wp_after_execute_ability`). The execution actions are observation-only —
no hook exists that lets plugins transform input, modify output, override
permission decisions, or short-circuit execution.
This ticket proposes four new filters placed inside `WP_Ability` public
methods and the `execute()` pipeline.
== Background ==
=== Prior exploration ===
[https://github.com/WordPress/abilities-api/pull/37 abilities-api#37]
proposed four filters (`ability_input_schema`, `ability_output_schema`,
`ability_permission_result`, `ability_execute_result`) placed inside read-
only getters on `WP_Ability`. It was closed as "not planned" because
getter-level filters fire many times during serialization (REST API schema
responses, MCP tool listing, any introspection), introducing performance
and behavioral unpredictability. Schema modification at read time also
conflates registration-time concerns with execution-time concerns —
registration-time customization is already handled by
`wp_register_ability_args`.
This proposal targets '''operational methods''' instead —
`normalize_input()`, `check_permissions()` — which fire at decision
points, typically once per request. This follows the same approach as the
validation filters proposed in #64311.
=== Demonstrated need from plugins ===
Two consumers of the Abilities API have independently built parallel hook
systems because core lacks execution-time filters:
'''MCP Adapter''' ([https://github.com/WordPress/mcp-adapter WordPress
/mcp-adapter]) implements `mcp_adapter_`-prefixed hooks for validation
toggling, caching, and post-execution audit. The team has decided to
maintain their own lower-level filters on top of whatever core provides
([https://github.com/WordPress/mcp-adapter/pull/151 mcp-adapter#151]), but
richer core hooks would reduce the scope of what the adapter needs to re-
implement.
'''AI Plugin''' ([https://github.com/WordPress/ai WordPress/ai]) needs
input transformation (prompt enrichment), output transformation (response
formatting, safety filtering), and failure handling.
[https://github.com/WordPress/ai/pull/304 ai#304] proposes
`wpai_`-prefixed hooks for these concerns. Whether the AI plugin should
add its own execution-lifecycle filters or rely on core is an open
question this ticket aims to resolve.
Both plugins validate a three-tier extensibility pattern ('''core →
protocol adapter → domain plugin''') where richer core hooks reduce
fragmentation.
== Proposal ==
Four new filters. Two live inside public `WP_Ability` methods
(`normalize_input()`, `check_permissions()`), ensuring consistent behavior
across all call sites — `execute()`, REST API, WP-CLI. Two live on
`execute()` itself because they govern orchestration flow. This mirrors
how `rest_pre_dispatch` short-circuits the REST API dispatch pipeline.
=== Proposed execution lifecycle ===
{{{
┌─ wp_pre_execute_ability (filter) ──── can short-circuit
│
├─ validate_input()
│
├─ normalize_input()
│ └─ wp_ability_normalize_input (filter) ── inside method
│
├─ check_permissions()
│ ├─ permission_callback()
│ └─ wp_ability_permission_result (filter) ── inside method
│
├─ wp_before_execute_ability (action) ── existing
│
├─ do_execute()
│
├─ wp_ability_execute_result (filter) ── transform output
│
├─ validate_output()
│
├─ wp_after_execute_ability (action) ── existing
│
└─ return result
}}}
=== 1. `wp_pre_execute_ability` — Short-circuit ===
{{{#!php
$pre = apply_filters( 'wp_pre_execute_ability', null, $ability_name,
$input, $ability );
if ( null !== $pre ) {
return $pre;
}
}}}
Fires in `execute()` before input validation. Returns non-null to bypass
the entire pipeline. Modeled on `rest_pre_dispatch`.
'''Use cases:''' Cached responses, rate limiting, maintenance mode, test
mocking.
=== 2. `wp_ability_normalize_input` — Input transformation ===
{{{#!php
// Inside WP_Ability::normalize_input(), after built-in normalization:
$input = apply_filters( 'wp_ability_normalize_input', $input,
$ability_name, $ability );
}}}
Fires inside `normalize_input()` after the method's built-in logic. The
filter receives schema-validated, normalized data. Returning a `WP_Error`
halts execution.
'''Use cases:''' AI prompt enrichment, parameter defaulting beyond what
JSON Schema handles, injecting caller metadata.
=== 3. `wp_ability_permission_result` — Permission override ===
{{{#!php
// Inside WP_Ability::check_permissions(), after permission_callback
returns:
$permission = apply_filters( 'wp_ability_permission_result', $permission,
$ability_name, $input, $ability );
}}}
Fires inside `check_permissions()` after `permission_callback` returns.
`check_permissions()` is called from `execute()`, from the REST API
`permissions_check` callback, and from WP-CLI's `wp ability can-run` —
placing the filter inside the method ensures overrides apply consistently
regardless of call site.
'''Use cases:''' MCP Adapter transport-level permission layering, multi-
factor authorization, temporary permission elevation for batch operations.
=== 4. `wp_ability_execute_result` — Output transformation ===
{{{#!php
$result = apply_filters( 'wp_ability_execute_result', $result,
$ability_name, $input, $ability );
}}}
Fires in `execute()` after `do_execute()` returns, before output
validation. Filters must return data that conforms to `output_schema` —
output validation remains the final integrity gate.
'''Use cases:''' AI response formatting, stripping internal metadata,
content safety filtering, response enrichment.
== Design principles ==
* '''Filters inside operational methods, not getters.'''
`normalize_input()` and `check_permissions()` fire at decision points,
typically once per request — unlike the getters targeted in
[https://github.com/WordPress/abilities-api/pull/37 abilities-api#37].
Placing filters here ensures consistent behavior across all call sites.
* '''Validation remains authoritative.''' Input transformation fires
after input validation; output transformation fires before output
validation. Filters cannot bypass schema validation.
* '''Short-circuit stays on `execute()`.''' `wp_pre_execute_ability`
governs orchestration flow, not individual method behavior.
* '''Naming follows established patterns.''' `wp_` prefix per 6.9
convention. `wp_ability_normalize_input` and
`wp_ability_permission_result` name the method and what they filter.
`wp_pre_execute_ability` mirrors `rest_pre_dispatch`.
== Related ==
* #64098 — Introduce Abilities API
* [https://github.com/WordPress/abilities-api/pull/37 abilities-api#37] —
Prior filter proposal (closed, lessons incorporated)
* #64311 — Validation hooks proposal (same pattern)
* [https://github.com/WordPress/ai/pull/304 ai#304] — AI plugin hook
audit
* [https://github.com/WordPress/mcp-adapter/pull/151 mcp-adapter#151] —
MCP Adapter filters
--
Ticket URL: <https://core.trac.wordpress.org/ticket/64989>
WordPress Trac <https://core.trac.wordpress.org/>
WordPress publishing platform
More information about the wp-trac
mailing list