ContinueAsNew Semantics
February 17, 2026 · View on GitHub
ContinueAsNew (CAN) terminates the current execution and starts a new execution for the same instance with fresh input and empty history.
API
// Returns a future that never resolves - use with `return` and `await`
fn continue_as_new(&self, input: impl Into<String>) -> impl Future<Output = Result<String, String>>
fn continue_as_new_typed<T: Serialize>(&self, input: &T) -> impl Future<Output = Result<String, String>>
fn continue_as_new_versioned(&self, version: &str, input: &str) -> impl Future<Output = Result<String, String>>
Usage pattern:
async fn my_orchestration(ctx: OrchestrationContext, input: String) -> Result<String, String> {
// Do some work...
let result = ctx.schedule_activity("DoWork", input.clone()).await?;
if should_continue(&result) {
// MUST use `return` and `.await` - future never resolves
return ctx.continue_as_new(result).await;
}
Ok(result)
}
Important: continue_as_new() returns a future that never resolves. You must:
- Call
.awaiton it - Use
returnto ensure code after is unreachable
The compiler will warn about unreachable code if you forget return.
Key Points
- Terminal for current execution: The current execution appends
OrchestrationContinuedAsNew { input }and stops. - New execution history: The runtime creates a new execution (incremented execution_id) and stamps a fresh
OrchestrationStartedwith the provided input. The provider simply persists what the runtime sends. - Event targeting: Activity/timer/external events always target the currently active execution. After CAN, the next execution must resubscribe/re-arm as needed.
- Result propagation: The initial start handle resolves with an empty output when the first execution continues-as-new; the runtime then automatically starts the next execution.
Flow
- Orchestrator calls
return ctx.continue_as_new(new_input).await. - Runtime extracts pending actions (including
ContinueAsNew) before checking future state. - Runtime appends terminal
OrchestrationContinuedAsNewto the current execution. - Runtime enqueues a
WorkItem::ContinueAsNewand later processes it as a new execution with empty history, stampingOrchestrationStarted { name, version, input: new_input, parent_instance?, parent_id? }. - Provider receives
ack_orchestration_item(lock_token, execution_id = prev + 1, ...)and atomically persists the new execution row, updatesinstances.current_execution_id = MAX(..., execution_id), and appends events.
Practical Tips
- Re-create subscriptions and timers in the new execution; carry forward any needed state via the new input.
- External events sent before the new execution subscribes will be dropped; raise them after the new execution has subscribed.
- Use deterministic correlation IDs if you coordinate sub-orchestrations across executions.
- Sessions survive ContinueAsNew — the session row persists, and the new execution
can schedule activities on the same
session_id. Activities route to the same worker.
Pruning Old Executions
Long-running workflows using ContinueAsNew will accumulate execution history over time. Use prune_executions to clean up old executions while preserving the current execution:
use duroxide::PruneOptions;
// Prune to only the current execution (recommended)
client.prune_executions("my-instance", PruneOptions {
keep_last: None, // No count-based retention
..Default::default()
}).await?;
// Keep the last 10 executions
client.prune_executions("my-instance", PruneOptions {
keep_last: Some(10),
..Default::default()
}).await?;
// Or prune by age (executions older than 30 days)
let thirty_days_ago = now_ms - (30 * 24 * 60 * 60 * 1000);
client.prune_executions("my-instance", PruneOptions {
completed_before: Some(thirty_days_ago),
..Default::default()
}).await?;
Safety guarantees:
- The current execution (highest execution_id) is never pruned
- This protection applies to both running and terminal instances
- Pruning can be done while the workflow is actively running
Note on keep_last semantics: Since the current execution is always protected and is always the highest execution_id, keep_last: None, Some(0), and Some(1) are all equivalent—they all prune down to exactly the current execution. Use None for clarity when you want to prune all historical executions.