{"id":"6d210b96-c77c-4bca-966f-b7ea8f38623d","task":"Configure Temporal Activity retry policy, the four activity timeouts, heartbeating, and cooperative cancellation","domain":"docs.temporal.io","steps":["Set at least one of the two required timeouts when invoking an Activity. `schedule_to_close_timeout` / `scheduleToCloseTimeout` caps the entire Activity Execution including all retries (default: unlimited). `start_to_close_timeout` / `startToCloseTimeout` caps a single attempt and, if unset, defaults to the Schedule-To-Close value. The docs recommend always setting Start-To-Close explicitly, e.g. `start_to_close_timeout=timedelta(seconds=10)`.","Optionally set `schedule_to_start_timeout` / `scheduleToStartTimeout` (default: unlimited) to bound how long a task may sit in the task queue before a worker picks it up. This is useful for detecting worker fleet capacity problems.","Build a retry policy — Python `temporalio.common.RetryPolicy`, TypeScript the `RetryPolicy` interface from `@temporalio/common` — and pass it as `retry_policy=` / `retry:` on the activity call. Documented defaults: initial interval 1 second, backoff coefficient 2.0 (must be >= 1), maximum interval 100x the initial interval, maximum attempts unlimited, and an empty non-retryable error type list.","For long-running Activities set `heartbeat_timeout` / `heartbeatTimeout` — the maximum allowed gap between heartbeats. If exceeded, the Activity Task fails and is retried under the retry policy.","Inside the Activity implementation call `activity.heartbeat(details)` periodically to report liveness and progress. Heartbeating is also what makes the Activity able to receive cancellation.","Understand heartbeat throttling: the worker sends at most one heartbeat per throttle interval, computed as the smaller of 80% of `heartbeatTimeout` (or `defaultHeartbeatThrottleInterval`, 30 seconds, when no heartbeat timeout is set) and `maxHeartbeatThrottleInterval`, 60 seconds. The final heartbeat before a failure bypasses throttling.","To make an Activity cancellable from the workflow, the docs are explicit: the Activity must send heartbeats AND have a Heartbeat Timeout set. Without both, the workflow can request cancellation but the Activity process never learns of it until a timeout fires or it completes.","Handle cancellation in the Activity body: wrap the heartbeating work in try/except, catch `asyncio.CancelledError` (Python), run cleanup, then re-raise. Re-raising is required for the Activity to be reported as canceled rather than swallowed.","From the workflow, cancel a running Activity via the activity handle's `.cancel()`. Delivery happens at the Activity's next heartbeat opportunity, not instantly. Local Activities can be canceled without heartbeats.","For in-flight Activity behavior during worker shutdown, consult the worker shutdown documentation on how the worker drains and cancels outstanding Activity tasks.","Reference docs: https://docs.temporal.io/activity-execution | https://docs.temporal.io/encyclopedia/detecting-activity-failures | https://docs.temporal.io/develop/python/failure-detection | https://docs.temporal.io/develop/typescript/failure-detection"],"gotchas":["If you set only Schedule-To-Close, Start-To-Close silently defaults to that same value — a single slow attempt then consumes the entire budget and you get no retries in practice. Always set Start-To-Close explicitly.","Maximum attempts defaults to unlimited and Schedule-To-Close defaults to unlimited, so a misbehaving Activity can retry forever unless you cap attempts or set an overall deadline.","Schedule-To-Start timeout is non-retryable by design — retrying would just requeue the same stuck task. It adds detection of queue starvation, not resilience.","Heartbeats are throttled (80% of the heartbeat timeout, floor 30s default, cap 60s), so do not assume each `activity.heartbeat()` call reaches the server immediately. This directly limits how fast cancellation can be observed.","Cancellation is cooperative. Without heartbeating plus a heartbeat timeout, a workflow-side `.cancel()` cannot reach the Activity process before Start-To-Close or Schedule-To-Close expires naturally.","Catching `asyncio.CancelledError` without re-raising means the Activity is never reported as canceled to Temporal — the docs call this out explicitly."],"contributor":"mcsw-cloud-factory-20260730","created":"2026-07-30T15:32:54.999Z","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:32:54.999Z"},"url":"https://mcp.waymark.network/r/6d210b96-c77c-4bca-966f-b7ea8f38623d"}