Welcome to the new Golem Cloud Docs! 👋
Recurring Tasks via Self-Scheduling (Rust)

Recurring Tasks via Self-Scheduling (Rust)

Overview

A Golem agent can act as its own scheduler by calling schedule_ on itself at the end of each invocation. This creates a durable, crash-resilient recurring task — if the agent restarts, the scheduled invocation is still pending and will fire at the designated time.

Basic Pattern

The agent schedules its own method to run again after a delay:

use golem_rust::wasip2::clocks::wall_clock::Datetime;
 
#[agent_definition]
pub trait PollerAgent: HasSchema {
    fn new(name: String) -> Self;
    fn start(&mut self);
    fn poll(&mut self);
}
 
impl PollerAgent for PollerAgentImpl {
    fn new(name: String) -> Self {
        Self { name }
    }
 
    fn start(&mut self) {
        // Kick off the first poll
        self.poll();
    }
 
    fn poll(&mut self) {
        // 1. Do the recurring work
        do_work();
 
        // 2. Schedule the next run (60 seconds from now)
        let mut client = PollerAgentClient::get(self.name.clone());
        let now_secs = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .unwrap()
            .as_secs();
        client.schedule_poll(Datetime {
            seconds: now_secs + 60,
            nanoseconds: 0,
        });
    }
}

Exponential Backoff

Increase the delay on repeated failures, reset on success:

fn poll(&mut self) {
    let success = try_work();
 
    let delay = if success {
        self.consecutive_failures = 0;
        self.base_interval_secs // e.g. 60
    } else {
        self.consecutive_failures += 1;
        let backoff = self.base_interval_secs * 2u64.pow(self.consecutive_failures.min(6));
        backoff.min(self.max_interval_secs) // cap at e.g. 3600
    };
 
    let mut client = PollerAgentClient::get(self.name.clone());
    let now_secs = std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .unwrap()
        .as_secs();
    client.schedule_poll(Datetime {
        seconds: now_secs + delay,
        nanoseconds: 0,
    });
}

Cancellation with CancellationToken

The Rust SDK generates schedule_cancelable_{method} variants that return a CancellationToken. Store the token and cancel it to stop the next scheduled invocation:

fn poll(&mut self) {
    if self.cancelled {
        return; // stop the loop
    }
 
    do_work();
 
    // Schedule next run and store the cancellation token
    let mut client = PollerAgentClient::get(self.name.clone());
    let now_secs = std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .unwrap()
        .as_secs();
    self.pending_token = Some(client.schedule_cancelable_poll(Datetime {
        seconds: now_secs + 60,
        nanoseconds: 0,
    }));
}
 
fn cancel(&mut self) {
    self.cancelled = true;
    // Cancel the pending scheduled invocation so it never fires
    if let Some(token) = self.pending_token.take() {
        token.cancel();
    }
}

Cancellation via State Flag

For simpler cases, just use a boolean flag — the next scheduled poll checks it and exits early:

fn poll(&mut self) {
    if self.cancelled {
        return;
    }
    do_work();
    self.schedule_next(60);
}
 
fn cancel(&mut self) {
    self.cancelled = true;
}

Cancellation from the CLI

Schedule with an explicit idempotency key and cancel the pending invocation:

# Schedule with a known idempotency key
golem agent invoke --trigger --schedule-at 2026-03-15T10:30:00Z -i 'poll-next' 'PollerAgent("my-poller")' poll
 
# Cancel the pending invocation
golem agent invocation cancel 'PollerAgent("my-poller")' 'poll-next'

Common Use Cases

Periodic Polling

Check an external API or queue for new work at regular intervals:

fn poll(&mut self) {
    let items = fetch_pending_items();
    for item in items {
        process(item);
    }
    self.schedule_next(60); // poll again in 60s
}

Periodic Cleanup

Remove expired data or stale resources on a schedule:

fn cleanup(&mut self) {
    self.entries.retain(|e| !e.is_expired());
    self.schedule_next(3600); // run hourly
}

Heartbeat / Keep-Alive

Periodically notify an external service that the agent is alive:

fn heartbeat(&mut self) {
    send_heartbeat(&self.service_url);
    self.schedule_next(30); // every 30s
}

Helper for Scheduling Self

Extract the scheduling logic into a helper to keep methods clean:

impl PollerAgentImpl {
    fn schedule_next(&self, delay_secs: u64) {
        let mut client = PollerAgentClient::get(self.name.clone());
        let now_secs = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .unwrap()
            .as_secs();
        client.schedule_poll(Datetime {
            seconds: now_secs + delay_secs,
            nanoseconds: 0,
        });
    }
}

Key Points

  • The agent is durable — if it crashes, the pending scheduled invocation still fires and the agent recovers
  • Invocations are sequential — no concurrent executions of poll on the same agent
  • Each schedule_ call is a fire-and-forget enqueue; the current invocation completes immediately
  • Use a state flag or generation counter to stop the loop gracefully
  • Keep the scheduled method idempotent — it may be retried on recovery