Skip to content

fix(routines): hourly WP-Cron reconcile repair and one atomic shared option lock - #589

Merged
chubes4 merged 1 commit into
mainfrom
fix/582-routines-cron-repair
Oct 21, 2026
Merged

chubes4 merged 1 commit into
mainfrom
fix/582-routines-cron-repair

Conversation

@chubes4

@chubes4 chubes4 commented Oct 21, 2026

Copy link
Copy Markdown
Contributor

Summary

Follow-up to #582/#588 (Extra-Chill/data-machine#3495): since declarations started trusting the persisted schedule record, a routine chain lost out of band (cancelled in the Action Scheduler admin UI, pruned or restored table) stayed lost because nothing ran reconcile() on a schedule, and the bridge's stale schedule-lock takeover let two observing processes both proceed. Agents API now declares one hourly WP-Cron event (wp_agent_routines_reconcile) on init, guarded by wp_next_scheduled(), whose callback runs the lock-protected WP_Agent_Routine_Registry::reconcile() and fires wp_agent_routines_reconcile_completed with the report, so a lost chain is restored within one hour. The atomic stale-lock takeover is extracted into one shared helper under src/Routines/ used by both the reconcile lock and the bridge's per-routine schedule lock, so exactly one acquirer wins a stale lock.

What changed

  • Agents API schedules exactly one hourly WP-Cron event (wp_agent_routines_reconcile) on init via a wp_next_scheduled() guard; its callback runs reconcile() in apply mode and reports through the new wp_agent_routines_reconcile_completed action (docs/channels-workflows-operations.md states a lost chain is restored within one hour; no deactivation path exists, so that is documented instead of hooked).
  • New internal helper AgentsAPI\AI\Routines\WP_Agent_Routine_Option_Lock (acquire/release by option name + TTL) performs the atomic $wpdb INSERT ... ON DUPLICATE KEY UPDATE takeover; both the reconcile lock and the bridge's schedule lock now use it and the duplicated logic is deleted.
  • The bridge's per-routine schedule lock moves off the options API entirely (agents_routine_schedule_lock_<id> is now a $wpdb-only row), closing the racy plain-update_option() stale takeover where two processes could both win.
  • WP-Cron is deliberate for the repair: the guard reads the autoloaded cron option (no queries per request) and the repair lives outside Action Scheduler, so losing Action Scheduler chains cannot also take out the mechanism that restores them.
  • tests/routines-durability-smoke.php gains WP-Cron fakes and assertions: init twice schedules exactly one event, an out-of-band-deleted chain is restored by the cron callback with a matching schedule record, and of two acquirers observing a stale lock exactly one wins while a fresh lock is never taken over.

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. The only behavior change is the schedule lock's storage (options API → $wpdb row): leftover agents_routine_schedule_lock_<id> option rows from prior versions are simply ignored and inert, and lock names, TTLs, and hook contracts are otherwise 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 e881986e6ad4272ac5be62b51fb4b1d06751f855, tree cd54bcd7b1a3a4393e9137c4a17a1459f23bd397, fingerprint 45e9cc1dca9da0b9ebd2230b13d601ea295e24b55d1d5dc229a2076c0e2fe9b5; durable gate gate-1
  • Base unchanged since verification: main remains at 246a0e0.
  • 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: hourly authoritative repair via WP-Cron, and atomic schedule-lock takeover
  • Verified candidate scope: 10 changed file(s): agents-api.php, docs/channels-workflows-operations.md, src/Routines/class-wp-agent-routine-action-scheduler-bridge.php, src/Routines/class-wp-agent-routine-option-lock.php, src/Routines/class-wp-agent-routine-registry.php, src/Routines/register-routine-bridge-sync.php, tests/routine-ability-target-smoke.php, tests/routine-smoke.php, tests/routines-backend-contract-smoke.php, tests/routines-durability-smoke.php.
  • Verified finalization base: main at 246a0e0
  • 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: The AI implemented the hourly WP-Cron reconcile repair, extracted the shared atomic option-lock helper used by both lock sites, updated the operations documentation, and extended the durability smoke tests, passing all required gates.

Source relationships

@chubes4
chubes4 merged commit 5d30607 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