Cron & Scheduled Jobs
Schedule recurring tasks — agent runs, webhook triggers, workflow invocations — with standard cron expressions.
Cron jobs are how Sophon does things on a schedule without your involvement. "Every weekday at 9am, have the morning-briefing agent summarize my calendar." "Every hour, check the build status." "Once at 15:00 today, remind me to call Ada." The scheduler handles all of it, powered by Quartz.NET.
Cron is separate from workflows. Workflows are reusable named automations; cron is pure scheduling. The two compose: a cron job can trigger a workflow, or an agent, or a webhook, or a custom action.
Types of jobs
| Type | Fires | Use case |
|---|---|---|
| Recurring | Cron expression (0 9 * * MON) | Daily briefings, hourly checks, weekly reports |
| One-shot | At a specific datetime | "Remind me at 15:00" |
| Interval | Every N minutes/hours | "Every 15 minutes, check the queue" |
Creating a job
From the Dashboard
Settings → Cron Jobs → New. Fill in:
- Name
- Schedule type (recurring / one-shot / interval)
- Expression (
0 9 * * MONor a datetime or an interval) - Action type: Agent (pick an agent + message), Workflow (pick a workflow), Webhook (POST to a URL), Custom (inline code — C# or Python)
- Timezone (default: your user setting)
- Enabled / disabled
Click Save. If the action is ≥ Medium risk (e.g., the agent message triggers a workflow that sends email), the job enters a pending approval state until you confirm.
From the CLI
sophon cron list
sophon cron add --name morning-briefing \
--schedule "0 9 * * MON-FRI" \
--action agent --agent sophon --message "summarize my day"
sophon cron trigger morning-briefing # run once now
sophon cron pause morning-briefing
sophon cron resume morning-briefing
sophon cron delete morning-briefing
sophon cron history morning-briefing # execution logCron expressions
Sophon uses standard 5-field Unix cron expressions (optionally 6-field with seconds):
┌───────────── minute (0 - 59)
│ ┌─────────── hour (0 - 23)
│ │ ┌───────── day of month (1 - 31)
│ │ │ ┌─────── month (1 - 12 or JAN-DEC)
│ │ │ │ ┌───── day of week (0 - 7 or SUN-SAT, 0 and 7 = Sunday)
│ │ │ │ │
* * * * *Examples:
| Expression | Means |
|---|---|
0 9 * * MON-FRI | Every weekday at 09:00 |
*/15 * * * * | Every 15 minutes |
0 0 1 * * | Midnight on the 1st of each month |
0 8,20 * * * | Daily at 08:00 and 20:00 |
0 22 * * SUN | Sunday at 22:00 |
Action types
Agent run
Send a message to a specific agent on a schedule. The agent processes it like any other chat message — through the full orchestration pipeline with tool calls, memory, approvals, everything.
Good for periodic check-ins: "summarize yesterday's activity", "check my inbox and flag urgent items."
Workflow
Trigger a workflow. Cron just calls workflow.trigger at the scheduled time. The workflow does the actual work.
Good for complex multi-step automations you've already built in the workflow builder.
Webhook
POST to an arbitrary URL at the scheduled time. Configurable headers and body; optional HMAC-SHA256 signature.
Good for integrating with external systems: "every hour, ping Zapier to run my scenario."
Custom action
Inline code — C# (Roslyn) or Python (CPython) — executed in the skills sandbox. Useful for one-off logic that doesn't deserve a full workflow or skill.
# At 09:00, compute budget remaining and message user
from sophon import budget, chat
remaining = budget.remaining_daily()
if remaining < 1000:
chat.send(user="me", text=f"Warning: only {remaining} tokens left today.")Execution history
Every cron fire is logged:
- Job ID, name
- Fire time (scheduled and actual)
- Status (
success,failed,misfire,cancelled) - Duration
- Output (truncated) or error message
Dashboard → Settings → Cron Jobs → <job> → History shows the log. Useful for debugging "it didn't run" mysteries — look for misfire (the scheduler was down when it should have fired) vs failed (it ran but the action errored).
Scheduling semantics
- Misfires — if the Gateway was down when a job should have fired, the scheduler handles it per your misfire policy:
DoNothing(default) — skip, log as misfireFireNow— run immediately on next startupIgnoreMisfires— treat as if it fired on time
- Concurrency — by default, a job running from a prior fire blocks the next fire. Configurable per job via
AllowConcurrent. - Clock drift — Sophon uses the server clock. For multi-instance deployments (Enterprise), make sure all Gateways sync against NTP; otherwise you may see duplicate fires.
Exponential backoff on errors
Cron jobs that fail are retried with exponential backoff — 1 min, 5 min, 15 min, 1 h, then give up. This prevents a misbehaving job from flooding logs and external services. You can disable retries per job if the action is idempotent and you'd rather fail fast.
Deterministic staggering
If you have many jobs scheduled for the same minute (e.g., 50 jobs at 0 9 * * *), Quartz staggers them over the minute based on job ID hash — no thundering herd.
Approval gates
Cron jobs that invoke Medium+ risk actions are gated at creation time, not on every fire. You approve once ("yes, this agent can send emails on my behalf daily at 9am"), and subsequent fires don't prompt.
Emergency override: disable a job (Dashboard or sophon cron pause) instead of rejecting each fire.
Data flow and variables
Cron jobs can bind variables from the schedule context into the action:
| Variable | Meaning |
|---|---|
$cron.fireTime | When this fire was scheduled |
$cron.actualFireTime | When it actually ran |
$cron.previousFireTime | When the previous fire ran |
$cron.nextFireTime | When the next fire is scheduled |
$cron.jobName | The job's name |
$cron.user | The owning user |
Use these in agent messages, workflow inputs, or webhook bodies — e.g., "Summarize activity since {{$cron.previousFireTime}}".
Limits and gotchas
- Timezones are per-job. A job at
0 9 * * *inEurope/Tiranawon't drift around DST; Quartz handles it correctly. Just make sure the job has the right timezone set. - One-shot jobs get deleted after they fire. They disappear from Settings → Cron Jobs. Check History if you need to confirm it ran.
- Personal tier has a 100-job cap. Pro / Enterprise is unbounded.
- Custom code jobs run in the sandbox — no host filesystem access, network allowlist applies.
- Don't schedule jobs more frequent than 1 minute apart. Quartz supports it, but you'll hit concurrency and token cost problems fast.
REST API
GET /api/cron
POST /api/cron
GET /api/cron/{id}
PATCH /api/cron/{id}
DELETE /api/cron/{id}
POST /api/cron/{id}/trigger
POST /api/cron/{id}/pause
POST /api/cron/{id}/resume
GET /api/cron/{id}/history