---
title: Pipeline
description: "launchd on the Mac polls a 7-day window daily: enumerate, fetch, transform, upload."
---

# Pipeline

**launchd on the Mac, daily at 06:00 and on wake, polling a 7-day overlapping
window.** The window is the error handling. There is no alerting and no
on-call, because a missed run is repaired by the next one.

```text
  launchd (Mac, 06:00 daily + on wake)
        │
        │  S2S OAuth token — grant_type=account_credentials, 1h TTL
        │
        │  ══ ENUMERATE ═══════════════════════════════════════════
        ├─▶ GET /meetings/meeting_summaries?from=today-7&to=today
        │      └─ one row per occurrence that has a summary
        │
        │   fallback, if that scope is not grantable or rows carry
        │   no UUID — for each of the 5 users:
        │     GET /report/users/<user-id>/meetings?from=&to=
        │     GET /past_meetings/<meeting-id>/instances
        │
        │  ══ FETCH ═══ per UUID not already on disk ══════════════
        ├─▶ GET /meetings/<meeting-uuid>/transcript      → VTT
        ├─▶ GET /meetings/<meeting-uuid>/meeting_summary → JSON, optional
        │
        │  ══ TRANSFORM ═══════════════════════════════════════════
        ├─▶ VTT ─▶ markdown + YAML frontmatter, .vtt kept alongside
        │
        │  ══ WRITE ═══════════════════════════════════════════════
        ├─▶ ~/transcripts/...            local mirror, outside git
        └─▶ aws s3 cp → private bucket   system of record
```

## Enumerate then fetch. You cannot skip the first half

`GET /meetings/<meeting-uuid>/transcript` needs a UUID you do not have yet.
Finding UUIDs is a separate step and it is the part most likely to need
adjusting at build time — see
[open questions](/be-pipeline-meeting-transcripts/open-questions).

The fallback enumerator does not depend on AI Companion or cloud recording
being on, so build it if the primary answer is inconvenient.

## Idempotency

The meeting UUID in frontmatter is the key. **If a file with this UUID exists,
skip.** That is the entire mechanism, and it is what makes re-running the poller
over an overlapping window safe. No state file, no cursor, no lock.

## Staleness [SPEC]

Two cadences, and they measure different legs. Do not confuse them.

```text
  Zoom publishes ──(0–24h, Zoom's processing)──▶ available via API
                 ──(daily 06:00 poll)──────────▶ in S3 + on the Mac
                 ──(15-min aws s3 sync)────────▶ on any other mirror
```

> **Spec line:** the daily poll sets end-to-end latency — a meeting is readable
> the next morning. Any additional mirror is within **15 minutes** of S3 via a
> launchd `aws s3 sync`. Zoom's own processing dominates either number.

The sync is a scheduled job, never the agent. Agents in this harness read files
and may have no shell at all. **Do not assume an agent refreshes its own
mirror.** Who owns the job is owed to Ryan and is listed as open.

## When to move it off the Mac

If the Mac stops being reliably on, or a non-Mac consumer appears: EventBridge
plus a scheduled Lambda writing to the same bucket. Same polling code, same
idempotency key, same file shape. One deploy, not a redesign.

**Not** a webhook-to-Lambda build. If you build that, you end up building the
reconciliation poller anyway and maintain two things.

## Off-the-shelf tools: no

RecordFlow, RecordMover and Zoom's own Dropbox integration all copy new **cloud
recordings** to Drive or Dropbox. Same disqualifier for all three: they handle
route 1 only, so you pay for video to get text, and none do the VTT to markdown
transform that makes the file useful to an agent. They solve archiving, not
agent-readable notes.
