# Crons & Loops

Schedule recurring work and long-running agent loops with cron expressions,
running either a shell command (`exec`) or an agent session (`session`), with
overlap modes (`allow`, `skip`, `queue`) and native `cronEvent` streaming to
monitor execution. Cron jobs keep the actor alive while a job runs; the actor
can sleep between executions.

## Schedule a command

Run a shell command on a recurring schedule. Pass a custom `id` to make a job easier to manage and cancel later.

examples/cron/schedule-command.ts:

```ts
import { createClient } from "@rivet-dev/agentos/client";
import type { registry } from "./server";

const client = createClient<typeof registry>({ endpoint: "http://localhost:6420" });

// Schedule a cleanup script every hour
const { id } = await client.vm.getOrCreate("my-agent").cron.schedule({
  schedule: "0 * * * *",
  action: {
    type: "exec",
    command: "rm",
    args: ["-rf", "/tmp/cache/*"],
  },
});
console.log("Cron job ID:", id);
```

examples/cron/server.ts:

```ts
import { agentOS, setup } from "@rivet-dev/agentos";
import pi from "@agentos-software/pi";

const vm = agentOS({
  software: [pi],
});

export const registry = setup({ use: { vm } });
registry.start();
```

## Schedule an agent session

Create a recurring agent session that runs a prompt on a schedule.

examples/cron/schedule-session.ts:

```ts
import { createClient } from "@rivet-dev/agentos/client";
import type { registry } from "./server";

const client = createClient<typeof registry>({ endpoint: "http://localhost:6420" });

// Run an agent every day at 9 AM to check for issues
await client.vm.getOrCreate("my-agent").cron.schedule({
  schedule: "0 9 * * *",
  action: {
    type: "session",
    agentType: "pi",
    prompt: "Review the logs in /workspace/logs/ and summarize any errors",
    cwd: "/workspace",
  },
});
```

examples/cron/server.ts:

```ts
import { agentOS, setup } from "@rivet-dev/agentos";
import pi from "@agentos-software/pi";

const vm = agentOS({
  software: [pi],
});

export const registry = setup({ use: { vm } });
registry.start();
```

## Overlap modes

Control what happens when a cron job triggers while a previous execution is still running.

| Mode | Behavior |
|------|----------|
| `"skip"` | Skip this trigger if the previous run is still active |
| `"allow"` | Allow concurrent executions (default) |
| `"queue"` | Queue this trigger and run it after the previous one finishes |

Prefer `"skip"` for most jobs to avoid unbounded concurrency if a run takes longer than the interval. Use `"queue"` when every trigger must eventually execute.

examples/cron/overlap.ts:

```ts
import { createClient } from "@rivet-dev/agentos/client";
import type { registry } from "./server";

const client = createClient<typeof registry>({ endpoint: "http://localhost:6420" });

// Queue overlapping executions
await client.vm.getOrCreate("my-agent").cron.schedule({
  schedule: "*/5 * * * *",
  overlap: "queue",
  action: {
    type: "session",
    agentType: "pi",
    prompt: "Process the next batch of tasks",
  },
});
```

examples/cron/server.ts:

```ts
import { agentOS, setup } from "@rivet-dev/agentos";
import pi from "@agentos-software/pi";

const vm = agentOS({
  software: [pi],
});

export const registry = setup({ use: { vm } });
registry.start();
```

## Monitor cron events

Subscribe with `connection.on("cronEvent", ...)` to track job execution. Event and job timestamps are ISO strings. The callback receives a `CronEvent` directly, with no `{ event }` wrapper.

examples/cron/monitor.ts:

```ts
const conn = handle.connect();
conn.on("cronEvent", (event) => {
  console.log("Cron event:", event);
});
```

Subscribe before scheduling so you do not miss early runs.

examples/cron/monitor.ts:

```ts
import { createClient } from "@rivet-dev/agentos/client";
import type { registry } from "./server";

const client = createClient<typeof registry>({ endpoint: "http://localhost:6420" });
const handle = client.vm.getOrCreate("my-agent");

const conn = handle.connect();
conn.on("cronEvent", (event) => {
  console.log("Cron event:", event);
});

await handle.cron.schedule({
  schedule: "*/1 * * * *",
  action: { type: "exec", command: "echo", args: ["heartbeat"] },
});
```

examples/cron/server.ts:

```ts
import { agentOS, setup } from "@rivet-dev/agentos";
import pi from "@agentos-software/pi";

const vm = agentOS({
  software: [pi],
});

export const registry = setup({ use: { vm } });
registry.start();
```

## List and cancel cron jobs

examples/cron/list-cancel.ts:

```ts
import { createClient } from "@rivet-dev/agentos/client";
import type { registry } from "./server";

const client = createClient<typeof registry>({ endpoint: "http://localhost:6420" });
const handle = client.vm.getOrCreate("my-agent");

// List all cron jobs
const jobs = await handle.cron.list();
for (const job of jobs) {
  console.log(job.id, job.schedule);
}

// Cancel a specific job
await handle.cron.cancel(jobs[0].id);
```

examples/cron/server.ts:

```ts
import { agentOS, setup } from "@rivet-dev/agentos";
import pi from "@agentos-software/pi";

const vm = agentOS({
  software: [pi],
});

export const registry = setup({ use: { vm } });
registry.start();
```

## Example: Heartbeat pattern

Schedule a recurring agent session to periodically check on a task. This is the core pattern behind [OpenClaw](https://openclaw.org), where an agent wakes up on a schedule to review progress, take action, and go back to sleep.

examples/cron/heartbeat.ts:

```ts
await handle.cron.schedule({
  schedule: "*/30 * * * *",
  overlap: "skip",
  action: {
    type: "session",
    agentType: "pi",
    prompt: "Check the status of open issues and take any necessary action",
  },
});
```

The agent sleeps between executions and only consumes resources when the cron job fires.
