Skip to main content
v2 has no scheduler service. A scheduled task is a row in an isolated per-task session’s messages_in table with kind='task' and a process_after timestamp — the same inbox machinery, swept by the same host sweep, as any chat message. You drive it through the ncl tasks CLI: ask the agent in chat and it runs ncl tasks on your behalf, or run the commands yourself from the host. This tutorial assumes a working install with a wired agent (see your first agent).
1

Schedule a task in chat

Ask your agent, in whatever chat it’s wired to:
Mechanically, the agent runs ncl tasks create with a --prompt, a cron --recurrence (0 9 * * 1-5), and (for one-shots) a --process-after timestamp for the first run. NanoClaw provisions an isolated per-task session for the series and inserts a messages_in row there with kind='task', status pending, your process_after, the cron expression, and series_id set to the task’s own id. See the session DB schema for the columns.The agent confirms with the generated id. Passing --name "standup" yields a readable id like standup-a25c; without a name, ids are t-<hex>. For a one-shot task — “remind me tomorrow at 2pm to review the budget” — it passes --process-after and omits --recurrence.
2

Know what fires it

Nothing watches the clock per-task. The host sweep visits every active session every 60 seconds, counts pending rows with process_after <= now, and wakes the task session’s container if one isn’t already running. The agent’s poll loop then picks the task up like any inbound message and executes the prompt.Two consequences worth internalizing:
  • ±60 seconds is the precision floor. A task due at 09:00:00 fires whenever the next sweep tick lands, plus container cold-start if nothing is warm. Don’t schedule anything that needs second-level timing.
  • Recurring tasks are clones, not loops. When a recurring occurrence completes, the next sweep tick computes the next run with cron-parser, inserts a fresh pending row carrying the same series_id forward, and clears recurrence on the completed row so it isn’t re-cloned. The series id is the stable handle; individual occurrence ids come and go. Once a series has no live occurrence left, the sweep garbage-collects its per-task session.
3

Manage it with ncl tasks

You can manage tasks by asking the agent in chat:
Behind those phrases (and available directly from the host) is the ncl tasks resource:Full flags are in the ncl tasks reference.
4

Gate frequent tasks with a script

Every firing that reaches the agent is a full model API call. For tasks that run more than a few times a day, ncl tasks create accepts an optional --script — a bash script that runs before the agent is invoked:
The contract, from container/agent-runner/src/scheduling/task-script.ts:
  1. When the task fires, the script runs first inside the container — 30-second timeout, 1 MB output cap.
  2. The last line of stdout must be JSON: { "wakeAgent": true|false, "data": ... }.
  3. wakeAgent: false — or any script error, timeout, or invalid output — marks the occurrence completed without invoking the agent. A recurring series still continues: the sweep clones the next occurrence from the completed row.
  4. wakeAgent: true — the agent wakes with data injected into the task prompt as scriptOutput.
Be honest about what this saves: the container still wakes on every firing (the sweep can’t know the script’s verdict in advance). The script gates the expensive part — the agent invocation — not the container spawn. Skip scripts entirely for tasks that need the agent’s judgment every time, like daily briefings.
5

Set the timezone

When you say “9am”, three layers have to agree on whose 9am. The chain: the host resolves TIMEZONE once at startup — process.env.TZ, then the .env file’s TZ, then the system locale, falling back to UTC, each candidate validated as a real IANA identifier (src/config.ts). Every container is spawned with -e TZ=<that value> (src/container-runner.ts), and the agent-runner’s timezone.ts resolves the same way inside.The result: naive timestamps the agent passes (2026-06-11T09:00:00, no offset) and cron expressions — both at schedule time and when the host sweep computes the next recurrence — are all interpreted in that one instance timezone. To pin it:
Restart the host after changing it; the value is read once at startup.
6

Inspect from the host

Because scheduling is a host CLI resource now, you read it directly:
Under the hood each series lives as messages_in rows with kind='task' in the agent group’s isolated per-task session (data/v2-sessions/<agent_group_id>/<task_session_id>/inbound.db), not the central DB. A healthy recurring series shows one pending row (the next run) plus one completed row per past firing, all sharing a series_id — but ncl tasks get already surfaces that history, so you rarely need to open the database yourself.

What you built

A recurring task that is nothing but data: a messages_in row the 60-second host sweep wakes a container for, a clone-on-completion loop keyed by series_id, and an optional script gate between “the task fired” and “the agent got called”. No daemon, no crontab — ncl tasks cancel ends the series and the sweep reclaims its session.

Next steps

Last modified on July 16, 2026