Plumbing is a small language and runtime for building systems out of agents,
tools, and ordinary programs. A .plumb file declares which components exist,
the type of message each one accepts and produces, and how messages are routed
between them. When the program runs, the runtime creates the channels, starts
the processes, and checks that whatever crosses each boundary has the declared
shape.
The reason such a language is needed is that the structure of an agent system is usually hidden. Most systems of this kind keep their real coordination logic in prompts, in callback code, or in an orchestration script. The components may be visible, but the architecture that connects them is not. Plumbing takes that architecture and makes it the program. The graph of which component sends what to which other component is written down, so that it can be read, drawn, type-checked, reused, and placed inside a larger graph as a single component.
This rests on a particular view of where the difficulty in these systems lies. As models become more capable and are given more autonomy, the wiring between parts must remain legible to a person: what is connected to what, what may travel along each connection, and in what order. The intelligence can sit in the models; the architecture is better kept as an explicit object that can be examined and checked.
It is useful to think of plumbing as a generalisation of Unix pipes. Instead of simple lines of text, typed values encoded in JSON flow between processes. Instead of a linear or tree-like arrangement of processes, convergent branches and loops are permitted so a plumbing program is a directed graph. Some processes may be non-deterministic AI agents, other processes may be deterministic programs. Plumbing draws on a long line of work that treats communication between processes as something with structure and types, from Unix pipes and the Plan 9 plumber through to session types for communicating processes, and applies it to the less tidy setting of language-model agents.
The name is meant literally. Plumbing connects things, and gives the connections
types so that the runtime knows what is allowed to flow through them. If a
writing agent passes a draft to a checking agent, and the checker either returns
comments or sends the accepted draft onward, that routing is part of the program
rather than something buried in code. If a component expects a record with a
score field, a message arriving without one is refused at the boundary instead
of failing somewhere deep in a run.
Types do not make a model correct. A type checker cannot judge whether an answer is any good, but it can reject a program whose ports do not line up, whose data does not have the expected shape, or whose declared protocol cannot be compiled into the graph. This moves a class of failures from run time back to load time, which is worth doing because agent runs cost money and have effects in the world. It also gives an agent a more tractable thing to produce: a program can be generated, checked, corrected against the type errors, and only then run.
A further consequence is that a pipeline is itself a process. A whole arrangement of agents, once given a name and a type, can be used exactly where a single agent would be used, and whatever is inside it is invisible at the boundary. Systems built this way grow by composition, rather than by relying on the models to muddle through a structure no one can see.
At run time, plumbing creates the channels, starts the child processes, validates messages at the declared boundaries, and moves JSON values through the graph. Agents can call tools, including tools exposed by MCP servers. A protocol declaration can describe an ordered exchange, such as send this, wait for that, then finish, and the compiler lowers it into the same channel graph used by an ordinary pipeline.
Plumbing originated at Leith Document Company and is now developed in the Quantum Software Lab at the University of Edinburgh.
Copyright Leith Document Company and the University of Edinburgh. Released under the GNU Affero General Public License, version 3 or later. See LICENSE.
The public source repository is:
https://github.com/quantumsoftwarelab/plumbing
Documentation is published at https://leithdocs.com/plumbing.
The supported source build uses Nix flakes.
git clone https://github.com/quantumsoftwarelab/plumbing.git
cd plumbing
nix develop -c dune build
nix develop -c dune testRun a no-credential example:
printf '%s\n' '"hello"' | \
nix develop -c ./_build/default/bin/plumb/main.exe examples/pydantic/pipeline.plumbRun an interactive local example:
nix develop -c ./_build/default/bin/chat/main.exe examples/doctor/doctor.plumb| Command | Purpose |
|---|---|
plumb |
Run a pipeline |
plumb-check |
Type-check a .plumb file |
plumb-agent |
LLM conversation process |
plumb-chat |
Interactive pipeline frontend |
plumb-mcp |
MCP server for plumbing pipelines |
plumb-render |
Render a pipeline graph |
Installed releases put these commands on PATH. In a source checkout, use the
paths under _build/default/bin/ after nix develop -c dune build.
The Python package is persevere-plumbing; the import path is
persevere.plumbing.
From a source checkout, build the OCaml binaries first, then use
PYTHONPATH=python:
nix develop -c dune build
PYTHONPATH=python python3 - <<'PY'
from pathlib import Path
import persevere.plumbing as pb
print(pb.check(Path("examples/pydantic/pipeline.plumb")).bindings)
PYFor Linux and macOS, the Python bindings can be installed from PyPI:
pip install persevere-plumbingThe package wraps the compiled plumbing binaries. See doc/python-bindings.md for the API.
Plumbing checks the declared shape of the graph before it runs. It catches common mistakes such as connecting incompatible ports, using an unknown binding, or sending a message whose JSON shape does not match the declared type.
During execution, plumbing validates messages as they cross process boundaries. For protocol declarations, it compiles the declared ordering into plumbing channels, so the generated graph follows that ordering for the supported protocol forms.
Plumbing runs trusted local code. It is not a sandbox and does not isolate secrets between child processes. It does not prove that an LLM answer is true, that every pipeline terminates, or that the whole runtime has been formally verified. The proof and TLA material document specific compiler and runtime boundaries; they are not a claim that the whole system is proved.
Start with doc/INDEX.md.
Key docs:
- doc/plumbing.5.md — language reference
- doc/processes.md — process and morphism semantics
- doc/tools.md — plumbing tools and MCP integration
- doc/protocols.md — protocol declarations
- doc/python-bindings.md — Python API
- examples/README.md — example programs
- proof/README.md — proof-side reading order
- tla/README.md — runtime-semantics model corpus
Older binary releases, Docker images, and Python wheels still exist. This source release does not republish them. Future binary and package releases should link to the exact source tag.
Use the public issue tracker for bugs and development discussion:
https://github.com/quantumsoftwarelab/plumbing/issues
See CONTRIBUTING.md, SECURITY.md, and RELEASE.md.