Skip to content

Abilities: bridge wp_ability_execute_result onto agents_api_ability_executed#199

Merged
chubes4 merged 1 commit into
mainfrom
add/ability-lifecycle-bridge
May 27, 2026
Merged

Abilities: bridge wp_ability_execute_result onto agents_api_ability_executed#199
chubes4 merged 1 commit into
mainfrom
add/ability-lifecycle-bridge

Conversation

@lezama
Copy link
Copy Markdown
Contributor

@lezama lezama commented May 25, 2026

Summary

Adds WP_Agent_Ability_Lifecycle_Bridge, a passive observer that forwards WordPress 7.1's Abilities API execution lifecycle filters onto substrate-level actions.

This first slice wires the wp_ability_execute_result filter: every time an ability's execute_callback runs (after permission and input checks pass), agents_api_ability_executed fires with the same shape as the underlying filter — ability name, result, normalized input, ability instance. The handler returns the result unchanged, so observers cannot accidentally transform an ability's output.

Why

Tracked in #94. WP core PR WordPress/wordpress-develop#11731 landed in trunk on 2026-05-21 via changeset 62397, adding four execution lifecycle filters to WP_Ability (wp_pre_execute_ability, wp_ability_normalize_input, wp_ability_permission_result, wp_ability_execute_result). The substrate's adoption plan in #94 mapped each filter to a substrate primitive; this PR is the smallest, lowest-risk first slice: observability via the post-execute filter.

What this slice does and does not cover

wp_ability_execute_result fires inside do_execute(), so it sees the result only once execute_callback was reached. That means agents_api_ability_executed fires for:

  • successful execution
  • execute_callback returned a WP_Error
  • ability had no callable execute_callback

It does not fire for:

  • wp_pre_execute_ability short-circuit (handled by a separate slice)
  • wp_ability_normalize_input returning WP_Error
  • validate_input() failure
  • permission_callback / wp_ability_permission_result denial
  • validate_output() failure after the filter

Those failure modes get their own observer surfaces in follow-up slices. There is also no duration_ms available at this seam — the outer execute() has not returned yet — so timing has to come from a later hook.

Boundary

The bridge is a pure observer:

  • Returns the filter input unchanged. No transformation of ability results.
  • Emits one action: agents_api_ability_executed( \$ability_name, \$result, \$input, \$ability ) — mirrors the filter's 4-arg shape verbatim.
  • No coupling to agents_api_loop_event or the conversation loop. Ability execution can happen outside any agent flow (a REST call, a workflow step, a scheduled job) and the observer surface is consistent for all of them.

On WordPress < 7.1 the underlying filter is never applied, so the registered handler stays idle. No version gate needed. (Note: capability detection by host version is unreliable because the Abilities API also ships standalone; future slices that need to detect host capability should reflect on WP_Ability::execute() source rather than checking \$wp_version.)

Changes

  • src/Abilities/class-wp-agent-ability-lifecycle-bridge.php — new class with one static register() and one observer.
  • agents-api.php — require the new class; register the bridge on init at priority 5.
  • tests/ability-lifecycle-bridge-smoke.php — 12 assertions covering pass-through, failure result, multi-execution.
  • tests/bootstrap-smoke.php — add 'Abilities' to the source-directory whitelist.
  • composer.json — include the new smoke in composer test.

Follow-ups (separate PRs in the #94 family)

  • wp_pre_execute_ability → approval gate returning a pending-action envelope (resolves the sentinel-shape design question; @ibrahimhajjaj has offered to take this slice).
  • wp_ability_normalize_input → inject WP_Agent_Execution_Principal / WP_Agent_Caller_Context into ability input.
  • wp_ability_permission_result → route through WP_Agent_Tool_Access_Policy and capability ceiling.

Each of those reuses the same WP_Agent_Ability_Lifecycle_Bridge class (extra static methods + add_filter calls in register()).

Test plan

  • php tests/ability-lifecycle-bridge-smoke.php — 12 assertions, all pass.
  • composer test — full suite green; bootstrap whitelist updated for the new src/Abilities/ directory.

Part of #94.

…xecuted

Adds WP_Agent_Ability_Lifecycle_Bridge, a passive observer that forwards
WordPress 7.1's Abilities API execution lifecycle filters onto
substrate-level actions.

This first slice wires the wp_ability_execute_result filter: every
WP_Ability::execute() call (whether or not it succeeded) emits an
agents_api_ability_executed action with the same shape exposed by the
underlying filter (ability name, result, normalized input, ability
instance). The handler returns the result unchanged, so observers cannot
accidentally transform an ability's output.

This is the first of four lifecycle slices tracked in #94. Follow-ups
will add hooks for wp_pre_execute_ability (approval boundary),
wp_ability_normalize_input (principal injection), and
wp_ability_permission_result (tool access policy).

On WordPress < 7.1 the underlying filter never fires, so the registered
handler stays idle — no version gate needed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@chubes4
Copy link
Copy Markdown
Contributor

chubes4 commented May 26, 2026

Should we use wp_agents_api_ability_executed or agents_api_ability_executed?

@lezama
Copy link
Copy Markdown
Contributor Author

lezama commented May 27, 2026

The tree uses both prefixes today, with a loose split:

  • `agents_api_*` — substrate-wide buses / multiplexers / aggregation surfaces. Examples in tree: `agents_api_loop_event`, `agents_api_context_sections`, `agents_api_memory_sources`, `agents_api_tool_source_order`, `agents_api_execution_principal`, `agents_api_tool_action_policy`.
  • `wp_agent_*` — value-object / store / handler scoped filters and entity-lifecycle actions. Examples: `wp_agent_conversation_store`, `wp_agent_memory_store`, `wp_agent_pending_action_store`, `wp_agent_routine_registered`, `wp_agent_routine_run_completed`.

The ability-executed event is closer to the first bucket — it's an observable stream that any consumer can subscribe to, not an entity lifecycle hanging off a substrate value object. `WP_Ability` lives in WP core, not in agents-api, so there's no `wp_agent_*` entity to anchor it to either. The natural sibling is `agents_api_loop_event` (also a multiplexable observability bus).

So I'd keep `agents_api_ability_executed` as-is, and use the same prefix for the rest of the lifecycle bridge slices when they land (`agents_api_ability_invoked`, `agents_api_ability_completed`, etc.).

Worth a follow-up to codify this in the README's "Public Surface" section since the convention is implicit today.

@chubes4
Copy link
Copy Markdown
Contributor

chubes4 commented May 27, 2026

Ok, merging

@chubes4 chubes4 merged commit 88f9923 into main May 27, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants