@booplex/bpx-consult
v0.10.1
Published
Pi extension. A council of AI advisors — consult one model or run a full multi-model consensus before you commit to a direction.
Maintainers
Readme
bpx-consult — a council of AI advisors for pi | pi extension
Your coding agent runs on a cheap, fast model most of the time, and most of the time that's the right call — it's grinding through edits, running tests, moving quick. The trouble is the handful of moments that actually decide how the thing turns out: the architecture choice, the "should I even build this," the bug it's been circling for twenty minutes. That's where cheap-and-fast quietly makes the wrong call, and you don't catch it until three commits later.
bpx-consult is the smarter, pricier model you keep on the bench for exactly those moments. One advisor for a quick gut-check, a whole council when the call's genuinely hard, two of them arguing it out when it's contentious. Let the cheap model do the typing; pay senior rates only when senior judgment is actually worth it.
None of this is a new trick for me. I'd already run the same idea — cheap models doing the grunt work, a stronger, pricier one taming and steering them — in a private MCP of my own, well before I needed it in pi. So when I wanted it here, I wasn't starting from scratch; I knew the pattern worked.
The catch was the advisor extension I tried in pi kept dying on me: it forwarded the entire session to the advisor without checking whether the advisor's own window could hold it, so my second opinion errored out at exactly the moment the session got long enough to need one. So I built bpx-consult — same pattern, done right for pi. And once you're already paying to call a second model, why stop at one, and why only to review? A council. A debate. A cheap model driving and an expensive one steering — with a context engine that finally makes it survive a long session, which is the part the old extension never got right.
Four modes, all wired: solo (one model, fast), council (several models in parallel with stances and a synthesizer), debate (advocate vs critic, sequential rounds, closing verdict), and gut-check (one cheap model, terse read). Plus triggers that fire a consult automatically when you're stuck or when a turn finishes.
Works in pi (the coding agent, v0.80+).
Install
pi install npm:@booplex/bpx-consultThen restart your pi session. The consult tool and /consult command register automatically.
git clone https://github.com/gabelul/bpx-mono
cd bpx-mono
pi install ./packages/bpx-consultThe window bug, and the fix
The reason this extension exists. The advisor extension I tried forwarded the whole session to the advisor without checking whether the advisor's own window could hold it — so my second opinion errored out at exactly the moment the session got long enough to need one. Every consult path here runs the conversation through a context engine first, and it fits to that advisor's real window — read live from the registry, never a global constant. Point a 32k flash-tier advisor at a 128k session and it fits. Point an 8k CLI advisor at the same session and it still fits. Council fits every member to the smallest window in the roster, so the weakest member can't overflow.
That's the guarantee. What changed in 0.2.0 is how it fits.
Evidence-aware fit (0.2.0)
The old fit was blunt: keep the first couple messages, keep the last several, drop the middle, char-cap what's left. It never overflowed — but "keep recent, drop old" is a lousy proxy for "keep what matters." In a long debugging session the line that explains why you're stuck is usually in the middle, and blindly char-truncating a message shreds the one stack trace or diff the advisor needed.
So now the engine classifies before it cuts. Every message gets a deterministic tag — directive, diff, failing-output, stack-trace, test, exploration, and so on — from its role and content, never from the model's own judgment (the stuck model is the worst judge of what's safe to drop). Then it fills the window by priority, not by position:
- Your question and task framing — pinned, first.
- The payload — the latest failure and the latest diff, verbatim. This is the thing you're actually asking about.
- Pinned artifacts — reviewer findings, repeated-failure signatures.
- Recent tail, verbatim while there's room.
- Older path — compressed to one-line signals (
read x.ts,edit y.ts (+30/-5),$ npm test (exit 1)), then dropped.
Compress the path, preserve the payload. Pinned items never get silently dropped — if one's too big it degrades (verbatim → signal → clipped-to-anchors with markers), and in the pathological case where even the essentials won't fit, it fails closed with a clean error instead of overflowing. Every drop, every clip is marked. The full design is in SPEC §E.
The ledger
Because it now decides what to keep, it can tell you what it did. Every consult result carries a ledger — { kept, compressed, clipped, dropped } counts plus the fittedTokens estimate. Check the tool-result details after a consult and you can see exactly how a 150k session got fitted into a 28k advisor: what survived verbatim, what got compressed to a signal, what fell off the end. It's the measuring tape — if you're wondering whether the fit is losing something it shouldn't, the ledger is where you look.
The four modes
| mode | what it does | when to reach for it | |---|---|---| | solo | One advisor model, one response. | Default. Fast, cheap, the second opinion you reach for most days. | | council | Several models in parallel, each with a stance (for/against/neutral) and a persona. A synthesizer merges their verdicts with a confidence score. | Real decisions. Architecture, "should I even do this," tricky bugs where one voice isn't enough. | | debate | Advocate proposes, critic attacks, advocate rebuts. Sequential rounds (1–4), then a synthesizer issues a verdict. | Controversial calls where you want the strongest case on both sides before you commit. | | gut-check | One cheap fast model, terse output. | The "does this smell off?" sanity check before you do something you're 90% sure about. |
Call consult() with no args and solo runs. Pass mode: "council" (or debate, gut-check) to pick another.
Type /consult to configure everything interactively — no file editing. It opens a menu: default mode, solo and gut-check models and effort, the council roster, both triggers, and enable/disable. Pick a setting, choose from a fuzzy-filterable list of the models you actually have authed, and the change saves immediately and the menu reopens so you can set several in one go. (/consult status prints the old read-out if you just want a glance.)
The Council members submenu (one entry on the main /consult menu) manages who's on the council. Each seated member opens a detail view:
- Assign a model to the member (fuzzy-filter your authed models). Before committing, you can Test with this persona first — it probes the candidate model with the member's actual prompt and reports whether it responds, so a dead key or 401 is caught at selection time, not in a live council call. Assign only if it passes (or skip the test and assign now).
- Route the seat to a CLI backend — inline, or codex / claude / opencode CLI, or a Custom CLI (any executable: command + structured args + a required context window). Each CLI seat runs as a subprocess in parallel with the inline seats, so one provider dying doesn't collapse the council. The same Test probe works for CLI routes (missing executable, timeout, nonzero exit, empty output), and a custom CLI must pass it before it's saved — it has to accept bpx-consult's stdin contract (markdown transcript in, text/JSONL out).
- Retest the assigned route any time from the same detail view.
- Enable / disable a persona — unseating keeps its definition, so re-enabling restores its model
- Add manually — type a name, pick a stance (for/against/neutral), pick a model
- Add AI-generated — describe the advisor's focus, pick a model to draft it, confirm the
{name, stance, system prompt}it returns, and seat it - Synthesizer model — the model that merges member verdicts into one call
The default roster seats architect/critic/simplifier on distinct model tiers so parallel calls don't all hammer one provider. CLI routing is persona-scoped — two seats on the same model can route differently (one inline, one CLI). Advanced settings (context-budget char caps, per-backend timeouts) still live in the config file.
What it looks like
The executor calls consult() on its own when it wants a second opinion, or you invoke it by hand. Every arg is optional:
consult() // solo, default model
consult({ question: "Is this auth flow sane?" }) // solo, with a specific ask
consult({ mode: "council" }) // full roster, in parallel
consult({ mode: "debate", question: "Rewrite the parser, or patch it?" })Here's a real council call — I asked whether to ship a half-built feature for the next morning's demo. The architect argued for it, the critic argued against, and the synthesizer refused to average them into mush (trimmed, but the verdict and numbers are from the actual run):
COUNCIL — architect (for) · critic (against) · simplifier (neutral)
The council split. Surfaced honestly, not papered over:
• architect (FOR) — a demo doesn't need production rigor; ship it, caveat it live.
• critic (AGAINST) — a broken demo teaches the room the wrong thing; the risk IS the story.
• simplifier — the pre-recorded path costs nothing and removes the failure mode entirely.
VERDICT — STOP. Demo the pre-recorded flow; ship the real feature once it's tested.
confidence 0.83 (success 1.0 · agreement 0.5 · alignment 1.0)That agreement 0.5 is the disagreement showing up in the math: two of three held opposing stances, so the confidence dial drops. That's the feature, not a bug. A council that always reported high confidence wouldn't be worth the tokens.
Council, in more detail
This is the reason I went past a bugfix. The default roster seats three personas: architect (advocates for the design), critic (attacks it), simplifier (questions the complexity). Each runs on a distinct model tier so parallel calls don't trip provider rate limits, and each gets a stance-injected system prompt.
The stance framing biases what a persona hunts for, never the verdict. A for persona can still land on "don't do this" if the evidence says so. The guardrail is baked into the prompt because the alternative is theater: a critic that rubber-stamps, an advocate that caves.
When members genuinely disagree, the synthesizer is told to surface the split, not paper over it. A false consensus is worse than an honest "the architect argued X, the critic demolished it, here's my call." Every council result carries a confidence score (0.4·success + 0.35·agreement + 0.25·stance-alignment). It's a rough signal, not a verdict. The "agreement" term measures whether members landed on the same stance regardless of persona, which is roster-shaped: a default for/against/neutral trio will score lower on agreement than three neutrals. Treat it as a dial, not a grade.
Triggers
Consults don't have to be manual. Two auto-triggers plus phrase triggers:
- whenStuck:N — off by default (N = 0). Set it to a positive number in
/consultto arm it. Once armed, it fires after N consecutive tool errors or N identical tool calls (loop detection via an un-truncatedtoolName:inputfingerprint), runs a solo advisor, and steers you out. Off out of the box so the safety net never spends advisor tokens unless you ask it to — the model decides when to consult, nudged by the tool guidelines. - onDone — off by default. When on, it fires when the agent finishes a turn and reviews the work before it stops.
Auto-triggers always run solo, regardless of your default mode. An auto-fire is a safety net, not a deliberate consultation. A council burning 3+ model calls every time you hit a loop would be a surprise-quota footgun. If you want a council, call it explicitly.
Triggers never fire in untrusted projects.
Phrase triggers
You can also fire a consult by just typing a phrase — no tool call, no slash command:
| Say something like… | Runs | |---|---| | "ask the council", "use the council", "council on this", "convene the council", "run the council" | council | | "ask the advisor", "second opinion", "get advice" | solo | | "debate this", "have them debate", "start a debate" | debate | | "gut check this", "gut-check" | gut-check |
Matching is case-insensitive and word-boundaried. Add "about …" to focus it — "ask the council about the retry strategy" passes the retry strategy as the question. Phrase triggers only fire on your own interactive input in a trusted, enabled project, and they honor your feedbackMode (below). Because they're user-initiated, they're not subject to the per-turn consult cap.
One deliberate non-trigger: a bare "the council" won't fire — it needs a verb ("ask/use/consult/run the council") or the "council on this" shape. Otherwise a passing mention ("what did the council say?") would spend real money on a council you didn't ask for.
How advice reaches the executor
Advice comes back differently depending on who asked for it. feedbackMode (default steer) controls only the paths where bpx-consult injects on your behalf.
- The model's own
consult()tool call → always a normal tool result. It asked for the advice, so it always gets it back inline.feedbackModedoes not apply here — there's nothing to route. - Phrase triggers and manual standalone runs → honor
feedbackMode:- steer — injects the advice as a steering message mid-run; the executor sees it and continues without you leaving the flow.
- pipe — injects it as a user message (queued as a follow-up), so the executor treats it as your input.
- show — UI-only. You read it in a boxed "not sent to the model" message; the executor never sees it. In show mode a phrase trigger also suppresses the agent run — "show me, don't act" actually stops the turn (via pi's
{ action: "handled" }input result).
- Auto-triggers (whenStuck / onDone) → fixed delivery, independent of
feedbackMode: whenStuck steers (so it doesn't interrupt), onDone queues a followUp.
Per-mode override. feedbackMode can be set per mode as well as globally, and the per-mode value wins. So you can keep the top-level default at steer but set modes.council.feedbackMode to show — now "ask the council" gives you a read you act on yourself, while "second opinion" (solo) still steers. Any mode without its own feedbackMode inherits the top-level one, which falls back to steer.
{
"feedbackMode": "steer", // default for every mode
"modes": {
"council": { "feedbackMode": "show" } // …but council results are UI-only
}
}Backends
Solo and council members can route to an external CLI instead of pi's inline provider. The /consult menu's Council members submenu sets this per persona: inline, a preset (codex / claude / opencode), or a Custom CLI (any executable — command + structured args + a required context window, probe-tested before it's saved). The legacy backends.<model> config map still works as a fallback. Each CLI reads the fitted context from stdin. The subprocess is non-blocking, so it doesn't serialize under the hood — and a council can mix inline and CLI seats in parallel, so one provider dying (rate limit, dead key) no longer collapses the whole council.
Resilience
Two things that would silently bite, both handled.
Each council member runs under its own AbortController linked to the parent signal, so one member timing out or erroring drops only that member. The rest proceed and the synthesizer works with whoever replied. A flaky member never crashes the council.
Wall-clock timeouts cover the hang case across all modes. Council (council.timeoutMs, default 120s), debate (debate.timeoutMs, default 180s), and CLI (timeoutMs per backend) all have explicit budgets. A provider that accepts-then-hangs settles as a clean failure instead of hanging the executor turn.
What v1 does not have: per-member circuit-breaker with exponential backoff. Isolation plus timeouts is the resilience story today. Smarter retry is on the v1.1 list.
Config
~/.pi/agent/bpx-consult.json (global) or .pi/bpx-consult.json (project-local, trusted projects only). Project overrides global at the leaf level.
{
"enabled": true,
"defaultMode": "solo",
"modes": {
"solo": { "model": "anthropic/claude-sonnet-4-6", "thinkingLevel": "high" },
"gutCheck": { "model": "google/gemini-2.5-flash", "thinkingLevel": "low", "terse": true },
"council": { "members": ["architect", "critic", "simplifier"], "synthesizer": { "model": "anthropic/claude-sonnet-4-6" }, "parallel": true, "timeoutMs": 120000 },
"debate": { "advocate": "architect", "critic": "critic", "rounds": 2, "timeoutMs": 180000 }
},
"personas": {
"architect": { "defaultModel": "anthropic/claude-opus-4-6" },
"critic": { "defaultModel": "anthropic/claude-sonnet-4-6" },
"simplifier": { "defaultModel": "anthropic/claude-haiku-4-5" }
},
"backends": {
"openai/codex": { "type": "cli", "command": "codex", "timeoutMs": 60000 }
},
"triggers": { "onDone": false, "whenStuck": 0 },
"feedbackMode": "steer",
"maxConsultsPerTurn": 3,
"disabledForModels": [],
"contextBudget": { "responseReserveTokens": 4096 }
}enabled (default true) is the master switch. Set it false to turn the whole extension off without uninstalling — the consult tool and triggers go dormant.
maxConsultsPerTurn (default 3, 0 = unlimited) is a soft cap on how many times the model itself may call consult() in a single turn. Hit the cap and the next call returns a cheap "cap reached" note instead of spending another advisor call — the model proceeds on its own judgment or you raise the cap in /consult. It counts only the model's own tool calls; phrase triggers and auto-triggers have their own guards and are never blocked by it. The counter resets each turn and on your next input.
disabledForModels (default []) turns consult off for specific executor models. Two shapes: a bare model key disables it outright ("openai/gpt-5"), or an object gates on effort — { "model": "anthropic/claude-opus-4-6", "minEffort": "high" } keeps consult available only when that model is thinking at high or above, and off below it (so a cheap low-effort turn doesn't drag in an advisor). When disabled, consult() returns a short "disabled for this model" note instead of running.
The defaults are pinned to specific model versions, which means they'll drift as Anthropic ships new ones. The registry supports tier aliases in some places; where it does, prefer an alias. Otherwise expect to update these periodically, or override personas.*.defaultModel with whatever you actually have authed.
Roadmap
Where this is heading. The package is pre-1.0, so these are milestone groupings, not version promises.
Before 1.0
- Smarter retry. Resilience today is per-member isolation plus wall-clock timeouts. Per-member circuit-breaker with exponential backoff is the next layer for flaky providers.
After 1.0
- Research-backed council. Council today argues from stances and the session transcript alone. The next layer grounds those arguments: advisors that web-search for evidence behind their position, focus-area steering (weigh security, or performance, or cost specifically), and context beyond the transcript (files, diagrams, images). Built natively, not as a call-out to another MCP — the pattern's proven, owning it beats delegating it.
- Memory compression and branched-session handoff. For very long sessions and dedicated per-persona advisor forks.
The full design (including the decisions behind each of these) is in SPEC.md on GitHub.
Prerequisites
- pi 0.80+ (uses the
@earendil-works/pi-ai/compatcompleteSimpleentry, event handlers,sendUserMessage) - Node 22.19+ — pi's own packages require it, so this does too.
- At least one provider authed via
/login. The default roster uses Anthropic; overridepersonas.*.defaultModelto match what you have. - For the CLI backend:
codex,claude, oropencodeinstalled and on PATH.
Related
Other tools for agents that care about quality:
- slopbuster — AI text humanizer. 100+ patterns, two-pass audit, three-tier scoring. Makes AI-generated prose, code comments, and academic writing sound human.
- pixelslop — Design quality scanner. Opens real pages in Playwright, measures actual pixels, catches visual AI slop.
- pixeltamer — Generate, edit, and compose images with gpt-image-2. It drew this project's brand art.
- stitch-kit — Design superpowers for AI coding agents. 35 skills for ideation, generation, iteration, and production conversion via Google Stitch MCP.
Built by Gabi @ Booplex.com — because the advisor that dies when you need it most isn't an advisor, it's a liability. MIT license.
