Skip to main content

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)

  1. 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.
  2. 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.
  3. Hit Enable. The automation lands in automation.automations in enabled state; the AutomationDaemon's scheduler tick (default 30s) computes next_fire_at and the worker pool claims the run via SELECT ... 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.

TypeWakes onConfig keys
scheduledCron expression or interval secondscron · interval_seconds · timezone
webhookPOST /webhooks/{tenant_id}/genericsecret · path
githubGitHub webhook (X-Hub-Signature-256 verified)secret · event_types · repository
slackSlack events API (X-Slack-Signature + 5-min replay window)secret · channel · event_types
linearLinear webhook (Linear-Signature bare hex)secret · event_types
pagerdutyPagerDuty webhook (v1= w/ comma rotation)secret · severity
emailMailgun/Postmark inbound or IMAP pollsecret · filter · from
rssRSS feed poll (RSS 2.0 + Atom)feed_url · poll_interval_minutes · filter_keywords
manualHit "Run now" in Studio
research_eventResearch module emits an eventevent_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 — GraphQL issueCreate with 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_credits per automation; the daemon refuses to start a run that would exceed the remaining budget.
  • tool_allowlist per 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}/history returns 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.

See also