Reference

Manifest

Edit source

Manifest reference

automation.json lives at <appCodeDir>/automations/<id>/automation.json. It is the source of truth for the automation — toggling enabled, changing the schedule, or rewriting the prompt are all just edits to this file. There is no SQLite definition table; the scheduler and runtime read the manifest directly.

The schema lives in packages/runtime-core/src/automation-manifest.ts — the validator that ships at runtime is the canonical source.

A complete example

From packages/app-templates/auto.briefing/automations/briefing/automation.json:

json
{  "name": "Briefing",  "version": "0.1.0",  "description": "A scannable catch-up on your calendar, inbox, and messages — waiting for you each evening.",  "enabled": false,  "prompt": "Generate a briefing to help me catch up. Include:\n\n1. Schedule — upcoming meetings…",  "triggers": [{ "kind": "cron", "expr": "0 18 * * 1-5" }],  "requires": {},  "history": { "keep": { "count": 100 } },  "generated": { "by": "centraid-template", "at": "2026-05-23T00:00:00.000Z" }}

Fields

Identity

Field Type Required Notes
name string Display name. Non-empty.
version string optional Defaults to "0.1.0" if omitted.
description string optional One-liner shown next to the automation in the desktop UI.
prompt string The human intent the builder agent translated into handler.js. The handler is the source of execution truth; the prompt is documentation.

Lifecycle

Field Type Default Notes
enabled boolean true The user's on/off toggle. Toggling rewrites the file. A scheduler host that finds enabled: false skips registration entirely.

The conversational builder always writes enabled: false at scaffold time — a human must explicitly turn the automation on. That's a deliberate constraint, not a default the agent is allowed to override.

Execution

Field Type Required Notes
triggers AutomationTrigger[] optional An empty array (or omitted) is legal — "manual fire only." At most one entry may be a webhook trigger. See Triggers.
trigger single object optional Legacy single-trigger shape. Dual-read for backward compatibility; rewritten to plural on next save. New manifests should use triggers.

Capability requirements

Field Type Notes
requires.mcps string[] MCP server ids the handler needs (["github", "linear"]).
requires.tools string[] Fully-qualified tool names (["github.list_pull_requests"]).
requires.model string Model ctx.agent calls route through. Format: provider/model-id (e.g. "anthropic/claude-3-5-sonnet", "openai/gpt-4o").

Rejected: requires.model starting with centraid-mock/ (the mock provider would recurse into the runtime itself). The validator throws mock_model_disallowed.

Association

Field Type Notes
apps string[] App ids this automation is associated with — used by the desktop's per-app Settings screen to surface "automations operating on this app." Independent of the owning app folder; an automation in auto.briefing/automations/briefing/ can declare apps: ["calendar", "mail"] to show up under both.

Cost surface

Field Type Notes
costEstimate.model string The model that's billed per fire (typically matches requires.model).
costEstimate.tokensPerFire number Non-negative finite estimate. The desktop's Automations gallery surfaces this before the user enables a paid automation.

Output shape

Field Type Notes
outputSchema JSON Schema Validates the handler's returned output. Only { "type": "object" } is supported — top-level must be an object. properties and required are honored. A validation failure marks the run as error with the schema message attached.

Failure handoff

Field Type Notes
onFailure string Automation handle to fire when this one fails. Format: <appId>/<automationId>, or a bare <id> for a sibling within the same app. Validated against the same grammar as ctx.invoke targets.

A typical pattern: the main automation does its work; onFailure points at a sibling that logs to a "needs-attention" table or notifies a Slack channel.

Retention

Field Type Default Notes
history.keep {count: N} | {days: N} | "all" | "errors" { count: 100 } Applied at end-of-run to the runs table (and via cascade, run_nodes). "errors" keeps only failed runs; "all" is a no-op.

Provenance

Field Type Required Notes
generated.by string Who wrote this manifest. Convention: "template", "centraid-template", "builder-agent", "hand".
generated.at string ISO 8601 timestamp.

The generated block is required — every manifest declares where it came from. The conversational builder writes "builder-agent"; template scaffolds write "centraid-template"; hand-authored files declare "hand".

Validation behavior

parseManifest(json) and validateManifest(raw) throw AutomationManifestError with one of these codes:

Code When
invalid_json The string didn't parse as JSON.
missing_field A required field is absent or empty.
invalid_field A field is the wrong type or shape.
invalid_trigger A trigger entry has an unknown kind, an invalid cron expression, an invalid webhook slug, or the manifest declares two webhook triggers.
invalid_history history.keep isn't one of the four allowed shapes.
invalid_on_failure onFailure is not a valid automation-handle string.
mock_model_disallowed requires.model targets the mock provider.
invalid_output_schema outputSchema isn't {type: "object"} or has invalid properties / required.

The error carries a field path (e.g. "triggers[0].expr") so the desktop UI can highlight the offending entry.

A minimal manifest

The shortest legal manifest — useful for "fire on demand only, with no tooling requirements":

json
{  "name": "On-demand task",  "prompt": "Do the thing when I click Run.",  "generated": { "by": "hand", "at": "2026-05-26T00:00:00.000Z" }}

Defaults fill in version: "0.1.0", enabled: true, triggers: [], requires: {}, history: {keep: {count: 100}}.

Where to go next

  • Triggers — what schedules and webhooks look like in triggers[].
  • Handler runtime — what handler.js can do.
  • Webhooks — pending-to-provisioned handshake and the route handler.
Was this useful?