feat(database,hosted): automations research, and the automation and automation_run tables with their store (automations 1/7)

Merged
DA
7d ago
+10418-0

Automations 1/7: the market research for scheduled and triggered agents, and the first backend layer for them. Nothing reads these tables yet. The schedule engine (2/7), run execution (3/7), the API (4/7) and the triggers (5–6/7) come in later PRs in this stack.

Research

docs/proposals/automations-research.md covers how other products do scheduled and event-triggered agents, with a URL for every claim:

  • Coding-agent products: Cursor, Devin, Codex, Copilot and GitHub Actions, Claude Code Routines, Conductor routines, Jules, Factory, Warp, Amp, Replit and Sweep.
  • Automation platforms: Linear Loops, Slack Workflow Builder, Zapier, Make, n8n, Lindy and Relay.
  • Infrastructure: Vercel Cron and Connect triggers, and the GitHub, Stripe and Standard Webhooks delivery semantics.

It ends with the patterns that matter, the pitfalls, and a recommended v1 for Luke. Two corrections to the brief:

  • --trigger-path is a flag of vercel connect attach, not of create.
  • Relay.app shut down in September 2026.

Wire vocabulary

packages/hosted/src/automation-wire.ts declares each shape once, as Effect schemas:

  • Trigger:
    • schedule: cron plus an IANA zone.
    • github: an event, plus optional label and branch filters.
    • webhook.
  • Action: a coding agent, given a prompt and an optional model choice.
  • Guards: max concurrent runs (1–5) and a daily cap (1–50), defaulting to 1 and 10.
  • Firing: schedule, github, webhook or manual.
  • Run status: queued, started, skipped or failed.
  • Run reason: a fixed word, so nothing a provider says reaches the run history.

Tables

packages/database/src/schemas/automation.ts and the generated migration 20261014130146_automation add:

  • automation:
    • Belongs to the account and one of its plans.
    • Trigger, action and guards are stored as the wire's JSON.
    • trigger_kind and next_run_at are real columns because statements filter on them.
    • The sweep's partial index covers next_run_at is not null.
    • webhook_secret_hash holds only a SHA-256, never the secret.
    • enabled and paused_reason record whether it is paused and why.
  • automation_run:
    • One row per firing.
    • A unique index on (automation_id, dedupe_key) makes a firing claimed twice record one run.
    • coding_agent_id is set null, so a deleted agent leaves its run in the history.

Both tables are new and the change is purely additive, so v0.12.0 is unaffected.

Store

packages/database/src/models/automation-store.ts is effects over the ambient SqlClient:

  • createAutomation writes only on a plan the account reaches (reachablePlan). Otherwise it answers none.
  • Every other read and write is keyed by the account.
  • recordAutomationRun uses on conflict do nothing on the dedupe key, and answers none for a repeat.
  • settleAutomationRun moves a run from queued to one outcome, once.
  • listAutomationRuns and automationRunsSince read the history; the guards (3/7) count recent runs with the second.

Tests

packages/database/src/models/__tests__/automation-store.test.ts runs on PGlite:

  • An account outside the plan's workspace makes nothing on the plan, and cannot read, change, delete or list another account's automation or its runs.
  • A change leaves the fields it doesn't name as they were.
  • The same firing recorded twice makes one run.
  • A run settles once.
  • The "since" read counts only runs at or after the given instant.

storage-schema.test.ts now lists the two new tables.

pnpm check:affected passes. CI's Postgres job runs the store tests on a real Postgres.

No UI changes in this PR, so there is no macOS evidence and verify.sh was not run.

šŸ¤– Generated with Claude Code


Open workspace in Conductor


Devin Review


Summary by cubic

The first part of the automations work: the market research behind scheduled and triggered agents, and the backend layer that stores them. Nothing reads the new tables yet; the schedule engine (2/7), run execution (3/7), the API (4/7), and the triggers (5–6/7) follow in this stack.

Research

  • docs/proposals/automations-research.md surveys how coding-agent products (Cursor, Devin, Codex, Copilot, Claude Code Routines, Conductor, Jules, Factory, Warp, Amp, Replit) and automation platforms (Linear Loops, Slack, Zapier, Make, n8n, Lindy, Relay) handle scheduled and event-triggered agents, with a URL for every claim.
  • It concludes with the patterns that matter, the pitfalls, and a recommended v1 for Luke.

Database and store

  • packages/hosted/src/automation-wire.ts declares each shape once as Effect schemas: schedule/GitHub/webhook triggers, the coding-agent action, concurrency and daily-cap guards, and run statuses and reasons.
  • The new automation and automation_run tables are purely additive. Trigger, action, and guards are stored as JSON; trigger_kind and next_run_at are real columns because statements filter on them. Webhook secrets are kept as SHA-256 only.
  • automation_store.ts scopes every read and write to the account's own automations and gates each on the account still reaching the automation's plan; createAutomation refuses plans the account doesn't reach. Changing a trigger clears the columns only its kind uses: next_run_at outside a schedule, webhook_secret_hash outside a webhook.
  • A unique index on (automation_id, dedupe_key) makes one run per firing; a run settles once, from queued to one outcome.