Automation setup
Build a durable, multi-pod automation that wakes up on schedule, webhook, or inbound event, picks the right credentials, runs against your SaaS stack, and survives both your laptop closing and a pod restart. This guide walks the full lifecycle: template selection, trigger configuration, credential attachment, memory, safety, monitoring, and migration of legacy state.
Quick start (3 minutes)
- Open Studio → Automations (or the VS Code extension's Automations sidebar). Eight customer-ready templates are listed: Social Media Marketing, Email Triage, Daily Task Timeline, Weekly Status Report, Lead Capture & CRM Sync, Customer Support Triager, Competitor Monitor, Newsletter Compiler.
- Pick one. Fill the form-driven variables (brand name, voice, Notion content calendar id, target Slack channel, daily cap, etc.). Each template ships with production-grade prompts and per-template safety rules.
- Hit Enable. The automation lands in
automation.automationsin enabled state; the AutomationDaemon's scheduler tick (default 30s) computesnext_fire_atand the worker pool claims the run viaSELECT ... FOR UPDATE SKIP LOCKED.
Your laptop can close from this point forward — the work runs on the cloud-resident API pod. Track progress under Sessions or the per-automation history view.
Triggers (10 types)
A trigger is one row in automation.automation_triggers attached to an automation. Mix and match — an automation can have any number of triggers; any one firing queues a run.
| Type | Wakes on | Config keys |
|---|---|---|
scheduled | Cron expression or interval seconds | cron · interval_seconds · timezone |
webhook | POST /webhooks/{tenant_id}/generic | secret · path |
github | GitHub webhook (X-Hub-Signature-256 verified) | secret · event_types · repository |
slack | Slack events API (X-Slack-Signature + 5-min replay window) | secret · channel · event_types |
linear | Linear webhook (Linear-Signature bare hex) | secret · event_types |
pagerduty | PagerDuty webhook (v1= w/ comma rotation) | secret · severity |
email | Mailgun/Postmark inbound or IMAP poll | secret · filter · from |
rss | RSS feed poll (RSS 2.0 + Atom) | feed_url · poll_interval_minutes · filter_keywords |
manual | Hit "Run now" in Studio | — |
research_event | Research module emits an event | event_types |
Webhook signatures are verified per trigger at the route layer — see API reference for the canonical schemes per provider. Per-trigger secrets let a single tenant route two different repos to two different automations using two different shared secrets.
Credentials (encrypted at rest)
Templates that talk to SaaS providers reference an agent_credentialsrow by id (or by label). The row holds a Fernet-encrypted access token and an optional refresh token; the plaintext is decrypted in-memory immediately before the adapter call and dropped at end of call. Cross-tenant access is rejected at the resolver — never at the route handler.
Supported credential kinds (drives the 11-adapter dispatch):
gmail_oauth·outlook_oauth— SMTP/IMAP via XOAUTH2; also Google Calendar.slack_bot·slack_user— chat.postMessage, conversations.open, users.lookupByEmail.notion— database query, page create. Notion-Version pinned to a stable date.hubspot— contacts/batch/upsert by email (idempotent by design).linear— GraphQLissueCreatewith priority remap.x_twitter·linkedin— social_post:{platform} routing.github— PR open / comment / reviewers (also supports unsigned read).generic_api_key·oauth_client— generic SMTP, IMAP, or HTTP-bearer use.
Memory across runs
Each automation has a per-automation key/value store backed by automation.agent_memory_entries. The store survives across runs, across pod restarts, and across regions. Templates use it for:
- "What did I post about yesterday?" — so the Social Media template doesn't repeat a topic on Friday that ran on Wednesday.
- "Last seen issue id in this Linear team" — so the support triager doesn't re-open closed tickets.
- "Latest RSS item GUID per feed" — so the competitor monitor only acts on truly-new items.
AutomationMemoryEvolution scores recommended-pattern entries by past-run quality and injects the top-K into the prompt context at run start — so each run begins with "what's worked before" rather than rediscovering it.
Safety rails
Every shipped template carries inline, prompt-level safety rules that survive runtime mutation. Examples:
- "Never auto-reply to personal mail" (Email Triage).
- "Never claim work that wasn't actually shipped" (Weekly Status Report).
- "Never buy ads, send money, or commit pricing" (Social Media Marketing).
- "Default to URGENT and escalate when unsure" (Customer Support Triager).
Additional platform-level rails:
cost_budget_creditsper automation; the daemon refuses to start a run that would exceed the remaining budget.tool_allowlistper automation — the engine refuses to dispatch a tool not on the list.- HTTP adapter SSRF guard rejects private / loopback / link-local addresses unless
MIDCORE_HTTP_ADAPTER_ALLOW_PRIVATE_NETWORKS=1. - Webhook intake enforces per-provider replay window (Slack: 5 min) and idempotency dedup (in-memory LRU + DB row guard).
Monitoring
Three streams to watch a running automation:
- Sessions board (/app/sessions) — agent roster, task DAG, recent runs, live SSE trace.
- Per-automation history —
/api/v1/automations/{id}/historyreturns the last N runs with status, duration, and summary. - Daemon worker logs — every run carries a worker id (
<hostname>-<pid>-<random>) so a single run is traceable across pod restarts.
Migrating legacy file-based state
Automations that ran before the DB-backed platform have run records in ~/.maestro/automations/<name>/runs.jsonl. Migrate them in one shot:
python -m services.autonomy.automation_filesystem_migrator \
--root ~/.maestro/automations \
--tenant-id <UUID>The utility walks every runs.jsonl, normalizes legacy status strings, and idempotently imports each row via a deterministic UUIDv5 — so re-running the migrator is safe and produces no duplicates.