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.

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.
write_outputsfalseLiving-doc commits from fired runs.
write_memoryfalseLong-term memory commits.
external_searchfalseMarket fan-outs.
include_divergencefalseDivergence-map fusion on external reads.

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 ride the Amdahl MCP server's agents tool (the same tool that carries the Chat lifecycle, the agent library, and Routines): list_subscription_kinds, list_subscriptions, get_subscription, list_subscription_fires, create_subscription, update_subscription, delete_subscription, and test_fire_subscription — the last returns the same Chat/run handles a start_chat does. Subscription reads are also 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