How to Configure Scheduled Work
GSV has two scheduling surfaces:
- Cron files and
crontabfor user/process-owned work. - Package daemon schedules for package-owned backend RPC methods.
Use cron files when scheduled work should run a command, including commands that create or notify a process. Use package daemon schedules when a package backend needs to call one of its own RPC methods.
Cron is an execution contract, not a separate natural-language interface. Use your personal agent to author or revise recurring intent, then run scheduled work through a stable shell command, timezone, and audit history.
Add a User Crontab
User crontabs use the standard five-field cron format:
From a GSV shell:
proc agents
cat > ~/daily.cron <<'EOF'
CRON_TZ=Europe/Amsterdam
0 9 * * * proc spawn --as friday --non-interactive --label "daily ops check" "Check system health and summarize anything that needs attention."
EOF
crontab ~/daily.cronReplace friday with the agent account username listed by proc agents.
The same file can be written directly:
cat > /var/spool/cron/sam <<'EOF'
CRON_TZ=Europe/Amsterdam
0 9 * * * proc spawn --as friday --non-interactive --label "daily ops check" "Check system health and summarize anything that needs attention."
EOFManage the current user's crontab:
crontab -l
crontab ~/daily.cron
crontab -rCron lines use:
minute hour day-of-month month day-of-weekThe timezone must be an IANA timezone. If the system was initialized through onboarding, the selected system timezone is available as config/server/timezone.
Notify an Existing Process
Use proc send when the schedule should wake an existing process conversation instead of spawning a new process.
From a GSV shell:
cat > ~/pulse.cron <<'EOF'
*/15 * * * * proc send init:1000 --conversation ops "Run the scheduled ops pulse."
EOF
crontab ~/pulse.cronInside a process shell, $GSV_PID and proc self both identify the current process.
The target process receives normal process mail.
Add a System Cron File
Root can install /etc/cron.d/<name> files. These use the system crontab format, with a user field between the five time fields and the command:
CRON_TZ=Europe/Amsterdam
0 4 * * * root proc compact init:0 --conversation default --keep-last 80 --generate-summary
30 8 * * 1-5 sam proc spawn --as friday --non-interactive --label "morning brief" "Prepare morning brief."Manage Kernel Schedules
sched remains the low-level schedule inspector and control surface:
await kernel.request("sched.list", { includeDisabled: true });
await kernel.request("sched.update", {
id: "schedule-id",
patch: { enabled: false },
});
await kernel.request("sched.remove", { id: "schedule-id" });To run a schedule manually:
await kernel.request("sched.run", { id: "schedule-id", mode: "force" });To sweep currently due schedules:
await kernel.request("sched.run", { mode: "due" });Add a Package Daemon Schedule
Package backends can schedule their own RPC methods through this.daemon. Schedules live in the package AppRunner Durable Object, not in the Kernel scheduler table.
import { PackageBackendEntrypoint } from "@gsv/package/backend";
export default class ReportsBackend extends PackageBackendEntrypoint {
async enableDailyReport() {
if (!this.daemon) {
throw new Error("daemon scheduling is unavailable");
}
return this.daemon.upsertRpcSchedule({
key: "daily-report",
rpcMethod: "runDailyReport",
schedule: { kind: "every", everyMs: 24 * 60 * 60 * 1000 },
payload: { channel: "ops" },
enabled: true,
});
}
async runDailyReport(payload: { channel?: string }) {
const files = await this.kernel.request("fs.search", {
path: "/home/root/projects",
query: "TODO",
});
await this.storage?.sql.exec(
"INSERT INTO report_runs(created_at, payload_json) VALUES (?, ?)",
Date.now(),
JSON.stringify({ payload, files }),
);
return { ok: true };
}
}