Docs

Subscriptions

Event-fired Chats. A source watches your world - first source: upcoming Google Calendar events - and fires a fresh Chat a configurable lead time before each occurrence, with no human in the loop.

Put an agent behind an event — /subscriptions is event-fired Chats. A Subscription is the event-driven sibling of a Routine: where a routine fires on a cron, a subscription fires when its source observes an occurrence. Each fire starts one turn of Master — or the agent you pin — in a fresh Chat named "{Subscription name} — {date}", with the triggering event rendered into the turn so the agent knows exactly what woke it.

Google Calendar connections are currently unavailable

The calendar source below reads through a Google Calendar connection, and new Google Calendar connections (and reconnects) are paused for now. Existing subscriptions stay stored but do not fire. This page describes the source as it works when the connector is available.

The first source is calendar.event_upcoming: fire a configurable lead time before each event on a connected Google Calendar. "Prep me 6 hours before every customer meeting, and again 10 minutes before" is two subscriptions on the same calendar connection — one per lead time, each with its own prompt, agent, and per-fire policy.

The model

One occurrence
  1. The source observes

    For calendar: one bounded window read per connection, shared across every subscription riding it.

    sweep, every minute

  2. A fire is planned

    One row per (event, lead time), due at max(now, start − lead). Structural dedupe: re-observing an unchanged event costs nothing.

    the fires ledger

  3. The fire dispatches

    A fresh Chat opens and one Master turn runs, headless — on_question is always none.

    when due

  4. The ledger settles

    Every planned occurrence records its outcome, so "did my trigger actually fire?" is one read.

    fired / skipped / canceled

code
event observed (e.g. "Acme QBR", starts 15:00)
   │
   ▼
fire planned: due_at = max(now, start − lead)
   - discovered OUTSIDE the lead window → fires exactly at start − lead
   - discovered already INSIDE the window → fires immediately
   - start already passed → never fires
   - rescheduled → the old occurrence's pending fires cancel; the new start plans fresh
   - cancelled → pending fires cancel
   │
   ▼
new Chat "Meeting prep — 2026-08-24"
   │
   ▼
one Master turn, headless:
   - prompt = the subscription's prompt + the event rendered as a readable block
   - config = the subscription's run_config (depth, actions_allowed, …)
   - on_question is ALWAYS "none"

Every fire is a normal Chat run: watch it over the same SSE stream / read handles, find it in the Chats list (GET /chats?subscription_id=<id> scopes the list to one subscription's fires), and every Chat it starts carries a subscription trigger badge.

Connect a calendar first

The calendar source reads through a google_calendar connection (Connections → Google Calendar; OAuth, per-member — each member connects their own calendar). The connect flow requests offline access so the subscription sweep can keep reading after the initial sign-in. A subscription then names the connection it watches via config.connection_id, and may only name a connection in its own workspace.

Create a subscription

Read GET /subscriptions/kinds first — the source-kind catalog is self-describing: each kind ships its config fields (key, type, required, description), so the config you write matches what the kind expects.

bash
curl -X POST https://app.amdahl.ai/api/platform/v1/subscriptions \
  -H "X-API-Key: $AMDAHL_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Meeting prep",
    "source_kind": "calendar.event_upcoming",
    "prompt": "Prep me for this meeting: who is attending, what we last discussed with their company, open deals, and the three things to bring up.",
    "config": {
      "connection_id": "<google_calendar connection id>",
      "lead_minutes": 360,
      "require_attendees": true
    },
    "agent": "gtm-strategist"
  }'

Body fields

FieldRequiredWhat it does
nameyesHuman label. Each fired Chat is named "{name} — {date}".
source_kindyesWhich source fires it (discover via GET /subscriptions/kinds). Immutable.
promptyesThe Chat input every fire starts with; the event is appended as a readable block.
configyes*Per-kind event config, validated against the kind's own schema (below).
agentnoPin an agent: the fired Master turn runs AS that agent. null clears the pin.
run_confignoPer-fire Chat policy (below).
enablednoDefault true. false pauses without deleting.

* Required in practice for calendar.event_upcoming (it declares required fields); a kind with no required fields accepts an empty config.

calendar.event_upcoming config

KeyRequiredNotes
connection_idyesThe google_calendar connection to watch. Must belong to this workspace.
calendar_idnoGoogle calendar id. Defaults to primary.
lead_minutesyesMinutes before the event start to fire (360 = 6 hours, 10 = 10 minutes). One subscription = one lead.
summary_containsnoOnly fire for events whose title contains this text (case-insensitive).
require_attendeesnoOnly fire for events with at least one attendee.

Run config

The persisted per-fire Chat policy. on_question is not configurable — subscription fires are headless, so a human ask always fails fast with no_human_in_loop.

KeyDefaultNotes
depthstandardquick / standard / deep. A fire is a reaction to one event, so the default is the interactive tier.
actions_allowed(absent = all)Outbound actions each fired run may invoke. Absent = every cataloged action; a list narrows, [] disables.
external_searchfalseMarket fan-outs.
include_divergencefalseDivergence-map fusion on external reads.

The retired write_outputs and write_memory knobs are refused like any other unknown key.

Manage subscriptions

VerbWhat it does
GET /subscriptions/kindsThe source-kind catalog, each kind with its config fields.
GET /subscriptionsList (filter ?source_kind= / ?enabled=, paginate ?limit=&offset=).
GET /subscriptions/:idOne subscription, including the last fire's Chat + run ids and a derived source_health block.
GET /subscriptions/:id/firesThe fire ledger: planned occurrences, due times, outcomes, event snapshots.
POST /subscriptionsCreate.
PATCH /subscriptions/:idPatch name / prompt / config / agent / run_config / enabled.
DELETE /subscriptions/:idRemove it and its fire history. Fired Chats are untouched.
POST /subscriptions/:id/test-fireFire once now with a labeled synthetic event. Returns the Chat + run handles.

test-fire returns the same { chat_id, run_id, stream_url, read_url, resume_url } envelope a Chat START does, so you can watch the fire live before trusting the source. A test fire is not written to the fires ledger — it is a preview, not an occurrence.

The fires ledger

GET /subscriptions/:id/fires is the answer to "is my trigger actually firing?". Each row is one planned occurrence:

StatusMeaning
pendingPlanned, waiting for its due time.
firedStarted a run — the row carries the chat_id + agent_run_id.
skippedDeliberately not run; skip_reason says why (subscription_disabled, rate_limited, agent_unresolvable, …).
failedThe run could not start (start_failed), or a dead worker's claim was reaped.
canceledThe occurrence no longer stands (event_canceled, event_rescheduled).

Reliability semantics

  • Fresh Chat per fire. A subscription never appends into an old Chat.
  • One fire per occurrence, structurally. The ledger's dedupe key (for calendar: event + start time + lead) is UNIQUE, so sweep re-runs and concurrent workers cannot double-plan, and a claim race resolves to exactly one dispatcher.
  • Reschedules re-fire; cancellations never fire. A rescheduled event is a new occurrence — its old pending fires cancel and the new start plans fresh. A cancelled event's pending fires cancel.
  • Never after start. A fire is due at max(now, start − lead) and an event whose start has passed never fires — a "prep me before the meeting" agent is never woken after the meeting began.
  • Rate-capped per workspace. Fires that start runs are capped per business per hour (default 30, operator-tunable), so a calendar with hundreds of imminent events cannot start hundreds of agent runs.
  • A broken credential is a skip, not an outage — and a NAMED one. A connection whose Google credential fails skips its groups for the tick and is retried next tick; other connections are unaffected. The failure is classified and recorded: a definitive credential death (Google rejects the refresh with invalid_grant, or no usable credential is stored) flips the connection to needs_reauth on the Connections surface, while a transient Google error never does.

Source health

A quiet fires ledger has two very different causes — "no matching events" and "we cannot read the calendar" — and GET /subscriptions/:id tells them apart. The detail read carries a derived source_health block:

json
{
  "status": "connection_needs_reauth",
  "detail": "Google rejected the stored credential, reconnect Google Calendar under Connections, then fires resume automatically.",
  "last_swept_at": "2026-08-25T12:00:00Z",
  "checked_at": "2026-08-25T12:06:31Z"
}
statusMeaning + remedy
okThe source is being read on schedule; last_swept_at is the last successful read.
never_sweptNo sweep has read this source yet — new subscriptions are picked up within about a minute.
sweep_failingThe last read failed for a non-credential reason; retries continue automatically.
connection_needs_reauthThe Google credential is dead — reconnect the connection under Connections; fires resume on their own.
connection_unavailableThe named connection is missing, disconnected, or has not finished authorizing.
config_invalidThe stored per-kind config no longer validates — edit the subscription.

source_health can also be null, which means "not assessed" (the health lookup itself failed, or the source kind has no health resolver) — never a claim that the source is healthy. Recovery is automatic in both directions: a successful read clears a recorded failure, and a successful token refresh clears a needs_reauth stamp.

Over MCP

Subscriptions are not on the Amdahl MCP server's tool list: their only event source, Google Calendar, is retired, so no Subscription can fire. Use the REST endpoints above. Subscription reads are still MCP resources: subscription://kinds, subscription://list, subscription://<id>, and subscription://<id>/fires. Scopes: subscriptions:read / subscriptions:write (both on the customer-agent key bundle; existing keys were backfilled).

Scopes

ScopeGrantsDefault role
subscriptions:readkinds catalog, list, get, fire ledgerviewer
subscriptions:writecreate / update / delete / test-fireeditor