Scheduled agents
Prompts that run automatically on a cadence on the user's device - built-in ones you ship, plus ones the end-user creates - and, when the app is connected, their account's schedules beside them.
A schedule runs a prompt on a cadence, with no user present. A local schedule is entirely on-device: it is stored on the device, fired by the local companion daemon, and executed through the user's own connected CLI - its creation, runs, and results never touch a backend.
When the app is connected to your backend, the Schedules tab also shows the account's backend-origin schedules beside the local ones, origin-tagged (On this device / From <product>). Those are a live projection, read fresh over the app's own session and never stored on the device - disconnect and they vanish from the tab with nothing to clean up. An edit writes through to each schedule's origin (a local edit stays on the device, a backend edit goes to your backend); moving a schedule between the two is not a v1 action.
Schedules appear on the Schedules page, in two sections:
| Section | Authored by |
|---|---|
| Built-in | You - schedules the app ships. The user enables the ones they want; the prompt and cadence are fixed, and they can never be deleted. |
| Your schedules | The end-user, when config.desktop.agents.userSchedules is true (the default). They create, enable, run now, edit, and delete their own. |
Set config.desktop.agents.userSchedules to false to ship only your built-in schedules.
When schedules fire
- While the app is open, by default. The daemon runs with the app, so schedules fire only while the app is running - and the tab says so. A schedule that comes due while the app is closed fires once at the next launch, never once per missed interval.
- Or in the background. The companion can instead run as a background OS service, so schedules keep firing while the app is closed. That service runs from a stable copy staged outside the app bundle, so an app update or move never strands it - and because it runs detached, config and schedule changes reach it the next time the app opens, not live. The Schedules tab states which mode is active.
- On an interval - the presets (hourly, daily, weekly) and every-N-minutes are all intervals, with a 5-minute floor. Cron expressions are not supported for on-device schedules.
- Unattended, confined like chat. A scheduled run goes through the same audited executor as chat: same folder confinement, same local audit log, same policy checks.
Each schedule shows its last run and last result on the tab, and any schedule can be run once on demand. A schedule the user creates starts disabled, so nothing runs until they turn it on.
Run outcomes
| Outcome | Meaning |
|---|---|
| Completed | The run finished; its output is recorded on-device and readable from the tab. |
| Failed | The CLI run errored; the schedule waits for its next interval. |
| Blocked | The device's local policy refuses scheduled work (policy set --local --schedule deny). The refusal is audited like any other run. |
Built-in schedules
Built-ins are declared in packages/config/src/ai/schedules.ts and staged into the app at launch. An entry appears on the tab when its surfaces includes "desktop" or is omitted (omitted means every surface); the user controls nothing but the enable switch. Editing a built-in's config requires an app restart to reach the daemon - the staged copy refreshes when the app launches.
Terminal and side panel
The right side panel's two built-in tabs - Chat and Terminal - how to choose which ship and which opens first, and what the Terminal gives the user's own CLI.
Companion setup
A desktop app with AI bundles a local companion daemon on the user's machine - one consent click, no download, and nothing to pair for local use. It runs while the app is open, or as a background service when the user turns that on, and can optionally connect to your backend.