Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Running a part

A part is a command that prints the capture contract. millpond starts it, reads the lines it prints and banks the files it writes, and Flywheel’s tick later starts it the same way (215, 231). The contract is the one way captures reach an inbox, and millpond’s book describes it whole (the capture contract, and running adapters).

How a host starts a part

eddy’s Flywheel plugin declares each adapter part: its command, the shape of its configuration and the secrets it needs (228). millpond reads the same declaration Flywheel reads, and runs the part as

eddy capture wispr --raw-dir <dir> [--cursor <c>]

with the part’s configuration in the file the CAPTURE_CONFIG environment variable names, and each declared secret in the environment under its declared name. A part that signs in to its service keeps that sign-in itself (A part per service).

millpond starts a part in one of two ways:

  • millpond run <part> makes a fresh raw directory, starts the part with that directory and the part’s last cursor, and lands what the part prints. It then keeps the new cursor and the run’s status: its exit, its failures and the tail of its standard error.
  • millpond tick runs every part of an inbox whose schedule is due, so one timer on a host serves every part there. A schedule is five-field cron, and a run missed while the host slept is caught up once (231).

An inbox’s settings name the one host whose tick runs each part. An inbox checked out on two hosts therefore runs a meeting part once, on the host that holds its sign-in.

What a part prints

A part writes JSON Lines to standard output, and each line holds exactly one of three keys: capture, cursor or done.

One run of the wispr part millpond run starts eddy capture wispr with a raw directory, the last cursor, the configuration and the secrets. The part asks Wispr Flow for meetings recorded since the cursor and gets them with their details. For each meeting not too short to hold anything, it fetches the transcript, writes two raw files and prints one capture line to millpond. It then prints its cursor and its done line, and millpond keeps the cursor and the run's status. No model runs in any step. millpond run wispr eddy capture wispr Wispr Flow starts it: raw dir, cursor, config, secrets meetings recorded since the cursor each meeting with its details FOR EACH MEETING NOT TOO SHORT fetch its transcript the transcript writes two raw files {"capture": …} {"cursor": …} {"done": {listed, failed}} millpond keeps the cursor and the run's status no model runs in any step (115)
millpond starts a part with a raw directory, its last cursor, its configuration and its secrets. The part asks its service for meetings since the cursor, writes each transcript as raw files and prints one capture line per meeting, then its cursor and its done line.

A run of the wispr part that lists three meetings, lands two and fails to fetch the third prints four lines:

{"capture": {"key": "meeting/wispr/m42", "source": "meeting", "event_at": "2026-10-16T15:00:00Z", "title": "Inbox planning", "raw": ["m42/transcript.json", "m42/turns.txt"]}}
{"capture": {"key": "meeting/wispr/m43", "source": "meeting", "event_at": "2026-10-16T17:30:00Z", "title": "Release review", "raw": ["m43/transcript.json", "m43/turns.txt"]}}
{"cursor": "2026-10-17T04:30:05Z"}
{"done": {"listed": 3, "failed": [{"what": "meeting/wispr/m44", "why": "HTTP 502"}]}}

A meeting capture fills the contract’s fields this way:

FieldA meeting part writes
keymeeting/<service>/<meeting id>
sourcemeeting
event_atwhen the meeting started
titlethe meeting’s title
urlwhere the meeting lives on its service, when the service gives it an address
rawthe transcript as served and the plain copy, as paths under the raw directory
excerptnever present

A meeting capture never carries an excerpt. A meeting is not its own excerpt: what it says becomes signals only through a reader’s judgment (115). millpond therefore writes no signal for it and marks it unread, which charges a reader.

The cursor line is the part’s own and opaque to millpond. The done line ends a complete run with its counts and the meetings that failed.

A run that fails

One failed meeting never stops the others. A part names it in the done line’s failed list, with what failed and why, and carries on with the next meeting.

A run that exits without done, or with a status other than 0, keeps every capture already taken, since captures are idempotent. Its cursor does not advance, and millpond records the run as failed. The next run lists the same window again from the old cursor, and millpond passes over every key it already holds.

What millpond does with each line

millpond checks each line and refuses one with a bad key, a missing field or an empty excerpt, naming it in the run. It looks the key up in every capture the inbox holds and passes over a key it already holds. It banks the raw files, writes the capture with its pointer, marks the capture unread, and records the run, which a fleet’s doctor reads from millpond status --json. millpond’s book holds each of those steps (landing a capture).