Declare a schedule
Create a scheduled function from the agent root:cron/daily_report.py with a same-named @cron function and its own queued task. It works in managed and advanced mode for ADK and LangGraph. Use --project AGENT_DIR from another directory. Existing files are never overwritten. Without --schedule, the default is daily at 09:00 UTC. Edit the generated function, queue, retry policy, and arguments as needed.
New projects include this pattern in cron/_README.md. With harnest init --example, copy cron/_example.py to cron/daily_report.py to register the sample schedule. It creates its own queued task; leave the independent tasks/_example.py untouched unless you also need that task. Configure task and cron storage before serving.
Define a function with @cron in cron/. Harnest creates its queued task; you do not need a separate tasks/ file. This example runs at 09:00 UTC, Monday–Friday:
cron/daily_report.py
cron/<name>.py file and match the export to the filename: daily_report.py defines daily_report. Nested folders are unsupported; names beginning with _ or . are ignored. You do not need to import schedules in agent.py.
Reuse an existing Task
UseCron when you already have a Task or want multiple fixed schedules for the same work. With the shared build_report task from Create and run Tasks, a declaration looks like this:
cron/daily_report.py
daily_report.py. See the Cron API reference for constructor fields, decorator parameters, and expression constraints.
Migrate an existing Cron declaration
Cron(...) remains supported for backward compatibility and for scheduling an existing reusable Task. It is not deprecated. Use @cron(...) when you want the scheduled function and its task policy in one file.
Run harnest upgrade . to find existing Harnest Cron(...) calls. The plan lists their source locations under Optional migrations and links to this checklist. --apply performs required upgrades but does not convert these declarations or move their Tasks automatically.
1
Review callers and retained work
Check whether the existing Task is used by
.defer(), other fixed schedules, or stored dynamic jobs. Keep Cron(..., task=...) if the Task is shared. Inspect pending and retrying work before removing any old task definition.2
Move dedicated work into the cron function
Replace the declaration with a function whose name matches its
cron/<name>.py filename, using the decorator example above. Move the dedicated implementation and its imports, preserving synchronous or asynchronous behavior and its docstring. Copy the expression, arguments, queue, and retry policy. The defaults apply if you omit those options; they are not inherited from the old Task. Use only @cron, without stacking @task.For work scheduled dynamically only, use @cron() and update new cron.create(...) calls to select the function by name, as shown in Dynamic schedules.3
Plan the schedule transition
The generated Task has a new identity, even when the cron filename stays the same. At startup, Harnest pauses the old fixed schedule and registers a replacement whose next occurrence starts from deployment time. The old next-run cursor is not transferred. Avoid running conflicting old and new deployments together, and keep the old Task available until queued or retrying occurrences have finished.Existing dynamic jobs retain their original Task target. Editing a job does not retarget it. If you choose to move those jobs, pause or cancel each old schedule and create a replacement with a new key and the intended arguments. Already queued work continues. Keep the old Task while any retained jobs still need it.
4
Verify before removing the old Task
Run
harnest test ., check your storage configuration, and review the next scheduled run. Remove the old Task only when no callers or unfinished jobs depend on it. Keeping the supported Cron(...) declaration avoids this identity transition entirely.Run the scheduler
Register the same durable storage instance with@lifecycle.storage.tasks and @lifecycle.storage.cron using Task and cron storage, then run:
harnest run does not activate schedules. Replicas sharing the provider coordinate occurrence submission through storage.
Plan for retries and permissions
Schedules use five cron fields and UTC only. Account for daylight-saving changes if your users expect a fixed local time. A scheduled occurrence queues a Task, and workers may retry it. Make the task’s effects idempotent even though the shared provider atomically commits each occurrence. See Make retries safe. Fixed schedules run with Harnest’s automation identity. If a scheduled task invokes an agent that needs governed tools or MCP operations, supply an explicit service principal.Change a schedule
Edit the application declaration and deploy it through your normal workflow. A fixedCron declaration has no runtime pause or cancel methods. For user-controlled schedules, create a CronJob through the dynamic schedule API.