Skip to content

fix: retry branch reconciliation without re-executing effects - #534

Merged
chubes4 merged 12 commits into
mainfrom
fix/533-reconcile-result-retry
Aug 26, 2026
Merged

fix: retry branch reconciliation without re-executing effects#534
chubes4 merged 12 commits into
mainfrom
fix/533-reconcile-result-retry

Conversation

@chubes4

@chubes4 chubes4 commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Summary

  • persist terminal reconcile receipts in each branch's existing durable descriptor row before reconciliation
  • retry lock contention through a dedicated reconcile-only Action Scheduler hook that never calls branch execution
  • reconcile missing or expired descriptors through deterministic receipt-only rows
  • preserve custom branch-store ownership and carry opaque continuation state for multi-phase reconciliation
  • include reconcile retries in scoped drains and run-scoped terminal cleanup

Closes #533.

Root cause

reconcile_branch_result() retried agents_reconcile_lock_unavailable by enqueueing the original branch hook and payload. The new action entered run_branch_action(), rehydrated the descriptor, and executed completed branch steps again before making another reconciliation attempt.

Behavior change

A completed branch now stores a read-after-write-verified reconcile receipt before the first reconciliation attempt. The receipt normally lives inside the branch's already-indexed descriptor row, avoiding concurrent appends to the shared run index. A failed write stops the original action before reconciliation rather than enqueueing an unreadable retry.

When a built-in descriptor is missing or expired, its deterministic store_ref receives a verified receipt-only row instead. No shared-index write occurs. The reconcile-only retry records the terminal workflow_branch_descriptor_missing result, then deletes that exact row after successful reconciliation. Expired local rows also delete themselves when read.

Lock contention enqueues RECONCILE_HOOK with a small durable receipt reference. Its callback reloads that receipt and calls only the idempotent reconciliation path; it cannot re-enter execute_branch(). Late duplicate retries whose receipts were already cleaned up verify the authoritative completed state and no-op.

Consumer-owned refs continue through the existing wp_agent_workflow_branch_store_put, wp_agent_workflow_branch_store_get, and wp_agent_workflow_branch_store_forget contract. Missing custom descriptors create receipt-only records only through those owner filters, and exact receipt cleanup also stays in that store. No local option fallback occurs.

Receipts persist opaque reconcile_continuation error data and expose wp_agent_workflow_reconcile_retry, allowing PR #536's authoritative multi-phase continuation to advance after rebase instead of relying only on completed-handle redelivery.

Regression coverage

  • side-effect counter remains exactly 1 through lock contention and duplicate reconcile delivery
  • failed receipt write plus forced lock contention performs no reconcile and queues no stranded retry
  • stale concurrent shared-index state cannot orphan terminal receipt options during cleanup
  • missing and expired descriptors survive contention, retry reconciliation, and reach terminal workflow failure
  • receipt-only built-in rows and custom-store receipts are removed after successful reconciliation
  • custom stores own receipt persistence, retrieval, and cleanup without local fallback
  • opaque authoritative continuation state survives into the reconcile-only retry seam

Tests

  • php tests/workflow-as-branch-smoke.php
  • php tests/workflow-async-branch-payload-smoke.php
  • php tests/workflow-request-controller-smoke.php
  • php tests/workflow-scoped-drain-smoke.php
  • composer test
  • composer phpstan
  • git diff --check

AI assistance

Implemented with OpenCode AI assistance. The implementation, review remediation, diff, and test results were reviewed during development.

@chubes4
chubes4 merged commit 1d1b9f0 into main Aug 26, 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: retry branch reconciliation without re-executing completed effects

1 participant