edda-framework 0.4.0__tar.gz → 0.6.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (158) hide show
  1. {edda_framework-0.4.0 → edda_framework-0.6.0}/.github/workflows/ci.yml +2 -2
  2. {edda_framework-0.4.0 → edda_framework-0.6.0}/PKG-INFO +32 -5
  3. {edda_framework-0.4.0 → edda_framework-0.6.0}/README.md +27 -4
  4. {edda_framework-0.4.0 → edda_framework-0.6.0}/docs/core-features/hooks.md +43 -8
  5. {edda_framework-0.4.0 → edda_framework-0.6.0}/docs/index.md +22 -0
  6. {edda_framework-0.4.0 → edda_framework-0.6.0}/docs/integrations/mcp.md +30 -4
  7. edda_framework-0.6.0/docs/integrations/opentelemetry.md +179 -0
  8. {edda_framework-0.4.0 → edda_framework-0.6.0}/edda/app.py +16 -1
  9. {edda_framework-0.4.0 → edda_framework-0.6.0}/edda/hooks.py +11 -11
  10. {edda_framework-0.4.0 → edda_framework-0.6.0}/edda/integrations/mcp/decorators.py +101 -5
  11. {edda_framework-0.4.0 → edda_framework-0.6.0}/edda/integrations/mcp/server.py +36 -15
  12. edda_framework-0.6.0/edda/integrations/opentelemetry/__init__.py +39 -0
  13. edda_framework-0.6.0/edda/integrations/opentelemetry/hooks.py +579 -0
  14. edda_framework-0.6.0/examples/observability_with_opentelemetry.py +211 -0
  15. {edda_framework-0.4.0 → edda_framework-0.6.0}/pyproject.toml +6 -1
  16. edda_framework-0.6.0/tests/integrations/mcp/test_cancel.py +166 -0
  17. {edda_framework-0.4.0 → edda_framework-0.6.0}/tests/integrations/mcp/test_integration.py +2 -2
  18. {edda_framework-0.4.0 → edda_framework-0.6.0}/tests/integrations/mcp/test_jsonrpc.py +1 -1
  19. {edda_framework-0.4.0 → edda_framework-0.6.0}/tests/integrations/mcp/test_prompts.py +3 -3
  20. {edda_framework-0.4.0 → edda_framework-0.6.0}/tests/integrations/mcp/test_server.py +80 -1
  21. edda_framework-0.6.0/tests/integrations/opentelemetry/__init__.py +1 -0
  22. edda_framework-0.6.0/tests/integrations/opentelemetry/test_hooks.py +696 -0
  23. {edda_framework-0.4.0 → edda_framework-0.6.0}/uv.lock +226 -2
  24. {edda_framework-0.4.0 → edda_framework-0.6.0}/.github/workflows/docs.yml +0 -0
  25. {edda_framework-0.4.0 → edda_framework-0.6.0}/.github/workflows/release.yml +0 -0
  26. {edda_framework-0.4.0 → edda_framework-0.6.0}/.gitignore +0 -0
  27. {edda_framework-0.4.0 → edda_framework-0.6.0}/.python-version +0 -0
  28. {edda_framework-0.4.0 → edda_framework-0.6.0}/Justfile +0 -0
  29. {edda_framework-0.4.0 → edda_framework-0.6.0}/LICENSE +0 -0
  30. {edda_framework-0.4.0 → edda_framework-0.6.0}/demo_app.py +0 -0
  31. {edda_framework-0.4.0 → edda_framework-0.6.0}/docs/core-features/durable-execution/replay.md +0 -0
  32. {edda_framework-0.4.0 → edda_framework-0.6.0}/docs/core-features/events/cloudevents-http-binding.md +0 -0
  33. {edda_framework-0.4.0 → edda_framework-0.6.0}/docs/core-features/events/wait-event.md +0 -0
  34. {edda_framework-0.4.0 → edda_framework-0.6.0}/docs/core-features/retry.md +0 -0
  35. {edda_framework-0.4.0 → edda_framework-0.6.0}/docs/core-features/saga-compensation.md +0 -0
  36. {edda_framework-0.4.0 → edda_framework-0.6.0}/docs/core-features/transactional-outbox.md +0 -0
  37. {edda_framework-0.4.0 → edda_framework-0.6.0}/docs/core-features/workflows-activities.md +0 -0
  38. {edda_framework-0.4.0 → edda_framework-0.6.0}/docs/examples/ecommerce.md +0 -0
  39. {edda_framework-0.4.0 → edda_framework-0.6.0}/docs/examples/events.md +0 -0
  40. {edda_framework-0.4.0 → edda_framework-0.6.0}/docs/examples/fastapi-integration.md +0 -0
  41. {edda_framework-0.4.0 → edda_framework-0.6.0}/docs/examples/saga.md +0 -0
  42. {edda_framework-0.4.0 → edda_framework-0.6.0}/docs/examples/simple.md +0 -0
  43. {edda_framework-0.4.0 → edda_framework-0.6.0}/docs/getting-started/concepts.md +0 -0
  44. {edda_framework-0.4.0 → edda_framework-0.6.0}/docs/getting-started/first-workflow.md +0 -0
  45. {edda_framework-0.4.0 → edda_framework-0.6.0}/docs/getting-started/installation.md +0 -0
  46. {edda_framework-0.4.0 → edda_framework-0.6.0}/docs/getting-started/quick-start.md +0 -0
  47. {edda_framework-0.4.0 → edda_framework-0.6.0}/docs/markdown.md +0 -0
  48. {edda_framework-0.4.0 → edda_framework-0.6.0}/docs/viewer-ui/images/cloudevents-cli-trigger.png +0 -0
  49. {edda_framework-0.4.0 → edda_framework-0.6.0}/docs/viewer-ui/images/compensation-execution.png +0 -0
  50. {edda_framework-0.4.0 → edda_framework-0.6.0}/docs/viewer-ui/images/conditional-branching-diagram.png +0 -0
  51. {edda_framework-0.4.0 → edda_framework-0.6.0}/docs/viewer-ui/images/detail-overview-panel.png +0 -0
  52. {edda_framework-0.4.0 → edda_framework-0.6.0}/docs/viewer-ui/images/execution-history-panel.png +0 -0
  53. {edda_framework-0.4.0 → edda_framework-0.6.0}/docs/viewer-ui/images/form-generation-example.png +0 -0
  54. {edda_framework-0.4.0 → edda_framework-0.6.0}/docs/viewer-ui/images/hybrid-diagram-example.png +0 -0
  55. {edda_framework-0.4.0 → edda_framework-0.6.0}/docs/viewer-ui/images/nested-pydantic-form.png +0 -0
  56. {edda_framework-0.4.0 → edda_framework-0.6.0}/docs/viewer-ui/images/start-workflow-dialog.png +0 -0
  57. {edda_framework-0.4.0 → edda_framework-0.6.0}/docs/viewer-ui/images/status-badges-example.png +0 -0
  58. {edda_framework-0.4.0 → edda_framework-0.6.0}/docs/viewer-ui/images/wait-event-visualization.png +0 -0
  59. {edda_framework-0.4.0 → edda_framework-0.6.0}/docs/viewer-ui/images/workflow-list-view.png +0 -0
  60. {edda_framework-0.4.0 → edda_framework-0.6.0}/docs/viewer-ui/setup.md +0 -0
  61. {edda_framework-0.4.0 → edda_framework-0.6.0}/docs/viewer-ui/visualization.md +0 -0
  62. {edda_framework-0.4.0 → edda_framework-0.6.0}/edda/__init__.py +0 -0
  63. {edda_framework-0.4.0 → edda_framework-0.6.0}/edda/activity.py +0 -0
  64. {edda_framework-0.4.0 → edda_framework-0.6.0}/edda/compensation.py +0 -0
  65. {edda_framework-0.4.0 → edda_framework-0.6.0}/edda/context.py +0 -0
  66. {edda_framework-0.4.0 → edda_framework-0.6.0}/edda/events.py +0 -0
  67. {edda_framework-0.4.0 → edda_framework-0.6.0}/edda/exceptions.py +0 -0
  68. {edda_framework-0.4.0 → edda_framework-0.6.0}/edda/integrations/__init__.py +0 -0
  69. {edda_framework-0.4.0 → edda_framework-0.6.0}/edda/integrations/mcp/__init__.py +0 -0
  70. {edda_framework-0.4.0 → edda_framework-0.6.0}/edda/locking.py +0 -0
  71. {edda_framework-0.4.0 → edda_framework-0.6.0}/edda/outbox/__init__.py +0 -0
  72. {edda_framework-0.4.0 → edda_framework-0.6.0}/edda/outbox/relayer.py +0 -0
  73. {edda_framework-0.4.0 → edda_framework-0.6.0}/edda/outbox/transactional.py +0 -0
  74. {edda_framework-0.4.0 → edda_framework-0.6.0}/edda/pydantic_utils.py +0 -0
  75. {edda_framework-0.4.0 → edda_framework-0.6.0}/edda/replay.py +0 -0
  76. {edda_framework-0.4.0 → edda_framework-0.6.0}/edda/retry.py +0 -0
  77. {edda_framework-0.4.0 → edda_framework-0.6.0}/edda/serialization/__init__.py +0 -0
  78. {edda_framework-0.4.0 → edda_framework-0.6.0}/edda/serialization/base.py +0 -0
  79. {edda_framework-0.4.0 → edda_framework-0.6.0}/edda/serialization/json.py +0 -0
  80. {edda_framework-0.4.0 → edda_framework-0.6.0}/edda/storage/__init__.py +0 -0
  81. {edda_framework-0.4.0 → edda_framework-0.6.0}/edda/storage/models.py +0 -0
  82. {edda_framework-0.4.0 → edda_framework-0.6.0}/edda/storage/protocol.py +0 -0
  83. {edda_framework-0.4.0 → edda_framework-0.6.0}/edda/storage/sqlalchemy_storage.py +0 -0
  84. {edda_framework-0.4.0 → edda_framework-0.6.0}/edda/viewer_ui/__init__.py +0 -0
  85. {edda_framework-0.4.0 → edda_framework-0.6.0}/edda/viewer_ui/app.py +0 -0
  86. {edda_framework-0.4.0 → edda_framework-0.6.0}/edda/viewer_ui/components.py +0 -0
  87. {edda_framework-0.4.0 → edda_framework-0.6.0}/edda/viewer_ui/data_service.py +0 -0
  88. {edda_framework-0.4.0 → edda_framework-0.6.0}/edda/visualizer/__init__.py +0 -0
  89. {edda_framework-0.4.0 → edda_framework-0.6.0}/edda/visualizer/ast_analyzer.py +0 -0
  90. {edda_framework-0.4.0 → edda_framework-0.6.0}/edda/visualizer/mermaid_generator.py +0 -0
  91. {edda_framework-0.4.0 → edda_framework-0.6.0}/edda/workflow.py +0 -0
  92. {edda_framework-0.4.0 → edda_framework-0.6.0}/edda/wsgi.py +0 -0
  93. {edda_framework-0.4.0 → edda_framework-0.6.0}/examples/__init__.py +0 -0
  94. {edda_framework-0.4.0 → edda_framework-0.6.0}/examples/cancellable_workflow.py +0 -0
  95. {edda_framework-0.4.0 → edda_framework-0.6.0}/examples/compensation_workflow.py +0 -0
  96. {edda_framework-0.4.0 → edda_framework-0.6.0}/examples/event_waiting_app.py +0 -0
  97. {edda_framework-0.4.0 → edda_framework-0.6.0}/examples/event_waiting_workflow.py +0 -0
  98. {edda_framework-0.4.0 → edda_framework-0.6.0}/examples/event_waiting_workflow_complete.py +0 -0
  99. {edda_framework-0.4.0 → edda_framework-0.6.0}/examples/mcp/README.md +0 -0
  100. {edda_framework-0.4.0 → edda_framework-0.6.0}/examples/mcp/order_processing_mcp.py +0 -0
  101. {edda_framework-0.4.0 → edda_framework-0.6.0}/examples/mcp/prompts_example.py +0 -0
  102. {edda_framework-0.4.0 → edda_framework-0.6.0}/examples/mcp/remote_server_example.py +0 -0
  103. {edda_framework-0.4.0 → edda_framework-0.6.0}/examples/mcp/simple_mcp_server.py +0 -0
  104. {edda_framework-0.4.0 → edda_framework-0.6.0}/examples/observability_with_logfire.py +0 -0
  105. {edda_framework-0.4.0 → edda_framework-0.6.0}/examples/pydantic_saga.py +0 -0
  106. {edda_framework-0.4.0 → edda_framework-0.6.0}/examples/retry_example.py +0 -0
  107. {edda_framework-0.4.0 → edda_framework-0.6.0}/examples/retry_with_compensation.py +0 -0
  108. {edda_framework-0.4.0 → edda_framework-0.6.0}/examples/simple_workflow.py +0 -0
  109. {edda_framework-0.4.0 → edda_framework-0.6.0}/examples/typeddict_example.py +0 -0
  110. {edda_framework-0.4.0 → edda_framework-0.6.0}/examples/with_outbox.py +0 -0
  111. {edda_framework-0.4.0 → edda_framework-0.6.0}/tests/__init__.py +0 -0
  112. {edda_framework-0.4.0 → edda_framework-0.6.0}/tests/conftest.py +0 -0
  113. {edda_framework-0.4.0 → edda_framework-0.6.0}/tests/integrations/__init__.py +0 -0
  114. {edda_framework-0.4.0 → edda_framework-0.6.0}/tests/integrations/mcp/__init__.py +0 -0
  115. {edda_framework-0.4.0 → edda_framework-0.6.0}/tests/test_activity.py +0 -0
  116. {edda_framework-0.4.0 → edda_framework-0.6.0}/tests/test_activity_retry.py +0 -0
  117. {edda_framework-0.4.0 → edda_framework-0.6.0}/tests/test_activity_sync.py +0 -0
  118. {edda_framework-0.4.0 → edda_framework-0.6.0}/tests/test_app.py +0 -0
  119. {edda_framework-0.4.0 → edda_framework-0.6.0}/tests/test_ast_analyzer.py +0 -0
  120. {edda_framework-0.4.0 → edda_framework-0.6.0}/tests/test_atomic_wait_event.py +0 -0
  121. {edda_framework-0.4.0 → edda_framework-0.6.0}/tests/test_binary_data.py +0 -0
  122. {edda_framework-0.4.0 → edda_framework-0.6.0}/tests/test_cloudevents_http_binding.py +0 -0
  123. {edda_framework-0.4.0 → edda_framework-0.6.0}/tests/test_compensation.py +0 -0
  124. {edda_framework-0.4.0 → edda_framework-0.6.0}/tests/test_compensation_crash_recovery.py.wip +0 -0
  125. {edda_framework-0.4.0 → edda_framework-0.6.0}/tests/test_concurrent_outbox.py +0 -0
  126. {edda_framework-0.4.0 → edda_framework-0.6.0}/tests/test_context.py +0 -0
  127. {edda_framework-0.4.0 → edda_framework-0.6.0}/tests/test_ctx_session.py +0 -0
  128. {edda_framework-0.4.0 → edda_framework-0.6.0}/tests/test_distributed_event_delivery.py +0 -0
  129. {edda_framework-0.4.0 → edda_framework-0.6.0}/tests/test_events.py +0 -0
  130. {edda_framework-0.4.0 → edda_framework-0.6.0}/tests/test_lock_race_condition.py +0 -0
  131. {edda_framework-0.4.0 → edda_framework-0.6.0}/tests/test_lock_timeout_customization.py +0 -0
  132. {edda_framework-0.4.0 → edda_framework-0.6.0}/tests/test_locking.py +0 -0
  133. {edda_framework-0.4.0 → edda_framework-0.6.0}/tests/test_multidb_storage.py +0 -0
  134. {edda_framework-0.4.0 → edda_framework-0.6.0}/tests/test_outbox.py +0 -0
  135. {edda_framework-0.4.0 → edda_framework-0.6.0}/tests/test_pydantic_activity.py +0 -0
  136. {edda_framework-0.4.0 → edda_framework-0.6.0}/tests/test_pydantic_enum.py +0 -0
  137. {edda_framework-0.4.0 → edda_framework-0.6.0}/tests/test_pydantic_events.py +0 -0
  138. {edda_framework-0.4.0 → edda_framework-0.6.0}/tests/test_pydantic_saga.py +0 -0
  139. {edda_framework-0.4.0 → edda_framework-0.6.0}/tests/test_pydantic_utils.py +0 -0
  140. {edda_framework-0.4.0 → edda_framework-0.6.0}/tests/test_received_event.py +0 -0
  141. {edda_framework-0.4.0 → edda_framework-0.6.0}/tests/test_replay.py +0 -0
  142. {edda_framework-0.4.0 → edda_framework-0.6.0}/tests/test_retry_policy.py +0 -0
  143. {edda_framework-0.4.0 → edda_framework-0.6.0}/tests/test_saga_parameter_extraction.py +0 -0
  144. {edda_framework-0.4.0 → edda_framework-0.6.0}/tests/test_serialization.py +0 -0
  145. {edda_framework-0.4.0 → edda_framework-0.6.0}/tests/test_skip_locked.py +0 -0
  146. {edda_framework-0.4.0 → edda_framework-0.6.0}/tests/test_stale_workflow_recovery.py +0 -0
  147. {edda_framework-0.4.0 → edda_framework-0.6.0}/tests/test_storage.py +0 -0
  148. {edda_framework-0.4.0 → edda_framework-0.6.0}/tests/test_storage_mysql.py +0 -0
  149. {edda_framework-0.4.0 → edda_framework-0.6.0}/tests/test_storage_postgresql.py +0 -0
  150. {edda_framework-0.4.0 → edda_framework-0.6.0}/tests/test_transactions.py +0 -0
  151. {edda_framework-0.4.0 → edda_framework-0.6.0}/tests/test_viewer_pydantic_form.py +0 -0
  152. {edda_framework-0.4.0 → edda_framework-0.6.0}/tests/test_viewer_start_saga.py +0 -0
  153. {edda_framework-0.4.0 → edda_framework-0.6.0}/tests/test_wait_timer.py +0 -0
  154. {edda_framework-0.4.0 → edda_framework-0.6.0}/tests/test_workflow.py +0 -0
  155. {edda_framework-0.4.0 → edda_framework-0.6.0}/tests/test_workflow_auto_register.py +0 -0
  156. {edda_framework-0.4.0 → edda_framework-0.6.0}/tests/test_workflow_cancellation.py +0 -0
  157. {edda_framework-0.4.0 → edda_framework-0.6.0}/viewer_app.py +0 -0
  158. {edda_framework-0.4.0 → edda_framework-0.6.0}/zensical.toml +0 -0
@@ -53,8 +53,8 @@ jobs:
53
53
  - name: Set up Python ${{ matrix.python-version }}
54
54
  run: uv python install ${{ matrix.python-version }}
55
55
 
56
- - name: Install dependencies (all database drivers)
57
- run: uv sync --extra dev --extra postgresql --extra mysql
56
+ - name: Install dependencies (all database drivers and opentelemetry)
57
+ run: uv sync --extra dev --extra postgresql --extra mysql --extra opentelemetry
58
58
 
59
59
  - name: Run tests (all databases)
60
60
  env:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: edda-framework
3
- Version: 0.4.0
3
+ Version: 0.6.0
4
4
  Summary: Lightweight Durable Execution Framework
5
5
  Project-URL: Homepage, https://github.com/i2y/edda
6
6
  Project-URL: Documentation, https://github.com/i2y/edda#readme
@@ -42,6 +42,10 @@ Provides-Extra: mcp
42
42
  Requires-Dist: mcp>=1.22.0; extra == 'mcp'
43
43
  Provides-Extra: mysql
44
44
  Requires-Dist: aiomysql>=0.2.0; extra == 'mysql'
45
+ Provides-Extra: opentelemetry
46
+ Requires-Dist: opentelemetry-api>=1.20.0; extra == 'opentelemetry'
47
+ Requires-Dist: opentelemetry-exporter-otlp>=1.20.0; extra == 'opentelemetry'
48
+ Requires-Dist: opentelemetry-sdk>=1.20.0; extra == 'opentelemetry'
45
49
  Provides-Extra: postgresql
46
50
  Requires-Dist: asyncpg>=0.30.0; extra == 'postgresql'
47
51
  Provides-Extra: server
@@ -91,6 +95,28 @@ Edda excels at orchestrating **long-running workflows** that must survive failur
91
95
  - **🤖 AI Agent Workflows**: Orchestrate multi-step AI tasks (LLM calls, tool usage, long-running inference)
92
96
  - **📡 Event-Driven Workflows**: React to external events with guaranteed delivery and automatic retry
93
97
 
98
+ ### Business Process Automation
99
+
100
+ Edda's waiting functions make it ideal for time-based and event-driven business processes:
101
+
102
+ - **📧 User Onboarding**: Send reminders if users haven't completed setup after N days
103
+ - **🎁 Campaign Processing**: Evaluate conditions and notify winners after campaign ends
104
+ - **💳 Payment Reminders**: Send escalating reminders before payment deadlines
105
+ - **📦 Scheduled Notifications**: Shipping updates, subscription renewals, appointment reminders
106
+
107
+ **Waiting functions**:
108
+ - `wait_timer(duration_seconds)`: Wait for a relative duration
109
+ - `wait_until(until_time)`: Wait until an absolute datetime (e.g., campaign end date)
110
+ - `wait_event(event_type)`: Wait for external events (near real-time response)
111
+
112
+ ```python
113
+ @workflow
114
+ async def onboarding_reminder(ctx: WorkflowContext, user_id: str):
115
+ await wait_timer(ctx, duration_seconds=3*24*60*60) # Wait 3 days
116
+ if not await check_completed(ctx, user_id):
117
+ await send_reminder(ctx, user_id)
118
+ ```
119
+
94
120
  **Key benefit**: Workflows **never lose progress** - crashes and restarts are handled automatically through deterministic replay.
95
121
 
96
122
  ## Architecture
@@ -683,7 +709,7 @@ def process_payment(ctx: WorkflowContext, amount: float) -> dict:
683
709
  @workflow
684
710
  async def payment_workflow(ctx: WorkflowContext, order_id: str) -> dict:
685
711
  # Workflows still use async (for deterministic replay)
686
- result = await process_payment(ctx, 99.99, activity_id="pay:1")
712
+ result = await process_payment(ctx, 99.99)
687
713
  return result
688
714
  ```
689
715
 
@@ -722,13 +748,14 @@ if __name__ == "__main__":
722
748
 
723
749
  ### Auto-Generated Tools
724
750
 
725
- Each `@durable_tool` automatically generates **three MCP tools**:
751
+ Each `@durable_tool` automatically generates **four MCP tools**:
726
752
 
727
753
  1. **Main tool** (`process_order`): Starts the workflow, returns instance ID
728
- 2. **Status tool** (`process_order_status`): Checks workflow progress
754
+ 2. **Status tool** (`process_order_status`): Checks workflow progress with completed activity count and suggested poll interval
729
755
  3. **Result tool** (`process_order_result`): Gets final result when completed
756
+ 4. **Cancel tool** (`process_order_cancel`): Cancels workflow if running or waiting, executes compensation handlers
730
757
 
731
- This enables AI assistants to work with workflows that take minutes, hours, or even days to complete.
758
+ This enables AI assistants to work with workflows that take minutes, hours, or even days to complete, with full control over the workflow lifecycle.
732
759
 
733
760
  ### MCP Prompts
734
761
 
@@ -39,6 +39,28 @@ Edda excels at orchestrating **long-running workflows** that must survive failur
39
39
  - **🤖 AI Agent Workflows**: Orchestrate multi-step AI tasks (LLM calls, tool usage, long-running inference)
40
40
  - **📡 Event-Driven Workflows**: React to external events with guaranteed delivery and automatic retry
41
41
 
42
+ ### Business Process Automation
43
+
44
+ Edda's waiting functions make it ideal for time-based and event-driven business processes:
45
+
46
+ - **📧 User Onboarding**: Send reminders if users haven't completed setup after N days
47
+ - **🎁 Campaign Processing**: Evaluate conditions and notify winners after campaign ends
48
+ - **💳 Payment Reminders**: Send escalating reminders before payment deadlines
49
+ - **📦 Scheduled Notifications**: Shipping updates, subscription renewals, appointment reminders
50
+
51
+ **Waiting functions**:
52
+ - `wait_timer(duration_seconds)`: Wait for a relative duration
53
+ - `wait_until(until_time)`: Wait until an absolute datetime (e.g., campaign end date)
54
+ - `wait_event(event_type)`: Wait for external events (near real-time response)
55
+
56
+ ```python
57
+ @workflow
58
+ async def onboarding_reminder(ctx: WorkflowContext, user_id: str):
59
+ await wait_timer(ctx, duration_seconds=3*24*60*60) # Wait 3 days
60
+ if not await check_completed(ctx, user_id):
61
+ await send_reminder(ctx, user_id)
62
+ ```
63
+
42
64
  **Key benefit**: Workflows **never lose progress** - crashes and restarts are handled automatically through deterministic replay.
43
65
 
44
66
  ## Architecture
@@ -631,7 +653,7 @@ def process_payment(ctx: WorkflowContext, amount: float) -> dict:
631
653
  @workflow
632
654
  async def payment_workflow(ctx: WorkflowContext, order_id: str) -> dict:
633
655
  # Workflows still use async (for deterministic replay)
634
- result = await process_payment(ctx, 99.99, activity_id="pay:1")
656
+ result = await process_payment(ctx, 99.99)
635
657
  return result
636
658
  ```
637
659
 
@@ -670,13 +692,14 @@ if __name__ == "__main__":
670
692
 
671
693
  ### Auto-Generated Tools
672
694
 
673
- Each `@durable_tool` automatically generates **three MCP tools**:
695
+ Each `@durable_tool` automatically generates **four MCP tools**:
674
696
 
675
697
  1. **Main tool** (`process_order`): Starts the workflow, returns instance ID
676
- 2. **Status tool** (`process_order_status`): Checks workflow progress
698
+ 2. **Status tool** (`process_order_status`): Checks workflow progress with completed activity count and suggested poll interval
677
699
  3. **Result tool** (`process_order_result`): Gets final result when completed
700
+ 4. **Cancel tool** (`process_order_cancel`): Cancels workflow if running or waiting, executes compensation handlers
678
701
 
679
- This enables AI assistants to work with workflows that take minutes, hours, or even days to complete.
702
+ This enables AI assistants to work with workflows that take minutes, hours, or even days to complete, with full control over the workflow lifecycle.
680
703
 
681
704
  ### MCP Prompts
682
705
 
@@ -30,9 +30,10 @@ class LogfireHooks(HooksBase):
30
30
  instance_id=instance_id,
31
31
  workflow_name=workflow_name)
32
32
 
33
- async def on_activity_complete(self, instance_id, step, activity_name, result, cache_hit):
33
+ async def on_activity_complete(self, instance_id, activity_id, activity_name, result, cache_hit):
34
34
  logfire.info("activity.complete",
35
35
  instance_id=instance_id,
36
+ activity_id=activity_id,
36
37
  activity_name=activity_name,
37
38
  cache_hit=cache_hit
38
39
  )
@@ -69,9 +70,9 @@ The `WorkflowHooks` Protocol defines these methods (all optional):
69
70
  | `on_workflow_complete` | `instance_id`, `workflow_name`, `result` | Called when a workflow completes successfully |
70
71
  | `on_workflow_failed` | `instance_id`, `workflow_name`, `error` | Called when a workflow fails with an exception |
71
72
  | `on_workflow_cancelled` | `instance_id`, `workflow_name` | Called when a workflow is cancelled |
72
- | `on_activity_start` | `instance_id`, `step`, `activity_name`, `is_replaying` | Called before an activity executes |
73
- | `on_activity_complete` | `instance_id`, `step`, `activity_name`, `result`, `cache_hit` | Called after an activity completes successfully |
74
- | `on_activity_failed` | `instance_id`, `step`, `activity_name`, `error` | Called when an activity fails with an exception |
73
+ | `on_activity_start` | `instance_id`, `activity_id`, `activity_name`, `is_replaying` | Called before an activity executes |
74
+ | `on_activity_complete` | `instance_id`, `activity_id`, `activity_name`, `result`, `cache_hit` | Called after an activity completes successfully |
75
+ | `on_activity_failed` | `instance_id`, `activity_id`, `activity_name`, `error` | Called when an activity fails with an exception |
75
76
  | `on_event_sent` | `event_type`, `event_source`, `event_data` | Called when an event is sent (transactional outbox) |
76
77
  | `on_event_received` | `instance_id`, `event_type`, `event_data` | Called when a workflow receives an awaited event |
77
78
 
@@ -150,8 +151,9 @@ class LogfireHooks(HooksBase):
150
151
  instance_id=instance_id,
151
152
  workflow_name=workflow_name)
152
153
 
153
- async def on_activity_complete(self, instance_id, step, activity_name, result, cache_hit):
154
+ async def on_activity_complete(self, instance_id, activity_id, activity_name, result, cache_hit):
154
155
  logfire.info("activity.complete",
156
+ activity_id=activity_id,
155
157
  activity_name=activity_name,
156
158
  cache_hit=cache_hit)
157
159
 
@@ -181,7 +183,7 @@ class DatadogHooks(HooksBase):
181
183
  span.set_tag("workflow.name", workflow_name)
182
184
  span.set_tag("instance.id", instance_id)
183
185
 
184
- async def on_activity_complete(self, instance_id, step, activity_name, result, cache_hit):
186
+ async def on_activity_complete(self, instance_id, activity_id, activity_name, result, cache_hit):
185
187
  statsd.increment('edda.activity.completed',
186
188
  tags=[f'activity:{activity_name}', f'cache_hit:{cache_hit}'])
187
189
  ```
@@ -199,7 +201,7 @@ class PrometheusHooks(HooksBase):
199
201
  async def on_workflow_start(self, instance_id, workflow_name, input_data):
200
202
  workflow_started.labels(workflow_name=workflow_name).inc()
201
203
 
202
- async def on_activity_complete(self, instance_id, step, activity_name, result, cache_hit):
204
+ async def on_activity_complete(self, instance_id, activity_id, activity_name, result, cache_hit):
203
205
  activity_executed.labels(activity_name=activity_name, cache_hit=str(cache_hit)).inc()
204
206
  ```
205
207
 
@@ -218,17 +220,50 @@ class SentryHooks(HooksBase):
218
220
  })
219
221
  sentry_sdk.capture_exception(error)
220
222
 
221
- async def on_activity_failed(self, instance_id, step, activity_name, error):
223
+ async def on_activity_failed(self, instance_id, activity_id, activity_name, error):
222
224
  with sentry_sdk.push_scope() as scope:
223
225
  scope.set_context("activity", {
224
226
  "instance_id": instance_id,
227
+ "activity_id": activity_id,
225
228
  "activity_name": activity_name,
226
229
  })
227
230
  sentry_sdk.capture_exception(error)
228
231
  ```
229
232
 
233
+ ### OpenTelemetry (Official Integration)
234
+
235
+ Edda provides an official OpenTelemetry integration with full tracing, optional metrics, and W3C Trace Context propagation.
236
+
237
+ ```python
238
+ from edda import EddaApp
239
+ from edda.integrations.opentelemetry import OpenTelemetryHooks
240
+
241
+ hooks = OpenTelemetryHooks(
242
+ service_name="order-service",
243
+ otlp_endpoint="http://localhost:4317", # Optional
244
+ enable_metrics=True, # Optional
245
+ )
246
+
247
+ app = EddaApp(
248
+ service_name="order-service",
249
+ db_url="sqlite:///workflow.db",
250
+ hooks=hooks,
251
+ )
252
+ ```
253
+
254
+ **Features:**
255
+
256
+ - ✅ Distributed tracing with parent-child span relationships
257
+ - ✅ Optional metrics (counters, histograms)
258
+ - ✅ W3C Trace Context propagation via CloudEvents
259
+ - ✅ Automatic context inheritance from ASGI/WSGI middleware
260
+
261
+ 👉 **See [OpenTelemetry Integration](../integrations/opentelemetry.md) for full documentation.**
262
+
230
263
  ## See Also
231
264
 
265
+ - **[OpenTelemetry Integration](../integrations/opentelemetry.md)**: Official OpenTelemetry integration with full documentation
232
266
  - **[Complete Logfire Example](https://github.com/i2y/edda/blob/main/examples/observability_with_logfire.py)**: Full implementation with multiple workflows
267
+ - **[Complete OpenTelemetry Example](https://github.com/i2y/edda/blob/main/examples/observability_with_opentelemetry.py)**: Full implementation with tracing, optional metrics, and CloudEvents context propagation
233
268
  - **[Observability Guide](https://github.com/i2y/edda/blob/main/examples/README_observability.md)**: Detailed guide with more integration examples
234
269
  - **[API Reference](https://github.com/i2y/edda/blob/main/edda/hooks.py)**: WorkflowHooks Protocol definition
@@ -36,6 +36,28 @@ Edda excels at orchestrating **long-running workflows** that must survive failur
36
36
  - **🤖 AI Agent Workflows**: Orchestrate multi-step AI tasks (LLM calls, tool usage, long-running inference)
37
37
  - **📡 Event-Driven Workflows**: React to external events with guaranteed delivery and automatic retry
38
38
 
39
+ ### Business Process Automation
40
+
41
+ Edda's waiting functions make it ideal for time-based and event-driven business processes:
42
+
43
+ - **📧 User Onboarding**: Send reminders if users haven't completed setup after N days
44
+ - **🎁 Campaign Processing**: Evaluate conditions and notify winners after campaign ends
45
+ - **💳 Payment Reminders**: Send escalating reminders before payment deadlines
46
+ - **📦 Scheduled Notifications**: Shipping updates, subscription renewals, appointment reminders
47
+
48
+ **Waiting functions**:
49
+ - `wait_timer(duration_seconds)`: Wait for a relative duration
50
+ - `wait_until(until_time)`: Wait until an absolute datetime (e.g., campaign end date)
51
+ - `wait_event(event_type)`: Wait for external events (near real-time response)
52
+
53
+ ```python
54
+ @workflow
55
+ async def onboarding_reminder(ctx: WorkflowContext, user_id: str):
56
+ await wait_timer(ctx, duration_seconds=3*24*60*60) # Wait 3 days
57
+ if not await check_completed(ctx, user_id):
58
+ await send_reminder(ctx, user_id)
59
+ ```
60
+
39
61
  **Key benefit**: Workflows **never lose progress** - crashes and restarts are handled automatically through deterministic replay.
40
62
 
41
63
  ## Architecture
@@ -7,10 +7,11 @@ Edda provides seamless integration with the [Model Context Protocol (MCP)](https
7
7
  MCP is a standardized protocol for AI tool integration. Edda's MCP integration automatically converts your durable workflows into MCP-compliant tools that:
8
8
 
9
9
  - **Start workflows** and return instance IDs immediately
10
- - **Check workflow status** to monitor progress
10
+ - **Check workflow status** to monitor progress with completed activity count and suggested poll interval
11
11
  - **Retrieve results** when workflows complete
12
+ - **Cancel workflows** if running or waiting, with automatic compensation execution
12
13
 
13
- This enables AI assistants to work with long-running processes that may take minutes, hours, or even days to complete.
14
+ This enables AI assistants to work with long-running processes that may take minutes, hours, or even days to complete, with full control over the workflow lifecycle.
14
15
 
15
16
  ## Installation
16
17
 
@@ -97,7 +98,7 @@ Add to your MCP client configuration (e.g., Claude Desktop: `~/Library/Applicati
97
98
 
98
99
  ## Auto-Generated Tools
99
100
 
100
- Each `@durable_tool` automatically generates **three MCP tools**:
101
+ Each `@durable_tool` automatically generates **four MCP tools**:
101
102
 
102
103
  ### 1. Main Tool: Start Workflow
103
104
 
@@ -125,12 +126,16 @@ Input: {"instance_id": "abc123..."}
125
126
  Output: {
126
127
  "content": [{
127
128
  "type": "text",
128
- "text": "Workflow Status: running\nCurrent Activity: payment:1\nInstance ID: abc123..."
129
+ "text": "Workflow Status: running\nCurrent Activity: payment:1\nCompleted Activities: 1\nSuggested Poll Interval: 5000ms\nInstance ID: abc123..."
129
130
  }],
130
131
  "isError": false
131
132
  }
132
133
  ```
133
134
 
135
+ The status tool provides progress metadata for efficient polling:
136
+ - **Completed Activities**: Number of activities that have finished
137
+ - **Suggested Poll Interval**: Recommended wait time before checking again (5000ms for running, 10000ms for waiting)
138
+
134
139
  ### 3. Result Tool: Get Final Result
135
140
 
136
141
  ```
@@ -147,6 +152,27 @@ Output: {
147
152
  }
148
153
  ```
149
154
 
155
+ ### 4. Cancel Tool: Stop Workflow
156
+
157
+ ```
158
+ Tool Name: process_order_cancel
159
+ Description: Cancel process_order workflow (if running or waiting)
160
+
161
+ Input: {"instance_id": "abc123..."}
162
+ Output: {
163
+ "content": [{
164
+ "type": "text",
165
+ "text": "Workflow 'process_order' cancelled successfully.\nInstance ID: abc123...\nCompensations executed.\n\nThe workflow has been stopped and any side effects have been rolled back."
166
+ }],
167
+ "isError": false
168
+ }
169
+ ```
170
+
171
+ The cancel tool:
172
+ - Only works on workflows with status `running`, `waiting_for_event`, or `waiting_for_timer`
173
+ - Automatically executes SAGA compensation transactions to roll back side effects
174
+ - Returns an error for already completed, failed, or cancelled workflows
175
+
150
176
  ## Advanced Configuration
151
177
 
152
178
  ### Authentication
@@ -0,0 +1,179 @@
1
+ # OpenTelemetry Integration
2
+
3
+ Edda provides official integration with [OpenTelemetry](https://opentelemetry.io/), enabling distributed tracing and optional metrics for your durable workflows.
4
+
5
+ ## Overview
6
+
7
+ OpenTelemetry is an industry-standard observability framework. Edda's OpenTelemetry integration provides:
8
+
9
+ - **Distributed Tracing**: Workflow and activity spans with parent-child relationships
10
+ - **Optional Metrics**: Counters for workflow/activity execution, histograms for duration
11
+ - **W3C Trace Context**: Propagate traces across service boundaries via CloudEvents
12
+ - **Automatic Context Inheritance**: Inherit from ASGI/WSGI middleware or CloudEvents headers
13
+
14
+ ## Installation
15
+
16
+ Install Edda with OpenTelemetry support:
17
+
18
+ ```bash
19
+ pip install edda-framework[opentelemetry]
20
+
21
+ # Or using uv
22
+ uv add edda-framework --extra opentelemetry
23
+ ```
24
+
25
+ ## Quick Start
26
+
27
+ ```python
28
+ from edda import EddaApp, workflow, activity, WorkflowContext
29
+ from edda.integrations.opentelemetry import OpenTelemetryHooks
30
+
31
+ # Create hooks (console exporter for development)
32
+ hooks = OpenTelemetryHooks(
33
+ service_name="order-service",
34
+ otlp_endpoint=None, # Use console exporter
35
+ enable_metrics=False,
36
+ )
37
+
38
+ # Or with OTLP exporter for production (Jaeger, Tempo, etc.)
39
+ hooks = OpenTelemetryHooks(
40
+ service_name="order-service",
41
+ otlp_endpoint="http://localhost:4317",
42
+ enable_metrics=True,
43
+ )
44
+
45
+ app = EddaApp(
46
+ service_name="order-service",
47
+ db_url="sqlite:///workflow.db",
48
+ hooks=hooks,
49
+ )
50
+
51
+ @activity
52
+ async def reserve_inventory(ctx: WorkflowContext, order_id: str):
53
+ return {"reserved": True}
54
+
55
+ @workflow
56
+ async def order_workflow(ctx: WorkflowContext, order_id: str):
57
+ await reserve_inventory(ctx, order_id)
58
+ return {"status": "completed"}
59
+
60
+ async def main():
61
+ await app.initialize()
62
+ await order_workflow.start(order_id="ORD-123")
63
+ ```
64
+
65
+ ## Span Hierarchy
66
+
67
+ Edda creates a hierarchical span structure:
68
+
69
+ ```
70
+ workflow:order_workflow (parent)
71
+ ├── activity:reserve_inventory:1 (child)
72
+ ├── activity:process_payment:1 (child)
73
+ └── activity:ship_order:1 (child)
74
+ ```
75
+
76
+ ## Span Attributes
77
+
78
+ **Workflow Spans**:
79
+ - `edda.workflow.instance_id`
80
+ - `edda.workflow.name`
81
+ - `edda.workflow.cancelled` (when cancelled)
82
+
83
+ **Activity Spans**:
84
+ - `edda.activity.id` (e.g., "reserve_inventory:1")
85
+ - `edda.activity.name`
86
+ - `edda.activity.is_replaying`
87
+ - `edda.activity.cache_hit`
88
+
89
+ ## Metrics (Optional)
90
+
91
+ When `enable_metrics=True`:
92
+
93
+ | Metric | Type | Description |
94
+ |--------|------|-------------|
95
+ | `edda.workflow.started` | Counter | Workflows started |
96
+ | `edda.workflow.completed` | Counter | Workflows completed |
97
+ | `edda.workflow.failed` | Counter | Workflows failed |
98
+ | `edda.workflow.duration` | Histogram | Workflow execution time |
99
+ | `edda.activity.executed` | Counter | Activities executed |
100
+ | `edda.activity.cache_hit` | Counter | Activity cache hits |
101
+ | `edda.activity.duration` | Histogram | Activity execution time |
102
+
103
+ ## Trace Context Propagation
104
+
105
+ ### Automatic Context Inheritance
106
+
107
+ OpenTelemetryHooks automatically inherits trace context from multiple sources, with the following priority:
108
+
109
+ 1. **Explicit `_trace_context` in input_data** (highest priority)
110
+ - Extracted from CloudEvents extension attributes
111
+ - Useful for cross-service trace propagation
112
+
113
+ 2. **Current active span** (e.g., from ASGI/WSGI middleware)
114
+ - Automatically detected using `trace.get_current_span()`
115
+ - Works with OpenTelemetry instrumentation middleware
116
+
117
+ 3. **New root span** (if no parent context is found)
118
+
119
+ ### CloudEvents Integration
120
+
121
+ Inject trace context when sending events:
122
+
123
+ ```python
124
+ from edda.integrations.opentelemetry import inject_trace_context
125
+
126
+ event_data = {"order_id": "ORD-123"}
127
+ event_data = inject_trace_context(hooks, ctx.instance_id, event_data)
128
+ await send_event_transactional(ctx, "order.shipped", "orders", event_data)
129
+ ```
130
+
131
+ When a CloudEvent contains W3C Trace Context extension attributes (`traceparent`, `tracestate`), they are automatically extracted and used as the parent context:
132
+
133
+ ```bash
134
+ # CloudEvent with trace context
135
+ curl -X POST http://localhost:8001/ \
136
+ -H "Content-Type: application/json" \
137
+ -H "ce-specversion: 1.0" \
138
+ -H "ce-type: order.created" \
139
+ -H "ce-source: external-service" \
140
+ -H "ce-id: event-123" \
141
+ -H "ce-traceparent: 00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01" \
142
+ -d '{"order_id": "ORD-123"}'
143
+ ```
144
+
145
+ ### ASGI/WSGI Middleware
146
+
147
+ OpenTelemetryHooks automatically inherits from the current active span:
148
+
149
+ ```python
150
+ from opentelemetry.instrumentation.asgi import OpenTelemetryMiddleware
151
+
152
+ # Middleware creates parent span for each HTTP request
153
+ app = OpenTelemetryMiddleware(edda_app)
154
+
155
+ # Workflow spans automatically inherit from the request span
156
+ ```
157
+
158
+ ### Existing TracerProvider Reuse
159
+
160
+ If a TracerProvider is already configured (e.g., by ASGI middleware or your application), OpenTelemetryHooks will reuse it instead of creating a new one:
161
+
162
+ ```python
163
+ from opentelemetry import trace
164
+ from opentelemetry.sdk.trace import TracerProvider
165
+
166
+ # Configure your own provider
167
+ provider = TracerProvider(resource=my_resource)
168
+ trace.set_tracer_provider(provider)
169
+
170
+ # OpenTelemetryHooks will use the existing provider
171
+ hooks = OpenTelemetryHooks(service_name="my-service")
172
+ # No new provider is created!
173
+ ```
174
+
175
+ ## Related Documentation
176
+
177
+ - [Lifecycle Hooks](../core-features/hooks.md) - Detailed hooks documentation
178
+ - [Example](../../examples/observability_with_opentelemetry.py) - Complete working example
179
+ - [OpenTelemetry Documentation](https://opentelemetry.io/docs/)
@@ -238,7 +238,9 @@ class EddaApp:
238
238
  Register a default CloudEvent handler for a workflow.
239
239
 
240
240
  The default handler extracts the CloudEvent data and passes it
241
- as kwargs to workflow.start().
241
+ as kwargs to workflow.start(). If the CloudEvent contains
242
+ traceparent/tracestate extension attributes (for distributed tracing),
243
+ they are automatically injected into _trace_context.
242
244
 
243
245
  Args:
244
246
  event_type: CloudEvent type (same as workflow name)
@@ -250,11 +252,24 @@ class EddaApp:
250
252
  # Extract data from CloudEvent
251
253
  data = event.get_data()
252
254
 
255
+ # Extract trace context from CloudEvent extension attributes
256
+ # (W3C Trace Context: traceparent, tracestate)
257
+ trace_context: dict[str, str] = {}
258
+ attrs = event.get_attributes()
259
+ if "traceparent" in attrs:
260
+ trace_context["traceparent"] = str(attrs["traceparent"])
261
+ if "tracestate" in attrs:
262
+ trace_context["tracestate"] = str(attrs["tracestate"])
263
+
253
264
  # Start workflow with data as kwargs
254
265
  if isinstance(data, dict):
266
+ # Inject trace context if present
267
+ if trace_context:
268
+ data = {**data, "_trace_context": trace_context}
255
269
  await wf.start(**data)
256
270
  else:
257
271
  # If data is not a dict, start without arguments
272
+ # (trace context cannot be injected)
258
273
  await wf.start()
259
274
 
260
275
  # Register the handler
@@ -12,8 +12,8 @@ Example:
12
12
  ... async def on_workflow_start(self, instance_id, workflow_name, input_data):
13
13
  ... print(f"Workflow {workflow_name} started: {instance_id}")
14
14
  ...
15
- ... async def on_activity_complete(self, instance_id, step, activity_name, result, cache_hit):
16
- ... print(f"Activity {activity_name} completed (cache_hit={cache_hit})")
15
+ ... async def on_activity_complete(self, instance_id, activity_id, activity_name, result, cache_hit):
16
+ ... print(f"Activity {activity_name} ({activity_id}) completed (cache_hit={cache_hit})")
17
17
  >>>
18
18
  >>> app = EddaApp(service_name="my-service", db_url="...", hooks=MyHooks())
19
19
  """
@@ -86,7 +86,7 @@ class WorkflowHooks(Protocol):
86
86
  async def on_activity_start(
87
87
  self,
88
88
  instance_id: str,
89
- step: int,
89
+ activity_id: str,
90
90
  activity_name: str,
91
91
  is_replaying: bool,
92
92
  ) -> None:
@@ -95,7 +95,7 @@ class WorkflowHooks(Protocol):
95
95
 
96
96
  Args:
97
97
  instance_id: Unique workflow instance ID
98
- step: Step number in the workflow
98
+ activity_id: Activity ID (e.g., "reserve_inventory:1")
99
99
  activity_name: Name of the activity function
100
100
  is_replaying: True if this is a replay (cached result)
101
101
  """
@@ -104,7 +104,7 @@ class WorkflowHooks(Protocol):
104
104
  async def on_activity_complete(
105
105
  self,
106
106
  instance_id: str,
107
- step: int,
107
+ activity_id: str,
108
108
  activity_name: str,
109
109
  result: Any,
110
110
  cache_hit: bool,
@@ -114,7 +114,7 @@ class WorkflowHooks(Protocol):
114
114
 
115
115
  Args:
116
116
  instance_id: Unique workflow instance ID
117
- step: Step number in the workflow
117
+ activity_id: Activity ID (e.g., "reserve_inventory:1")
118
118
  activity_name: Name of the activity function
119
119
  result: Return value from the activity
120
120
  cache_hit: True if result was retrieved from cache (replay)
@@ -124,7 +124,7 @@ class WorkflowHooks(Protocol):
124
124
  async def on_activity_failed(
125
125
  self,
126
126
  instance_id: str,
127
- step: int,
127
+ activity_id: str,
128
128
  activity_name: str,
129
129
  error: Exception,
130
130
  ) -> None:
@@ -133,7 +133,7 @@ class WorkflowHooks(Protocol):
133
133
 
134
134
  Args:
135
135
  instance_id: Unique workflow instance ID
136
- step: Step number in the workflow
136
+ activity_id: Activity ID (e.g., "reserve_inventory:1")
137
137
  activity_name: Name of the activity function
138
138
  error: Exception that caused the failure
139
139
  """
@@ -231,7 +231,7 @@ class HooksBase(WorkflowHooks, ABC):
231
231
  async def on_activity_start(
232
232
  self,
233
233
  instance_id: str,
234
- step: int,
234
+ activity_id: str,
235
235
  activity_name: str,
236
236
  is_replaying: bool,
237
237
  ) -> None:
@@ -240,7 +240,7 @@ class HooksBase(WorkflowHooks, ABC):
240
240
  async def on_activity_complete(
241
241
  self,
242
242
  instance_id: str,
243
- step: int,
243
+ activity_id: str,
244
244
  activity_name: str,
245
245
  result: Any,
246
246
  cache_hit: bool,
@@ -250,7 +250,7 @@ class HooksBase(WorkflowHooks, ABC):
250
250
  async def on_activity_failed(
251
251
  self,
252
252
  instance_id: str,
253
- step: int,
253
+ activity_id: str,
254
254
  activity_name: str,
255
255
  error: Exception,
256
256
  ) -> None: