Conversation
Commit hook creation and disposal in per-token order, even when the workflow never awaits the hook. Reconcile remaining history before a terminal outcome without delivering results into a closed workflow loop. Keep recorded disposal separate from explicit disposal so replay does not discard buffered payloads. Cover lifecycle retries and terminal replay across hooks, steps, waits, and attributes.
fantix
force-pushed
the
fantix/workflow-hook-lifecycle
branch
from
September 23, 2026 18:24
4e0575b to
fbf24da
Compare
Keep LocalWorld hook disposal durable across overlapping invocations. Serialize hook creation and disposal across processes and reject stale creation requests before they can reclaim a released token. Cover token reuse and concurrent handlers with regression tests.
This was referenced Sep 25, 2026
fantix
added a commit
that referenced
this pull request
Sep 29, 2026
Catch replay mismatches before a run ends, preparing for hook lifecycle writes in #410.
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Make hook registration and explicit disposal durable even when a workflow never awaits the hook or ends without another suspension. Protect disposed hook identities in LocalWorld so overlapping invocations cannot recreate a released hook or reclaim its successor's token.
Committing explicit disposal before termination supports the retention follow-up's rule that disposal releases a token immediately, even when the run would otherwise retain it. This PR adds no retention API or deadline-based token cleanup.
The old runtime flushed hook operations only on suspension, with all disposals before all creations. It also conflated recorded disposal with a new disposal by user code. World cleanup at run termination hid some missing lifecycle events, but was not equivalent to recording explicit disposal.
User-visible behavior and TS comparison
Compared with the TS SDK at fixed revision
5093edeb, using its implementation and regression tests:SomeHook.wait(), then return or raise without awaiting/iterating the hook or callingget_conflict()hook_createdorhook_conflict.dispose(), including an immediately exitedasync withasync withafter registration, then immediately finish/failhook_disposedbefore the terminal event. Merely suspending inside the scope is not scope exit.usingthrough disposal and drains before termination.hook_disposedbefore earlier buffered payloads are claimedREPLAY_DIVERGENCEduring active or terminal replay, including disposed hooks, without flushing new user-hook operations.Final draining is best-effort: storage errors are logged without replacing the body's result or original exception, matching TS. Suspension flush errors still propagate for retry. Independent token groups run concurrently, and all groups settle before a flush returns or raises, as in TS's settled hook phase.
Compatibility and operational impact
REPLAY_DIVERGENCE. This can expose inconsistencies that earlier terminal exits skipped.Implementation and boundaries
WorkflowOrchestratorContext.resume()still delivers one historical event at a time. After isolated task/generator cleanup and result serialization, terminal reconciliation applies remaining history without waking user code or hydrating unused results.create_hook(). TS uses subscribed event consumers; this PR preserves Python's scheduling and replay-clock model instead of porting TS's scheduler.awaitafter explicit disposal raisesHookDisposedError; TS's empty-history case suspends. That difference is unchanged. Python's new token check covers creation/conflict events, while TS checks token-bearing hook events more broadly.Tests extend the existing registration, conflict, disposal, and determinism modules. Coverage includes partial writes, terminal-write retries, legacy histories, same-run and other-run token successors, and two independent LocalWorld worker processes starting from the same stale history.
Validation
uv run poe qa vercel-workflow -q— passed on Python 3.14.2: 1,540 collected (1,527 passed, 11 skipped, 2 xfailed); lint and type checks passed.tox exec -e py310 -- python -m pytest ....uv run poe check-news-fragments— passed.git diff --check 0c72a39f...HEAD— passed.The multiprocess storage regression was exercised against LocalWorld on macOS. Windows file locking and live Vercel backend concurrency were not exercised. TS comparisons are based on the linked implementation and regression tests; the TS suite was not run.