Automated agents
On-device prompts that run on a cadence - the built-ins you ship, the ones end-users create, and how to keep them firing with the app closed.
An automation runs a prompt on a cadence with no user present. The Automations page is ONE list, and every row wears a lane badge saying where it runs - the lanes answer "when does this fire" in opposite ways, so each row states its own answer rather than inheriting it from a section heading.
| Badge | Where it runs | Authored by |
|---|---|---|
| Local | This device, while the app is open | The product's built-ins (prompt, cadence and model fixed, never deletable; the user enables the ones you left them) and the end-user's own (when config.desktop.agents.userAutomations is true - the default). |
| Shared | Each member's own machine, once they turn it on there | Any member. ONE row, whether this machine runs it or not - its switch is this machine's. See Shared automations. |
| Cloud | Your backend | Anyone in the workspace. They keep firing with this app closed, metered to the workspace owner. |
The lane is chosen first, and it routes the create. The authoring dialog opens on Where it runs - On this device (the user's own coding CLI, free, firing only while the app is open) or In the cloud (your backend, metered, firing with the app closed) - and the model picker then offers that lane's models and only those. A cloud pick posts to /ai/automations, which is the only thing that can fire it: this app's runtime holds no backend access at all. A device automation stays here unless its author ticks Share with your workspace, which is offered under the local lane alone. Device automations are scoped to the active project and are never reported to the account.
When automations fire
Cloud automations fire on the backend and are unaffected by everything in this section. On-device ones - built-in, the user's own, and shared ones switched on here - fire:
- While the app is open, by default. One that comes due while it is closed fires once at the next launch, never once per missed occurrence.
- After the window closes, when you set
config.desktop.agents.background- see below. - Unattended, in one folder: the same audited executor, local audit log, and policy checks as chat, inside the project's managed work folder - or its connected folder when the user has bound one.
- With your app's tools, the same ones a coding-CLI chat gets, whenever the app is running - so an unattended run answers account questions from your product instead of guessing. The backend half needs a signed-in user; the four device-automation tools do not. Firing with the app closed carries neither.
- For every reachable project, not only the active one. Leaving an organization, deleting a project, or signing out stops that project's automations here.
- Never from a workspace no screen shows: with
projectsormultiTenanton, the legacy no-project bucket goes quiet and anything left in it stays on disk. With both off, that bucket is the only workspace and is unaffected.
Each automation shows its last run and result, and can be run once on demand. Edit on the detail dialog reopens it in the authoring dialog - name, prompt, model and cadence - in whichever store owns it; a built-in takes no edit at all, and its enable switch only where you left the user one (see Built-in automations). Editing never changes an automation's enabled state or moves it between lanes.
Cadence
An automation fires on exactly one of two cadences:
| Cadence | How it fires |
|---|---|
| Interval | Presets (hourly, daily, weekly) plus every-N-minutes, with a 5-minute floor. |
| Cron | An expression typed under the cadence picker's Custom automation option - 0 9 * * 1-5 for weekday mornings. |
A cron is evaluated in the device's own timezone, captured at creation, so 0 9 * * 1-5 means the user's 9am even when the runtime starts at login under another zone. Due automations are checked every 60 seconds - the granularity floor - and an invalid expression is refused at creation by the parser that fires it.
A new automation's first run is one full cadence away, never immediate: an hourly one first fires in an hour, and the list says so. Editing it later - a rename, a new model - does not re-anchor that; only a cadence change does.
Keep automations firing with the app closed
Off by default. Set desktop.agents.background: true in packages/config/src/desktop.mjs and rebuild; users then get a Keep running after I close the window switch in Settings > General > Automation.
- At quit the app leaves the runtime running, and retires it once nothing has used it for a few minutes.
- At login the switch also registers a login item, so automations fire before anyone opens the app. Only the runtime starts - no window, no dock or taskbar item.
- Per platform: macOS registers a launch agent (macOS 13+; the system may ask the user to allow it under System Settings > General > Login Items, and the switch says so). Windows writes a startup entry. Linux writes a
~/.config/autostartentry.
With background off there is nothing to turn on: no switch, no login item, and the runtime stops with the window.
The three login-item registrations are built and unit-tested on every platform, but none has been exercised on a real login. Verify boot at login on each platform you ship before depending on it.
Run outcomes
| Outcome | Meaning |
|---|---|
| Completed | The run finished; its output is recorded on-device and readable from the page. |
| Failed | The CLI run errored; the automation waits for its next due time. |
| Blocked | The device's local policy refused automated work. The refusal is audited like any other run. |
Built-in automations
Declare built-ins in packages/config/src/ai/automations.ts; the app stages them at launch. An entry appears when its surfaces includes "desktop" or is omitted. Either cadence stages - a spec's cron carries no timezone, so the device evaluates it in its own zone. Editing a built-in needs an app restart to reach the runtime.
Three spec fields decide how much say the user gets. See the full spec table.
| Field | On this device |
|---|---|
toggleable: false | The enable switch disappears. The daemon refuses the override and the runner ignores one already on disk, so the automation runs at your enabled state whatever is stored. |
hidden: true | The row never reaches the Automations page, and it keeps firing on its cadence. Requires enabled: true, toggleable: false. |
defaults.desktop | { cli, model } pins which coding CLI and model the fire runs on. Unset falls back to the user's device default - normally the right choice, since you cannot know which CLIs they connected. |
A pinned model must be a model id the pinned cli speaks; pinning a model with no cli runs it on whatever CLI the device defaults to.
Terminal and side panel
The side panel's two built-in tabs - Chat and Terminal - how to choose which ship and which opens first, and what a Terminal session gives the user's own CLI.
Shared automations
A definition one member writes and every member can choose to run on their own machine - what it stores, how turning it on works, and why consent is the only gate on it.