Automattic / Automattic/agents-api

Consume Abilities API lifecycle filters (WP core PR #11731) once they land

Open
#94 8 comments 0 reactions 0 assignees View on GitHub
Dominant language
PHP
Stars
33
Forks
8
Avg merge
1h 39m
Merged PRs (30d)
30

Description

## 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](https://github.com/WordPress/wordpress-develop/pull/11731) — original four: \`wp_pre_execute_ability\`, \`wp_ability_normalize_input\`, \`wp_ability_permission_result\`, \`wp_ability_execute_result\`. [Changeset 62397](https://core.trac.wordpress.org/changeset/62397).
- [PR #11852](https://github.com/WordPress/wordpress-develop/pull/11852) — new \`wp_ability_invoked\` action + enriched \`\$ability\` parameter on \`wp_before_execute_ability\` / \`wp_after_execute_ability\`. [Trac #65248](https://core.trac.wordpress.org/ticket/65248).
- [PR #10557](https://github.com/WordPress/wordpress-develop/pull/10557) — input/output validation filters: \`wp_ability_validate_input\`, \`wp_ability_validate_output\`. [Trac #64311](https://core.trac.wordpress.org/ticket/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:

1. **Capability detection cannot key off \`\$wp_version\`.** The Abilities API also ships as a standalone repo ([\`WordPress/abilities-api\`](https://github.com/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.)
2. **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.
3. **\`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.
4. **\`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.
5. **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.
6. **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.

Contributor guide

No contributing guide indexed for this repository

Research direction

Start with WP_Agent_Ability_Lifecycle_Bridge in src/Abilities/ and trace mediate_tool_calls, then review the README and existing smoke-test locations. Done means the nine lifecycle hooks are covered by separate slices, approval_required is recognized, capability detection uses the execute() reflection described here, and the adoption documentation and smokes meet the acceptance criteria.

Written by the indexing model from the issue text.

Assessment

Tech stack
php, wordpress
Domain
backend-api-design, documentation, testing
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.