Unattended runs: propose, activate, run, and the webhooks that tell a runner what happened.

View as Markdown

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

HalfWhoWhat
Proposean agent, or youpasskey_automation_propose: a name, prose instructions, the accounts it uses, a trigger. Nothing runs.
Activateyou, on the dashboardA consent screen naming the accounts and a window. Mints the grant, the unattended-signing policies, and the automation token (shown once).
Runthe runnerpasskey_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). 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.