Durable pipelines for code, agents and people.
Millstream runs the work your team builds around LLMs as a declarative graph of steps over persisted streams of items. A crawler streams records, an agent scores each one, a filter keeps the good ones, an analyst approves and a report goes out. Every item is stored, every agent call is costed, and the pipeline learns from what happens in production.
Read the quick start See how pipelines learn
- One Go binary, from SQLite on a laptop to Postgres on a server
- Actions in any language, agents on Anthropic or any OpenAI-compatible model
- Open-source core under Apache-2.0
Steps know streams, never each other
A pipeline is one YAML file. Steps read streams and write streams; they never call each other. That makes the graph easy to change: several crawlers write one stream, several consumers each receive every item, and inserting a step means pointing it at a stream and re-pointing one consumer.
pipeline: property-sourcing
vars: { minScore: 70 }
triggers:
daily: { cron: "0 6 * * *", tz: Europe/Paris }
steps:
crawl:
action: lab/crawl-listings@1.2
in: trigger.daily
out: listings.raw
dedupe:
action: dedupe
in: listings.raw
out: listings.unique
config: { key: "item.addressNorm + ':' + string(item.surfaceM2)", window: 30d }
score:
action: lab/score-listing@2 # an agent
in: listings.unique
out: listings.scored
concurrency: 8
keep-good:
action: filter
in: listings.scored
out: listings.shortlist
config: { expr: "item.score >= vars.minScore" }
millstream check validates the whole package before it runs: schemas between steps, CEL expressions, secrets and
actions. There is no version field to bump, because each distinct package content is a revision and every run
records the one it used. When a step changes, replay it over past items and compare before you ship:
millstream replay --pipeline property-sourcing --step keep-good --since 7d --var minScore=75
An agent is an action like any other
An agent step declares its prompt, its model, its output schema and what it may touch. Millstream renders the prompt with the item, runs the model in a sandbox, validates the answer against the schema, and records the transcript, the tokens and the cost.
name: lab/score-listing
version: 2.1.0
kind: agent
output: { $ref: schema/output.json }
runtime:
agent:
harness: claude-code # or api: Anthropic, or any OpenAI-compatible endpoint
model: claude-sonnet-5-5
tools: [WebFetch]
network: [api.anthropic.com]
maxTurns: 20
maxCostUsd: 0.50
- Schema-validated output. An answer that does not match is a retryable error, and never reaches the next step.
- Budgets that stop spending. Per delivery, per run and per day, enforced by reservation before a call starts.
- Every attempt transcribed, secrets redacted, kept with the delivery that produced it.
- Record and replay. Tests, evals and CI replay recorded model calls offline, with no API key.
People decide where it matters
A review step parks each item in an inbox until someone approves, edits or rejects it. Every decision records
who, when, why and the diff of any edit, and feeds the pipeline's learning.
analyst-review:
action: review
in: listings.selected
out: listings.approved
config:
inbox: analysts
timeout: 72h
onTimeout: { escalate: leads }
editable: [/marginPct, /notes]
reasons: [overpriced, badLocation, needsVisit]
autoApprove: "item.marginPct >= 25"
sampleRate: 0.05
Rules approve what is obvious, and a sample of what they approve still goes to a person, so a rule that stops being right shows up as a measured drop in agreement. The headline figure is the intervention rate: how often a person had to step in, per step and per pipeline.
The cheapest step that works
Each step runs a rule, code, an agent or a person, and the pipeline file says which. As reviews and deliveries accumulate, Millstream proposes moving a step to a cheaper kind: code in place of an agent, with the agent kept as its fallback, or an approval rule in place of a review, with a sample still checked by a person. Each proposal is checked against past items before anyone accepts it.
A rule
filter,dedupeand routes, written in CEL- Run by the engine, at no cost per item
- Every threshold visible in the pipeline file
Code
- A program in any language, or a container
- For rules that are known, however long
- Abstains on what it cannot decide, for a fallback
An agent
- For rules nobody has written yet, and free text
- A schema, a sandbox, a budget and a transcript per call
- Proposed for replacement by code once its answers are predictable
A person
- For judgement, and for effects someone must authorise
- Inboxes, edits and reason codes
- An approval rule proposed where approvals are near-universal
Pipelines that learn from production
Millstream watches every run and raises insights with no LLM involved: slow steps, failure clusters, cost per
approved item, what reviewers reject and which fields they edit, items an agent paid for that a later filter
discarded. In a development deployment, the improve pipeline drafts fixes from that evidence and validates them
before a person sees them.
- Insights come from the event log, in every deployment.
- Evidence leaves production as a redacted bundle that expires.
- Proposals are drafted in development and backtested against that evidence: a lessons file, a prompt, a threshold, a filter moved upstream, or code that replaces an agent.
- Nothing ships unreviewed. A person accepts each proposal, and it reaches production as a commit or a pull request through the normal release.
When an agent's answers have become predictable, Millstream proposes code that does the same job, keeps the agent as the fallback for the items the code is unsure of, and proves it on a holdout the drafting agent never saw. The platform's own acceptance test measured this end to end, on synthetic listings whose answers follow a rule:
- 3.7% of the agent cost, the day after distillation
- 1.000 agreement on 260 held-out items
- 0.58 → 1.00 reviewer agreement after a lessons proposal
Every item has a history
Each item is immutable, carries a deterministic id, and links to the items it came from and the items it produced. Every state change is an event, and the metrics, run reports, insights and the live UI are all projections of that one log.
millstream run report listings run_replay_20261020T162620Z-b9df9171
Run run_replay_20261020T162620Z-b9df9171 of listings (replay): succeeded
Started 2026-10-20T16:26:20Z, settled 2026-10-20T16:26:21Z, 540ms, cost $0.0000
Items: listings.ready 1, listings.renovate 1
Critical path (540ms):
STEP DELIVERY LATENCY WAIT WORK BACKOFF REVIEW SHARE
affordable dl_719ce262d713c5774527c64ea86e39e2 540ms 539ms 1ms 0s 0s 100%
The web UI ships in the open-source core: a live graph with throughput, latency and errors per step, run timelines, item lineage with every attempt and transcript, dead letters grouped by cause, the review inbox and the insights. It reads in English and French and meets WCAG 2.2 AA.
Kill it at any point
Delivery is at least once, and idempotency comes from deterministic item ids: a retried step produces the same ids, so nothing downstream sees an item twice. Outputs publish only when a delivery is done. The crash suite kills the engine and its nodes at every boundary of every atomic unit, on SQLite and on Postgres, and checks that nothing is lost and nothing is published twice.
The soak runs the Docker Compose deployment, an active engine, a hot standby and two nodes, while a chaos driver kills engines and cuts nodes off the network:
- 15,837 items through 30 minutes of chaos
- ~30 ms for the standby to take over the lock
- 0 items lost or published twice
From a laptop to a server, unchanged
The same package runs everywhere; only the environment file says where. On a laptop, millstream run is the
engine, a node and the web UI on SQLite, with nothing to install. On a server, the same binary runs on Postgres with
a hot standby, and nodes on other hosts pull work under fenced leases.
millstream run # laptop: SQLite, an embedded node, the UI on 127.0.0.1:7070
docker compose up -d # server: Postgres, active and standby engines, two nodes
millstream node --join https://engine:7070 --labels gpu=true
environment: production
store: { dsn: postgres://db.internal/millstream }
artifacts: { url: s3://millstream-artifacts }
auth: { token: { env: MILLSTREAM_AUTH_TOKEN } }
retention: { events: 90d, transcripts: 30d }
Each customer gets a deployment of their own, on their infrastructure. Several pipelines share it, isolated from each other, and a pipeline can import another's stream without changing it.
Write actions in any language
An action is any program that speaks JSON lines over stdin and stdout. The protocol fits on one page, a conformance suite checks an implementation, and the Go SDK turns an action into one function.
func main() {
millstream.Map(func(ctx millstream.Context, in Listing) ([]Priced, error) {
ctx.Metric("works_estimate_eur", float64(estimate(in)), nil)
return []Priced{price(in, ctx.Config())}, nil
})
}
millstream test action ./actions/rentability --conformance
Built for the day it goes live
Operations
- Prometheus metrics, run reports and an event tail
- Pause, resume, cancel and roll back from the CLI or the UI
- Dead letters with replay and discard
- Retries with backoff, timeouts, concurrency and rate limits
Agent governance
- Tools, MCP servers and network destinations allowlisted per action
- Cost per call, per run and per day, with hard stops
- Evals with assertions and LLM judges, scored per case
- A check that warns when agent output can reach an external effect unreviewed
Data
- Retention per stream, for payloads, transcripts and events
- Erasure by key that follows lineage across pipelines
- Secrets redacted from transcripts and recordings
- Personal data hashed out of evidence bundles
Security
- Loopback by default, a 32-character token and TLS when exposed
- Agents and third-party actions in containers with no capabilities
- Proposed code built and run only in a sandbox with no network
- Images and registry actions pinned by digest
Packaging
- A distroless image running as a non-root user
- Archives for Linux and macOS, amd64 and arm64, each with an SBOM
- Docker Compose and systemd units in the repository
- Signed checksums and images from the first release
Open source at the core
The engine, the CLI, the Go SDK, the builtins, the agent adapter, the protocol and the whole web UI are open source under Apache-2.0. A commercial edition adds what larger organisations pay for: single sign-on, roles per pipeline and inbox, audit export, a catalog of production connectors, approval policies on proposals, and support.
Build your next AI workflow on Millstream
The five milestones of the platform are built and tested, and the first open-source release is next. Get early access to run it on your own pipelines, or read how it works first.