v0.16.4
Agent-to-agent messaging, a trust gate that covers the settings that can run code, structured failure classification, and build provenance.
Full detail for every item is in CHANGELOG.md — this is the readable summary.
Security
[collab]'s channel switches are forced to the user's global value. A project's.plumb/config.tomlis an untrusted surface — a cloned repository ships one — and[collab]was missing from the list of sections a repo may not set for itself, so a clone could openintents,mailbox,cross_projectorknowledge_handoffon its own. (Already superseded onmain, shipping in the next release: these are gated onplumb trustinstead, so per-workspace chat settings work again — with the user, not the repository, holding the switch.)- A project's
.plumb/config.tomlcould run arbitrary code, and could open settings it should not.plumb trustnow covers a project's[lsp.<lang>]and[git]settings — the ones that decide what gets executed — rather than only the obvious exec fields. - A fold-variant key escaped the trust gate entirely. go-toml folds a table name to a struct field the same way it folds a field name, so a case-variant
[lsp.<lang>]decoded into the config while an exact lookup left it out of the trust spec. Matching is now case-insensitive. plumb trustnow asks. It previously printed the disclosure and granted in the same breath.protected_brancheswarned only on an empty list, though the list is the complete set — a shortened one is the more likely mistake.- A published threat model:
docs/threat-model.mdnames plumb's assets, trust boundaries and non-goals.
Added
- The mailbox is a real agent-to-agent conversation channel.
leave_note+check_messages: messages ride on any tool result so you receive them while working,check_messages({wait_seconds})blocks server-side so you can hand your turn to a peer instead of polling, replies thread byconversation_id, and a thread is capped by an exchange budget. Same-workspace by default; cross-project delivery is the recipient's opt-in. - Tool failures carry a stable, machine-readable classification. Write, boundary, rate-limit, language-server and git refusals are all classified, a failing git child carries its own kind, and a failed
tools/callcarries the classification on the wire. Classification happens exactly once per call, at the MCP dispatch boundary. plumb setup codex --lean/--leanfor Gemini CLI write a client-side tool allowlist, andplumb doctorgrades every one of them.- A project config plumb is ignoring now says so, in four places, rather than being silently skipped.
Changed
- Build provenance. A binary can say which source commit it was built from:
plumb version --jsonprints a machine-readable report anddaemon_infogained asource commit:row. Both stampers measure source-tree dirtiness instead of asserting it, a revision that is not a plausible SHA is treated as no stamp, and "dirty state unknown" no longer renders as clean. plumb stats --failures(a breakdown by kind) andplumb stats --since <age>(scoping the whole command), with a "Failures by Kind" widget on the TUI dashboard. Stats schemauser_version16.- The TUI settings editor exposes the
[git]tier rows and the exec-deciding fields. - The
plumb-diagnoseskill teaches the structured lane first.
Fixed
- Doc comments in the topology index. Zig symbols carry their
///span; a//!container doc no longer attaches to the following declaration; Python methods in a class body and every decorated declaration carry theirs; and a comment trailing a body's header line is no longer collected as the first doc line. - Several
plumb doctorcorrections: no longer suggesting a repoint that would overwrite a client-side allowlist, naming a mistyped Codex allowlist correctly, not claiming a backup that may not exist, and a removeKey canary that was a no-op on Codex. session_startno longer steers a--leanclient at tools it cannot call.
Install: brew install plumbkit/plumb/plumb, or grab a binary below (macOS and Linux, arm64 and amd64).