Plumbify
Give an open LLM a fast decision path, then serve it with vLLM.
Guide · Architecture · Recipes · Results · Changelog
Language models are slow and inconsistent at the small judgements that fill real conversations: which team gets this ticket, is this refund allowed, how urgent is it. Thinking helps, at the cost of seconds per decision.
Plumbify trains a small decision head, a plumb, onto an open model without changing its weights. With the
plugin installed, vllm serve loads the result like any other model. The model generates as usual, and when a
conversation reaches a decision, the plumb answers it in one forward pass, reading the KV cache the conversation
already filled, with a calibrated probability for every option.
Faster, with thinking on.
Qwen3.5-35B-A3B, one server, real time, thinking on in both panes. A support case asks for two decisions: approve the refund, and escalate to a manager. The normal model reasons both out in its thinking. The plumbed model hands them to its plumb, which reads the conversation from the KV cache (the escalation call takes 116 ms and reuses 1,056 cached tokens). Both plumb answers come back with two options in their conformal set, which marks them as unsure, so the model checks each one briefly before it replies. Both panes reach the same answer: approve, don't escalate. The plumbed model finishes in 10.5 s, the normal model in 32.6 s, 3.1× faster. Video (MP4)
More accurate, with thinking off.
Qwen3.5-35B-A3B, thinking off in both panes. A grading item states a decision: what is the correct answer, given a student's working? The normal model agrees with the student's wrong answer, d ($278.40). The plumb picks b ($290.00), the correct answer, with probability 0.995 in 220 ms, and the model answers b. Both take under a second. Video (MP4)
A plumb is a Jev model built into the LLM. It answers the same kind of typed decision as a Jev-style decision model such as TypeSafe's Jev: pick one of these options, yes or no, or a score on a scale. The difference is that it is not a second model next to the LLM. It is a Jev decision head inside the model, sharing its weights and its KV cache, so plumbifying a model is jevifying it.
Quickstart
Plumbify needs Linux, an NVIDIA GPU and Python 3.10 or later. The vllm extra installs vLLM 0.30.0, the version the
plugin is tested against.
pip install "plumbify[vllm]"
To try it without training, serve one of the six plumbed models in the Plumbify collection on the Hugging Face Hub, then chat with it in a second terminal:
vllm serve totum-labs/Qwen3.5-9B-plumb --max-model-len 32768
plumbify chat
The chat shows each decision as the model hands it to its plumb: the question, the probability of every option and
how long it took. Try a message that states a decision, such as
Charged twice.\n\nWhich team?\n- billing\n- technical\n- sales (type \n for a line break).
Or train a plumb on a frozen base model, then serve the result:
plumbify train --base Qwen/Qwen3.5-9B \
--rows train.jsonl --dev dev.jsonl --out plumbed-qwen3.5-9b
vllm serve plumbed-qwen3.5-9b
Ask it a decision through the standard OpenAI API (for a model from the Hub, model is its repo id):
curl -s localhost:8000/v1/chat/completions \
-H 'content-type: application/json' \
-d '{"model": "plumbed-qwen3.5-9b",
"messages": [{"role": "user", "content":
"Charged twice.\n\nWhich team?\n- billing\n- technical\n- sales"}]}' \
| jq -c .plumb.answer
The response is a normal chat completion with an extra plumb field. Its answer says which option won, who
decided (system1 is the plumb), and with what probability:
{"choice":"billing","by":"system1","p":0.973}
The guide covers the training data format, the request options and the response fields.
plumbify chat opens a live chat that shows each decision as the model hands it off.
Results
The same model alone and with its plumb, on one vLLM server, over 447 held-out decisions (task families and templates no model trained on). Each prompt states a decision: context, question, bulleted options.
| Model | No thinking: alone → plumbed | Thinking: alone → plumbed | Thinking latency p50: alone → plumbed |
|---|---|---|---|
| Qwen3.5-27B | 0.732 → 0.839 | 0.866 → 0.875 | 26.6 s → 17.1 s (1.6× faster) |
| Qwen3.5-35B-A3B (MoE) | 0.720 → 0.810 | 0.852 → 0.875 | 6.8 s → 2.8 s (2.4× faster) |
| Gemma 4 12B | 0.736 → 0.826 | 0.770 → 0.872 | 24.1 s → 5.0 s (4.8× faster) |
| Qwen3.5-9B | 0.696 → 0.779 | 0.808 → 0.846 | 28.0 s → 5.7 s (4.9× faster) |
| Qwen3.5-4B | 0.667 → 0.801 | 0.841 → 0.826 | 12.0 s → 3.8 s (3.1× faster) |
| Qwen3-1.7B | 0.570 → 0.658 | 0.642 → 0.707 | 3.9 s → 3.1 s (1.2× faster) |
Without thinking, the plumb adds 8 to 13 points on every model. With thinking, the plumbed model is at least as accurate on five of six models (Qwen3.5-4B is within noise) and answers 1.2 to 4.9 times faster, because it thinks 30 to 75% less: it hands the decision to the plumb instead of reasoning it all out. It also always answers, where the model alone runs out of tokens or gives no clear answer on 5 to 15% of decisions when thinking. With 447 decisions, one standard error is about 2 points.
With thinking on, the model alone is scored in its better setup. These models think much less when their prompt
lists a tool (the plumbed model's prompt always lists plumb_decide), which is often but not always more accurate,
so each model alone gets whichever prompt, with or without a tool, scores higher. Settings, training time and
serving memory for each run are in recipes/; the benchmark is
scripts/bench_s1_vs_s2.py.
How it works
Take a support chat that reaches a decision: which team should handle this ticket: billing, technical or sales?
- The question is added to the conversation. The question and the three options are appended, written like a normal message to the model.
- The model reads only the new part. It has already read the conversation, and that work is cached, so only the few words of the question are processed.
- The plumb scores the options. It looks at what the model computed while reading the question and turns that into a probability per option: billing 0.97, technical 0.02, sales 0.01.
- The model carries on. No text was generated for the decision. The model uses the answer and continues its reply.
conversation so far (cached) + "Which team? billing / technical / sales" → plumb → billing 0.97
Training adds two small pieces and nothing else: the plumb itself, and an adapter that switches on only while the
model reads a decision question. The model's own weights never change. The server does add the plumb_decide tool
and a one-line note to the system prompt, and listing a tool changes how a model reasons: these models think less
with it, on small talk as much as on decisions.
Architecture explains each piece and
where it lives in the code.
Documentation
| Document | Covers |
|---|---|
| Guide | Install, training data, the plumbify commands, the serving API, limits |
| Architecture | How training and serving fit together, the invariants, a map of the code |
| Recipes | Tested settings and measured results for each model |
| Scripts | Benchmark, vLLM parity check, live chat |
| Contributing | Development setup, tests, conventions |
Development
pip install -e ".[dev]" && pre-commit install
pytest -q tests
ruff check . && ruff format --check .
The tests run on CPU in seconds with a tiny random model and don't need vLLM. See CONTRIBUTING.md.
Citation
@misc{plumbify2026,
title = {Plumbify: a System 1 decision branch for open
language models},
author = {Kamalakannan, Naveenraj},
year = {2026},
url = {https://github.com/therealnaveenkamal/plumbify}
}
License
Metadata
Release files for plumbify 0.3.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| plumbify-0.3.0.tar.gz | 81.7 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| plumbify-0.3.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 163.0 kB
Release files / plumbify-0.3.0.tar.gz
| Download URL | plumbify-0.3.0.tar.gz |
|---|---|
| Size | 81.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
6b4ad6bc2425c206b7e7e3ee78b544d5e2ad1e104ce560e94d0388a262dd6012
|
|
BLAKE2b-256 checksum How to use checksums |
21ea42c75f37457c7391002c542f3bc39cd55b64bc4f476c237614446d53634c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 2, 2026.
Transparency logRelease files / plumbify-0.3.0-py3-none-any.whl
| Download URL | plumbify-0.3.0-py3-none-any.whl |
|---|---|
| Size | 81.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
cf0485d01127ba07646ebc15b2e9c320fb49db5bb0994b113ad45b1d8d668c36
|
|
BLAKE2b-256 checksum How to use checksums |
34c9c33f852692a56b08980303fc3addb35f8fed3db93034947b6c72df8ad03b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 2, 2026.
Transparency logSecurity for plumbify 0.3.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for plumbify-0.3.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 2, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.