Skip to content

docs: fix determinism return-value-passing example (closes #217)#218

Merged
yaythomas merged 1 commit into
mainfrom
fix/determinism-return-value-example
Jul 9, 2026
Merged

docs: fix determinism return-value-passing example (closes #217)#218
yaythomas merged 1 commit into
mainfrom
fix/determinism-return-value-example

Conversation

@yaythomas

Copy link
Copy Markdown
Contributor

Summary

Fixes the "Pass data through return values, not closures" example so it demonstrates the failure mode the surrounding prose describes.

The current wrong example accumulates total from item["price"] in event["items"]. Both values are pure functions of the handler input, which is stable across replays. Replay reconstructs the same total. The example matches the page's own rule for allowed outside-step code rather than the anti-pattern the prose is teaching.

The new wrong example writes to an outer list from inside the step body via a closure. Replay returns the cached value without running the body, so the write does not land and the list stays empty. The handler's return value differs between the first invocation and replay. The new right example returns the receipt id from the step and appends it to the list in the handler.

Changes

  • Rewrite the section prose and the danger admonition to describe the real failure mode. Steps checkpoint their return value. Replay returns the cached value in place of running the body. Any state a step body writes to outer variables through a closure disappears on replay.
  • Replace the TypeScript and Python return-value-passing wrong and right examples.
  • Add a wrong companion to the Java return-value-passing example, which previously showed only the right implementation.

Trace on replay (Python wrong version)

receipts: list[str] = []                       # reinitializes
for item in event["items"]:
    context.step(save_and_track(item, receipts), name=...)
    # step returns cached None on replay
    # save_and_track body does not run
    # receipts.append never fires
return {"receipts": receipts}                  # []

First invocation returns {"receipts": [id1, id2, ...]}. Replay returns {"receipts": []}. Same shape for TypeScript and Java.

Verification

  • Ran mdformat on the edited page.
  • Ran zensical build --clean. Build finished cleanly, no issues.
  • Verified context.step signatures against SDK source for all three languages before writing examples.

Closes

The wrong version of the Pass data through return values example did
not demonstrate a replay divergence. The mutation accumulated total
from event data, which is a pure function of handler input, so replay
reconstructed the same total. The example matched the page's own
rule for allowed outside-step code rather than the anti-pattern the
prose was teaching.

Rewrite the section around the real failure mode. A step body that
writes to outer state through a closure loses the write on replay,
because replay returns the cached value and skips the body. The new
wrong version writes to an outer list from inside the step body and
returns void, so replay leaves the list empty. The new right version
returns the value from the step and the handler appends to the list
outside.

- Rewrite the prose and danger admonition to match the concept
- Replace TypeScript and Python wrong/right pairs
- Add a wrong companion to the Java example, which previously showed
  only the right implementation

Closes #217
@yaythomas yaythomas merged commit 15e9c59 into main Jul 9, 2026
4 checks passed
@yaythomas yaythomas deleted the fix/determinism-return-value-example branch July 9, 2026 17:01
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.

docs: "Pass data through return values, not closures" example does not demonstrate the failure it warns about

2 participants