Overview

Overview

Edit source

Automations

An automation is an app-owned project that fires on a trigger — a cron schedule, an inbound webhook, or a manual "Run now" — instead of waiting for a UI click. It lives on disk as a directory inside its owning app:

Code
<appCodeDir>/automations/<id>/  automation.json    # the manifest (this is the source of truth)  handler.js         # the generated handler the runtime executes

That directory is the automation. There is no separate registration step, no SQLite definition table; the manifest file on disk is the only thing the scheduler needs to register the trigger and the runtime needs to execute the handler.

Two structural shapes, one runtime

You'll encounter automations in two flavors. They run identically:

1. A UI app that also owns automations. A regular app — index.html, queries, actions, the lot — with an automations/<id>/ subdirectory carrying one or more automations that operate on the app's data. The Hydrate template's weekly-encouragement is this shape: a hydration tracker that also writes a Sunday-evening summary into its own table.

2. A headless automation app. An app whose folder exists only to hold automations — no index.html, no UI, empty actions[] and queries[] in app.json. The convention is to prefix the app id with auto. (e.g. auto.briefing, auto.email-triage). The desktop gallery uses the prefix as a hint to render the app in the Automations section instead of among UI apps.

There is no kind: "automation" flag in app.json and no headless: true field. The auto. prefix is a convention, not a runtime permission boundary. Whether an app has a UI is determined by whether index.html exists.

Code
# Per-app automation (Hydrate)hydrate/  app.json  index.html  queries/get-today.js  actions/set-cups.js  automations/    weekly-encouragement/      automation.json      handler.js # Headless automation app (Briefing)auto.briefing/  app.json                         # actions: [], queries: []  automations/    briefing/      automation.json      handler.js

What a trigger does

When a trigger fires — the cron clock hits its expression, or an HTTP POST arrives at the webhook route — the runtime:

  1. Resolves the automation by its handle (<appId>/<automationId>) and reads the manifest.
  2. Spawns a worker thread, loads handler.js, and invokes its default export with { ctx, log, input }.
  3. The handler runs JavaScript. It can call MCP tools via ctx.tool, ask the model for a constrained turn via ctx.agent({ prompt }), read/write its KV state via ctx.state, look at prior runs via ctx.runs, and chain to another automation via ctx.invoke. See Handler runtime.
  4. The handler returns { summary?, output? }. The summary shows in the run list; the output is persisted in the run ledger and validated against outputSchema if declared.
  5. Tool calls, model turns, sub-invocations, and the final result are written to per-app runtime.sqlite as a runs row plus run_nodes children. See Run history.

The manifest's prompt is the human intent the builder agent originally translated into handler.js. At runtime it's documentation — what the handler is supposed to do. The handler itself is the executable, and you're free to edit it.

What an automation can not do

A few load-bearing constraints, called out so they don't surprise you:

  • No direct database access. Automation handlers don't get the db proxy that queries and actions receive. If an automation needs to read or write the parent app's data, it does so by calling the parent app's tools through ctx.tool — going through the three-tool dispatcher the same way a UI iframe would.
  • No UI surface. Automations have no index.html, no iframe, no postMessage channel. They communicate by writing data (via tools) and by emitting summaries into the run ledger.
  • requires.model can't be centraid-mock/*. The mock provider is reserved for testing and would recurse into the runtime. The manifest validator rejects it.
  • Webhook triggers don't fire on the desktop. They're preserved in the manifest, but the desktop is a gateway client, not an HTTP host. Webhook fires happen only when the app is deployed to a remote gateway. See Webhooks.

What's in this section

Was this useful?