Machine
Automations

Work that startswhile nobody is watching.

A trigger starts a session with no one at the keyboard. A cron schedule fires it on the clock. A signed webhook fires it when something happens. Either way, the agent gets its own cloud computer, its own branch, and the same review on the way back.

Two trigger types · Declared in your project config · Reviewed like any change

triggersscheduled
Types
cron or webhook
Schedule
6-field cron, any IANA timezone
Webhook auth
HMAC-SHA256. No unsigned path.
Runs as
An agent you name

daily-digest · 0 0 9 * * 1-5

Weekdays at 09:00, America/Los_Angeles

2
Trigger types
4
Session modes
3
Provisioning at once, by default
5min
Idle, then it stops itself
01Two types

A clock or a signature. Nothing else.

Every trigger is one or the other. The rest of it (which agent runs, what it’s told, which session it lands in) works the same way for both.
cron

On the clock

A 6-field cron expression (second, minute, hour, day, month, weekday) in any IANA timezone. Or a single run_at timestamp, for a job that should happen once and then stay quiet.
webhook

On an event

Another service POSTs to the trigger URL. Machine checks the signature, drops the payload into the prompt, and starts the session. If the payload fails your filter, it’s accepted and ignored.
Both name the agent they run as, and inherit that agent’s deny-by-default reach.
Both carry a prompt template that becomes the session’s first message.
Both live in your project config, so both have a history and an author.
02The schedule

Your whole schedule, in one table.

Each trigger is a row: its name, when it fires, in which timezone, as which agent, and which session it lands in. No hidden state to click into.
triggers · this project4 enabled
TriggerCronTimezoneAgentSession
daily-digest0 0 9 * * 1-5Weekdays at 09:00America/Los_Angelesmachinefresh
invoice-sweep0 30 6 1 * *The 1st of the month at 06:30Europe/Berlinfinancereuse
oncall-handoff0 0 17 * * 5Fridays at 17:00UTCsupportfresh
roadmap-review0 0 8 * * 1Mondays at 08:00America/New_Yorkplannerpinned

Real timezones

A trigger stores an IANA timezone name, not an offset, and defaults to UTC. Set America/Los_Angeles and it stays at 09:00 local through daylight saving. An abbreviation like PST gets rejected, not guessed.

No cron required

The Schedules screen is a picker: every few minutes, weekdays, monthly, or once at a moment you choose. Raw cron is there when you want it. You never have to start with it.

One switch pauses it all

A project-level pause stops every trigger at once, on top of each trigger’s own enabled flag. Handy when one repo runs on two control planes and nothing should fire twice.

Bursts wait their turn

By default a project provisions 3 triggered sessions at once. A fire past that limit comes back queued and runs when a slot opens. It doesn’t fail.
03Declared in the repo

An automation is a file, not a dashboard setting.

Triggers live in your project config, right next to your agents and sandbox images. Each one names its agent, its schedule or secret, and the prompt that becomes the session’s first message.
your project config
# fires on the clocktriggers:  - slug: daily-digest    type: cron    agent: machine    cron: "0 0 9 * * 1-5"    timezone: America/Los_Angeles    session_mode: fresh    prompt: |      Summarize yesterday’s commits.      Open a change request against main. # fires on an event  - slug: new-lead    type: webhook    agent: sales    secret_env: WEBHOOK_SECRET    prompt: >-      A new lead arrived: {{ body.name }}      ({{ body.email }}). Add it to the CRM.
machine triggers
# add it, ship it, and the schedule is live$ machine triggers add daily-digest --type cron \    --cron "0 0 9 * * 1-5" \    --timezone America/Los_Angeles \    --prompt "Summarize yesterday. Open a CR."$ machine ship→ project config pushed. daily-digest is scheduled. # every trigger, and when it last fired$ machine triggers ls # no need to wait for 09:00 to find out$ machine triggers fire daily-digest→ session started

The prompt is a template

A prompt fills in {{ token.dotted.path }} from whatever fired it. A webhook fire gets {{ body.* }} and the request headers. A cron fire gets {{ cron.schedule }}, {{ cron.timezone }} and {{ cron.scheduled_for }}. A missing value renders as nothing: no error, and no stray braces in the message your agent reads.

04Webhooks

Signed, or it doesn’t fire.

Every webhook trigger names a project secret that signs it. A trigger without one fails validation, so there’s no open webhook to forget about and lock down later.
webhook delivery
Request

POST /v1/webhooks/projects/{projectId}/{slug}

X-Hub-Signature-256: sha256=<hmac>

HMAC-SHA256 over the raw request body, compared in constant time. Machine reads its own signature header or the GitHub-compatible X-Hub-Signature-256, so a repo webhook works with no adapter.

Responses
202
The signature checks out. The session fired, queued behind the concurrency limit, or was deduped against a delivery Machine had already seen.
200
Valid, and skipped on purpose: the project is paused, or the payload didn’t match the trigger’s filter.
401
Signature and token are both missing or wrong. Nothing runs.
404
No such trigger, it’s disabled, or it isn’t a webhook trigger.
409
The secret named by secret_env has no value. It fails loudly instead of firing unprotected.
A filter is a dotted path matched against the same payload the prompt sees. It’s there to break loops: a source that reports both sides of a conversation would otherwise wake the agent on its own reply.
05Session strategy

Fresh by default. A thread when you want one.

Out of the box, every fire is a clean slate. When the work is an ongoing thread rather than a one-off errand, a trigger can re-prompt a session it already owns. Machine tries the modes in order and falls through on failure, so a fire never just vanishes.
Try 01

pinned

Re-prompt one exact session, named by id. If it’s gone or failed, fall through.
Try 02

keyed

Render a key from the payload, then re-prompt the latest healthy session stamped with that exact key. One customer, one thread. It never falls through into another key’s session.
Try 03

reuse

Re-prompt the latest healthy session this trigger created. A pinned trigger lands here before falling any further.
Try 04

freshdefault

Cut a new branch and boot a new cloud computer. It’s the default, and the last stop for every other mode.
A triggered session is visible to the whole project, not just whoever set up the trigger. It stops itself after 5 minutes idle, so a 3am run isn’t a machine billing you until morning.
06Overnight

It fires at 3am. A person still decides.

An automation gets nothing a person wouldn’t. Same isolation, same scoped reach, same single road back to main.

It runs as an agent

A trigger names an agent and inherits exactly that agent’s grants: the connectors, secrets and skills listed in its block of your project config, and nothing more. An agent with no grants gets no access.

It gets its own computer

A fresh fire boots its own isolated machine on its own branch, just like a session you start by hand. Nothing it installs or breaks touches another session.

Nothing merges itself

Work reaches main only through a change request a person reviews and approves. You read the diff over coffee. The machine never gets the last word.

The automation has a history too

A trigger is a block of YAML in your repo. Who added the 3am job, when, and what it was told to say all sit in the log, like any other change.
Automate it

Write the schedule.Read the change request.

Governed automations on one platform. Any model, your keys. Every run on the record.
  • cron + webhook
  • HMAC-signed
  • Change requests only