> ## Documentation Index
> Fetch the complete documentation index at: https://docs.matrices.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Run on a schedule

> Send an agent a message later, or every morning.

A [schedule](/model/schedules) sends a message to an agent when you're not
around, either once at a time you pick or over and over on a cron. You'll set up
both, point one at a conversation that already exists, and read back what
happened.

## 1. Schedule one message

`Idempotency-Key` is required. Send the same key twice and you get the same
schedule back instead of a second one:

```sh theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
export SCHEDULE_ID="$(
  curl --fail --silent --show-error \
    "https://api.matrices.com/v1/agents/$AGENT_ID/schedules" \
    -H "Authorization: Bearer $MATRICES_API_KEY" \
    -H "Idempotency-Key: ai-act-followup" \
    -H "Content-Type: application/json" \
    -d '{
      "name": "Follow up on the AI Act",
      "message": "Check whether the EU AI Act guidance moved, and summarize what changed.",
      "target": { "mode": "spawn" },
      "timing": { "kind": "once", "runAt": "2026-09-01T15:00:00Z" }
    }' | jq -er '.id'
)"
```

`runAt` is any RFC 3339 time in the future. Set it a couple of minutes out and
you can watch this one go. `spawn` starts a fresh thread every time the schedule
fires, which keeps unrelated runs out of one context window. Once a `once`
schedule fires, it sets `completedAt` and flips `enabled` to `false`. Give it
new `once` timing to bring it back.

## 2. Make it recurring

Five-field cron plus an IANA timezone, so it follows daylight saving where the
person reading it lives:

```sh theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
export DIGEST_ID="$(
  curl --fail --silent --show-error \
    "https://api.matrices.com/v1/agents/$AGENT_ID/schedules" \
    -H "Authorization: Bearer $MATRICES_API_KEY" \
    -H "Idempotency-Key: morning-digest" \
    -H "Content-Type: application/json" \
    -d '{
      "name": "Morning digest",
      "message": "Summarize anything that changed since yesterday.",
      "target": { "mode": "spawn" },
      "timing": { "kind": "repeated", "cron": "0 8 * * 1-5", "timezone": "America/Los_Angeles" }
    }' | jq -er '.id'
)"
```

## 3. Or keep it in one conversation

Point `target` at a thread the same agent already owns. Each fire is queued onto
that thread, so the agent keeps its context from run to run:

```sh theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
curl --fail --silent --show-error \
  "https://api.matrices.com/v1/agents/$AGENT_ID/schedules" \
  -H "Authorization: Bearer $MATRICES_API_KEY" \
  -H "Idempotency-Key: standup-thread" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Standup notes",
    "message": "Add the standup notes for today to our running summary.",
    "target": { "mode": "thread", "threadId": "'"$THREAD_ID"'" },
    "timing": { "kind": "repeated", "cron": "30 9 * * 1-5", "timezone": "America/Los_Angeles" }
  }'
```

## 4. Pause or delete

Pausing keeps the schedule and its history:

```sh theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
curl --fail --silent --show-error -X PATCH \
  "https://api.matrices.com/v1/schedules/$DIGEST_ID" \
  -H "Authorization: Bearer $MATRICES_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "enabled": false }'
```

`DELETE /v1/schedules/{scheduleId}` removes it for good. Threads it already
started stay where they are.

## Check it worked

After the run time passes, read the schedule back:

```sh theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
curl --fail --silent --show-error -H "Authorization: Bearer $MATRICES_API_KEY" \
  "https://api.matrices.com/v1/schedules/$SCHEDULE_ID" |
  jq '{ enabled, nextAt, lastStatus, lastThreadId, disabledReason }'
```

`lastStatus` is `fired`, `skipped`, or `failed`. `lastThreadId` points at the
thread the message landed in; open its [events](/reference/thread-events) to
see what the agent did. `nextAt` is when it runs again.

## When it goes sideways

* **`lastStatus: skipped`.** The previous scheduled turn was still running.
  Fires don't stack up behind each other, so the late one is dropped.
* **`disabledReason: thread_not_found`.** A `thread` target pointed at a thread
  that no longer exists, so Matrices switched the schedule off rather than
  guessing. A fire that fails outright switches it off the same way.
* **Nothing fires at all.** Check the agent's
  [identity](/model/identities). Disabling one stops every schedule under it.

## Where next

<CardGroup cols={2}>
  <Card title="Wire up a team" icon="users" href="/guides/agent-team">
    Let one agent's output wake another.
  </Card>

  <Card title="Schedules" icon="clock" href="/model/schedules">
    Timing, targets, and what a fire freezes.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.