Recurring Tasks via Self-Scheduling (TypeScript)
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:
import { agent, BaseAgent } from '@golemcloud/golem-ts-sdk';
@agent()
class PollerAgent extends BaseAgent {
name: string;
constructor(name: string) {
super();
this.name = name;
}
start(): void {
this.poll();
}
poll(): void {
// 1. Do the recurring work
doWork();
// 2. Schedule the next run (60 seconds from now)
const self = PollerAgent.get(this.name);
const nowSecs = BigInt(Math.floor(Date.now() / 1000));
self.poll.schedule({ seconds: nowSecs + 60n, nanoseconds: 0 });
}
}Exponential Backoff
Increase the delay on repeated failures, reset on success:
@agent()
class PollerAgent extends BaseAgent {
name: string;
consecutiveFailures: number = 0;
baseIntervalSecs: bigint = 60n;
maxIntervalSecs: bigint = 3600n;
constructor(name: string) {
super();
this.name = name;
}
poll(): void {
const success = tryWork();
let delay: bigint;
if (success) {
this.consecutiveFailures = 0;
delay = this.baseIntervalSecs;
} else {
this.consecutiveFailures++;
const exp = Math.min(this.consecutiveFailures, 6);
delay = this.baseIntervalSecs * BigInt(2 ** exp);
if (delay > this.maxIntervalSecs) delay = this.maxIntervalSecs;
}
const self = PollerAgent.get(this.name);
const nowSecs = BigInt(Math.floor(Date.now() / 1000));
self.poll.schedule({ seconds: nowSecs + delay, nanoseconds: 0 });
}
}Cancellation with CancellationToken
Every method on the generated client has a .scheduleCancelable() variant that returns a CancellationToken. Store the token and call .cancel() to prevent the scheduled invocation from firing:
import { CancellationToken } from '@golemcloud/golem-ts-sdk';
@agent()
class PollerAgent extends BaseAgent {
name: string;
cancelled: boolean = false;
pendingToken: CancellationToken | undefined;
constructor(name: string) {
super();
this.name = name;
}
poll(): void {
if (this.cancelled) {
return;
}
doWork();
const self = PollerAgent.get(this.name);
const nowSecs = BigInt(Math.floor(Date.now() / 1000));
this.pendingToken = self.poll.scheduleCancelable(
{ seconds: nowSecs + 60n, nanoseconds: 0 },
);
}
cancel(): void {
this.cancelled = true;
if (this.pendingToken) {
this.pendingToken.cancel();
this.pendingToken = undefined;
}
}
}Cancellation via State Flag
For simpler cases, just use a boolean flag — the next scheduled poll checks it and exits early:
poll(): void {
if (this.cancelled) return;
doWork();
this.scheduleNext(60n);
}
cancel(): void {
this.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 cancel-invocation 'PollerAgent("my-poller")' 'poll-next'Common Use Cases
Periodic Polling
Check an external API or queue for new work at regular intervals:
poll(): void {
const items = fetchPendingItems();
for (const item of items) {
process(item);
}
this.scheduleNext(60n);
}Periodic Cleanup
Remove expired data or stale resources on a schedule:
cleanup(): void {
this.entries = this.entries.filter(e => !e.isExpired());
this.scheduleNext(3600n); // run hourly
}Heartbeat / Keep-Alive
Periodically notify an external service that the agent is alive:
heartbeat(): void {
sendHeartbeat(this.serviceUrl);
this.scheduleNext(30n); // every 30s
}Helper for Scheduling Self
Extract the scheduling logic into a helper to keep methods clean:
private scheduleNext(delaySecs: bigint): void {
const self = PollerAgent.get(this.name);
const nowSecs = BigInt(Math.floor(Date.now() / 1000));
self.poll.schedule({ seconds: nowSecs + delaySecs, 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
pollon 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