Feat: Wait wrapper signature improvements#3241
Feat: Wait wrapper signature improvements#3241mrkaye97 merged 29 commits intofeat-durable-executionfrom
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
📝 Documentation updates detected! New suggestion: Document aio_wait_for_event() method and typed return models for Python SDK Tip: Use labels in the Promptless dashboard to categorize suggestions by release or team 🏷️ |
There was a problem hiding this comment.
Pull request overview
This PR updates Hatchet durable SDK “wait” helpers to return structured, user-friendly results (sleep duration + event metadata) instead of raw internal callback payloads, adds a memoized “now” helper for durable contexts, and aligns backend/UI/examples with the new semantics.
Changes:
- Add higher-level durable context helpers (
waitForEvent, structuredsleepFor, memoizednow) and update SDK examples to use them. - Extend backend matching/callback payloads to include richer user-event data (id, tenant, seen_at, scope, additional metadata, decoded payload).
- Remove the “Restore” button from the workflow run UI and update Python tests/examples accordingly.
Reviewed changes
Copilot reviewed 29 out of 30 changed files in this pull request and generated 17 comments.
Show a summary per file
| File | Description |
|---|---|
| sdks/typescript/src/v1/client/worker/context.ts | Adds SleepResult, HatchetEvent, waitForEvent, memoized now, and changes sleepFor return type. |
| sdks/typescript/src/v1/examples/durable/workflow.ts | Updates durable example to use new sleepFor/waitForEvent outputs and adds memoNowCaching example. |
| sdks/typescript/src/v1/examples/durable_event/workflow.ts | Updates durable-event examples to use waitForEvent (with optional CEL filter). |
| sdks/typescript/src/v1/examples/durable_eviction/workflow.ts | Updates eviction example to use waitForEvent. |
| examples/typescript/durable/workflow.ts | Mirrors TypeScript SDK examples with new helper signatures and memoized now. |
| examples/typescript/durable_event/workflow.ts | Mirrors TypeScript durable-event examples with waitForEvent. |
| examples/typescript/durable_eviction/workflow.ts | Mirrors TypeScript eviction example with waitForEvent. |
| sdks/python/hatchet_sdk/context/context.py | Adds Event/SleepResult models, wrappers aio_sleep_for/aio_wait_for_event, memoized aio_now, and renames memo helper. |
| sdks/python/hatchet_sdk/utils/timedelta_to_expression.py | Adds expr_to_timedelta utility to parse duration expressions. |
| sdks/python/tests/test_durations.py | Adds unit tests for expr_to_timedelta. |
| sdks/python/examples/durable/worker.py | Updates Python durable examples to use the new wrappers and adds memoized aio_now example. |
| sdks/python/examples/durable_event/worker.py | Updates durable-event example to use aio_wait_for_event. |
| sdks/python/examples/durable_eviction/worker.py | Updates eviction example to use aio_wait_for_event. |
| sdks/python/examples/worker.py | Registers the new memo_now_caching durable task example. |
| sdks/python/examples/durable/test_durable.py | Updates durable tests for async event push + new outputs and adds memo-now replay assertion. |
| sdks/guides/python/human_in_the_loop/worker.py | Updates guide to use aio_wait_for_event helper. |
| examples/python/durable/worker.py | Mirrors Python SDK examples with new helper signatures. |
| examples/python/durable_event/worker.py | Mirrors Python durable-event example using aio_wait_for_event. |
| examples/python/durable_eviction/worker.py | Mirrors Python eviction example using aio_wait_for_event. |
| examples/python/worker.py | Registers the new memo_now_caching example task. |
| examples/python/guides/human_in_the_loop/worker.py | Mirrors guide update to aio_wait_for_event. |
| sdks/python/poetry.lock | Updates Python lockfile (Poetry-generated metadata and dependency markers). |
| pkg/repository/trigger.go | Adds JSON tags to event trigger option struct fields. |
| pkg/repository/match.go | Expands candidate matches and marshals a richer matched-event JSON payload for user events. |
| pkg/repository/durable_events.go | Propagates wait-for callback result payload through ingest results. |
| internal/services/dispatcher/v1/server.go | Passes wait-for result payload through callback completion delivery. |
| internal/services/controllers/task/controller.go | Populates candidate match fields for user event scope + additional metadata. |
| frontend/app/src/pages/main/v1/workflow-runs-v1/$run/v2components/step-run-detail/step-run-detail.tsx | Removes Restore button and related query/mutation logic. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| def expr_to_timedelta(expr: str) -> timedelta: | ||
| unit = expr[-1] | ||
| value = int(expr[:-1]) | ||
|
|
There was a problem hiding this comment.
expr_to_timedelta only supports single-unit expressions like "10m" by slicing the last character as the unit. But Duration also allows arbitrary strings, and the engine uses Go-style duration strings (e.g. "1h30m", "1h30m5s") in several places. This implementation will raise ValueError for those valid durations; consider parsing the full Go-style h/m/s format (and keeping behavior consistent with the "engine only supports hours/minutes/seconds" comment).
| APPROVAL_EVENT_KEY, | ||
| f"input.runId == '{run_id}'", | ||
| ) | ||
| return approval |
There was a problem hiding this comment.
aio_wait_for_event now returns an Event model, but this helper returns it directly while downstream code in this guide treats the result like a dict (e.g. approval.get(...)). To keep the guide working, either return approval.payload here (so callers receive the event body), or update the rest of the guide to use approval.payload[...] / approval.payload.get(...).
| return approval | |
| return approval.payload |
| APPROVAL_EVENT_KEY, | ||
| f"input.runId == '{run_id}'", | ||
| ) | ||
| return approval |
There was a problem hiding this comment.
aio_wait_for_event now returns an Event model, but this helper returns it directly while downstream code in this example treats the result like a dict (e.g. approval.get(...)). Either return approval.payload here or update the rest of the example to use approval.payload.get(...) so it doesn’t fail at runtime.
| return approval | |
| return approval.payload |
| "runtime": time.time() - start, | ||
| "key": key, | ||
| "event_id": event_id, | ||
| } |
There was a problem hiding this comment.
wait_for_or_group_2 is annotated to return dict[str, str | int], but runtime is now a float (time.time() - start). This will fail mypy under the SDK’s strict settings. Update the return annotation to include float (or cast/round the runtime back to int).
| now = await self._aio_memo( | ||
| self._now, | ||
| MemoNowResult, | ||
| ) | ||
|
|
There was a problem hiding this comment.
aio_now uses _aio_memo(self._now, MemoNowResult) with no args/kwargs, but _compute_memo_key only hashes the task_run_external_id + args/kwargs (it does not include the function identity). This makes the memo key collide with any other memoization call that also has no args/kwargs, potentially returning the wrong cached payload/type. Consider namespacing the key (e.g. pass a constant arg like 'now', or include fn.__qualname__ in the memo key computation).
| async now(): Promise<Date> { | ||
| const result = await this.memo(async () => { | ||
| return { ts: new Date().toISOString() }; | ||
| }, []); |
There was a problem hiding this comment.
now() memoizes with deps: []. That means it shares the same memo key as any other ctx.memo(fn, []) call in the same task run, which can lead to collisions and type/shape mismatches when reading memoized payloads. Consider including a reserved dep value (e.g. a constant like 'now') so the memo key is namespaced per built-in helper.
| }, []); | |
| }, ['now']); |
| "runtime": time.time() - start, | ||
| "key": key, | ||
| "event_id": event_id, | ||
| } |
There was a problem hiding this comment.
wait_for_or_group_2 is annotated to return dict[str, str | int], but runtime is now a float (time.time() - start). This will fail type checking if these examples are included. Update the return annotation to include float (or cast/round the runtime back to int).
| } | ||
| } | ||
|
|
||
| return { durationMs: durationToMs(duration) }; |
There was a problem hiding this comment.
this might be worth a conversation....
|
📝 Documentation updates detected! New suggestion: Document TypeScript DurableContext methods: waitForEvent, now, sleepUntil, and SleepResult Tip: Request one-off documentation tasks in the Dashboard under New Task 🚀 |
Description
Working on improving wrapper signatures on the SDK (wait for event, sleep for, ...) to return something helpful instead of the internal event which is returned now.
Also implemented the memoized
now()method on the durable context in Python, and removed theRestorebutton from the UIType of change