@@ -185,6 +185,72 @@ receipt = yield ctx.start_child_workflow(
185185)
186186```
187187
188+ ## Local activities
189+
190+ Use a local activity for short work that should run in the workflow worker
191+ process without a separate activity task. Register it as an activity on the
192+ same ` Worker ` , then yield ` ctx.local_activity(...) ` from the workflow:
193+
194+ ``` python
195+ import asyncio
196+ import os
197+ from uuid import uuid4
198+
199+ from durable_workflow import Client, Worker, activity, workflow
200+
201+
202+ @activity.defn (name = " orders.normalize" )
203+ def normalize (order_id : str ) -> str :
204+ return order_id.strip().upper()
205+
206+
207+ @workflow.defn (name = " orders.prepare" )
208+ class PrepareOrder :
209+ def run (self , ctx , order_id ):
210+ normalized = yield ctx.local_activity(
211+ " orders.normalize" ,
212+ [order_id],
213+ retry_policy = {" max_attempts" : 2 , " backoff_seconds" : [1 ]},
214+ start_to_close_timeout = 5 ,
215+ )
216+ return {" order_id" : normalized}
217+
218+
219+ async def main ():
220+ async with Client(
221+ os.getenv(" DURABLE_WORKFLOW_RUNTIME_URL" , " http://127.0.0.1:8080" ),
222+ token = os.getenv(" DURABLE_WORKFLOW_TOKEN" , " dev-token-123" ),
223+ namespace = " default" ,
224+ ) as client:
225+ worker = Worker(
226+ client,
227+ task_queue = " orders" ,
228+ workflows = [PrepareOrder],
229+ activities = [normalize],
230+ )
231+ handle = await client.start_workflow(
232+ workflow_type = " orders.prepare" ,
233+ workflow_id = f " order- { uuid4().hex} " ,
234+ task_queue = " orders" ,
235+ input = [" a-123 " ],
236+ )
237+ await worker.run_until(workflow_id = handle.workflow_id, timeout = 30.0 )
238+ print (await handle.result(timeout = 10.0 ))
239+
240+
241+ asyncio.run(main())
242+ ```
243+
244+ Use the Server setup from the [ quickstart] ( index.md#first-workflow ) , or replace
245+ the URL and credentials with those of a provisioned Cloud namespace. The worker
246+ records the local result with its workflow task. Once Server accepts that
247+ record, replay reads the result instead of running the handler again. If the
248+ worker loses its lease or crashes before the record is committed, the handler
249+ may run again on a replacement worker. Make side effects idempotent and keep
250+ local attempts short; use ` ctx.schedule_activity(...) ` for separately queued
251+ work. Local activities are yielded one at a time; they are not members of a
252+ parallel workflow group.
253+
188254## Deterministic parallel groups
189255
190256Yield a list to schedule one durable parallel barrier. Lists can nest and mix
0 commit comments