Skip to content

fix(routines): persist schedule records for zero-I/O declarations, single-flight scheduling, and duplicate-chain collapse (#582) - #588

Merged
chubes4 merged 1 commit into
mainfrom
fix/issue-582-agents-api
Oct 21, 2026
Merged

chubes4 merged 1 commit into
mainfrom
fix/issue-582-agents-api

Conversation

@chubes4

@chubes4 chubes4 commented Oct 21, 2026

Copy link
Copy Markdown
Contributor

Summary

Routines are re-declared in memory on every request, and every declaration synced to Action Scheduler: one as_get_scheduled_actions() query plus one fetch_action() per pending action per request (~720 queries per page view at Data Machine's scale), with no lock around check-then-schedule and no collapse of duplicate chains. The bridge now keeps one persisted record of what it last scheduled (routine id → trigger signature + action id in a single option), so an unchanged declaration returns with zero scheduler I/O, and only a missing or changed signature reaches the store under a short single-flight per-routine lock. Any call that does reach Action Scheduler — declarations and reconcile's new authoritative repair() path alike — collapses duplicate matching chains to the one live chain the watermark model considers current and cancels the rest by id, while reconcile still repairs drift the record cannot see (including an action deleted out-of-band).

What changed

  • Declarations are O(1): one non-autoloaded map option (agents_routine_schedules: routine id → trigger signature + action id) answers unchanged register() calls without a single Action Scheduler read; only a changed or missing signature reaches the store (WP_Agent_Routine_Action_Scheduler_Bridge).
  • New WP_Agent_Routine_Backend::repair() is the authoritative check-and-repair; reconcile() now routes through it, so a recorded signature whose action was deleted out-of-band is repaired, while plain register() deliberately trusts the record.
  • One chain per routine: any register()/repair() that reaches Action Scheduler keeps the lowest-id live (at-or-above-watermark) matching chain and cancels the rest by id, never touching an in-flight claimed action; unregister()/pause() drop the record so resume()/register() cannot fast-path on a dead chain.
  • Check-then-schedule is single-flight across processes via a short add_option-based per-routine lock (30-second stale takeover); a losing caller no-ops cheaply instead of minting a second chain.
  • Durability smoke asserts the counts: 300 unchanged routines in a fresh request = 0 fetch_action() + 0 as_get_scheduled_actions() (before: 1 bulk query + 300 fetches on every request; the one-time first sync pays 1 + 300), plus targeted reschedule, collapse, lock, and drift-repair coverage.

How to test

  1. Run composer install -q --no-interaction && for f in tests/routine-smoke.php tests/routines-durability-smoke.php tests/routine-ability-target-smoke.php tests/routines-backend-contract-smoke.php; do php "$f" || exit 1; done && composer smoke && composer phpstan; expect passes as recorded by Cook's deterministic gate.

Compatibility

Additive except one named break: the WP_Agent_Routine_Backend contract now requires repair(), so third-party backend implementations must add it. The agents_routine_schedules record option, the per-routine lock options, and the removed matching_action_id() helper are internal to the default bridge; unregister/pause/resume/run-now semantics and the generation/watermark fence are unchanged.

Evidence

  • Verified command composer install -q --no-interaction && for f in tests/routine-smoke.php tests/routines-durability-smoke.php tests/routine-ability-target-smoke.php tests/routines-backend-contract-smoke.php; do php "$f" || exit 1; done && composer smoke && composer phpstan : status=succeeded; candidate commit 1b61042f7b4af6f4c010682891979fd9b5aa2623, tree 47aae49b5477c397df1595319ad302c413f72fed, fingerprint 3fdcf1fb2fd0fa66949454a6d5aa407b38f742731aff35ee051e6e45689de735; durable gate gate-1
  • Base unchanged since verification: main remains at eb5715d.
  • CI expected: Homeboy CI after push
  • Candidate adoption provenance: the candidate was promoted from the recorded Cook task execution.
  • Cook deterministic verification: 1 gate(s) completed green.
  • Durable run execution: Succeeded
  • Reviewer-resolvable evidence from source_refs[0]: fix: register() never collapses duplicate matching routine chains — routines run twice per tick #582
  • Task objective: # Agents API routines: sync to the backend only when a routine changed, and collapse duplicate chains
  • Verified candidate scope: 7 changed file(s): docs/channels-workflows-operations.md, src/Routines/class-wp-agent-routine-action-scheduler-bridge.php, src/Routines/class-wp-agent-routine-registry.php, src/Routines/interface-wp-agent-routine-backend.php, src/Routines/register-routine-abilities.php, tests/routines-backend-contract-smoke.php, tests/routines-durability-smoke.php.
  • Verified finalization base: main at eb5715d
  • deterministic gate passed: sh -c composer install -q --no-interaction && for f in tests/routine-smoke.php tests/routines-durability-smoke.php tests/routine-ability-target-smoke.php tests/routines-backend-contract-smoke.php; do php "$f" ||… (Passed)

AI assistance

  • AI assistance: Yes
  • Tool(s): Homeboy (pi)
  • Model: subagent
  • Used for: Implemented the schedule-record fast path, the single-flight scheduling lock, watermark-aware duplicate-chain collapse, and the authoritative repair() backend verb in src/Routines/, extended both routine smokes with counting assertions covering all six proof cases, and updated the routines docs and the reconcile-ability description.

Source relationships

…ngle-flight scheduling, and duplicate-chain collapse (#582)
@chubes4
chubes4 merged commit 246a0e0 into main Oct 21, 2026
4 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.

fix: register() never collapses duplicate matching routine chains — routines run twice per tick

1 participant