plungeai-scheduling
Scheduling — cron jobs for workflows, agents, and missions
The scheduler runs platform work unattended on cron expressions: a saved workflow every morning, an agent query hourly, a pre-built agent card nightly.
The scheduler runs platform work unattended on cron expressions: a saved workflow every morning, an agent query hourly, a pre-built agent card nightly. Jobs, run history, retries, and stats are first-class — a scheduled job is not fire-and-forget; every run is logged and resolvable like any other execution.
Creating and managing jobs — plungeai_schedule
One MCP tool, action-routed:
plungeai_schedule {action: "create",
name: "Daily market brief",
job_type: "workflow", # agent | query | workflow | heartbeat
target: "<workflow id>",
schedule: "0 7 * * *" # 07:00 UTC daily
}
plungeai_schedule {action: "list"}
plungeai_schedule {action: "get", job_id: "…"}
plungeai_schedule {action: "update", job_id: "…", schedule: "0 8 * * 1-5"}
plungeai_schedule {action: "pause", job_id: "…"}
plungeai_schedule {action: "resume", job_id: "…"}
plungeai_schedule {action: "run_now", job_id: "…"} # returns the run's REAL outcome + execution id
plungeai_schedule {action: "runs", job_id: "…"} # execution history
plungeai_schedule {action: "stats"}
plungeai_schedule {action: "delete", job_id: "…"}Also on create: mission_ref schedules a pre-built agent card directly (a bounded
mission on cron — see plungeai-missions), and parameters carries the run input.
Heartbeat jobs — watch a condition, act only when it trips
job_type: "heartbeat" is a user-facing job kind (not just internal plumbing): each
tick it runs check_agent against condition_prompt and only acts when the
condition is met. Create requires name, schedule, check_agent,
condition_prompt; optional actions: trigger_workflow (a workflow id to fire) and
notify_channel (telegram | whatsapp | discord | slack | email) + notify_chat_id.
Heartbeat notify routes through the same per-channel delivery adapters as ordinary
scheduled-result delivery — telegram/discord/slack/email all deliver (subject to
each channel's normal pairing/confirmation fence, see plungeai-campaigns'
delivery-channel table); whatsapp has a known gap — the heartbeat payload never
carries the phone_number_id the whatsapp adapter requires, so a whatsapp target
silently no-ops today.
plungeai_schedule {action: "create", job_type: "heartbeat",
name: "Price watch", schedule: "*/30 * * * *",
check_agent: "<agent id from a live search>",
condition_prompt: "Is BTC below $50k? Answer MET or NOT MET with evidence.",
notify_channel: "telegram", notify_chat_id: "…"}Heartbeat condition checks run memory-free — they are watch-and-notify ticks, not learning runs.
Semantics you should rely on:
- Workflow jobs run by reference — the scheduler stores a pointer, not a YAML copy. Edit the workflow in Studio and the next scheduled run uses the latest version automatically. Never "update the schedule" to change workflow logic.
- Agent and query jobs are auto-wrapped at create time into a saved one-task
workflow (
Scheduled: <name>) and scheduled as workflow jobs — so their runs get full standard execution history too. - Delete is soft: history stays readable; a deleted job refuses run_now/update/pause/resume. There is no undelete — create a new job.
run_nowreturns the real outcome plus a resolvable execution id — use it to verify a job end-to-end right after creating it (always do this).
Cron expressions
Standard 5-field cron, minimum granularity 1 minute, evaluated in UTC:
minute hour day month day-of-week| Intent | Expression |
|---|---|
| Every 5 minutes | */5 * * * * |
| Hourly on the hour | 0 * * * * |
| Daily 07:00 | 0 7 * * * |
| Weekdays 09:00 | 0 9 * * 1-5 |
| Mondays 09:00 | 0 9 * * 1 |
| 1st of month 00:00 | 0 0 1 * * |
, lists, - ranges, / steps, * any. Cron accuracy is ±5 seconds — do not
build designs that need tighter timing.
@once and runAgain — one-time and self-scheduling jobs
@once is a valid schedule that fires exactly once, at the job's explicit next-run
time, then soft-deletes itself (run history survives). A failed @once run may
retry once with the standard +5 min delay before its final deletion.
runAgain is the mechanic behind a campaign that re-schedules itself without a
cron expression: the last row of its per-cycle pipeline is agent: scheduler, params: {operation: "runAgain", in: "1h"}. It creates exactly ONE @once job for
that workflow (never stacks — an existing pending @once job for the same workflow
is re-timed, not duplicated). in accepts a duration (5m, 1h, 90s) or a
number of seconds, default 1h. This is orthogonal to a campaign scheduled with a
cron expression, which is driven by an ordinary recurring job instead — see
plungeai-campaigns.
Date tokens — schedules that reason about time windows
Scheduled workflows should never bake concrete dates into YAML. The engine seeds
rolling tokens from each run's execution time — same {placeholder} syntax as
inputs, lower precedence than caller inputs:
{now} {today} {yesterday} {week_start} {week_end} {last_week_start}
{last_week_end} {month_start} {month_end} {last_month_start}
{last_month_end} — all UTC, ISO weeks (Monday = day 1).
- type: task
id: compare
agent: llm-agent
prompt: |
Compare this week ({week_start} to {week_end}) against last week
({last_week_start} to {last_week_end}). Identify the notable moves.The scheduler passes the execution time automatically — a job scheduled "Monday 09:00" reasons about the correct ISO week every time, forever, with zero YAML edits.
Runs, retries, and history
- Every run is logged: status (
running→completed/failed), start/end, duration, result pointer, error message, attempt number, what triggered it (schedule vs manual). - Failed runs auto-retry: up to 3 attempts by default, 5 minutes apart; after
max retries the job returns to its normal schedule (it is not paused by failure).
The per-job
max_retriesoverride is settable only on the REST/Studio surface (POST /jobs) —plungeai_schedulehas no such field. - Retry re-fires the full job — schedule only idempotent work, or make the workflow itself idempotent (e.g. keyed upserts), for anything with side effects.
action: "runs"lists history; its Execution ID column is the id to fetch output withplungeai_get_result/ inspect withplungeai_get_workflow_status. A scheduler run id itself is NOT resolvable by those tools — both will tell you to go throughrunsfirst; do that instead of retrying.action: "stats"summarizes: total/active/paused jobs, today's runs and success/failure split, average duration — the first read when "schedules seem broken".
Memory semantics of scheduled runs
A scheduled user workflow keeps the FULL per-user memory lifecycle — it recalls and
writes long-term memory exactly like a manual run (owner decision; see
plungeai-memory). Only system jobs and heartbeat condition checks run
memory-free. Design accordingly: a daily mission genuinely accumulates knowledge run
over run.
Patterns
- Morning brief: workflow (parallel news/search fan-out → synthesis) on
0 7 * * *, date tokens for "since yesterday". - Weekly comparison report: workflow with
{last_week_*}vs{week_*}windows on0 9 * * 1. - Standing watchdog:
quick-effort mission card viamission_refhourly; it checks a condition and only escalates (writes/notifies) when triggered. - Data hygiene: batch workflow nightly over a table; opt in with
track: trueon the batch so re-runs resume instead of repeat (plungeai-workflows). - Campaign cadence: a list-to-completion campaign is driven either by an
ordinary recurring cron job or by its own
runAgainself-scheduling — seeplungeai-campaigns.
Troubleshooting
- Job not firing →
getthe job:statusmust beactiveand next run in the future; thenrunsfor the last attempt's error. - Run failed →
runs→ take the Execution ID →plungeai_get_workflow_status(which also self-heals stuck runs) and the trace (plungeai-results-traces). - Wrong data window → check UTC:
0 7 * * *is 07:00 UTC, not local; date tokens are UTC/ISO too. - Output looks stale → the job runs the workflow by reference; confirm which version is saved (someone may have edited it — that IS the version that runs).
MCP tool: plungeai_schedule
Purpose: cron-scheduled jobs — run agents, workflows, or condition-check heartbeats on a schedule. Jobs are ownership-scoped.
Actions
| Action | Requires | Notes |
|---|---|---|
stats | — | Job counts by status. |
list | — | Table: Name, Type, Schedule, Status, Next run, ID. |
get | job_id | Full job JSON. |
create | see below | Four job shapes (below). |
update | job_id (+ any of name/description/schedule/target/parameters) | Deleted jobs refuse it. |
pause / resume | job_id | Toggle paused/active. |
delete | job_id | SOFT delete — run history stays readable via runs; deleted jobs refuse run_now/update/pause/resume. |
run_now | job_id | Fire immediately; reports the run's REAL outcome and a resolvable execution id. |
runs | optional job_id | Run history table; its Execution ID column is what plungeai_get_result accepts. |
Create — the four shapes
- Workflow job:
job_type: "workflow",target: "<workflow_id you own>",name,schedule(cron). The workflow must exist in the caller's account. - Agent job:
job_type: "agent",target: "<agent-id>",parameters: {"prompt": "what to do each run"},name,schedule. The agent is fence-checked NOW (unknown/inactive refused at create, not at fire time), and the job is auto-wrapped into a visible saved workflow ("Scheduled: <name>") so runs land in normal execution history. - Query job:
job_type: "query",targetis JSON:{"agent": "<agent-id>", "query": "<what to run>"}— same wrapping and fence as agent jobs. - Heartbeat:
job_type: "heartbeat", pluscheck_agent,condition_prompt(required), optionaltrigger_workflow(workflow id to fire when the condition is met),notify_channel(telegram|whatsapp|discord|slack|email) +notify_chat_id. Each tick it checks the condition and only acts when met. Delivery runs through the same per-channel adapters as regular scheduled-result delivery — telegram, discord, slack, and email all deliver; ⚠️ whatsapp is a known gap (the heartbeat payload doesn't carry thephone_number_idthe adapter requires, so it silently no-ops) — prefertrigger_workflowor another channel for a whatsapp-bound notice.
Also on create: mission_ref: "<agent-card-id>" (+ name, schedule,
optional parameters.prompt) schedules a pre-built agent card — the card
resolves LIVE at fire time, so card edits apply to future runs automatically.
Example
{"user_request": "every weekday at 7am send me an AI news brief",
"action": "create", "name": "Morning AI brief", "job_type": "agent",
"target": "brave-agent", "schedule": "0 7 * * 1-5",
"parameters": {"prompt": "Top AI news of the last 24h, with links"}}Failures & fixes
- "create requires: name, job_type (agent|query|workflow), target, schedule" → supply them (heartbeat/mission_ref list their own requireds).
- Fence refusal on
target/check_agent→ re-discover withplungeai_list_agents; never retry the same id. - "Agent schedules need a prompt" → add
parameters: {"prompt": ...}. - "No workflow
<id>found in your account" → pass an id fromplungeai_list_workflowsor build one first. - "That looks like an execution id (
exec_...), not a job id (job_...)" → execution ids identify single runs; useaction: "runs"to list them, or pass the job id. - "The scheduler's storage is briefly busy... Retry in a few seconds." → transient; the job most likely still exists — do NOT treat as not-found.
Scheduler error (HTTP <status>).(e.g.Scheduler error (HTTP 401).) → the gateway→scheduler hop itself failed; not your arguments. Report it rather than retry-looping.- To fetch a scheduled run's output:
action: "runs"→ take the Execution ID column →plungeai_get_result {workflow_id: <that id>}.