@nicknisi/pi-llm-council
LLM Council — multiple models answer independently, chairman synthesizes
Package details
Install @nicknisi/pi-llm-council from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:@nicknisi/pi-llm-council- Package
@nicknisi/pi-llm-council- Version
0.2.4- Published
- Aug 15, 2026
- Downloads
- 507/mo · 507/wk
- Author
- nicknisi
- License
- MIT
- Types
- extension
- Size
- 105 KB
- Dependencies
- 2 dependencies · 2 peers
Pi manifest JSON
{
"extensions": [
"./index.ts"
]
}Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README
llm-council
An LLM Council tool for pi: multiple models answer the same question independently, in parallel, as in-process child agent sessions (via @nicknisi/pi-shared's subagent runtime), then a chairman model synthesizes their (anonymized) answers into one unified response. Useful for questions that benefit from multiple perspectives or cross-checking — divergent answers flag uncertainty. Not for simple factual questions or routine tasks. Progress streams inline in the tool result with animated spinners, per-member status, cumulative token usage, and elapsed times; expanding the result shows the full markdown of every member response plus the chairman's synthesis.
Install
pi install /Users/nicknisi/Developer/pi-extensions/packages/llm-council
What it adds
- Tool:
llm_council(label "LLM Council"). No slash commands, no keybindings, no overlays/widgets, no events, no custom entry types. - Custom
renderCall/renderResultfor the tool: live member/chairman tree with spinner, status icons, elapsed times, and an expanded view rendering full member + chairman markdown. Expand/collapse uses the standardapp.tools.expandkeybinding (defaultctrl+o).
Tool parameters
| Parameter | Type | Description |
|---|---|---|
question |
string |
The question to pose to the council |
Prompt guidance registered with the tool tells the agent to use it for complex questions that benefit from multiple perspectives, and not for simple factual questions.
How it works
- Members — each council member receives the same question and answers independently, in parallel (
Promise.all). Each runs as a hermetic in-process child session spawned through pi's SDK (createAgentSession), shared via@nicknisi/pi-shared'screateSubagentRuntime; the answer is the child's final assistant message. - Chairman — receives the question plus all successful member answers (labeled Member A/B/C) and synthesizes a unified answer. If
chairman.exposePersonasistrue, each member's system prompt is included as(persona: "..."). The chairman's text is the tool's final content. - If every member fails, the tool returns an error result; the chairman never runs. If the chairman fails, its error text is returned.
Exec config → spawn options
The tools / thinking / extensions / skills / contextFiles options on member and chairman map onto the shared runtime's spawn options:
| Option | Value | Effect |
|---|---|---|
tools |
null/[] |
No tools |
tools |
[...] |
Exactly those built-in tools (allowlist) |
thinking |
null |
(pi default) |
thinking |
"..." |
Thinking level (off/minimal/low/medium/high/xhigh/max) |
extensions |
null/[] |
(none — children are hermetic) |
extensions |
[name] |
Load ~/.pi/agent/extensions/<name>/src/index.ts (per name, containment-checked) |
skills |
null/[] |
(none — children are hermetic) |
skills |
[name] |
Load ~/.pi/agent/skills/<name>/SKILL.md (per name, containment-checked) |
contextFiles |
false |
No AGENTS.md / project context files |
contextFiles |
true |
Context files load |
Behavior change from the subprocess era:
extensions: null/skills: nullused to mean "inherit pi defaults" (ambient extensions/skills loaded into the child). Children are now hermetic by construction —nulland[]both mean none; only explicitly named resources load.
System prompts are appended to pi's default system prompt through the child's resource loader (no temp files). The runtime honors the ecosystem recursion guard: when PI_SUBAGENT_DEPTH/PI_SUBAGENT_CHILD are set (i.e. the council itself is running inside a pi-subagents child), spawns are refused with a typed crashed result.
Default council
The built-in lineup assumes models enabled in ~/.pi/agent/settings.json enabledModels:
| Role | Model | Label |
|---|---|---|
| Member | fireworks/accounts/fireworks/models/glm-5p2 |
Member A |
| Member | fireworks/accounts/fireworks/models/kimi-k3 |
Member B |
| Member | anthropic/claude-fable-5 |
Member C |
| Chairman | anthropic/claude-opus-5 |
Chairman |
Members run with read-only built-in tools (read, grep, find, ls), no extensions, no skills, thinking: medium, and no project context files. The chairman has no tools — it only synthesizes.
Usage
No command to run yourself — ask in a pi session and the agent decides when to convene the council, e.g.:
Which approach is better for X: A or B? Convene the council.
Or steer it directly: "use llm_council to compare these two designs". The tool result shows the chairman's synthesis; press the tools-expand key (ctrl+o) on the tool block to see every member's full response.
Configuration
Two layers:
- Global:
~/.pi/agent/configs/llm-council.json— copyllm-council.example.json. Loaded once at module load; this is the only source forshared(display) settings. The path follows pi's agent dir, so it moves withPI_CODING_AGENT_DIRif you set it. - Project-local:
<cwd>/.pi/configs/llm-council.json— copyllm-council.project.example.json. Deep-merged over the global file per tool call, so only differing keys are needed — typicallymember.councilandchairman.modelto give a work project a different lineup. Display (shared) settings do not apply from the project file.
No environment variables are read for configuration. (PI_SUBAGENT_DEPTH is set internally to block recursion.)
member
| Key | Type | Default | Description |
|---|---|---|---|
council |
object[] |
(3 members, above) | Each entry: model (required), label (default: "1", "2", …), displayName, systemPrompt (both optional) |
defaultSystemPrompt |
string |
(built-in; see config.ts) |
System prompt for members without their own. The built-in default forbids spawning subprocesses |
display.labelColor |
string |
"accent" |
Member label color |
display.modelColor |
string |
"dim" |
Model name color |
tools |
string[] | null |
["read","grep","find","ls"] |
Tool allowlist for member child sessions (null/[] → no tools) |
thinking |
string | null |
"medium" |
Thinking level (null → pi default) |
extensions |
string[] | null |
[] |
Extension names, resolved to ~/.pi/agent/extensions/<name>/src/index.ts (null → pi defaults) |
skills |
string[] | null |
[] |
Skill names, resolved to ~/.pi/agent/skills/<name>/SKILL.md (null → pi defaults) |
contextFiles |
boolean |
false |
false → --no-context-files |
chairman
| Key | Type | Default | Description |
|---|---|---|---|
model |
string |
"anthropic/claude-opus-5" |
Chairman model |
displayName |
string |
"Claude Opus 5" |
Human-readable name shown in the UI |
systemPrompt |
string |
(built-in; see config.ts) |
Chairman system prompt (treats member answers as anonymous) |
exposePersonas |
boolean |
true |
Include each member's system prompt as a persona in chairman input |
display.icon |
string |
"" |
Icon prefix before the "Chairman" label |
display.labelColor |
string |
"accent" |
Chairman label color |
display.modelColor |
string |
"dim" |
Chairman model name color |
tools |
string[] | null |
[] |
Chairman tool allowlist (none by default) |
thinking |
string | null |
"medium" |
Thinking level |
extensions |
string[] | null |
[] |
Extensions (null → pi defaults) |
skills |
string[] | null |
[] |
Skills (null → pi defaults) |
contextFiles |
boolean |
false |
Context files for chairman |
shared (display — global config only)
| Key | Default | Description |
|---|---|---|
spinner.prefixChars |
["·","✢","✳","✶","✻","✽"] |
Spinner frames (played forward then reverse) |
spinner.interval |
80 |
Frame interval, ms |
spinner.color |
"muted" |
Spinner color |
successPrefix.prefix/color |
"✓" / "success" |
Success icon |
errorPrefix.prefix/color |
"✗" / "error" |
Error icon |
branch.prefix/color |
"└─" / "separator" |
Sub-line branch prefix |
status.doneLabel/doneColor |
"Done" / "success" |
Completed-status label/color |
status.errorLabel/errorColor |
"Error" / "error" |
Error-status label/color |
status.workingLabel/workingColor |
"Working..." / "dim" |
In-progress label/color |
status.waitingIcon/waitingIconColor |
"↪" / "muted" |
Pending member icon/color |
status.synthesizingLabel |
"Synthesising..." |
Chairman in-progress label |
status.waitingLabel |
"Waiting for members..." |
Chairman pending label |
status.elapsedColor |
"dim" |
Elapsed-time color |
toolHeader.titleColor/summaryColor |
"toolTitle" / "dim" |
Tool call header colors |
expandHint.color |
"dim" |
"ctrl+o to expand" hint color |
questionPreview.maxLength |
40 |
Chars of the question shown in the header |
Color values
Any color field accepts a pi theme token ("text", "accent", "success", "error", "muted", "dim", "separator", "toolTitle", …) or a 6-digit hex string ("#ff6600", rendered as a 24-bit ANSI fg). Unknown tokens fall back to uncolored text.
Dependencies
@earendil-works/pi-coding-agent(peer) —ExtensionAPI(pi.registerTool),Theme/ThemeColor,getMarkdownTheme.@earendil-works/pi-tui(peer) —MarkdownandTextrender components,getKeybindings(for the expand-hint key label).typebox— tool parameter schema (Type.Object).@nicknisi/pi-shared(workspace) — the in-process subagent runtime (createSubagentRuntime) that members and the chairman spawn through.- No
pibinary requirement: children are in-process SDK sessions, not subprocesses.
Caveats
- Extension resolution path is hardcoded.
extensions: ["name"]resolves to~/.pi/agent/extensions/<name>/src/index.ts— only directory-style extensions with that layout work. Single-file.tsextensions and npm-package extensions don't match; the code comments recommend keepingextensions: []for members. Same forskills→~/.pi/agent/skills/<name>/SKILL.md. - Depends on pi's SDK surface:
createAgentSession,DefaultResourceLoader(itsnoExtensions/additionalExtensionPathssemantics),SessionManager.inMemory,SettingsManager.inMemory,ModelRuntime/resolveCliModel. These are pi internals that could change across versions; the runtime is version-matched at runtime because pi aliases@earendil-works/*imports to the host, but type-level drift would surface at extension load. - Pi internals: the spinner relies on the
renderCall/renderResultctx.statebag andctx.invalidate(). A module-levelliveDetailsbridgesonUpdate→renderCallas a workaround for anisPartialbug (per code comment); only one council can render live at a time. - Recursion guard: the shared runtime refuses to spawn when
PI_SUBAGENT_DEPTH/PI_SUBAGENT_CHILDare set — this tool won't work if invoked from inside a pi-subagents child session. - Global config is read once at module load — edits to
~/.pi/agent/configs/llm-council.jsonrequire a pi restart; project-local config is re-read on every tool call. - Members and chairman run with the current working directory as
cwd;contextFiles: falsekeeps CLAUDE.md/AGENTS.md out of member context by default.