The Temporal adapter is signal-based. Inside a workflow,Documentation Index
Fetch the complete documentation index at: https://docs.awaithumans.dev/llms.txt
Use this file to discover all available pages before exploring further.
await_human() registers a signal handler and workflow.wait_condition() parks the workflow. Outside, your web server receives the awaithumans completion webhook and signals the workflow back to life.
Install
Workflow side
workflow.unsafe.imports_passed_through() context tells Temporal’s sandbox the import is safe — required because the awaithumans adapter module imports things the sandbox would otherwise block.
Web-server side
Your web server hosts the callback receiver. The awaithumans server POSTs there when the human completes; you signal the workflow back.dispatch_signal() does the security-critical bits (HMAC verify, payload parse, signal routing). The route is just web-framework glue.
Constructing callback_url
The workflow ID has to round-trip through the awaithumans server. The simplest pattern: bake it into the callback URL as a query param.
Error contract
The adapter maps webhook status to typed Python exceptions:| Webhook status | Exception |
|---|---|
completed | (return validated response) |
timed_out | TaskTimeoutError |
cancelled | TaskCancelledError |
verification_exhausted | VerificationExhaustedError |
Why this works under failure
- Worker dies during the await — Temporal restarts the workflow;
await_human()re-registers the signal handler with the same idempotency key. The awaithumans server returns the existing task (idempotency dedup). When the human eventually completes, the signal fires on the live worker. - Callback server is down when the human submits — the awaithumans server’s outbound webhook fails-loudly in its logs but doesn’t retry. The workflow times out at
timeout_seconds, raisesTaskTimeoutError, and the operator sees the abandoned task in the dashboard. - awaithumans server restarts — tasks are persisted; on restart the timeout scheduler resumes and the dashboard reconnects.
End-to-end example
Theexamples/temporal/ directory in the repo is runnable on a laptop in three terminal windows. See the README in that folder for the full setup.
Cross-language
The TypeScript adapter atawaithumans/temporal produces the same wire format. A Python workflow can have its callback handler resume from a TypeScript web server and vice versa — the HMAC derivation is identical (HKDF over PAYLOAD_KEY with the same salt + info bytes).
Common gotchas
AWAITHUMANS_PAYLOAD_KEYmismatch — the HMAC signing key derives from this. If the awaithumans server and the callback receiver have different keys, signatures never verify. Use the same value on both processes.callback_urlnot reachable — the awaithumans server logs showWebhook delivery failed task=… url=…: …. Most often a tunnel/firewall issue.- Duplicate idempotency key — two
await_human()calls in the same workflow with the same(task, payload)get the same key by default. Passidempotency_key=explicitly to disambiguate. See Idempotency.
Where to next
- Webhooks (
callback_url) — full wire format, retry policy, signature scheme - Testing — driving fixtures past the workflow boundary
- LangGraph adapter — the same pattern with interrupt/resume instead of signals