Reference
Manifest
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:
{ "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":
{ "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.jscan do. - Webhooks — pending-to-provisioned handshake and the route handler.