Skip to content

Frona has three ways to run work asynchronously: scheduled tasks, heartbeats, and signals. They serve different purposes.

Scheduled tasksHeartbeatsSignals
What it doesRuns a specific task at specific timesWakes the agent up at regular intervalsPauses the agent until a matching inbound arrives
InstructionsDefined when you create the scheduleWritten by the agent to a HEARTBEAT.md file in its workspaceDefined when the agent calls await_signal
Best forReports, briefings, recurring jobsOngoing monitoring, watching for changesVerification codes, single replies, channel watching
Example"Every Monday, compile a weekly report""Keep an eye on my project and let me know if anything needs attention""When the 2FA code arrives, enter it"

Signals get their own page. See Signals.

Scheduled tasks

Ask an agent in chat to set up a recurring task:

  • "Every weekday at 9 AM, summarize overnight news and my pending items"
  • "Every Monday morning, compile a weekly status report"
  • "Check the status of my deployments every hour"
  • "Remind me to drink water every two hours"

The agent calls the create_recurring_task tool, picks a 5-field cron expression that matches your phrasing, and registers it. The scheduler then fires the task automatically.

In the Tasks panel, a recurring task shows a Recurring badge and the next-run time. Each individual fire ("CronRun") appears as a child entry with its own chat, so you can scroll through the history of every run.

Timezone

Cron expressions are interpreted in your timezone, not UTC. The resolution order is:

  1. The timezone field on the recurring task itself, if the agent set one. The agent does this when you explicitly name a different zone ("every weekday at 9am Tokyo time").
  2. Your profile timezone (Settings → Profile → Timezone).
  3. The server's default timezone (Settings → Timezone), which you set during the setup wizard.
  4. UTC, if nothing else is set.

Daylight saving transitions are handled by the IANA database. "0 8 * * *" means 8am every day in the resolved zone, even across DST jumps.

Every agent inference also receives a <temporal_context> block in its system prompt with the current local date and your timezone, so the agent can reason about "tomorrow", "next Monday", etc. correctly.

Modes and concurrency

Recurring tasks have two knobs the agent picks based on your phrasing:

ModeBehaviorPick when
Singleton (default)Only the latest fire matters. Older runs are hidden from the task tree.Reminders, polls, "tell me if X" patterns.
Per-instanceEach fire is a distinct audited work item. Every run is visible.Monthly reports, recurring payments, anything where each fire must not overlap.
ConcurrencyBehavior
AllowSpawn anyway, runs in parallel.
ForbidSkip the new fire while a previous one is still running.
ReplaceCancel the previous fire and start fresh.

Defaults: singleton → replace, per_instance → forbid. The agent picks these from context, so you don't usually need to specify.

Per-fire chats and process_result

Each fire of a recurring task gets its own chat with the agent. By default, the agent doesn't re-engage after a fire. The result lands in the chat, you see it, and that's it. This is fire-and-forget mode.

Set process_result: true (or ask for it: "every hour check stock prices and tell me if AAPL moves >5%") and the parent agent re-runs inference on each fire's result. This is how you build "watcher" agents that take action only when a condition is met.

Managing scheduled tasks

  • View schedules. Ask: "What tasks do I have scheduled?" Or open the Tasks panel and look for the Recurring badge.
  • Cancel a schedule. Ask: "Cancel the daily briefing." Or delete the task from the Tasks panel.
  • Modify a schedule. Ask the agent to cancel the old one and create a new one: "Change the weekly report to run on Fridays instead of Mondays."

How it works under the hood

  1. You ask an agent to schedule a recurring task.
  2. The agent calls create_recurring_task with a cron expression, instruction, and optional mode/concurrency.
  3. The scheduler checks for due cron templates every 60 seconds.
  4. When a template is due, the scheduler spawns a CronRun task: a new task entry with its own chat and a source_cron_id pointing back at the template.
  5. The agent runs the task in that chat. The result summary is broadcast to the source chat (where you asked for the schedule) so you see it.
  6. If a fire crashes mid-run, it's marked Failed and not restarted (the next scheduled fire still runs normally).

Heartbeats

Heartbeats are different from scheduled tasks. Instead of running a fixed set of instructions, a heartbeat wakes the agent up at regular intervals to read a HEARTBEAT.md checklist in its workspace and autonomously decide what to do.

The key difference: a scheduled task runs the same instruction every time. A heartbeat gives the agent a checklist to reason about. The agent can update its own checklist, skip items that don't apply, and adapt its behavior over time.

How heartbeats work

  1. The agent writes a checklist to HEARTBEAT.md in its workspace (this must exist before enabling a heartbeat).
  2. The agent sets its own heartbeat interval using the set_heartbeat tool.
  3. At each interval, the agent wakes up, reads HEARTBEAT.md, and acts on the checklist.
  4. All heartbeat runs share the same persistent chat, so the agent has context from previous runs.
  5. The agent can update HEARTBEAT.md during a run to change what it does next time.

Setting up a heartbeat

Ask the agent: "Keep an eye on my project and let me know if anything needs attention."

The agent decides what to watch for, writes its checklist to HEARTBEAT.md, sets its own heartbeat interval, and starts waking up periodically.

When to use heartbeats vs. scheduled tasks

  • Use scheduled tasks when you know exactly what needs to happen and when. "Every Monday at 9 AM, send a weekly report."
  • Use heartbeats when the agent needs to continuously monitor something, reason about changing conditions, or evolve its behavior over time.
  • Use signals when the agent should block until a matching message arrives, instead of polling on a schedule.

Task results

Scheduled tasks and signal tasks can carry a JSON Schema (result_schema) that constrains the shape of the result. If a schema is set, the agent must produce a result that validates against it. The platform validates server-side and constrains generation when the LLM supports it. This is how you stop attacker-controlled message content from leaking into a parent chat through a signal result.

Quarantined tasks

A task can be marked quarantined when its trigger came from an inbound source that the policy engine restricted. A quarantined task runs with a reduced tool registry (typically just terminal/recording tools) and cannot reply or act on the rest of the agent's tools. This is how the dispatcher contains tag-only inference (see Channels).

Example: daily morning briefing

You: "Every weekday at 8 AM, check the latest tech news and summarize the top 5 stories for me."

What happens:

  1. The agent calls create_recurring_task with cron_expression: "0 8 * * 1-5", your local timezone, and singleton + replace.
  2. The task appears in the Tasks panel with a Recurring badge and next-run time.
  3. Every weekday at 8 AM in your zone, the scheduler spawns a CronRun in a fresh chat.
  4. The Researcher (or whichever agent you asked) searches for recent tech news, reads articles, and creates a summary.
  5. The summary lands in your original chat.

Next steps