Skip to content

routines: introduce WP_Agent_Routine_Backend so Action Scheduler is a backend, not a dependency #544

Description

@chubes4

Problem

Routines is Action-Scheduler-optional in behavior (is_available() guards everywhere) but not in shape: the generic registry names the AS bridge directly.

  • src/Routines/class-wp-agent-routine-registry.php calls WP_Agent_Routine_Action_Scheduler_Bridge::{current_generation, is_available, pending_routine_actions, is_paused, register, cancel_action_by_id} in 7 places (lines 209–321 on main @ v0.10.0).
  • register-routine-bridge-sync.php wires the five lifecycle hooks (wp_agent_routine_{registered,unregistered,paused,resumed,run_now_requested}) straight to the bridge.
  • Paused state and generation options are bridge-owned, yet the registry reads them as if they were registry state.

The write side already goes through hooks, which is the right seam. The read side and reconcile() bypass it. Net effect: there is exactly one backend and the generic layer knows its name. For a Core-bound substrate that's the difference between "Agents API depends on Action Scheduler" and "Agents API defines a scheduling contract that Action Scheduler satisfies." Only the second survives a Core review.

The Workflows module has the same coupling (register-workflow-bridge-sync.php, branch executor) — out of scope here; file separately once the Routines pattern is settled.

Ask

  1. Add interface WP_Agent_Routine_Backend (in src/Routines/) with the minimal surface the registry actually needs:
    public function is_available(): bool;
    public function register( WP_Agent_Routine $routine ): bool;
    public function unregister( string $routine_id ): void;
    public function pause( string $routine_id ): void;
    public function resume( WP_Agent_Routine $routine ): bool;
    public function run_now( WP_Agent_Routine $routine ): bool;
    public function is_paused( string $routine_id ): bool;
    public function current_generation( string $routine_id ): ?string;
    /** @return array<string, list<int>> routine_id => pending backend handles */
    public function pending_by_routine(): array;
    public function cancel( int $handle ): bool;
    Handles are opaque ints; the registry never interprets them.
  2. WP_Agent_Routine_Action_Scheduler_Bridge becomes an instance class implementing it (keep the static facade for one release as a thin deprecation shim if any consumer calls it directly — grep says only agents-api itself does).
  3. WP_Agent_Routine_Registry::backend(): ?WP_Agent_Routine_Backend resolves via apply_filters( 'wp_agent_routine_backend', $default ) where $default is the AS bridge when AS is available, else null. Registry and reconcile() use only the interface. register-routine-bridge-sync.php becomes backend-agnostic (calls backend()), or is folded into the registry.
  4. Generation/paused options stay in the AS backend (they are how that backend implements fencing/pause); the interface exposes them as reads so a different backend can implement them differently.
  5. Smoke: a tests/routines-backend-contract-smoke.php that registers a fake in-memory backend via the filter and proves register/pause/resume/run_now/reconcile all route through it with AS functions undefined.

Also (layer purity, found while reading)

src/Routines/register-action-scheduler-listener.php:85,95 adds/removes a openclawp_chat_ability_permission filter. That is a vendor/product hook name in the generic substrate — the exact thing the layer-purity rule forbids. It should be a generic hook the product subscribes to (or the product's own filter registered from the product). Fix in the same PR or file a one-liner; either way it should not survive to Core.

Non-goals

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions