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.
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:
| Field | Required | Meaning |
|---|---|---|
key | yes | The source event’s identity. The same event gets the same key from every host and every run (111). |
source | yes | The source’s name: release, meeting, folder, deploy. |
event_at | yes | When the event happened, in RFC 3339. |
title | no | One line a person reads in a list. |
url | no | Where the event lives upstream. |
raw | no | The files the adapter wrote under the raw directory, as relative paths. |
excerpt | no | Present 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.
- 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:
| Host | Kinds of part |
|---|---|
| Claude Code | skills, hooks, commands, MCP servers |
| herdr | actions, panes, events |
| Flywheel | adapter, 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.
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 landis 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.
- 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.
- Look up the key in every capture the inbox holds. A key already held is passed over and counted.
- Bank the raw files in the host’s raw bank, under the capture’s name. Raw material never enters git (111).
- Write the capture with a pointer of the form
<host>:<path>(217h). - 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). - Mark the capture unread when it carries no excerpt, which charges a reader.
- 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.
| What | Where | In git |
|---|---|---|
| captures, signals, moves and items | the inbox | yes |
| the settings | millpond.toml at the inbox’s root | yes |
| raw material | the host’s raw bank, under millpond’s data directory, keyed by inbox and capture | no |
| cursors and run status | millpond’s state directory, keyed by inbox and part | no |
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.mdand oneNN-<slug>.mdper 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:
| Tool | Does |
|---|---|
signals | Lists signals by state, source, age, capture or subject. |
capture | Reads a capture and its raw material. |
read | Writes a capture’s signals. millpond refuses one whose excerpt does not appear verbatim in the capture’s raw material. |
moves | Writes a batch of moves in one commit. |
items | Lists items with their signals, briefs and sends. |
brief | Writes an item’s brief. |
send | Sends 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
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
| Move | Consequence |
|---|---|
drop | Records the reason. |
answered | Names the claim or record that settled it. |
challenge | Weighs against a claim. Only an inbox beside Flywheel’s claims has one. |
join | Makes an item, or grows a proposed one, from any number of signals. |
attach | Puts the signal on an item already sent. |
route | Offers 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.
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:
joinmakes an item from any number of signals, or grows one still proposed.attachputs a signal on an item already sent, so a late signal reaches the work it belongs to.routeoffers 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:
- The send names the repository and the kind, a fix or an OpenSpec change.
- millpond records the send and hands flume the brief.
- flume makes the worktree and starts the agent.
- The work submits through sluice.
- 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_atis the date the file’s name begins with, or else its last write.sourceisfolder. 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:
sourceisrelease, andevent_atis when the release was published;rawholds 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).
| Part | Source | Key |
|---|---|---|
wispr | Wispr Flow’s MCP endpoint, through an MCP client with its own sign-in; no Claude session runs to list meetings | meeting/wispr/<meeting id> |
webex | Webex’s transcripts API | meeting/webex/<meeting id> |
teams | Microsoft Graph’s meeting transcripts | meeting/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 own | With crew | With Flywheel | |
|---|---|---|---|
| Adapters | headwater, eddy, the folder drop | the same | the same |
| Host | millpond tick on a timer | millpond, called by crew and the fleet’s timers | Flywheel’s host, on the same crates |
| Inbox | a git repository of the user’s own | the partition’s flywheel branch in crew’s state repository | the instance’s state repository, the same files |
| Curation | a Claude session through millpond’s tools; the user accepts | crew’s curator through the same tools; the operator decides per item on crew’s rail (109) | Flywheel’s curation session |
| Work | an item sent through flume to one worktree, landed by sluice | the planner’s chores and units, each stage launched through flume, landed by sluice | Flywheel’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 signalwrites 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
readtool. - 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 oflib/release-sources.nix, and each inbox’smillpond.tomlis generated from it (lists.1). - A launchd agent per inbox on its host runs
millpond tick, and the doctor shows a row per part frommillpond 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
| Part | Owns | Never |
|---|---|---|
| An adapter | One 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). |
| headwater | Releases 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. |
| eddy | Recorded meetings across meeting services, with a part per service. It stands alone as headwater does. | Reads a meeting into signals. |
| millpond | Runs 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. |
| flume | Places 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 session | Reads a capture that is not its own excerpt into signals, through millpond (113, 217e). | Lists a source. |
| Curation, a session | Proposes one move per signal, in batches, through millpond’s tools (116). | Writes captures or signals. |
| sounding | One 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 operator | Accepts or changes each batch of moves, sends an item, and decides each conclusion. | Picks through signals one at a time. |
| crew | Teams, plans and bolts. Its planner turns items into chores and units across teams. | Writes an inbox’s records except through millpond. |
| sluice | Tests a change as merged and lands it. | Knows where the change came from. |
| The fleet | Says 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
| Part | Built in | Used by |
|---|---|---|
| The contract’s types and key rules, JSON in flight and rec at rest, a sha256 content hash, and the excerpt cut | flywheel-next’s capture crate | millpond, Flywheel, headwater’s and eddy’s adapter parts |
| The signal, move and item records and the inbox’s layout | flywheel-next’s signals crate | millpond, Flywheel |
| The folder drop, the meeting file and the signals-folder import | flywheel-next’s adapters crate | millpond, Flywheel |
| Cadence: five-field cron, caught up once | flywheel-next’s cadence | millpond tick, Flywheel’s tick |
| The conformance pack: lines in, records expected | flywheel-next | millpond 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 excerpt | millpond, from crew’s checks | Flywheel, for 217h and 217e |
| Finding releases and reading their notes | swancloud’s release adapter | headwater’s lookups and its Flywheel plugin |
| One question, one session, one conclusion: the question, its rounds and the conclusion’s shape | sounding, from crew ADR 0019 | crew, Flywheel’s elaborations |
| Places, a fresh agent per stage, prompt delivery, stages that end on their deliverable, and tidying | flume, from crew’s teams.2–4, teams.11, teams.15 and plan.9, 10, 12, 17 | millpond, 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.
| word | meaning |
|---|---|
| adapter | a 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 part | a plugin’s part of the adapter kind: a command, the shape of its configuration, and the secrets it needs (228) |
| answered | the move that names the claim or record that settled a signal |
| attach | the move that puts a signal on an item already sent |
| batch | the moves a curation session proposes together, which the operator accepts or changes and one moves call writes in one commit |
| brief | the text an item’s work starts from, written by the curation session and kept on the item |
| capture | the 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 contract | the JSON Lines an adapter prints and every host takes, each line holding exactly one of capture, cursor or done |
| challenge | the move that weighs a signal against a claim, in an inbox beside Flywheel’s claims |
| bolt | in crew, a team’s branch of work: its units and chores land on it, and it lands on the shared branch |
| chore | in crew, a piece of work that needs no design of its own, such as a tool’s bump; a route move offers one |
| claim | in Flywheel, a standing statement of what a repository must satisfy; answered and challenge name one |
| conformance pack | contract lines and the records a host must write from them, with the meeting checks; every host and every adapter part passes it |
| curation | a Claude session that proposes one move per signal, in batches, through millpond’s tools; the operator accepts or changes each batch (116) |
| conclusion | what 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 |
| cursor | a value only its adapter understands, kept from the part’s last run that ended in done and handed back on the next |
| cut | where 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 |
| dedupe | millpond’s lookup of each capture’s key in every capture the inbox holds, so a key lands once |
| drop | the move that records why a signal leads nowhere |
| event date | event_at: when the event happened, never when it was captured (118) |
| excerpt | the verbatim text a signal quotes from its source, with its position (113) |
| flywheel branch | the branch of crew’s state repository that holds a partition’s signals; it becomes the partition’s inbox |
| flume | the 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 drop | the built-in adapter that makes one capture of each file dropped in a watched folder |
| inbox | a git repository, or a branch in one, holding captures, signals, moves and items in Flywheel’s record formats |
| item | the unit of work: the signals curation joined, one or many, with its brief and its sends; Flywheel’s proposed intent (109) |
| join | the move that makes an item from signals, or grows one still proposed |
| key | a source event’s identity, the same from every host and every run (111) |
| kind | a 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 file | the built-in adapter for a meeting’s transcript file |
| move | the one standing decision on a signal: drop, answered, challenge, join, attach or route, each with a stated consequence (107, 116) |
| operator | the person who accepts or changes each batch of moves, sends items, and decides each conclusion; on its own, the user |
| own excerpt | a 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 |
| part | one thing of one kind in a plugin; an inbox’s settings name the adapter parts it runs |
| partition | in crew, one domain of work with its own teams and its own flywheel branch |
| planner | crew’s agent that orders items across teams and turns each into a chore or a unit |
| plugin | the 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 bank | a host’s store of raw material, under millpond’s data directory, keyed by inbox and capture; never in git |
| raw material | the event as its source served it, whole: notes, a transcript, a log; the adapter writes it as raw files |
| reader | a Claude session that turns an unread capture into signals with millpond’s read tool |
| rec | the plain-text record format of GNU recutils, in which records rest |
| route | the move that offers a chore, which becomes an item of one, or an ask, which millpond records |
| run | one start of an adapter part by millpond run, complete when it ends in done |
| schedule | when a part runs: five-field cron, a missed run caught up once |
| send | millpond giving one item to flume, as a fix or a change with its brief, or an item that asks a question to a sounding |
| settings | millpond.toml at the inbox’s root: its parts, its cut, its reader command, and where items go |
| setup | one of the three ways millpond runs: on its own, with crew, or with Flywheel |
| signal | one 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) |
| sounding | one question, worked by one session in one place, ending on one conclusion; also the tool that runs them (its book) |
| source | where events come from, named in each capture: release, meeting, folder, deploy |
| tick | millpond tick, which runs every part of an inbox whose schedule is due |
| unit | in crew, one designed piece of a team’s work, landed on its bolt |
| unmoved | a signal with no move yet |
| unread | a capture with no excerpt, waiting for a reader |
| watch list | the list of tools a repository pins, from which headwater reads each pin; headwater’s book defines it |
| weight | how 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.
- headwater is for dependencies and eddy for meetings. The work every source shares is millpond’s, through the contract (Sources).
- 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).
- 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).
- 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).
- 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).
- 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).
- 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).
- 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).
- “Plugin” names the installable unit in every host, for what Flywheel’s statement calls a package and a bundle (Running adapters).
- The release adapter lands through millpond on trial before headwater exists, so the contract carries live captures first.
- 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).
- 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-territorymove, andcuration-and-agendaleaves intents out and replaces that move withjoin. 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.