Scheduling Workflows And Automations
The control panel is not using a separate scheduler. It sends schedule payloads to the same engine APIs your SDK code can call directly.
The Two Schedule Shapes
Tandem currently has two scheduling families:
1. Routines and legacy automations
Used by:
client.routinesclient.automations
Accepted schedule shapes:
- Cron string:
"0 8 * * *" - Cron object:
{ type: "cron", cron: "0 8 * * *" } - Interval object:
{ type: "interval", intervalMs: 3600000 } - Manual:
{ type: "manual" }
Use this family for simple scheduled jobs or older automation definitions.
2. Workflow plans and V2 automations
Used by:
client.workflowPlans/client.workflow_plansclient.automationsV2/client.automations_v2
Accepted schedule shape:
{ "type": "interval", "interval_seconds": 3600, "timezone": "UTC", "misfire_policy": "run_once"}Cron uses the same shape with type: "cron" and cron_expression.
This is the same payload family the control panel sends for planner-created and V2 automations.
Default Recommendation
For new scheduled automation work, prefer automationsV2 / automations_v2.
Reasons:
- It matches the control panel’s current automation model.
- It supports explicit multi-agent DAG flows.
- It uses the richer schedule object the planner and UI already produce.
- It is the best fit when another agent will generate or revise automations from requirements.
Use workflowPlans / workflow_plans when you want the engine to generate the V2 automation for you from natural-language requirements.
Keep using routines for simple recurring single-agent jobs, and keep automations only for legacy compatibility or existing installs that already depend on it.
TypeScript Examples
Planner-generated automation with an interval schedule
const draft = await client.workflowPlans.chatStart({ prompt: "Create an automation that reviews the repo and opens a markdown report.", schedule: { type: "interval", interval_seconds: 6 * 60 * 60, timezone: "UTC", misfire_policy: "run_once", }, planSource: "chat",});
await client.workflowPlans.chatMessage({ planId: draft.plan.plan_id!, message: "Keep it single-agent and write output to reports/repo-review.md",});
await client.workflowPlans.apply({ planId: draft.plan.plan_id, creatorId: "docker-agent",});V2 automation every 15 minutes
await client.automationsV2.create({ name: "incident-watch", status: "active", schedule: { type: "interval", interval_seconds: 15 * 60, timezone: "UTC", misfire_policy: "run_once", }, agents: [ { agent_id: "watcher", display_name: "Watcher", model_policy: { default_model: { provider_id: "openrouter", model_id: "openai/gpt-4o-mini", }, }, tool_policy: { allowlist: ["read", "websearch"], denylist: [] }, mcp_policy: { allowed_servers: [] }, }, ], flow: { nodes: [ { node_id: "scan", agent_id: "watcher", objective: "Check for new incidents and summarize urgent changes.", }, ], },});Routine every hour
await client.routines.create({ name: "hourly-repo-summary", schedule: { type: "interval", intervalMs: 60 * 60 * 1000 }, entrypoint: "Summarize changes in the repo from the last hour.",});Legacy automation every day at 08:00 UTC
await client.automations.create({ name: "daily-security-scan", schedule: "0 8 * * *", mission: { objective: "Review the repo for security-sensitive changes.", successCriteria: ["Write findings to reports/security-daily.md"], }, policy: { tool: { externalIntegrationsAllowed: false }, approval: { requiresApproval: false }, }, outputTargets: ["file://reports/security-daily.md"],});Python Examples
Planner-generated automation with an interval schedule
draft = await client.workflow_plans.chat_start( prompt="Create an automation that reviews the repo and opens a markdown report.", schedule={ "type": "interval", "interval_seconds": 6 * 60 * 60, "timezone": "UTC", "misfire_policy": "run_once", }, plan_source="chat",)
await client.workflow_plans.chat_message( plan_id=draft.plan.plan_id or "", message="Keep it single-agent and write output to reports/repo-review.md",)
await client.workflow_plans.apply( plan_id=draft.plan.plan_id, creator_id="docker-agent",)V2 automation every 15 minutes
await client.automations_v2.create( { "name": "incident-watch", "status": "active", "schedule": { "type": "interval", "interval_seconds": 15 * 60, "timezone": "UTC", "misfire_policy": "run_once", }, "agents": [ { "agent_id": "watcher", "display_name": "Watcher", "model_policy": { "default_model": { "provider_id": "openrouter", "model_id": "openai/gpt-4o-mini", } }, "tool_policy": {"allowlist": ["read", "websearch"], "denylist": []}, "mcp_policy": {"allowed_servers": []}, } ], "flow": { "nodes": [ { "node_id": "scan", "agent_id": "watcher", "objective": "Check for new incidents and summarize urgent changes.", } ] }, })Routine every hour
await client.routines.create( { "name": "hourly-repo-summary", "schedule": {"type": "interval", "intervalMs": 60 * 60 * 1000}, "entrypoint": "Summarize changes in the repo from the last hour.", })Legacy automation every day at 08:00 UTC
await client.automations.create( { "name": "daily-security-scan", "schedule": "0 8 * * *", "mission": { "objective": "Review the repo for security-sensitive changes.", "successCriteria": ["Write findings to reports/security-daily.md"], }, "policy": { "tool": {"externalIntegrationsAllowed": False}, "approval": {"requiresApproval": False}, }, "outputTargets": ["file://reports/security-daily.md"], })Important Distinction
client.workflows is for registered workflow definitions and runs. It is not the scheduling surface.
If you want a recurring job, schedule one of these:
routinesfor simpler scheduled jobsautomationsfor legacy mission-based automationsautomationsV2/automations_v2for the recommended persistent DAG automation modelworkflowPlans/workflow_planswhen you want the engine planner to generate the automation first, then apply it
Practical Recommendation
For new work:
- Use
automationsV2/automations_v2if you already know the automation shape you want to create programmatically. - Use
workflowPlans/workflow_plansif another agent will describe requirements in natural language and let Tandem generate the automation definition. - Use
routinesonly when you want a much simpler recurring job and do not need the V2 automation model.