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

millpond

Some events outside a repository should change its work: a release of something it depends on, a decision in a meeting, a deploy that failed. millpond is the inbox those events reach. A program notices each event and hands it to millpond as a capture. millpond records the capture once, turns it into signals, and serves the tools a curation session gives each signal its move through. Curation joins signals into items, and millpond passes each item on: flume launches its work in one worktree, or crew’s plan takes it. sluice lands the work an item becomes. An item that asks a question goes to a sounding, which ends on a conclusion the operator accepts.

adapters the inbox the work landing headwater releases of what a repository declares eddy recorded meetings, a part per service folder drop a file in a folder built into millpond lines millpond the inbox runs adapters on a tick keeps each capture once banks raw files off git holds signals and moves serves curation's tools passes items on a git repository, or a branch in one One worktree flume starts an agent on the brief A sounding one question, one conclusion crew's plan the planner's chores and units send send sluice tests the work as merged, then lands it Flywheel, later built on millpond's crates; reads the same inboxes
headwater and eddy each stand alone, and each carries an adapter part that prints capture lines into millpond. The folder drop needs no tool of its own, so it ships inside millpond. millpond sends an item through flume to one worktree, or to a sounding when it asks a question, or hands it to crew's plan. sluice lands the work of a worktree and of crew's teams.

Why it exists

Every source of events needs the same plumbing: a record of what it has already reported, a place for the raw material, a cut that keeps a quote small, a status a fleet can watch, and a loop that lands each capture. swancloud’s release adapter carries all of it alone today: its own landed list, its work/ and raw/ folders, its 8 KiB cut, its status.json and its landing loop. A meeting source would carry the same plumbing again, and so would every source after it.

millpond holds that plumbing once. An adapter shrinks to the part that knows its source: it lists the source’s events, keys each one, fetches the raw material, and marks a capture that is its own excerpt (115). The dedupe by key, the raw bank, the records, the cut, the cursor and the run’s status are millpond’s.

What it promises

  • A source event is one capture, however many hosts and runs report it (111).
  • Raw material stays on the host and never enters git. The capture points at it (111, 217h).
  • A capture that is its own excerpt gets its one signal as it lands, cut at a line under the inbox’s limit. Every other capture waits, unread, for a reader.
  • A reader’s excerpt appears verbatim in its capture’s raw material, or millpond refuses it (113).
  • Every signal takes exactly one move, every move has a stated consequence, and an unmoved signal is never discarded (107, 116, 118).
  • Weight counts by event date, never by the date of capture (118).
  • The operator decides a batch of moves at a time, and an item at a time, never a signal at a time (109).

Where it stops

millpond never judges what a capture says and never plans across items. A model runs in two places only, a reader and a curation session, and each is a Claude session working through millpond’s tools. Ordering items across teams is crew’s planner today and Flywheel’s later. millpond reads a source only through an adapter, its own built-in ones included. It starts no agent, since flume does; it works no question, since a sounding does; and it tests and lands nothing, since sluice does.

The tools beside it

  • headwater reports releases and advisories for what a repository declares. Its book is at ../headwater/.
  • eddy lists recorded meetings from each meeting service. Its book is at ../eddy/.
  • flume launches and tracks one agent in one worktree through a kind’s stages, and tidies the place when the work lands or is dropped. Its book is at ../flume/.
  • sounding works one question in one session, in a worktree of its own, and ends on a conclusion the operator accepts, sends back for another round, or drops. Its book is at ../sounding/.
  • sluice tests a change as merged and lands it. Its book is at ../sluice/.
  • crew runs teams, plans and bolts. It calls millpond’s, flume’s and sluice’s command lines.
  • Flywheel is built later on the same crates as millpond and reads the same inboxes. crew becomes it (crew ADR 0026).

How to read this book

The chapters follow a capture: the contract it arrives under, the adapters that print it, the seven steps that land it, the inbox that keeps it, the reader that turns it into signals, the curation that moves them, and the items and sends that turn signals into work. Then come the sources millpond runs, the three setups it runs in, and who owns what.

A bare number in parentheses cites Flywheel’s statement, design/flywheel-next/requirements.md, whose record formats and moves millpond keeps. A number after headwater cites headwater’s statement. A rule id such as signals.6 or code.1 cites crew’s docs/architecture/; inputs.6, lists.1, doctor.1 and fleet.6 are swancloud’s. millpond’s own statement is not written yet; Open questions says what it will hold.

The capture contract

The contract is the one way captures reach an inbox. Flywheel’s statement defines it, a crate in flywheel-next implements it, and millpond and Flywheel both take it. One conformance pack proves every host and every adapter part against it: lines in, records expected.

The lines

An adapter writes JSON Lines to standard output. Each object has exactly one of three keys: capture, cursor or done.

{"capture": {"key": "release/mise/2026.10.10", "source": "release", "event_at": "2026-10-10T14:02:11Z", "title": "mise 2026.10.10", "url": "https://github.com/jdx/mise/releases/tag/v2026.10.10", "raw": ["mise-2026.10.10/notes.md", "mise-2026.10.10/release.json"], "excerpt": {"text": "…the notes, whole…", "kind": "ask", "asserted_by": "jdx/mise", "assertion": "mise 2026.10.10 is released; the repository pins 2026.9.18.", "subjects": ["release", "mise"], "argues_with": ["packages/mise/default.nix"], "position": "release notes"}}}
{"cursor": "2026-10-17T04:30:05Z"}
{"done": {"listed": 14, "failed": [{"what": "github:jdx/mise", "why": "HTTP 502"}]}}

A capture

A capture line describes one source event:

FieldRequiredMeaning
keyyesThe source event’s identity. The same event gets the same key from every host and every run (111).
sourceyesThe source’s name: release, meeting, folder, deploy.
event_atyesWhen the event happened, in RFC 3339.
titlenoOne line a person reads in a list.
urlnoWhere the event lives upstream.
rawnoThe files the adapter wrote under the raw directory, as relative paths.
excerptnoPresent only when the capture is its own excerpt. It holds the signal’s fields.

The key

The key is what makes a capture idempotent. millpond looks every key up before it writes, so a source event listed twice, by two hosts or by two runs over the same window, lands once (111). A / separates the key’s segments, as in release/mise/2026.10.10, meeting/wispr/<meeting id> and folder/<folder name>/<sha256 of the file>.

A key never starts with /, never has a .. segment, and never starts with captures or moves. A key becomes part of a folder’s name when the capture comes to rest, and these rules keep that folder inside the inbox and clear of the inbox’s own folders.

The event date

event_at is when the event happened: a release’s publication, a meeting’s start, the date a dropped file’s name begins with. It is never when the adapter noticed the event, because weight counts by event date (118). A backlog imported in one afternoon still weighs as the months it spans.

The excerpt

An adapter includes an excerpt only when the capture is its own excerpt: when the event’s own words are the signal, with no judgment needed to find it. A release is its own excerpt. Its notes are the excerpt, and its signal asks for a bump. A meeting is not, so a reader session turns its transcript into signals later.

The excerpt holds the signal’s fields (113):

  • text, the excerpt verbatim and whole;
  • kind, one of constraint, ask, question, commitment and reaction;
  • asserted_by, who said it;
  • assertion, what it asserts, in a sentence;
  • subjects, its subject tags;
  • argues_with, what it argues with, such as the declarations a bump moves;
  • position, where in the source the text sits.

The adapter never cuts the text. millpond cuts it when it writes the signal, so every adapter’s excerpt meets the same limit the same way.

The cursor

A cursor line carries a value only the adapter understands. millpond keeps the last cursor of a run that ended in done and hands it back on the next run. An adapter works without one: listing a window again is safe, because millpond dedupes by key.

The end of a run

A done line ends a complete run with what it listed and the items that failed, each with what failed and why. One failed item never stops the others.

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, so the next run lists the same window again and lands only what the failed run did not.

Lines and raw files

The lines describe each event, and the raw files hold it.

Adapter prints lines, writes raw files Lines small, one per event Raw files the material, whole The inbox, in git captures and signals, Flywheel's formats The raw bank on the host, never in git pointer <host>:<path> read by a curation session the herdr plugin crew's planner read by a reader session the verbatim check a person past the cut
Only millpond reads the lines; everything after it reads the records in git. The raw material stays on the host, so a meeting's words never reach GitHub. A release's notes sit in both places: whole in the raw bank, and cut in the excerpt that git keeps.
  • A line is small. It holds what millpond checks and what enters git as the capture and its signal.
  • A raw file is the material as the source served it, whole: a transcript, the full notes, a log. It can run to megabytes. It stays on the host and never enters git (111).

Once millpond banks the raw files, three readers use them:

  • a reader session, for which a meeting’s transcript is the whole input;
  • millpond’s check that each excerpt a reader writes appears verbatim in its capture’s raw material;
  • a person following the capture’s pointer past the cut.

Running adapters

An adapter is a program that knows one source. It lists the source’s events, keys each one, fetches the raw material, and marks a capture that is its own excerpt. It writes no records, keeps no list of what it reported, cuts no excerpt, and reads no capture into signals (115). millpond does the rest, whichever adapter printed the lines.

Plugins and adapter parts

A plugin is the installable unit in every host. It holds parts, each of one kind:

HostKinds of part
Claude Codeskills, hooks, commands, MCP servers
herdractions, panes, events
Flywheeladapter, chat sink, runner, router, sign-in, unit type, elaboration type, deliverable producer, map vocabulary, template, scenario pack

Flywheel’s statement names the same unit a package, and a named set of parts a bundle (228, 284). millpond runs a Flywheel plugin’s adapter parts and nothing else of it.

An adapter part declares a command, the shape of its configuration and the secrets it needs (228). millpond reads the same declaration Flywheel reads, so a part written for one host runs on the other unchanged.

How millpond starts a part

millpond runs the part’s command as:

<command> --raw-dir <dir> [--cursor <c>]

The part’s configuration is in the file the CAPTURE_CONFIG environment variable names, and each declared secret is in the environment under its declared name. The adapter writes its raw files under the raw directory, prints its lines on standard output, and exits.

An adapter that signs in to its source keeps that sign-in itself. On millpond the user signs in once per host. On Flywheel the host places the secrets the plugin declares.

Two ways in

Two commands take the same lines.

the new cursor and the run's status, kept by millpond run millpond run <part> a fresh raw directory and the last cursor Adapter process reads its source, nothing else headwater capture a part of eddy's, per service millpond capture folder each prints the same lines lines raw files millpond land 1 checks each line 2 looks up its key 3 banks the raw files 4 writes the capture 5 writes its one signal 6 or marks it unread 7 records the run A program on its own a deployer, a script, a person … | millpond land
millpond run starts one adapter part with a fresh raw directory and its last cursor, lands what it prints, and keeps the new cursor and the run's status. Anything else pipes the same lines into millpond land. No adapter knows where its captures are kept.
  • millpond land is the intake. It takes lines on standard input from whoever pipes them, such as a deployer, a script or a person, and knows nothing of who made them.
  • millpond run <part> is the scheduled form. It makes a fresh raw directory, starts the part’s command with that directory and its last cursor, and lands what the command prints. It then keeps the new cursor and the run’s status: its exit, its failures and the tail of its standard error. Only a run knows which adapter ran, so only a run keeps a cursor or a status per part.

The tick

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. A run missed while the host slept is caught up once, and the key makes the catch-up write nothing twice (111, 231).

Flywheel’s tick takes the same schedules (231), and its capture endpoint takes the same lines (216, 217g). An adapter never knows which host ran it.

Landing a capture

millpond takes every capture line through the same seven steps, whether it arrived through millpond land or millpond run. The steps are the plumbing every source shares, so no adapter repeats them.

  1. Check it. A line with a bad key, a missing field or an excerpt with no text is refused and named in the run. The rest are taken.
  2. Look up the key in every capture the inbox holds. A key already held is passed over and counted.
  3. Bank the raw files in the host’s raw bank, under the capture’s name. Raw material never enters git (111).
  4. Write the capture with a pointer of the form <host>:<path> (217h).
  5. Write the one signal of a capture that carries an excerpt. millpond cuts the text at a line under the inbox’s limit and closes it with a line saying how many lines and bytes the raw record holds beyond the cut (signals.6, inputs.6).
  6. Mark the capture unread when it carries no excerpt, which charges a reader.
  7. Record the run: its counts, its failures, its cursor, and the status a fleet’s doctor reads from millpond status --json.

A refused line

A refusal costs one line. millpond names the refused line and why in the run’s status and takes every other line of the run. An adapter that prints one malformed capture among fifty lands forty-nine, and the fleet sees the fiftieth by name.

A capture landed twice

Step 2 makes landing idempotent. A key reaches millpond twice when a run lists a window again, when a second host reports the same event, or when a run is caught up after the host slept. Each time, millpond writes nothing for a key the inbox already holds and counts the key as passed over. The lookup covers every capture the inbox holds, which is why the work that predates an inbox is imported into it once (see The inbox): after the import, the dedupe looks in one place.

The cut

The cut keeps a signal small enough to enter git and to read in a list, and keeps the whole within reach. millpond cuts the excerpt’s text at a line, never mid-line, under the inbox’s limit, which is 8 KiB unless the inbox’s settings say otherwise. The closing line tells a reader how much of the raw record lies past the cut, and the capture’s pointer leads to it (signals.6, inputs.6).

The text a signal keeps is the start of the source’s own words, unchanged. No model summarizes or rewords an excerpt on the way in.

A pointer

The pointer names the host that banked the raw files and the path under its raw bank, as <host>:<path>. A capture is read wherever its inbox is checked out, so the pointer says which host holds its material (217h). A raw bank lost with its host costs the material, never the record: the capture and its signals stay in git.

The run’s status

millpond status --json reports, per part and per host, the last run’s exit, its counts, each failed item with what failed and why, the tail of its standard error, and its cursor. A fleet’s doctor reads it to show a row per part, naming each failed item.

The inbox

An inbox is a git repository, or a branch in one, holding captures, signals, moves and items in Flywheel’s record formats: flywheel-capture/1, flywheel-signal/1, flywheel-move/1, and the item record (114). Each capture exists once at rest, as a folder named from its event date and its key. JSON exists only in flight, on an adapter’s standard output and between millpond and its tools. At rest each record is a rec file, the plain-text format of GNU recutils.

Keeping Flywheel’s formats is what lets the inbox outlive its host. crew reads it through millpond today, and Flywheel reads the same files later with no import (114).

Writes

millpond names an inbox by the path of its checkout and commits each write there. It holds a lock in the inbox’s git directory for each write, so a tick, a reader, a curation session and crew’s agents never write at once. Every write is a commit, so the inbox’s history is git log of its checkout: each landing, each batch of moves, each brief and each send.

Settings

The settings are one file at the inbox’s root, millpond.toml. It holds:

  • the inbox’s parts, each with its schedule, its configuration, and the host whose tick runs it;
  • the cut, when it differs from 8 KiB;
  • the command that starts a reader;
  • whether items go to worktrees or to crew.

A part names one host, so an inbox checked out on two hosts runs each part once. The settings travel with the inbox, and every host that checks the inbox out reads the same ones.

What stays on the host

Two things stay off git, each kept per host.

WhatWhereIn git
captures, signals, moves and itemsthe inboxyes
the settingsmillpond.toml at the inbox’s rootyes
raw materialthe host’s raw bank, under millpond’s data directory, keyed by inbox and captureno
cursors and run statusmillpond’s state directory, keyed by inbox and partno

The raw bank holds material too large or too private for git, and each capture points into it. Cursors and run status belong to the host whose tick runs a part, so they live beside that tick.

What comes in once

Two bodies of work predate their inbox, and millpond’s signals-folder import carries each in once:

  • crew’s folders on each partition’s flywheel branch: a capture.md and one NN-<slug>.md per signal. A release capture keeps the key it landed under, release/<name>/<version>. The import keys every other capture by its folder’s name.
  • The meeting corpus already read in a partition’s blueprints repository.

The import keeps each capture’s source, event date, signals and moves as they were read, and charges no reader for them (114). It keys each capture by its own key, so importing a folder twice writes nothing the second time. After the import every capture has one home, and the dedupe by key looks in one place.

Reading

Listing events is arithmetic, and turning a capture into signals is judgment (115). millpond does the arithmetic. A model runs in two places only: a reader, which writes a capture’s signals, and a curation session, which gives each signal its move. Each is a Claude session working through millpond’s tools.

The tools

millpond serves its tools over MCP as millpond mcp:

ToolDoes
signalsLists signals by state, source, age, capture or subject.
captureReads a capture and its raw material.
readWrites a capture’s signals. millpond refuses one whose excerpt does not appear verbatim in the capture’s raw material.
movesWrites a batch of moves in one commit.
itemsLists items with their signals, briefs and sends.
briefWrites an item’s brief.
sendSends an item to flume as a fix or a change, or an item that asks a question to a sounding.

A session reaches the inbox through these tools and nothing else. It never edits a record file, so every write passes millpond’s checks and takes the inbox’s lock.

Two paths to a signal

Captured once, by key Own excerpt? the line says so yes: a release no: a meeting One signal at once, cut at a line Unread charges a reader Reader session excerpts checked verbatim Unmoved signals oldest first, for curation its signals
A release is its own excerpt, so millpond writes its one signal as it lands. A meeting is not, so it waits unread until a reader session writes its signals. From there both take the same path.

A signal carries its capture, a kind from a fixed set (constraint, ask, question, commitment, reaction), who asserted it, its subject tags, the assertion in a sentence, the verbatim excerpt with its position, and the claims it argues with when any exist. A signal never changes once written (113).

A reader

A reader takes unread captures oldest first and writes their signals with read (113). Three things start one:

  • the user, with the read skill in a Claude Code session;
  • the inbox’s tick, with the command its settings name;
  • crew’s reader, in an inbox crew keeps.

The reader’s instructions are the inbox’s data (119): what makes a signal, how to name a speaker a transcript leaves unnamed, and the extraction rules the existing meeting pipeline wrote down. Changing how an inbox reads is an edit to its data, never to millpond.

A meeting recorded twice gives two captures, since each recording has its own key. Its reader reads them together and writes the second’s signals once.

The verbatim check

read refuses a signal whose excerpt does not appear verbatim in its capture’s raw material. The check holds a reader to quoting: an excerpt is the source’s own words, so a person who follows the pointer finds them where the signal says they are.

Curation and moves

Curation gives each signal one of Flywheel’s six moves (116). A curation session is a Claude session working through millpond’s tools. It proposes moves in batches, the operator accepts or changes each batch, and the session writes it. Curation writes no captures and no signals.

The six moves

MoveConsequence
dropRecords the reason.
answeredNames the claim or record that settled it.
challengeWeighs against a claim. Only an inbox beside Flywheel’s claims has one.
joinMakes an item, or grows a proposed one, from any number of signals.
attachPuts the signal on an item already sent.
routeOffers a chore, which millpond makes an item of one, or an ask, which it records.

Every signal has exactly one standing move, stored with the signal, its target, its reason and its date (107). Curation runs over signals with no move and never judges a signal that already has one (107). A route that offers neither a chore nor an ask is refused (116).

An unmoved signal is never discarded, and weight counts by event date (118). A signal moved a month after its meeting still counts on the meeting’s day.

Working through a backlog

Nobody picks through hundreds of signals by hand. The curate skill starts a session that asks signals for the oldest fifty unmoved, reads the raw material of the unclear ones with capture, and proposes a move for each. The operator accepts the batch or changes rows, and the session writes it with one moves call.

The operator Curation session the curate skill millpond its MCP tools curate the oldest fifty signals: unmoved, oldest, 50 fifty signals with their excerpts capture: the unclear ones proposes: drop 18, answered 6, join 26 into 5 items accepts, changing one row moves: the batch, one commit The next batch starts where this one ended.
The session proposes a move for each of fifty signals, the operator accepts the batch or changes rows, and one moves call writes it as one commit. The operator judges a proposal, never a signal at a time.

A batch is the operator’s unit of attention. The session does the reading, and the operator does the deciding: one decision per batch, and per item that the batch proposes (109). The next batch starts where that one ended.

The operator’s surfaces

The operator curates from whichever surface is at hand, and each reads and writes through the same tools:

  • millpond’s Claude Code plugin holds the curate and read skills and starts millpond mcp.
  • millpond’s herdr plugin shows the count and age of unmoved signals by source (118), lets the operator review a proposed batch row by row, and sends an item with one key.
  • crew’s rail shows an inbox crew keeps, one decision per item (109).

Items and sends

An item is the unit of work: the signals curation joined, one or many. It may be five of a meeting’s ten signals, or two bumps that move together. An item is Flywheel’s proposed intent (109). millpond keeps its signals, its brief, every place it was sent, and the conclusion that settled it when a sounding answered it.

How an item forms

Join, attach and route lead to items, and drop, answered and challenge settle a signal:

A signal's move one per signal Settled drop, answered, challenge An item join, attach, route one signal or many One worktree flume: the brief and an agent crew's planner chores and units across teams sluice lands the work a worktree's landing, written against the item a question A sounding one question, worked to a conclusion accepted: its work, new items
Drop, answered and challenge settle a signal. Join and attach put it on an item, and a route's chore becomes an item of one. The inbox's settings send an item through flume to one worktree or hand it to crew's planner, and sluice lands the work either way. An item that asks a question goes to a sounding, and the work its accepted conclusion proposes comes back as new items.
  • join makes an item from any number of signals, or grows one still proposed.
  • attach puts a signal on an item already sent, so a late signal reaches the work it belongs to.
  • route offers a chore, which millpond makes an item of one, or an ask, which millpond records and makes no item of.

Dropping a proposed item gives each of its signals a move that records the drop, and they are not joined again unless new signals join them (117).

The brief

The brief is the text an item’s work starts from. The curation session writes it with the brief tool, and millpond keeps it on the item. A send hands the brief to flume, the agent starts from it, and the item’s signals point the agent to the source’s own words.

A send

millpond sends one item to one worktree through flume:

  1. The send names the repository and the kind, a fix or an OpenSpec change.
  2. millpond records the send and hands flume the brief.
  3. flume makes the worktree and starts the agent.
  4. The work submits through sluice.
  5. millpond writes the landing against the item, from sluice’s record of that branch.

millpond keeps the item’s record, and flume keeps only the live state of the worktrees it runs. The item shows where it went and that it landed. flume’s book holds the worktree, the kind’s stages and how flume tidies up after the work lands or is dropped.

A question, sent to a sounding

millpond sends an item that asks a question to a sounding: one question, worked by one session in a worktree of its own, ending on one conclusion. The item’s work ends in an answer, such as whether to take a major release, or how to settle what a meeting left open. flume makes the sounding’s worktree and runs it as the kind question. sounding’s book holds how a sounding works.

The operator decides the conclusion. Accepting it settles the item: millpond keeps the conclusion as the item’s record, and each piece of work the conclusion proposes becomes an item of its own, or an entry in crew’s plan. The operator may instead send the sounding back for another round, or drop it with a reason.

A conclusion never becomes a signal. Curation never judges a moved signal again (107), and the operator is never asked the same approval twice (6). An accepted conclusion is the record curation names when it answers a later signal on the same subject (116). Only a finding beyond the question comes back, as a capture the sounding offers (see Sources).

An inbox crew keeps

An inbox crew keeps sends no item to flume itself, and millpond refuses a send from it. Its settings hand items to crew’s planner, which orders them across teams and turns each into a chore or a unit. crew then asks flume for each stage of the work, in a slot of the team’s. Planning across items is crew’s today and Flywheel’s later, never millpond’s.

A release, from capture to landed bump

In a repository crew runs, a bump comes only as a chore crew’s planner makes from an item (inputs.6). The release lands as a capture with its own excerpt, curation routes its signal into an item, the planner proposes the chore, and a team makes the bump, which sluice lands. The bump moves the pin, so headwater’s next scan no longer reports the release (headwater 9). Nobody writes an acknowledgement.

Sources

millpond runs adapters and writes none of their logic into its core. Where an adapter ships depends on what its source needs.

Built in or shipped with its tool

An adapter whose source needs no other tool ships inside millpond: the folder drop, the meeting file and the signals-folder import, all from flywheel-next’s adapters crate. Each is a command of millpond’s own that prints contract lines.

An adapter that wraps another tool’s domain ships with that tool, so headwater’s and eddy’s parts stay theirs, and millpond runs them like anyone else’s. A tool for dependencies, or for meetings, has a life of its own beside millpond, with its own plugins, and its adapter part is one more way in.

The folder drop

A file dropped in a watched folder is one capture. That covers a meeting transcript downloaded by hand, an email saved as .eml, or a PDF.

  • The adapter is Flywheel’s folder adapter (215): millpond capture folder <dir> prints contract lines.
  • The key is folder/<folder name>/<sha256 of the file>, so a file dropped twice, or under another name, is one capture (111).
  • event_at is the date the file’s name begins with, or else its last write.
  • source is folder. No capture carries an excerpt, so a reader reads each one and tells a transcript from an email by what it holds.

A service that sends many files of one kind earns a part of its own. Until then the folder takes them.

The meeting file

millpond capture meeting is Flywheel’s meeting transcript adapter (215), built into millpond from the same crate. It carries no excerpt, so a reader reads what it captures, as for any meeting.

The signals-folder import

millpond import carries work that predates an inbox into it once, keeping each capture’s key, source, event date, signals and moves as they were read (114). The inbox says what it imports.

Releases, from headwater

headwater reports every release newer than each pin a repository declares, with its notes and advisories. Its book, ../headwater/, holds the design. millpond sees it through its Flywheel plugin’s adapter part, headwater capture, which prints a capture for every release newer than its pin on each run:

  • source is release, and event_at is when the release was published;
  • raw holds the notes whole, plus what the lookup answered;
  • the excerpt is the notes, asserted by the upstream, of kind ask, asserting “<name> <version> is released; the repository pins <pin>”, and arguing with the declarations a bump moves.

The key is release/<name>/<version> for an entry of a watch list, under the name the list gives it. headwater keys every other release by its Package URL identity, release/<type>/<namespace>/<name>/<version>.

The pin decides what is new, and headwater keeps no list of what it reported (headwater 9). A release stays newer than the pin until a bump moves the pin, so every run prints it again, and millpond’s dedupe by key lands it once. In a repository crew runs, headwater’s fix key in herdr hands the release to millpond as a capture, and no agent starts outside crew’s plan.

Meetings, from eddy

Listing meetings is arithmetic and reading them is judgment, so a program lists them and a session reads them (115). eddy lists and fetches meetings across services. Its book, ../eddy/, holds the design. Its Flywheel plugin holds one adapter part per meeting service. Each part lists recorded meetings since its cursor, keys a meeting by its service and the meeting’s own id, writes the transcript as raw, and carries no excerpt. event_at is when the meeting started (118).

PartSourceKey
wisprWispr Flow’s MCP endpoint, through an MCP client with its own sign-in; no Claude session runs to list meetingsmeeting/wispr/<meeting id>
webexWebex’s transcripts APImeeting/webex/<meeting id>
teamsMicrosoft Graph’s meeting transcriptsmeeting/teams/<meeting id>

eddy’s parts chapter holds each service’s part. A part counts as a meeting part when it passes the meeting checks in the conformance pack (../eddy/contributing.html). Anyone can publish a meeting plugin of their own, listed in the plugin index beside eddy’s (228). A generic part lists meetings from any MCP endpoint that serves transcripts, and the folder drop takes the rest.

Findings, from a sounding

A sounding works one question, and a finding beyond that question is not its to answer. sounding offer hands the finding to millpond land as a capture of its own (62), and the session goes on (58). The finding is its own excerpt, of kind ask, asserted by the sounding, under the key sounding/<name>/<n>, one key per offer. From there it takes the same path as any other capture. sounding’s book holds the rest.

Sources already built

  • crew’s own signals, its agents’ and the operator’s findings, go through crew signal, which writes through millpond.
  • swancloud’s deployer pipes a failed step’s capture to millpond land, with the step’s last lines as its excerpt.
  • Flywheel’s page capture box and chat forwards arrive when Flywheel hosts the inbox.

Three setups

millpond runs in three setups, and all three use the same inbox files.

On its ownWith crewWith Flywheel
Adaptersheadwater, eddy, the folder dropthe samethe same
Hostmillpond tick on a timermillpond, called by crew and the fleet’s timersFlywheel’s host, on the same crates
Inboxa git repository of the user’s ownthe partition’s flywheel branch in crew’s state repositorythe instance’s state repository, the same files
Curationa Claude session through millpond’s tools; the user acceptscrew’s curator through the same tools; the operator decides per item on crew’s rail (109)Flywheel’s curation session
Workan item sent through flume to one worktree, landed by sluicethe planner’s chores and units, each stage launched through flume, landed by sluiceFlywheel’s units and bolts, landed by sluice

Going from one setup to the next changes the host and the curator. The adapters and the inbox stay put, so nothing is captured twice and Flywheel imports nothing when it takes over.

On its own

The inbox is a git repository of the user’s own, and one timer on the host runs millpond tick. The user curates in a Claude Code session with millpond’s plugin, accepts or changes each batch, and sends items from the session or from herdr. flume starts each send’s agent in a worktree, and the work submits through sluice.

With crew

The inbox is a partition’s flywheel branch in crew’s state repository. crew calls millpond’s, flume’s and sluice’s command lines, and links no crate: crew’s Python uses only the standard library (code.1).

  • crew signal writes the findings of crew’s agents and the operator through millpond.
  • crew’s board, its page and its planner read signals through millpond’s command line.
  • crew’s reader reads a partition’s unread captures, oldest first, and writes their signals with millpond’s read tool.
  • crew’s curator works the inbox through the same tools a curation session uses, joining signals into items, and the operator decides each item on crew’s rail (109).
  • The inbox’s settings hand items to crew’s planner, which turns each into a chore or a unit.

Each partition’s signals live in its inbox, written through millpond (signals.1). No crew code writes a signal file itself.

With Flywheel

The inbox is the instance’s state repository, holding the same files. Flywheel’s tick runs the same adapter parts on the same schedules (231), its capture endpoint takes the same lines (216, 217g), and its curation session gives the same moves. crew becomes Flywheel (crew ADR 0026), and Flywheel takes each partition’s inbox as it stands.

On a fleet

swancloud is the first fleet to run all of it. The fleet says which part runs for which partition’s inbox, on which host, and when; it runs no adapter’s logic itself.

  • One list in lib/ names each part, its partition’s inbox, its host and its schedule. It grows out of lib/release-sources.nix, and each inbox’s millpond.toml is generated from it (lists.1).
  • A launchd agent per inbox on its host runs millpond tick, and the doctor shows a row per part from millpond status --json (doctor.1).
  • The watch list gives headwater the pins that only Nix evaluates. It is generated from the tools list and committed, and a flake check refuses it when it drifts from the evaluation. Each entry has a name, an upstream, a tag pattern, a pin, and the files a bump moves.
  • A sign-in a part needs, such as Wispr’s, is made once per host by the user.
  • Every part is a single-host role (fleet.6), and the parts move together when a host hands over.

Who owns what

Each part of capture owns one job and never reaches into another’s. Each format and rule is written once and used by every part that needs it.

Each part’s job

PartOwnsNever
An adapterOne source. It lists the source’s events, keys each one, fetches the raw material, and marks a capture that is its own excerpt.Writes records, keeps a list of what it reported, cuts an excerpt, or reads a capture into signals (115).
headwaterReleases and advisories for what a repository declares. It stands alone with its Claude Code and herdr plugins, and its Flywheel plugin holds an adapter part.Knows which inbox records its releases.
eddyRecorded meetings across meeting services, with a part per service. It stands alone as headwater does.Reads a meeting into signals.
millpondRuns adapters and takes the capture contract. Dedupes by key, banks raw material, and writes each capture, plus the one signal of a capture that is its own excerpt. Keeps the inbox, checks a reader’s excerpts, writes moves and items, records each send and its outcome, and records each run.Judges what a capture says, starts an agent, or plans across items.
flumePlaces on the host it runs on: makes a worktree ready, starts each stage’s agent in it with its prompt confirmed, moves on when the stage’s deliverable exists, and tidies the place. Reports each place’s state.Decides what to launch, plans across items, or lands anything itself.
A reader, a sessionReads a capture that is not its own excerpt into signals, through millpond (113, 217e).Lists a source.
Curation, a sessionProposes one move per signal, in batches, through millpond’s tools (116).Writes captures or signals.
soundingOne question in one session, in a worktree of its own, ending on a conclusion. It offers each finding beyond its question to millpond as a capture.Lands code, writes the inbox except through millpond, or plans across items.
The operatorAccepts or changes each batch of moves, sends an item, and decides each conclusion.Picks through signals one at a time.
crewTeams, plans and bolts. Its planner turns items into chores and units across teams.Writes an inbox’s records except through millpond.
sluiceTests a change as merged and lands it.Knows where the change came from.
The fleetSays which adapter part runs for which inbox, on which host, and when. Hands headwater the pins that only Nix can read. Watches every run from the doctor.Runs an adapter’s logic itself.

Code built once

flywheel-next the formats and rules, written once capture contract, keys, cut signals records, moves, inbox adapters the built-in adapters cadence five-field cron conformance pack lines in, records out and later Flywheel's host, on these crates crew standard-library Python calls its command line millpond the inbox binary plugins/claude plugins/herdr headwater dependencies plugins/flywheel eddy meetings plugins/flywheel Meeting plugins from anyone, passing the meeting checks depend on the crates
Each format and rule is written once, in flywheel-next. millpond, headwater and eddy depend on its crates at a tag and prove themselves against its conformance pack. crew stays standard-library Python and reaches signals only through millpond's command line.
PartBuilt inUsed by
The contract’s types and key rules, JSON in flight and rec at rest, a sha256 content hash, and the excerpt cutflywheel-next’s capture cratemillpond, Flywheel, headwater’s and eddy’s adapter parts
The signal, move and item records and the inbox’s layoutflywheel-next’s signals cratemillpond, Flywheel
The folder drop, the meeting file and the signals-folder importflywheel-next’s adapters cratemillpond, Flywheel
Cadence: five-field cron, caught up onceflywheel-next’s cadencemillpond tick, Flywheel’s tick
The conformance pack: lines in, records expectedflywheel-nextmillpond and Flywheel as hosts, every adapter part
Raw banking with a host-qualified pointer, the checks on a capture, and the verbatim check on a reader’s excerptmillpond, from crew’s checksFlywheel, for 217h and 217e
Finding releases and reading their notesswancloud’s release adapterheadwater’s lookups and its Flywheel plugin
One question, one session, one conclusion: the question, its rounds and the conclusion’s shapesounding, from crew ADR 0019crew, Flywheel’s elaborations
Places, a fresh agent per stage, prompt delivery, stages that end on their deliverable, and tidyingflume, from crew’s teams.2–4, teams.11, teams.15 and plan.9, 10, 12, 17millpond, headwater, sounding, crew

The conformance pack is how a part proves it keeps the contract: a set of contract lines and the records a host must write from them, with the meeting checks a meeting part passes. millpond passes it as a host, and so does Flywheel; headwater’s, eddy’s and anyone’s adapter parts pass it as adapters.

millpond keeps its own Claude Code and herdr plugins under plugins/ beside its core, as headwater and eddy keep theirs. Its built-in adapters are commands of its own, so it has no Flywheel plugin.

Glossary

The words of the design, each defined once, as the capture design (design/capture/design.md) uses them. headwater’s and eddy’s books use the same words for the same things. A number in parentheses cites Flywheel’s statement, whose record formats and moves millpond keeps.

wordmeaning
adaptera program that knows one source: it lists the source’s events, keys each one, fetches the raw material, marks a capture that is its own excerpt, and prints contract lines (115)
adapter parta plugin’s part of the adapter kind: a command, the shape of its configuration, and the secrets it needs (228)
answeredthe move that names the claim or record that settled a signal
attachthe move that puts a signal on an item already sent
batchthe moves a curation session proposes together, which the operator accepts or changes and one moves call writes in one commit
briefthe text an item’s work starts from, written by the curation session and kept on the item
capturethe record of one source event: its key, its source, its event date and a pointer to its raw material; the same event captured twice is one capture (111)
capture contractthe JSON Lines an adapter prints and every host takes, each line holding exactly one of capture, cursor or done
challengethe move that weighs a signal against a claim, in an inbox beside Flywheel’s claims
boltin crew, a team’s branch of work: its units and chores land on it, and it lands on the shared branch
chorein crew, a piece of work that needs no design of its own, such as a tool’s bump; a route move offers one
claimin Flywheel, a standing statement of what a repository must satisfy; answered and challenge name one
conformance packcontract lines and the records a host must write from them, with the meeting checks; every host and every adapter part passes it
curationa Claude session that proposes one move per signal, in batches, through millpond’s tools; the operator accepts or changes each batch (116)
conclusionwhat a sounding ends on: the answer, its evidence, what it would change, the work it proposes and what stays open; the operator accepts it, sends it back for another round, or drops it
cursora value only its adapter understands, kept from the part’s last run that ended in done and handed back on the next
cutwhere millpond ends an own-excerpt signal’s text: at a line under the inbox’s limit, 8 KiB unless its settings say otherwise. A closing line says how much the raw record holds beyond it
dedupemillpond’s lookup of each capture’s key in every capture the inbox holds, so a key lands once
dropthe move that records why a signal leads nowhere
event dateevent_at: when the event happened, never when it was captured (118)
excerptthe verbatim text a signal quotes from its source, with its position (113)
flywheel branchthe branch of crew’s state repository that holds a partition’s signals; it becomes the partition’s inbox
flumethe tool that launches and tracks one agent in one worktree through a kind’s stages, on the host it runs on, and tidies the place when the work lands or is dropped (its book)
folder dropthe built-in adapter that makes one capture of each file dropped in a watched folder
inboxa git repository, or a branch in one, holding captures, signals, moves and items in Flywheel’s record formats
itemthe unit of work: the signals curation joined, one or many, with its brief and its sends; Flywheel’s proposed intent (109)
jointhe move that makes an item from signals, or grows one still proposed
keya source event’s identity, the same from every host and every run (111)
kinda signal’s type, one of constraint, ask, question, commitment and reaction (113); the kind a send names, a fix or a change, is flume’s word for a list of stages
meeting filethe built-in adapter for a meeting’s transcript file
movethe one standing decision on a signal: drop, answered, challenge, join, attach or route, each with a stated consequence (107, 116)
operatorthe person who accepts or changes each batch of moves, sends items, and decides each conclusion; on its own, the user
own excerpta capture is its own excerpt when its line carries the signal’s fields, so millpond writes its one signal as it lands; a release is one
partone thing of one kind in a plugin; an inbox’s settings name the adapter parts it runs
partitionin crew, one domain of work with its own teams and its own flywheel branch
plannercrew’s agent that orders items across teams and turns each into a chore or a unit
pluginthe installable unit in every host, holding parts; Flywheel’s statement calls it a package, and a named set of parts a bundle (228, 284)
pointer<host>:<path>, where a capture’s raw material is banked (217h)
raw banka host’s store of raw material, under millpond’s data directory, keyed by inbox and capture; never in git
raw materialthe event as its source served it, whole: notes, a transcript, a log; the adapter writes it as raw files
readera Claude session that turns an unread capture into signals with millpond’s read tool
recthe plain-text record format of GNU recutils, in which records rest
routethe move that offers a chore, which becomes an item of one, or an ask, which millpond records
runone start of an adapter part by millpond run, complete when it ends in done
schedulewhen a part runs: five-field cron, a missed run caught up once
sendmillpond giving one item to flume, as a fix or a change with its brief, or an item that asks a question to a sounding
settingsmillpond.toml at the inbox’s root: its parts, its cut, its reader command, and where items go
setupone of the three ways millpond runs: on its own, with crew, or with Flywheel
signalone assertion from a capture: its kind, who asserted it, its subjects, the assertion in a sentence, the verbatim excerpt with its position, and the claims it argues with; never changed once written (113)
soundingone question, worked by one session in one place, ending on one conclusion; also the tool that runs them (its book)
sourcewhere events come from, named in each capture: release, meeting, folder, deploy
tickmillpond tick, which runs every part of an inbox whose schedule is due
unitin crew, one designed piece of a team’s work, landed on its bolt
unmoveda signal with no move yet
unreada capture with no excerpt, waiting for a reader
watch listthe list of tools a repository pins, from which headwater reads each pin; headwater’s book defines it
weighthow much a proposed item’s signals count: how many, from which sources, over what span, by event date (109, 118)

Open questions

What the design does not settle, and where each question is held. A question leaves this page when the operator rules; the ruling lands in the design, and the chapter that rests on it is rewritten whole.

millpond’s statement

millpond’s statement of invariants, design/millpond/requirements.md, is being written from the capture design (plan step P1). It states the inbox, the contract’s intake, the seven steps, the reader and curation tools, moves, items and sends, the three setups, and what millpond never does. A fresh model then reconstructs millpond from the statement alone, and the operator rules the questions it asks.

The statement becomes this book’s appendix, and the chapters cite its clauses by number. Until it lands, the chapters rest on the capture design, design/capture/design.md, and cite Flywheel’s statement, which the contract and the inbox are to enter as clauses of their own.

Decisions awaiting a ruling

The capture design’s twelve decisions await the operator’s ruling (design/capture/plan.md, step D2). Each chapter states them as the design has them.

  1. headwater is for dependencies and eddy for meetings. The work every source shares is millpond’s, through the contract (Sources).
  2. millpond is a standalone tool, the inbox. It runs adapters, keeps captures and signals, serves the tools curation works through, and sends an item to a worktree. crew calls its command line as it calls sluice’s (Three setups).
  3. Flywheel’s statement owns the contract and the record formats. flywheel-next’s crates implement them once, millpond is built on them, and Flywheel’s host is later built on the same crates (Who owns what).
  4. An inbox is a git repository, or a branch in one, in Flywheel’s formats. crew’s partition branches become inboxes, crew’s folders and the meeting corpus are imported once, and Flywheel reads the same inboxes with no import (The inbox).
  5. The unit of work is an item, the signals curation joined, one or many. millpond sends one item to one worktree. Planning across items is crew’s planner today and Flywheel’s later (Items and sends).
  6. A Claude session works a backlog through millpond’s tools, proposing moves in batches the operator accepts or changes. herdr shows the counts, reviews a batch and sends an item (Curation and moves).
  7. A program lists meetings and a session reads them. eddy holds a part per meeting service, its Wispr part an MCP client that starts no Claude session. Others publish meeting plugins of their own (Sources).
  8. An adapter whose source needs no other tool ships inside millpond. One that wraps another tool’s domain ships with that tool, so headwater’s and eddy’s parts stay theirs (Sources).
  9. “Plugin” names the installable unit in every host, for what Flywheel’s statement calls a package and a bundle (Running adapters).
  10. The release adapter lands through millpond on trial before headwater exists, so the contract carries live captures first.
  11. sounding is a standalone tool that works one question in one session, in a worktree of its own, and ends on a conclusion the operator accepts, sends back or drops. millpond sends it an item that asks a question, an accepted conclusion is the item’s record, and only a finding beyond the question comes back as a capture (Items and sends).
  12. flume is a standalone tool that launches and tracks one agent in one worktree through a kind’s stages on the host it runs on, taken from crew’s places and stages. millpond’s send, headwater’s fix key, sounding and crew call it; millpond keeps the item’s record and flume the live state of each place (Items and sends).

Held by the design

The capture design’s own Open section holds these questions:

  • Acknowledging a dropped release. A curation drop of a release signal could acknowledge the release in headwater, so its plugins stop showing it. headwater’s ruling 24 says a recorder never acknowledges.
  • A package manager in the repository’s shell and headwater 5, which says reading a repository changes nothing in it. The commands headwater runs only read, but entering a repository’s shell can write, as devenv writes its lock.
  • Where a cursor lives when a single-host role moves hosts. A lost cursor costs one wider listing, which the dedupe absorbs.
  • Attestation in sluice: sluice signing the verdict it lands, with an executor vouched for from outside the team that ran it.
  • How a repository declares its kinds and their prompts, and how crew’s stage prompts move into that form.
  • sounding’s own details: the timebox’s length and who sets it, how a sounding is named, where its files sit and whether they are committed, what parks a sounding and resumes it, what accept and drop do to its branch, and the fields an offer’s capture carries. sounding’s statement rules them.
  • A standing sounding, which stays open and starts a round when a signal on its subject lands, as Flywheel’s standing elaborations do.
  • Where else an accepted conclusion lives. A repository may keep it in its own record, as crew’s kits keep openspec/explorations/.
  • crew’s two open records disagree. ADR 0019 rests an elaboration on an intent and the new-territory move, and curation-and-agenda leaves intents out and replaces that move with join. A sounding sent from an item settles both, once crew’s record says so.

The family’s storage

Plan step D4 sets every piece of state each tool keeps against Flywheel’s state repository. The aim is that moving from a tool on its own to crew, and then to Flywheel, moves no data. Its ruling sets the storage profile millpond’s statement names, which covers what The inbox keeps in git and on the host.

Left to the statement

The capture design names these and leaves their form to millpond’s statement or to flywheel-next’s crates:

  • the keys of millpond.toml, beyond what it holds;
  • the item record’s format and fields, beyond its signals, brief and sends (114);
  • the key the meeting file gives a transcript;
  • the name a send hands flume for an item, from which flume names the branch.