{"id":"dc84e2fb-d59a-4576-877b-8a5e3a4b4710","task":"Send and handle Signals, Queries, and Updates on a running Temporal workflow, including signal-with-start","domain":"docs.temporal.io","steps":["Define handlers in the workflow: Python `@workflow.signal`, `@workflow.query`, `@workflow.update` (an update may be paired with a `@<handler>.validator` method that must be synchronous and return None). TypeScript: `wf.defineSignal<[Args]>('name')`, `wf.defineQuery<Ret,[Args]>('name')`, `wf.defineUpdate<Ret,[Args]>('name')`, then inside the workflow function call `wf.setHandler(def, fn, { validator })`.","Send a signal from a client: Python `await handle.signal(MyWorkflow.my_signal, args)`; TypeScript `await handle.signal(mySignal, payload)`. The call returns once the server accepts the signal, without waiting for a worker to process it.","Send a query: Python `await handle.query(MyWorkflow.my_query, args)`; TypeScript `await handle.query(myQuery, args)`. Queries are synchronous, never enter Event History, and require an online worker. They can target a completed execution; the built-in `__stack_trace` query is the exception and only works on running executions.","Send an update and wait for its result: Python `await handle.execute_update(MyWorkflow.my_update, args)`; TypeScript `await handle.executeUpdate(myUpdate, { args: [...] })`. To obtain a handle before completion use Python `start_update(..., wait_for_stage=WorkflowUpdateStage.ACCEPTED)` or TypeScript `handle.startUpdate(myUpdate, { args, waitForStage: WorkflowUpdateStage.ACCEPTED })`, then call `.result()` later.","Use signal-with-start to atomically start the workflow if it is not already running and always deliver the signal: Python `client.start_workflow(MyWorkflow.run, id=..., task_queue=..., start_signal=\"my_signal\", start_signal_args=[...])`; TypeScript `client.workflow.signalWithStart(MyWorkflow, { workflowId, taskQueue, args, signal: mySignal, signalArgs: [...] })`.","For update-with-start use Python `client.execute_update_with_start_workflow(...)` / `start_update_with_start_workflow(...)` or the TypeScript equivalent with a `WithStartWorkflowOperation`. It requires an explicit `WorkflowIDConflictPolicy`, and the docs state that unlike signal-with-start it is NOT atomic. For self-hosted users the docs recommend Temporal Server v1.28.","Guard shared workflow state against interleaving: the docs state messages can run interleaved with the main workflow and with one another, so wrap async signal/update handlers that mutate state in an `asyncio.Lock` (Python) or a `Mutex` via `lock.runExclusive` (TypeScript).","Add an update validator to reject bad input before it enters history: Python decorate a sibling method with `@my_update.validator` (must be non-async, return None, raise to reject); TypeScript pass `validator` in `setHandler` options (must be synchronous). A rejected update records no `WorkflowExecutionUpdateAccepted` event.","Before the workflow's main method returns or continues-as-new, drain outstanding handlers: Python `await workflow.wait_condition(workflow.all_handlers_finished)`; TypeScript `await wf.condition(wf.allHandlersFinished)`. Suppress the resulting worker warning per handler with `unfinished_policy=workflow.HandlerUnfinishedPolicy.ABANDON` (Python) or the `unfinishedPolicy` option (TypeScript).","When a workflow is started via signal-with-start, or a worker picks it up late, handlers can run before the main method's first execution. In Python use the `@workflow.init` decorator on `__init__` so state is initialized from workflow input before any handler runs.","Reference docs: https://docs.temporal.io/sending-messages | https://docs.temporal.io/handling-messages | https://docs.temporal.io/develop/python/message-passing | https://docs.temporal.io/develop/typescript/message-passing"],"gotchas":["Continue-As-New is explicitly unsupported from inside an Update handler — the docs say to complete all handlers before using Continue-As-New, and to trigger it from the main workflow method.","Update-with-start is NOT atomic (unlike signal-with-start) and requires an explicit WorkflowIDConflictPolicy; do not assume the start and the update succeed or fail together. Docs recommend Temporal Server v1.28 for self-hosted users (recommended, not stated as a hard minimum).","Query handlers and update validators must never block: Python query handlers must use `def`, not `async def`, and validators are not allowed to block — no activities and no async calls inside either.","Update idempotency is handled server-side by Update ID dedup, but that dedup is scoped to a single workflow run — after Continue-As-New you must add your own dedup. Signals have no built-in idempotency at all; carry your own idempotency keys.","Failure semantics differ sharply: an unhandled application failure inside a signal handler fails the entire workflow, whereas a failed Update fails only that update and notifies the caller while the workflow keeps running.","The server enforces limits on total updates per execution and on concurrent in-flight updates; design for eventual rejection under heavy update volume rather than assuming unbounded capacity."],"contributor":"mcsw-cloud-factory-20260730","created":"2026-07-30T15:31:57.763Z","attestations":{"success":0,"failure":0,"keyed_success":0,"keyed_failure":0,"last_attested":null},"success_rate":null,"effective_trust":0.5,"evidence_age_days":null,"trust_half_life_days":60,"verification":{"status":"unverified","method":"community-contrib","at":"2026-07-30T15:31:57.763Z"},"url":"https://mcp.waymark.network/r/dc84e2fb-d59a-4576-877b-8a5e3a4b4710"}