# Guide: index your automations (https://systemhub.com/docs/guides/indexing-automations)

> Part of systemHUB Docs · Connect: Settings → AI Gateway in systemHUB · Auth: OAuth (scoping is workspace-level today — see Getting started) · MCP tools: 55 · Full corpus: https://systemhub.com/llms-full.txt

An automation is invisible by design. It runs at 7am from a laptop, a repo, or someone's scheduler, and the only time it ever announces itself is the day it breaks — by which point nobody remembers it existed, who owned it, or what quietly depended on it.

This is the companion to [publishing your AI's skills](/docs/guides/publishing-ai-skills). A skill is a job you trigger. An automation is a job that triggers itself, and that difference is the whole reason it needs a different page: a skill that stops working annoys the person who ran it, while an automation that stops working is discovered weeks later by its absence.

The pattern below is the one we run against our own library, refreshed by re-reading the sources rather than restamping the page. Request bodies are **abridged and illustrative** — full contracts in the [tool reference](/docs/tool-reference).

## What belongs on the page

One row per job that runs without a person. Six columns carry their weight:

| Column | Why it's there |
|---|---|
| **What it does** | In outcome terms. "Posts the daily sales figures to the team channel", not "runs `sync.py`". |
| **When** | The schedule as it actually is in the crontab or workflow file, not as a comment claims. |
| **Runs from** | The single most useful column. See below. |
| **Owner** | A person, not a team. Someone has to care when it stops. |
| **Source** | Repo path, plist name, or the tool it lives in, so the next person can find it. |
| **SOP** | A link to the written procedure where one exists, blank where it doesn't. Blanks are a to-do list. |

**"Runs from" is a reliability promise, not trivia.** A job on a hosted runner executes whether anyone is at a desk or not. The same job on someone's laptop runs only while that laptop is awake, and fails silently when it isn't. Two rows can look identical and have completely different odds of having run this morning. Say which is which on the page, in a line the reader can't miss.

## Read the real schedule

The rule that makes this worth doing: **derive every row from the source, never from the last version of the page.** A refresh that copies the previous table forward is a document about what you used to run.

Where to look, and most businesses use two or three of these rather than all:

- **Hosted CI** (GitHub Actions and similar) — check every repository the owner can reach, not just the obvious one. Automations are often deliberately parked in a separate repo so they don't depend on one machine.
- **The machine itself** — `crontab -l`, and on a Mac the launch agents, reading each one for its schedule and the script it calls.
- **Workflow tools** (Zapier, Make, n8n, Power Automate) and **scheduled AI agents** — usually not readable from here. Ask the owner to list them and keep those rows by hand, but keep them *on the same page*. An index that silently covers only the machine-readable half is worse than no index, because it reads as complete.

**Say what you read.** Put the sources for this run on the page, and name what you deliberately couldn't read. That one sentence is the difference between a reader trusting the page and a reader assuming it covers everything.

**Watch the clock.** If your schedules are set in UTC and your team reads local time, daylight saving moves every row by an hour twice a year. State the timezone and note the shift rather than silently being wrong for five months.

## Attach the runnable thing

Where a skill page carries its `SKILL.md`, an automation page carries whatever a person would actually need: the workflow file, the script, or a short runbook saying what to do when it fails.

Use `fileUrl` rather than `contentBase64` — anything above roughly 30KB fails as base64, and any public HTTP(S) URL works:

```
→ add_document_attachments {
    "id": "d2f02630-…",
    "attachments": [{
      "title": "daily-sales-sync.yml",
      "fileUrl": "https://example.com/exports/daily-sales-sync.yml"
    }]
  }
```

Two traps worth knowing before you build a refresh around this:

- **Attachments can't be replaced.** Re-uploading a file whose name already exists returns `skipped` with `reason: "duplicate"` and leaves the old content in place. It is not an error and nothing warns you. Version by filename (`daily-sales-sync-v2.yml`) or delete the old one in the app first.
- **Pass `mimeType` explicitly** for text formats like Markdown. Some perfectly valid URLs are rejected without it.

## Write the page for the person who finds it at 6am

The index's real reader is someone discovering that something didn't happen. Structure for them:

- **A count in the overview.** "95 automations across 6 departments on 5 runners." It tells a returning reader instantly whether anything moved.
- **A "how to read this" block** before the tables, covering the runner distinction and the timezone.
- **Grouped by department**, the same grouping the skills index uses, so the two pages feel like one system.
- **A dated stamp** of when it was last refreshed and by whom.

```
→ create_policy {
    "parent": "8c1f4a20-…",
    "title": "Automation Index",
    "description": "Every automation running in the business, in one place. 95 automations across 6 departments and 5 runners. Last refreshed 28 Sep by Sam.",
    "content": "<p>If it runs without anyone pressing a button, it belongs on this page.</p><h3>How to read this</h3><p><strong>Runs from</strong> is the reliability promise. Hosted jobs run whether anyone is at a desk or not; laptop jobs only run while that laptop is awake, and fail quietly when it is asleep. Times are local; schedules set in UTC shift by an hour over daylight saving.</p><h3>Sales &amp; CRM — 16</h3><table>…</table>",
    "state": 1
  }
```

## Refreshing it

Refresh the same page rather than creating a second one — a library with two automation indexes has none.

- **Report the delta, not just the new state.** Added, retired, and changed since last time. On our own last refresh that surfaced three automations that had been running for months and had never been indexed at all, which is exactly the finding the page exists to produce.
- **Put the delta in `note`,** and remember `note` is visible to anyone who can open the document, including anyone on a share link. Keep it factual.
- **Batch your edits.** Every edit is a real revision, so build the full table and write once rather than appending row by row.
- **Retire rather than delete.** The MCP cannot delete anyway. Mark a dead automation archived (`state: 4`) so the page stays honest about what happened to it.
- **Set a `reviewDate`.** An automation index is stale the moment someone ships a new job, and quarterly is not excessive.

## The checklist

- [ ] Every row derived from its source this run, not copied from the last version
- [ ] The sources you read, and the ones you couldn't, both named on the page
- [ ] "Runs from" filled for every row, with the reliability difference explained
- [ ] A named human owner per row
- [ ] Timezone stated, daylight saving noted
- [ ] Workflow file, script or runbook attached via `fileUrl`, versioned by filename
- [ ] Count and refresh date in the overview
- [ ] Delta from last run recorded in `note`
- [ ] One index page, refreshed in place
- [ ] `reviewDate` set
