Skip to Content
How-To GuidesRustRecurring tasks via self-scheduling (Rust)

Recurring tasks via self-scheduling (Rust)

A durable agent can schedule its own next invocation after completing each tick. Scheduled invocations survive recovery and execute sequentially with the agent’s other invocations.

Use the current #[agent_definition] and #[agent_implementation] macros, construct a golem_rust::ScheduledTime, pass method arguments before the scheduled time, and handle the generated scheduling result:

use golem_rust::{ScheduledTime, agent_definition, agent_implementation}; use std::time::{Duration, SystemTime, UNIX_EPOCH}; fn after(delay: Duration) -> ScheduledTime { let at = SystemTime::now() .duration_since(UNIX_EPOCH) .expect("time went backwards") + delay; ScheduledTime { seconds: at.as_secs() as i64, nanoseconds: at.subsec_nanos(), } } #[agent_definition] pub trait PollerAgent { fn new(name: String) -> Self; async fn poll(&mut self, endpoint: String); } struct PollerAgentImpl { name: String, stopped: bool, } #[agent_implementation] impl PollerAgent for PollerAgentImpl { fn new(name: String) -> Self { Self { name, stopped: false } } async fn poll(&mut self, endpoint: String) { if self.stopped { return; } do_work(&endpoint).await; let me = PollerAgentClient::get(self.name.clone()); me.schedule_poll(endpoint, after(Duration::from_secs(60))) .expect("failed to schedule next poll"); } }

Generated durable-agent scheduling methods return Result<(), golem_rust::golem_agentic::golem::agent::host::RpcError>. Never discard that result: a failed enqueue breaks the recurring chain. For a method with arguments, the signature is schedule_<method>(arguments..., scheduled_time); ScheduledTime is always last.

Backoff

Store the consecutive failure count in agent state. Reset it after success; otherwise compute a capped delay and schedule one next invocation. For example:

let delay = if succeeded { self.consecutive_failures = 0; 60 } else { self.consecutive_failures += 1; (60 * 2u64.pow(self.consecutive_failures.min(6))).min(3600) }; PollerAgentClient::get(self.name.clone()) .schedule_poll(endpoint, after(Duration::from_secs(delay))) .expect("failed to schedule retry");

Cancel the pending tick

schedule_cancelable_<method>(arguments..., scheduled_time) returns Result<CancellationToken, RpcError> for a durable agent. Store the token in agent state and call cancel() to prevent that scheduled invocation from starting:

let token = PollerAgentClient::get(self.name.clone()) .schedule_cancelable_poll(endpoint, after(Duration::from_secs(60))) .expect("failed to schedule next poll"); self.pending = Some(token); if let Some(token) = self.pending.take() { token.cancel(); }

A state flag is still useful because a tick may already have started when cancellation races with delivery. Keep only one pending tick unless overlapping schedules are intentional.

Recurring invocations grow the oplog. For long-lived or frequent loops, configure periodic snapshots; do not try to bypass durability. Load golem-custom-snapshot-rust for that workflow.