# Automations

An automation is a task that signs in as you **without you present**: on a schedule, when a URL
is called, or when an agent runs it. Agent Passkey owns *who* may sign in, *to where*, *until
when*, and *what happened*. It does not own the browser: a **runner** — a Claude Code routine,
your own server, a cron job — does the work and reports each run back.

## The three halves

| Half | Who | What |
|---|---|---|
| Propose | an agent, or you | `passkey_automation_propose`: a name, prose instructions, the accounts it uses, a trigger. Nothing runs. |
| Activate | **you**, on the dashboard | A consent screen naming the accounts and a window. Mints the grant, the unattended-signing policies, and the **automation token** (shown once). |
| Run | the runner | `passkey_run_begin` → `passkey_begin_signin` with the `runId` → do the work → `passkey_run_end`. Every signature is audited under the run. |

## Running one

With the automation token as a bearer on `https://app.solospace.dev/mcp`:

```
passkey_run_begin({ automationId })          → runId, instructions, the connectors it may use
passkey_begin_signin({ credentialId, runId }) → a ticket and the page script, as always
passkey_run_end({ runId, status, summary })   → succeeded | failed | needs_human
```

One run at a time per automation; a second `run_begin` returns the open run. A run left open is
marked `abandoned` after 30 minutes. A scheduled run nobody claims within 15 minutes is marked
`missed` — the one fact a runner can never state about itself.

Any other live grant of the same person may run an automation too, provided it covers every
account the automation names. An automation only ever narrows.

## Triggers

- `{ kind: "schedule", cron: "0 9 * * 1-5", tz: "Asia/Jerusalem" }` — five-field cron, an IANA
  zone. The service pre-creates a run and emits `run.due`; the runner claims it.
- `{ kind: "webhook" }` — `POST /hooks/<id>?token=…` raises a run. 60 per hour.
- `{ kind: "manual" }` — `passkey_run_begin` alone.

## Webhooks (outbound)

Register an endpoint on the dashboard and receive signed POSTs for `run.due`, `run.started`,
`run.finished`, `run.missed`, `run.abandoned`, `approval.requested`, `signin.succeeded`,
`signin.failed`, `automation.expiring`, `automation.paused` and `key.expiring` (seven days
and one day before an API key runs out — see [rotation](/docs/rest#post-v1-keys-rotate-the-key-itself)).
Headers:

```
Agent-Passkey-Event:     run.finished
Agent-Passkey-Delivery:  <uuid>
Agent-Passkey-Timestamp: <unix seconds>
Agent-Passkey-Signature: v1=<hex HMAC-SHA256(secret, timestamp + "." + body)>
```

Reject a timestamp older than five minutes. Retries: 1 m, 5 m, 30 m, 2 h, 12 h. Bodies carry ids
and fixed phrases — never instructions, notes or page content.

## The local runner

`agent-passkey-run <automationId> --exec "<command>"` (from `packages/mcp`) opens the run,
signs in to every account in a persistent Chromium, and hands the signed-in browser to the
command — `APK_INSTRUCTIONS`, `APK_CDP_URL` and `APK_RUN_ID` in its environment — then reports
the run from its exit code. The command is where the actions live; this service never sees them.

## What pausing does

Pause revokes the grant in the same transaction: the token dies, the policies expire, the next
signature is refused. Re-activation is a fresh consent, never an automatic renewal.
