Scheduled Agents

Scheduled Agents

Any agent type can be scheduled using the wake_up field. LeafMesh triggers the agent on a cron, interval, or keyword schedule.

Configuration

agents: # Every 5 minutes collector: name: "collector" agent_type: "programmatic" wake_up: "*/5 * * * *" yields: readings: "array" timestamp: "string" can_call: - agent: "analyzer" condition: "readings != []" # Every hour at :00 reporter: name: "reporter" model: "gpt-4o-mini" wake_up: "0 * * * *" yields: summary: "string" # Every day at midnight daily_digest: name: "daily_digest" model: "gpt-4o" wake_up: "0 0 * * *" yields: report: "string"

Schedule Formats

FormatExampleMeaning
Cron (5-field)*/5 * * * *Every 5 minutes
Cron (5-field)0 9 * * 1Every Monday at 9 AM
Cron (5-field)0 9 * * 7Every Sunday at 9 AM (0 and 7 are both Sunday)
Cron (5-field)0 9 * * 6-7Saturday and Sunday at 9 AM
Cron (5-field)0 9 * * 5#3The third Friday of the month at 9 AM
Cron (5-field)0 9 * * 5LThe last Friday of the month at 9 AM
Interval30sEvery 30 seconds
Interval5mEvery 5 minutes
Interval2hEvery 2 hours
Interval1dEvery day
Keyword@hourlyTop of every hour
Keyword@dailyMidnight
Keyword@weeklySunday midnight
Keyword@monthlyFirst of the month

Cron expressions follow the standard five-field format: minute hour day-of-month month day-of-week. When both day fields are set (0 9 1 * 1-5), the agent wakes on either — the 1st of the month or any weekday — as in standard cron.

Interval strings are a number plus a unit — s, m, h or d, with no space. Keywords must carry the leading @.

Minimum cadence. Intervals below scheduler.cron_min_interval_seconds (default 60 seconds) are rejected at load, so 30s needs that floor lowered first. See Configuration API → scheduler.

Timezones

Schedules run in UTC by default, so a laptop in one country and a server in another fire at the same moment. Set timezone on the agent to schedule in local time instead:

agents: morning_report: name: "morning_report" wake_up: "0 9 * * *" # 9 AM... timezone: "Asia/Kolkata" # ...India time, not UTC

Any IANA zone name works (Europe/London, America/New_York, Asia/Kolkata). The name is validated when config loads, so a typo is caught at startup rather than firing at the wrong hour for a week.

Daylight saving is handled by the zone itself — a 0 9 * * * schedule in Europe/London stays at 9 AM local across the March and October transitions.

When an agent holds several schedules, any one of them can override the agent's zone:

agents: regional_digest: name: "regional_digest" timezone: "UTC" # agent default wake_up: - cron: "0 9 * * *" timezone: "Asia/Kolkata" # India morning input: { region: "in" } - cron: "0 9 * * *" timezone: "America/New_York" # US morning input: { region: "us" }

Multiple Schedules per Agent

Since 2.4.107 an agent can hold several schedules, each carrying its own input:

researcher_agent: wake_up: - cron: "0 6 * * *" input: {mode: morning_briefing, depth: quick} - cron: "0 18 * * *" input: {mode: evening_summary, depth: full} wake_up_input: # shared base merged into EVERY wake audience: ops-team

Three accepted shapes: a single cron string (as above in the earlier examples), a list of cron strings, or a list of {cron, input} entries. Malformed entries are rejected at config load.

How It Works

When wake_up fires:

  1. The scheduler publishes a scheduled-run event (carrying that schedule's input, if declared)
  2. The agent is invoked with the merged wake payload — wake_up_input (shared base) → the firing schedule's input → the trigger markers wake_up_trigger: true and trigger_time (markers always win on collision). With none declared, the payload is just the trigger markers.
  3. The full pipeline executes: pre-compose → LLM call (or intelligence function) → yields parsing → can_call evaluation
  4. Downstream agents are triggered if conditions match
Scheduler timer fires
    │
    ▼
Scheduled-run event published
    │
    ▼
Agent pipeline executes
    │  (merged wake payload: wake_up_input → schedule input → markers)
    ▼
can_call conditions evaluated
    │
    ▼
Downstream agents triggered

The execution follows the same pipeline as interactive calls. Scheduled agents are not special — they just have an automatic trigger.

Changing a Schedule at Runtime

Schedules are declared on the agent, so you change one by changing its wake_up — through Agent Studio or the config API:

PATCH /api/yaml/config {"agents": {"collector": {"wake_up": "1m"}}}

The schedule is rebuilt in place; no restart is needed. Send null to stop an agent waking up at all:

PATCH /api/yaml/config {"agents": {"collector": {"wake_up": null}}}

Execution tracking records last execution time, execution count, and next run time.

Common Patterns

Periodic Data Collection

agents: sensor_collector: name: "sensor_collector" agent_type: "programmatic" wake_up: "*/5 * * * *" yields: readings: "array" timestamp: "string" can_call: - agent: "analyzer" condition: "readings != []"
# Function name "sensor_collector" matches the YAML agent name — auto_discover finds it async def sensor_collector(llm_response, input_data, context): """Collect sensor data every 5 minutes""" import random from datetime import datetime readings = [round(random.gauss(100, 10), 2) for _ in range(20)] return { "readings": readings, "timestamp": datetime.now().isoformat() }

Health Reporting

agents: health_reporter: name: "health_reporter" model: "gpt-4o-mini" wake_up: "0 9 * * *" # Daily at 9 AM prompt: | Summarize the system health metrics into a brief report. Include any anomalies or concerns. yields: report: "string" status: "string" can_call: - agent: "human_reviewer" condition: "status == 'critical'"

Cache Maintenance

agents: cache_monitor: name: "cache_monitor" agent_type: "programmatic" wake_up: "30m" yields: hit_rate: "number" action_taken: "string"
# Function name "cache_monitor" matches the YAML agent name — auto_discover finds it async def cache_monitor(llm_response, input_data, context): """Check cache health and invalidate if needed""" stats = await Studio's Dashboard (HTTP) hit_rate = stats.get("hit_rate", 0) action = "none" if hit_rate < 0.3: action = "invalidated_stale_entries" return { "hit_rate": round(hit_rate, 2), "action_taken": action }

Isolation

Scheduled agent executions run isolated from interactive traffic — they never block request processing.

Next Steps


LeafMesh — Time-triggered agents, same pipeline

Last updated: 11/10/2026