Cron via Minions Convention
How cron-scheduled agent work is dispatched in a GBrain-backed install.
Rule: scheduled work runs as Minion jobs, not agentTurn
When a cron fires, it should submit a Minion job. Not call OpenClaw's
native agentTurn (300s timeout, no durability, no transcript). Not
start an isolated session that races the gateway for resources.
# Bad: agentTurn with a fixed timeout, no durability.
{ "schedule": "*/30 * * * *", "kind": "agentTurn", "skill": "ea-inbox-sweep" }
# Good (Postgres): fire-and-forget submit with an idempotency key per
# cycle slot. The queue dedupes long-running overlaps at the DB layer.
{
"schedule": "*/30 * * * *",
"kind": "shell",
"cmd": "gbrain jobs submit ea-inbox-sweep --params '{\"slot\":\"$(date -u +%Y-%m-%dT%H:%M)\"}' --idempotency-key ea-inbox-sweep:$(date -u +%Y-%m-%dT%H:%M)"
}
# Good (PGLite): inline execution with --follow. PGLite's exclusive file
# lock blocks a separate worker daemon, so the cron runs the job directly.
{
"schedule": "*/30 * * * *",
"kind": "shell",
"cmd": "gbrain jobs submit ea-inbox-sweep --params '{}' --follow"
}
Why
- Durability. Gateway restart mid-task? Worker picks the job up on boot. No lost state.
- Observability.
gbrain jobs list+gbrain jobs get <id>show every run, its duration, its transcript, its token accounting. - Steering. Running jobs accept inbox messages. "Skip the newsletter thread, focus on the urgent DMs" lands as context on the next iteration.
- Concurrency safety. Idempotency-key on the cycle slot means a cron that fires during a still-running previous invocation produces a noop at the queue layer. Without this, a 5-min cron running 8-min jobs stacks 4 overlapping copies at steady state.
Who registers the handler?
GBrain only rewrites cron entries whose handler name matches a
gbrain builtin (sync, embed, lint, import, extract,
backlinks, autopilot-cycle). For host-specific handlers
(ea-inbox-sweep, morning-briefing, whatever your deployment runs
on cron), the host platform ships the handler as code.
See docs/guides/plugin-handlers.md for the plugin contract. In short:
import { MinionQueue, MinionWorker } from 'gbrain/minions';
const worker = new MinionWorker(engine, { queue: 'default' });
worker.register('ea-inbox-sweep', async (ctx) => {
// Host-specific agent turn. Call whatever LLM + tools the host has.
// ctx.data contains the cron slot payload; return a result object.
});
await worker.start();
Ship the bootstrap in the host repo. Autopilot spawns the worker as a
child; the host's custom worker binary (or a side-effect module the
stock worker auto-loads on startup) registers handlers before start().
Off mode
Users who set minion_mode: off in ~/.gbrain/preferences.json keep
using agentTurn. Respect that. No auto-rewrite.
Forward note
A native scheduler loop inside gbrain jobs work (owning cron
expressions directly, with no host-scheduler hand-off) has been on the
roadmap since v0.11.1 but has not shipped. The host scheduler keeps
firing on schedule; this convention only replaces the execution layer
(what the cron trigger does), not the scheduling layer.
Related
skills/conventions/subagent-routing.md— native subagents vs Minions for ad-hoc (not cron-scheduled) work.skills/minion-orchestrator/SKILL.md— patterns for managing jobs once they're in the queue.skills/cron-scheduler/SKILL.md— scheduling guidance (quiet hours, staggering, idempotency). Now references this convention.skills/migrations/v0.11.0.md— how GBrain migrates an existing host cron manifest to this convention.