@expert-council/pi-package
Expert Council: cost-aware multi-model expert orchestration for Pi — assemble a council of scout, oracle, implementation-worker, and reviewer experts and delegate bounded tasks
Package details
Install @expert-council/pi-package from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:@expert-council/pi-package- Package
@expert-council/pi-package- Version
0.8.3- Published
- Sep 15, 2026
- Downloads
- 2,375/mo · 2,090/wk
- Author
- labiey
- License
- MIT
- Types
- extension, skill
- Size
- 138.3 KB
- Dependencies
- 2 dependencies · 2 peers
Pi manifest JSON
{
"skills": [
"./skills"
],
"extensions": [
"./dist/extension.js"
]
}Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README
Expert Council
Expert Council is a local, multi-model, cost-aware expert orchestration system for Pi and MCP hosts such as Codex. It discovers the models connected through Pi's registered LLM APIs and coding plans, combines runtime metadata with user-defined billing policy, capability profiles, and local reliability data, dynamically assembles a small semantic expert team, executes bounded tasks through Pi, and returns compact structured results to the Main Agent.
Key advantages:
| Advantage | Description |
|---|---|
| Cost savings | Makes flexible use of subscribed plans and LLM APIs, automatically matching the most suitable model to each task's difficulty |
| Speed | Runs multiple suitable models concurrently, accelerating repository exploration and context compression |
| Safer | Assigns different read-only/writable permissions per expert; writable experts commit into an isolated Git worktree that the Main Agent reviews before integration |
| Context savings | The Main Agent no longer carries long tool-call-heavy context; it receives summarized, structured results from experts |
In practice, the theoretically strongest model is not automatically the best executor. A model with stable tool calls, reliable shell behavior, and low marginal cost can deliver more real task value than a stronger but less dependable one. We recommend configuring a cost-effective, execution-strong model as the Main Agent; when a complex problem appears, Expert Council can dispatch strong-reasoning models for review or planning.
Current status
The current version (0.8.3) includes:
- Interactive experts: a running expert can pause on a major, hard-to-reverse, or ambiguous decision and present 2-4 recommended options (+ optional free text) to the Main Agent through
request_decision; the host answers withexpert_respondand the expert continues in the same session. Non-terminal — distinct fromreport_and_stop. - Dynamic tool permissions: preset role tools are a seed, not a ceiling. An expert requests a missing tool via
request_tool; the host grantsonce(auto-revoked after one use),persistent(this session), orreject.security.toolGrantsgives operator-defined persistent per-role grants. A read-only execution can never be escalated to a mutating/shell tool (isolation guarantee). - Interaction discovery on a correctness channel: an open interaction surfaces as
pendingInteractioninexpert_status(view:"running")andexpert_result(includeProgress), and the native Pi package additionally wakes the host with anexpert-council-interactionnotice the moment one opens; headless hosts (Codex/MCP, no server-push) keep polling. Bounded per execution (default 3 rounds + wait timeout). - Cross-language environments:
provisionWorkspaceis no longer Node-only. A driver registry materializes node/pnpm/yarn/bun, python (uv/poetry/host venv), rust, go, jvm/maven, dotnet, ruby, php, elixir from each toolchain's already-global download cache. Environment-as-code backends (flake.nix,.devcontainer/) are detected and delegated, not re-implemented. Newsecurity.workspaceProvisioning.strategy(auto/drivers/as-code/in-place) andruntimeEnv(isolated/host-env); concurrent worktrees never share a recompiled target directory. - Progress observability toggle:
security.observability.expertWindow(offdefault /events) surfaces bounded live progress (messageCountplus the last assistant text) in the running view, andsecurity.observability.streamToHostcan switch that stream off.interactiveis accepted for forward compatibility but behaves aseventsuntil an RPC projection exists, andexpert_inspectwarns when it is configured. Neither toggle gates thependingInteractioncorrectness channel, nor an explicitexpert_result(includeProgress). - A host-agnostic Core: config validation, model normalization, per-model/per-provider billing multipliers, profile layering, role scoring, task classification, dynamic team sizing, retry/escalation, and telemetry aggregation.
- An execution runtime built on Pi's current
ModelRuntimeandcreateAgentSessionAPIs. - Per-expert hard tool allowlists and installed-Skill filtering.
- Detached Git worktree isolation for writable experts, plus automatic dependency provisioning from the repository lockfile (npm/pnpm/bun/uv) with
--ignore-scripts, an allowlisted child environment, per-execution worktree reuse, and direct host.venvinterpreter paths surfaced for Python ecosystems. - A runtime verification gate: after provisioning, the repository typecheck and tests run automatically; a failing gate downgrades a successful result to
partialand engages the corrected-retry path — zero Main Agent overhead. report_and_stopexpert tool: when an expert determines the task cannot be completed (missing tool, absent environment, denied permission), it submits a structured stop report — blocker, findings, risks,recommendedNextAction— delivered as apartialresult that terminates the delegation loop without retry or escalation, with the mutation worktree retained for inspection.- Host-bound expert lifetime (
security.expertLifetime, defaulthost-bound): running experts are aborted automatically when the host session quits or is replaced, so orphaned experts never burn quota with no receiver;detachedrestores the old behavior. - Failure results keep artifacts: on writable runs, timed-out, session-error, and thrown-error results include
filesChangedand the last assistant text, so a long timeout no longer returns an empty result. Read-only runs correctly report nofilesChangedat all. - State write-side clamp: oversized arrays and text are truncated at the single persistence choke point, so a verbose expert can no longer write an unreadable state file.
expert_availability_reset: clear misrecorded or recovered availability markers by*/provider/provider/idwithout editing files or restarting.expert_verify: run a bounded command plugin-side in a retained worktree or validated workspace and get the real exit code and output tail;tests[]now carry exit codes, counts, durations, and output tails.expert_statusviews: a boundedsummarydefault (running with remaining budget, recent completions, provider slots),running, and legacyfull— cutting observation-context cost.- Reliable completion notifications: the package retries the finish message briefly on the idle/streaming race instead of dropping it.
timeoutMsandreasoningLevelas required delegation arguments, chosen deliberately per task and model — on a batch every entry carries its own pair, so only single delegations read them from the top level; compositions can pin per-role/per-model reasoning levels that override the argument.- Expert fail-fast discipline: when the assigned tools or workspace make a task impossible, experts stop immediately with a structured
missing_context/permission_errorresult that is relayed in real time instead of burning retries. - Persistent council compositions: named rosters in
council-compositions.json(roles may pin multiple models and reasoning levels), a first-build composition menu, session bindings,modelpinning, and concurrent same-role dispatch. - Provider limits and concurrency: per-provider daily/weekly weighted-token caps (accounted in
usage-ledger.jsonviacostMultiplier) and concurrency limits fromroute-policy.json; excluded candidates report the reason. - Tiered runtime availability markers: dead models (24h), plan-quota exhaustion (6h, provider-wide), and transient TPM/RPM throttling (2 minutes) — throttling is no longer misread as quota exhaustion.
council-config.jsonoperator configuration discovered by default in the data directory (no environment variable);expert_inspectreturns its path, the effective provisioning mode, and the effective progress-visibility settings so the Main Agent can edit them on request.- A CLI with JSON output.
- An MCP Server with 13 async semantic tools, event-driven completion waits, a feedback loop, and explicit worktree cleanup.
- A native Pi Package.
- Provider session-error pass-through: upstream refusals such as
403 AccessDeniedreach the Main Agent with real diagnostics and the correct failure type. - Cross-process shared model assessment: availability markers are visible to parallel instances without restarts.
- A self-contained Codex plugin bundling the shared Skill, a stdio MCP Server, and the verified Pi SDK runtime, discovering workspaces through the Codex host's own
codex/sandbox-state-metacapability (no hooks or global SDK resolution). - Deterministic automated tests that never spend model quota.
Architecture
Codex or Pi Main Agent
|
| semantic tools / shared Skill
v
Expert Council Core
- resource and model normalization
- billing and capability profiles
- deterministic routing
- roles and team sizing
- retry and escalation
- telemetry aggregation
|
v
Pi Runtime
- callable model discovery
- hard tool allowlists
- installed-Skill filtering
- bounded expert sessions
- workspace isolation
/ | \
CLI Pi Package MCP Server
|
Codex plugin
TypeScript project references enforce that dependencies flow only in these directions:
core <- pi-runtime <- cli
<- mcp-server <- codex-integration
<- pi-package
Core does not import Pi, Codex, MCP transport, filesystem, shell, or process APIs. The CLI, MCP Server, and host distributions all use the same service and routing logic.
Quick start
Requirements:
- Node.js 22.19 or later.
- npm 11 or a compatible version.
- Pi installed and configured with at least one usable model.
- When writable experts need worktree isolation, the Git repository must have at least one commit.
Install the Pi Package
Install from npm (recommended):
pi install npm:@expert-council/pi-package
pi list
pi --verbose
pi list should show npm:@expert-council/pi-package and its resolved directory; a newly started verbose Pi session should load dist/extension.js, the expert-council Skill, and the twelve semantic tools. To upgrade later:
pi update npm:@expert-council/pi-package
Install the Codex plugin (optional)
To run Codex as the Main Agent, install the pinned prebuilt plugin directly from its Git marketplace; no repository clone or local build is required:
codex plugin marketplace add Labiey/expert-council-router --ref v0.8.3 --json
codex plugin add expert-council@expert-council-router --json
Fully restart Codex Desktop after installation. The plugin ships its own tested Pi SDK runtime, so the globally installed pi package is not used at runtime; Pi still must have been installed and configured once — or provider credentials placed manually under ~/.pi — so the account and model catalog are available. See Codex plugin for Windows CLI discovery, verification, upgrade, and removal instructions.
Build from source (development)
npm install
npm run build
npm test
Discover models without calling any of them:
node packages/cli/dist/bin.js models --json
node packages/cli/dist/bin.js inspect --json
Assemble an expert team without executing any expert:
node packages/cli/dist/bin.js build "fix the device hot-swap race condition" --max-experts 4 --json
Delegate only when you are sure actual Pi models should be called:
node packages/cli/dist/bin.js delegate architecture-oracle "analyze the concurrent invocation path" --workspace /path/to/repo --json
Zero configuration uses conservative capability defaults, marks unverifiable billing types as unknown, and refuses non-isolated writes. It never guesses that an API is free and never infers model quality from a model name.
Model discovery
PiExpertRuntime calls Pi's ModelRuntime.getAvailable() instead of using a hardcoded catalog. A model that merely exists in a registry or user profile is not routed to; only models Pi reports as currently callable are used.
The runtime normalizes:
- provider and model ID;
- display name;
- reasoning support and the reasoning-level map exposed by Pi;
- context and maximum output windows;
- input modalities;
- published API price fields;
- safe compatibility metadata.
The runtime resolves a locally installed compatible Pi SDK first, then an explicit PI_CODING_AGENT_MODULE directory, then a compatible global npm Pi installation. If all fail, it returns an actionable diagnostic instead of fabricating a model list.
Pi builds this inventory once per session, and provider catalogs can keep stale model names, so listAvailableModels() may include a model the upstream can no longer serve. Routing treats such a model as callable until a real attempt fails; this is why failures with dead-model evidence mark the model unavailable in the persisted model assessment (see the routing section below). Markers expire after 24 hours, and a recovered model is retried automatically.
Configuration
Specify a configuration file through the EXPERT_COUNCIL_CONFIG environment variable or the CLI's --config PATH flag. Start from config/examples/balanced.example.json.
Profile precedence:
built-in conservative defaults
< user configuration or optional presets
< current-task runtime overrides
Objective runtime metadata is merged separately. Local outcome data influences routing only after at least three samples accumulate, and the adjustment is bounded by routing.localLearningMaxAdjustment. Explicit user configuration always wins.
Billing policy
Supported billing types:
subscription metered quota free unknown
Marginal cost is expressed as a single numeric costMultiplier because published token prices cannot express subscription plans, fixed quotas, local inference, or promotional credits. The Pi Runtime adapter classifies runtime-reported subscription access or named Token Plan catalogs as subscription; otherwise a provider whose Pi model catalog exposes non-zero prices is metered, and providers without reliable evidence stay unknown. expert_inspect returns the inference source, and explicit user configuration always has the highest priority. Models within the same metered provider are still compared on specific prices through routing.apiPriceWeight (default 0.35); an all-zero price table is treated as "not provided", never guessed to be free.
Per-model billing entries. Subscription token plans carry periodic quotas (often weekly) with per-model burn rates, so one provider-level weight cannot express the real marginal cost. Add provider/id keys to override the provider default for specific models — routing checks the explicit billingProfile first, then the model-level entry, then the provider-level entry. The same works in model-assessment.json billing and in user configuration:
{
"billing": {
"providers": {
"subscription-provider": {
"billingType": "subscription",
"costMultiplier": 0.1
},
"subscription-provider/qwen3.8-max": {
"billingType": "subscription",
"costMultiplier": 2.0
},
"scarce-provider": {
"billingType": "quota",
"costMultiplier": 5.0
}
}
}
}
Billing multipliers
costMultiplier is a relative token-consumption weight used for cost scoring and provider cap accounting. It defaults to 1.0 when omitted and must be within 0.01–100. Lower is more cost-efficient for cost-weighted roles; the cost-efficiency score is 10 / (1 + costMultiplier) before the billing-type bonus. Reference values: metered flash-class models ≈1.0, metered flagship full-size ≈5.0, token-plan flash-class ≈0.1, token-plan flagship ≈2.0.
The removed tier vocabulary is still accepted and mapped deterministically so existing configuration keeps working:
Legacy marginalCostClass |
costMultiplier |
|---|---|
very-low |
0.1 |
low |
0.5 |
normal |
1.0 |
high |
3.0 |
scarce |
5.0 |
The legacy usagePreference field is ignored and dropped; it no longer affects routing.
Operator config (council-config.json)
council-config.json is the operator configuration file. It is optional and discovered by default in the shared data directory (the same folder as model-assessment.json and route-policy.json) — no environment variable is required. Resolution order:
configPathoption orEXPERT_COUNCIL_CONFIGenvironment variable — highest precedence, and the file must exist;<data directory>/council-config.json— read when present, silently skipped when absent;- otherwise everything runs on built-in defaults (
workspaceProvisioning.mode = "none").
The file accepts the full operator schema: security, billing, profiles, and routing. Typical example:
{
"security": {
"workspaceProvisioning": {
"mode": "auto",
"timeoutMs": 600000,
"maxConcurrent": 1,
"scrubEnv": true,
"removalTimeoutMs": 300000,
"verifyCommand": ["npm", "run", "typecheck"]
},
"worktreeRetentionMs": 86400000
},
"routing": {
"maxExperts": 4
}
}
Field notes:
security.workspaceProvisioning.mode—"none"(default, no installs),"auto"(lockfile-detected install per ecosystem), or"custom"(runscommandverbatim).security.workspaceProvisioning.verifyCommand— a single command as a flat argv array, run after a provisioned mutation expert finishes instead of the default typecheck-then-test pair.security.workspaceProvisioning.scrubEnv— whentrue(default), provisioning and verification children receive an allowlisted environment only;~/.npmrcregistry tokens can still reach the child (documented residual).security.worktreeRetentionMs— how long provisioned worktrees are retained for review before the retention prune (default 24h).billing/profiles/routing— the same schemas asmodel-assessment.jsonbilling entries, model capability profiles, and role weights.
Main-Agent editing contract: expert_inspect returns the file location and the effective provisioning mode under operatorConfig. When the user asks to change provisioning behavior, the Main Agent edits the file directly and tells the user a host session restart is required for the change to apply. A malformed JSON or invalid enum is a hard error at startup (an operator typo never silently disables a security setting).
Worktree provisioning
Mutation worktrees start from committed HEAD and therefore contain no untracked local artifacts. The runtime can provision them from the repository's own committed lockfile before the expert runs; it is opt-in and inert by default:
{
"security": {
"workspaceProvisioning": {
"mode": "auto",
"timeoutMs": 600000,
"maxConcurrent": 1,
"scrubEnv": true,
"removalTimeoutMs": 300000
}
}
}
This configuration lives in council-config.json inside the shared data directory (the same folder as model-assessment.json). The file is optional and discovered by default — no environment variable is required: if it exists it is read, if not everything stays at the defaults. expert_inspect returns its location under operatorConfig so the Main Agent can edit it on the user's behalf (changes apply after the host session restarts). An explicit configPath option or EXPERT_COUNCIL_CONFIG environment variable still takes precedence and must point at an existing file.
modeisnone(default, never provision),auto(detect the committed lockfile and install), orcustom(runcommandverbatim as an argv array).autorunspnpm install --frozen-lockfile --prefer-offline,npm ci --prefer-offline --no-audit --no-fund, orbun install --frozen-lockfile, always appending--ignore-scripts;uv.lock,requirements.txt,Cargo.toml, andgo.modare reported as skipped because no supported provisioning exists for those ecosystems.- Only mutation worktrees are provisioned; read-only roles run in the main workspace and are never provisioned.
- The child environment is allowlist-scrubbed (
PATH,HOME,USERPROFILE,APPDATA,LOCALAPPDATA,TEMP,TMP,SYSTEMROOT,SYSTEMDRIVE,COMSPEC,PROGRAMFILES,PROGRAMDATA,GIT_*,npm_config_registry,npm_config_cache); API tokens and cloud credentials are not forwarded. - When provisioning is
ready, the runtime runsverifyCommandif configured, otherwisenpm run typecheckthennpm test. A failing gate downgrades a successful expert result topartialwithfailureType: "test_failure".
Provider limits & concurrency
route-policy.json can carry an optional providers map next to the model allow/deny policy — see Route policy file for the allow/deny syntax, which this section does not repeat:
{
"version": 1,
"providers": {
"qwen-token-plan-cn": { "maxConcurrency": 2, "dailyTokenCap": 5000000, "weeklyTokenCap": 40000000 },
"zai": { "maxConcurrency": 0 }
}
}
maxConcurrency(integer ≥ 0) caps simultaneous running executions for the provider;0or omitted means unlimited.dailyTokenCapandweeklyTokenCap(integers > 0) cap weighted token consumption per UTC calendar day and per ISO week (Monday start, UTC). Defaults are20,000,000daily and150,000,000weekly.- Accounting is weighted: each completed attempt consumes
(inputTokens + outputTokens) × costMultiplierfor the attempt's model; cache read/write tokens are not counted. - Breaching a cap marks every model of the provider in the persisted assessment until the next UTC reset boundary — next UTC midnight for a daily breach, next Monday
00:00UTC for a weekly breach — so routing stops burning attempts on the depleted provider and retries it automatically after the reset. - In-flight counting uses running executions assigned to the provider; a provider at
maxConcurrencyis excluded from new candidates until one finishes. - When no usage ledger is wired, caps and concurrency limits are disabled and Core performs no I/O.
expert_inspect reports per-provider providerLimits with maxConcurrency, both caps, weighted usedToday/usedWeek, remainingDaily/remainingWeekly, and inFlight.
config/examples/ provides:
balanced.example.json: conservative defaults for zero configuration.subscription-heavy.example.json: prioritize subscription capacity, protect scarce quota.metered-quality.example.json: separate economical from high-quality metered APIs.qwen-glm.example.json: explicitly labeled as a hypothetical user preference, not an objective benchmark.
Capability profiles
Models can receive user scores from 0 to 10 on these dimensions:
reasoning planning architecture coding debugging review longContext
toolReliability bashReliability autonomousExecution speed
Model keys must use the exact provider/model returned by models --json. Newly discovered models without local profiles receive conservative defaults; stale configuration for unavailable models only produces warnings and never crashes routing.
Main Agent capability audit and first-council preference
Users do not need to maintain per-model usage priorities. After the Main Agent decides a task deserves a council, if this is the first council in a new conversation and the user has not expressed a preference, it should ask once:
price first (economy)
balance price, time, and success rate (balanced)
speed first (speed)
The Pi Package records the choice in the current Pi session's hidden extension state; later councils in that conversation reuse it automatically unless the user changes it. economy boosts cost weights, speed boosts the audited speed dimension, and balanced uses normal role weights. The legacy quality API value remains compatible but is not offered as a default prompt option.
Model capabilities are audited by the Main Agent rather than hand-ranked by users. expert_inspect returns a mandatory assessment gate: if there is no audit yet, the audit is older than 30 days, the callable model set changed, or the user explicitly requests a re-audit, the Main Agent must research every callable model listed by the gate using the host's own web tools; expert_build will not assemble a council until then. The Main Agent submits a complete modelAssessment whose ISO timestamp must come from the host's real clock, whose sources are consolidated into 1–12 URLs, and which contains 0–10 capability dimensions plus verifiable provider access/billing evidence. Future-dated timestamps are reported separately and can be corrected without repeating the web research. If the gate reports the saved assessment is still current, the host should omit modelAssessment when calling expert_build; incomplete, stale, or future-dated replacement snapshots cannot displace a current one. The assessment is stored once per user data directory and reused across conversations and workspaces as long as the callable model set remains compatible; ordinary plan and execution state saves never overwrite the global assessment from a stale in-memory snapshot, and only an explicitly submitted assessment that passes the gate replaces the stored scores. Explicit user billing configuration always outranks the Main Agent's judgment; unverifiable billing stays unknown.
Cross-check rather than trust a single leaderboard: the Artificial Analysis Data API provides coding, agentic, price, throughput, and latency data; LiveBench covers coding and agentic coding; Arena reflects human preference; provider documentation verifies versions, context, tools, and access method. OpenRouter Rankings mostly reflect real-world usage and are only an adoption signal, not proof of model quality. When the gate demands an audit and the host has no web tools, the Main Agent must state the limitation and stop instead of silently using unaudited defaults; Expert Council never auto-installs plugins, Skills, or third-party executables.
Role weights
Every semantic role has normalized default weights. Implementation Worker emphasizes tool reliability, coding, autonomous execution, and shell reliability; Architecture Oracle emphasizes architecture, planning, long context, and review.
{
"routing": {
"roleWeights": {
"implementation-worker": {
"toolReliability": 0.4,
"coding": 0.3,
"costEfficiency": 0.1
}
}
}
}
Weights are re-normalized automatically. costPolicy: economy boosts the cost factor, speed boosts speed and reduces cost, and the legacy quality reduces cost; none of them bypass safety or compatibility hard constraints.
Semantic roles and team sizing
Roles are defined by task semantics and never bound to a model name:
| Role | Default permissions | Purpose |
|---|---|---|
| Planner | read-only | Task decomposition, dependencies, and risks |
| Scout | read-only | Repository exploration and context compression |
| Architecture Oracle | read-only | Hard cross-file reasoning and second opinions |
| Implementation Worker | writable | Bounded code changes and focused testing |
| Debugger | writable | Reproduce, isolate, fix, and verify |
| Reviewer | read-only | Regressions, edge cases, and design review |
| Verifier | read-only | Check reported tests, diffs, and acceptance criteria |
Tiny tasks use one Worker; normal tasks use Worker plus Verifier; complex features use Planner, Worker, Reviewer, and Verifier; complex debugging uses Scout, Debugger, Oracle, and Verifier. maxExperts caps team size, and the Main Agent is never duplicated as a redundant lead expert.
Read-only experts run in the main workspace and therefore never report filesChanged: they have no mutation tool, so anything git-dirty there belongs to the Main Agent and attributing it to the expert would fabricate authorship. expert_cleanup on such a run answers not-required because no worktree exists. If you need changes made, dispatch an implementation-worker or debugger and review its isolated worktree instead.
Task classification and all scoring math are deterministic. Hosts can inspect the selected model, alternatives, scores, and concise reasons before delegating. Council assembly also applies configurable diversity penalties; Reviewers prefer a different provider and inferred model family from earlier members when economical, but role fit and hard constraints still dominate.
Routing pipeline
discover currently callable candidate models
-> apply hard constraints
-> merge capability and profile layers
-> score role fit and effective cost
-> conservatively apply local outcome adjustments
-> rank with stable tie-breaking
-> return selection, alternatives, and reasons
Hard constraints reject unavailable or disabled models, incompatible roles, insufficient tool reliability, insufficient context, mutation without runtime support, escalation-only resources on routine tasks, and models carrying an active runtime availability marker.
Pi caches its model inventory per session and provider catalogs can keep stale model names, so a council could otherwise be assembled around a model the upstream can no longer serve. When a delegated attempt fails as provider_error with dead-model evidence (for example model_not_found, unknown or discontinued models, or the runtime's own availability pre-check), the service records a modelAvailability marker into the persisted shared model assessment (EXPERT_COUNCIL_DATA_DIR, i.e. %LOCALAPPDATA%/ExpertCouncil/model-assessment.json on Windows) through an atomic read-modify-write that never reverts a newer snapshot written by another running Pi/Codex instance. The affected expert_result names the model in executionMetadata.unavailableModels and risks, expert_inspect warns about active markers, and later expert_build, delegation, and escalation hard-reject marked models. Markers are conservative local evidence: they expire after 24 hours, survive freshly submitted audits, and an explicit modelOverrides["provider/model"].overrideUnavailableMarker: true re-enables a model. Transient provider failures such as rate limits or authentication errors never create markers. When no saved assessment exists yet, the marker cannot be persisted, but the failure is still reported to the Main Agent and recorded in local telemetry.
Reasoning levels are optional and model-specific. A configured role preference applies only when Pi exposes it for the selected model; otherwise Pi keeps or clamps to the model's supported default.
Skills and least privilege
The canonical platform-neutral host guidance is shared/skills/expert-council/SKILL.md. The build composes it with the small host overlays under shared/skills/expert-council/hosts/ into separate Pi and Codex SKILL.md artifacts without duplicating the shared workflow. The Pi artifact only describes completion steer/followUp behavior and never exposes expert_wait; only the Codex artifact describes the bounded expert_wait flow. Shared role prompts live in packages/core/src/roles/prompts/ and are copied as package assets rather than rewritten per host.
Read-only roles never receive edit, write, bash, or powershell, even if a caller tries to include them. Pi sessions use a real tools allowlist, which is stronger than prompt-only guidance. Until a dedicated non-mutating command runner exists, tasks that need shell-driven testing belong to writable roles in isolated worktrees.
Only installed, enabled Pi Skills required by the role are activated. User-scope Skills are trusted by default; project and temporary Skills are excluded unless their exact name is listed in security.trustedSkills. Every expert resource loader disables extensions, prompt templates, themes, and project context files, and Pi SDK versions that cannot enforce these policies are rejected. Expert Council never downloads or installs Skills or executable extensions.
Expert prompts require: read before editing, verify paths, prefer targeted edits, diagnose failures before changing approach, use finite non-interactive commands, inspect results, never delegate recursively, and return compact JSON instead of private reasoning.
Retry and escalation
Failure types are normalized to:
tool_call_error reasoning_failure test_failure timeout provider_error
missing_context permission_error unknown
By default, the first correctable tool, context, or test failure receives at most one retry with a changed approach. Repeated relevant failures or provider errors switch to the next eligible, untried model. Attempt and escalation budgets are separately capped; with no candidates or exhausted budget, an unresolved state returns to the Main Agent.
There are no infinite loops, and the same failed action is never repeated blindly by policy.
Structured results and context efficiency
Expert results contain status, role, model, summary, changed files, tests, findings, risks, recommended next action, failure type, the approximate usage Pi exposes, and bounded execution metadata. The system prefers the expert's own structured failure type and deterministically classifies test, provider, tool, and context failures; unparseable non-JSON output is marked reasoning_failure. Private reasoning is never requested or stored, and whole source files are never copied back into the Main Agent's context.
Workspace safety
The system never assumes that Codex's own sandbox contains the external Pi process. The Pi Runtime uses an independent boundary:
- Canonicalize the requested workspace path.
- Require the path to fall under allowed roots.
- Create a detached worktree from the repository's current
HEADinside a current-user-private directory under the system temp directory. - Give the Worker write tools inside that worktree.
- Return the worktree path and changed-file list.
- The Codex or Pi Main Agent inspects, integrates, and finally accepts.
- After accepting or rejecting, call
expert_cleanup. One call removes every worktree created for that execution ID through retries or escalations and returns all removed paths. Unclaimed worktrees are pruned automatically aftersecurity.worktreeRetentionMs(24 hours by default), and Git metadata is pruned alongside.
Non-Git workspaces refuse writes by default. If in-place mutation is truly required, it must be explicitly enabled:
{
"security": {
"workspaceStrategy": "bounded-in-place",
"allowInPlaceMutations": true,
"allowedWorkspaceRoots": ["/absolute/path/to/project"]
}
}
Read SECURITY.md before enabling.
Telemetry and local learning
The default user data root is %LOCALAPPDATA%\ExpertCouncil on Windows, $XDG_STATE_HOME/expert-council or ~/.local/state/expert-council on Linux, and ~/Library/Application Support/ExpertCouncil on macOS. The shared telemetry.jsonl stores opaque execution outcomes so real reliability can be reused across conversations and workspaces; feedback for the same execution overwrites earlier samples and never double-counts. The shared model-assessment.json stores the latest explicit capability and billing scores plus runtime-learned model availability markers. EXPERT_COUNCIL_DATA_DIR, EXPERT_COUNCIL_TELEMETRY, EXPERT_COUNCIL_MODEL_ASSESSMENT, and EXPERT_COUNCIL_STATE override locations.
It never records prompts, source content, credentials, API keys, secrets, or reasoning. Aggregate metrics include per-role success rate, first-pass rate, tool error rate, retry rate, verification pass rate, and average attempts. Expert Council has no remote analytics endpoint.
Plans, execution state, and completed structured results remain workspace-scoped in workspaces/<workspace hash>/state.json. They survive process restarts; a task still running at restart is closed as an explicit interrupted failure. Legacy in-project .expert-council and %USERPROFILE%\.expert-council directories are not deleted automatically.
CLI
The CLI uses exactly the same Core and Pi Runtime as MCP and the Pi Package:
expert-council models
expert-council inspect
expert-council build <task>
expert-council delegate <role> <task>
expert-council feedback <execution-id> --verification passed|failed
expert-council cleanup <execution-id>
expert-council status
Common flags:
--json: machine-readable output.--cwd: project workspace.--config: user policy file.--telemetry: custom local telemetry path.--state: custom path for persisted plans, executions, and results.--timeout-ms: expert execution timeout.
MCP Server
The MCP surface is deliberately limited to thirteen semantic tools:
expert_inspectexpert_buildexpert_delegateexpert_waitexpert_resultexpert_abortexpert_feedbackexpert_cleanupexpert_escalateexpert_statusexpert_availability_resetexpert_verifyexpert_respond
expert_respond answers a running expert's open pendingInteraction (a request_decision choice or a request_tool grant) so its session continues; a headless host discovers open interactions by polling expert_status with view: "running" or expert_result with includeProgress.
expert_inspect and expert_build return compact host-facing views by default. Pass detail: "full" only when exact model metadata, alternatives, scores, tools, or Skills are genuinely required.
Council compositions
Saved council rosters live in council-compositions.json next to route-policy.json in the shared state directory — no dedicated tool. The default location is %LOCALAPPDATA%\ExpertCouncil\council-compositions.json on Windows, ~/Library/Application Support/ExpertCouncil/council-compositions.json on macOS, and $XDG_STATE_HOME/expert-council/council-compositions.json (or ~/.local/state/expert-council/council-compositions.json) on Linux; override it with EXPERT_COUNCIL_COMPOSITIONS.
{
"compositions": [
{
"name": "daily-cheap",
"roles": {
"scout": ["qwen-token-plan-cn/deepseek-v4-flash"],
"implementation-worker": ["zai/glm-5.3-flash", "deepseek/deepseek-v4-flash"]
}
}
],
"sessions": { "01a066b2-a166-7e67-983d-2bbec848c223": "daily-cheap" }
}
compositionsis an ordered list (menu priority) of at most 32 unique names (≤80 characters).rolesis keyed by the seven semantic role values; each value is a list ofprovider/idmodel keys (≤16 per role). A missing or empty role auto-routes that role.sessionsmaps a session key (host conversation id) to a composition name. Entries expire after 30 days; bindings written by the tool carry anupdatedAtstamp, while hand-written string entries are kept.- On the first build with neither
compositionnorcostPolicy,expert_buildreturns acompositionMenu: up to three saved rosters plus anautooption. Pass a saved name back ascomposition; theautooption is the cost-policy flow (economy/balanced/speed). - A successful explicit
compositionbuild binds that name to the session; a successfulcostPolicybuild unbinds it. A menu response binds nothing. - A role's candidate pool is the composition list intersected with route-policy filtering and provider cap/concurrency exclusions. Route-policy
denyalways wins over a pool, and a fully excluded role is reported as unstaffable. expert_delegateaccepts an optionalmodel(provider/id) per assignment and inassignments[]. Pin different pool models on same-role assignments to dispatch several experts concurrently; a pin outside the role's pool, the discovered inventory, or the route policy fails with a structured error.
Route policy file
Model allow/deny lists live in route-policy.json next to model-assessment.json in the shared state directory — no dedicated tool. The file holds a system entry that every session obeys and per-session entries under sessions keyed by the host conversation (Pi session IDs survive resume; MCP stdio conversations use a stable "default" key). Sessions may only narrow the system policy: deny lists union, allow lists intersect, and deny always wins. Entries are provider/id or a bare provider for a whole provider. expert_inspect returns this conversation's sessionKey, the effective policy, and the file's sourcePath so the host (or you) can edit it directly; changes apply on the next expert call, stale session entries are pruned after 30 days, and a corrupt file is ignored with a warning.
{
"version": 1,
"system": { "deny": ["bailian"] },
"sessions": {
"4f0c…": { "allow": ["qwen-token-plan-cn/qwen3.8-max"], "updatedAt": "2026-09-07T02:00:00+08:00" }
}
}
expert_delegate starts background work and immediately returns execution IDs; the original single-assignment parameters remain compatible. timeoutMs is required for every assignment (1000–3600000 ms) — omitting it is an error; set it from expected task difficulty. A timed-out attempt scales the retry budget by 1.5× automatically, and experts stop early with a structured missing_context/permission_error result when a task is impossible with their assigned tools. When two or more independent tasks exist, dispatch the entire batch before continuing other Main Agent work:
{
"assignments": [
{ "role": "scout", "task": "locate relevant files", "taskDescription": "repo mapping", "timeoutMs": 300000 },
{ "role": "reviewer", "task": "review boundary design", "taskDescription": "boundary review", "timeoutMs": 600000 }
]
}
assignments must be an actual JSON array, never a string containing JSON. The native Pi adapter offers bounded compatibility parsing for models that occasionally stringify the array, but normal callers should emit real arrays.
The optional taskDescription is a short host-facing label for identifying the task; it is not part of the expert's actual task content. After dispatching, the Main Agent should continue all independently completable work; when nothing useful remains, call expert_wait once with up to eight execution IDs, usually mode: "all" (use "any" when any early result unblocks progress), and a timeoutMs sized to the estimated remaining difficulty. Waiting is driven by execution-promise completion events rather than polling; blocking the current MCP call is expected behavior, and no main-model tokens are consumed while waiting.
{
"executionIds": ["exec_a", "exec_b"],
"mode": "all",
"timeoutMs": 900000
}
expert_wait returns only completion state and task IDs; fetch the formal feedback with expert_result and call expert_feedback after the Main Agent's acceptance. expert_wait.timeoutMs bounds only that wait and never extends each expert's own execution deadline. Every potentially blocking Expert Council, Bash, PowerShell, or other MCP call must still carry an explicit finite timeout sized to the operation; remaining synchronous Expert Council operations are protected by an independent 30-second in-server cap. expert_status returns a bounded per-attempt history. The native Pi Package uses proactive completion notifications and therefore does not expose expert_wait.
Start the stdio server directly:
node packages/mcp-server/dist/bin.js
Supported environment variables:
EXPERT_COUNCIL_WORKSPACE: default allowed workspace.EXPERT_COUNCIL_CONFIG: user JSON configuration.EXPERT_COUNCIL_TELEMETRY: local telemetry JSONL path.EXPERT_COUNCIL_STATE: persisted plans, executions, and results state path.EXPERT_COUNCIL_WORKTREES: parent directory for private expert mutation worktrees (the per-user private subdirectory is always kept); useful for short-path volumes or a faster disk.EXPERT_COUNCIL_COMPOSITIONS: saved council compositions path.EXPERT_COUNCIL_MCP_TIMEOUT_MS: bounded timeout for synchronous MCP operations, 30000 ms by default.PI_CODING_AGENT_MODULE: explicit Pi package directory when automatic resolution fails.
Environment overrides and CLI path flags are trusted operator inputs. In particular, PI_CODING_AGENT_MODULE loads executable code, while config, workspace, telemetry, and state paths select local files; never accept them from an untrusted repository, task text, or model output.
Native Pi Package
For daily use, install from npm (see Quick start); this section covers source development and local candidate validation.
Build and install the local candidate from the repository root. Even on Windows, use forward slashes whenever a command may pass through Pi's Bash-compatible shell; an unquoted .\packages\pi-package loses its backslashes before reaching Pi.
npm run build
pi install "./packages/pi-package"
pi list
pi --verbose
pi list should show the configured source and its resolved absolute package directory. A newly started verbose Pi session should list dist/extension.js, the expert-council Skill, and the twelve semantic tools without expert_wait. Running Pi processes do not hot-reload a rebuilt or removed package.
Or load it for one run without persisting:
pi --verbose -e "./packages/pi-package"
A zero-cost load check simply asks Pi to call expert_inspect. A real orchestration check should, in a new conversation, build one read-only council, batch two independent read-only assignments at once, confirm that expert_delegate immediately returns execution IDs, then accept each completion with expert_result and expert_feedback. When testing a writable expert, also confirm that one expert_cleanup call reports the worktree for the execution in workspaces (retries reuse and reset it instead of creating another) and that git worktree list afterwards contains only the main checkout.
Before removing a persistent installation, exit every Pi process that loaded the package, then run from the same repository root:
pi remove "./packages/pi-package"
pi list
If the working directory changed, pass the resolved absolute path instead. PowerShell example:
$ecPiPackage = (Resolve-Path "./packages/pi-package").Path
pi remove "$ecPiPackage"
pi list
If removal runs through Pi's Bash-compatible shell, use the forward-slash absolute path printed by pi list, for example pi remove "C:/path/to/ExpertCouncil/packages/pi-package". Do not copy the indented relative source shown by pi list unless the command is resolved from the same settings-directory context.
Pi loads dist/extension.js and the synchronized expert-council Skill through the package manifest's pi.extensions and pi.skills. The extension registers the twelve semantic tools; because native Pi already provides completion steer/followUp, the MCP-only expert_wait is omitted. It contains no second routing implementation.
Pi delegation is non-blocking; a single call can start up to eight independent background assignments before returning. When an expert finishes, the extension sends compact JSON containing the completed executionId and, only when supplied at dispatch, the taskDescription; it never carries feedback directly. Notifications use steer while the Main Agent is working and a triggerTurn followUp when it is idle. The Main Agent then calls expert_result for the structured feedback. Dispatch the entire ready batch before ending the turn, and avoid polling or silently waiting afterwards.
Codex plugin
The Codex plugin turns Codex into the Main Agent: it bundles the shared expert-council Skill and a stdio MCP Server, while experts execute through Pi. It ships no hooks — the server uses MCP client roots when available, otherwise derives the task workspace from Codex's host-owned codex/sandbox-state-meta capability, then falls back to the EXPERT_COUNCIL_WORKSPACE override, and refuses to use the plugin installation directory as a workspace.
The built plugin root is:
packages/codex-integration/plugin/expert-council/
.codex-plugin/plugin.json
.mcp.json
skills/expert-council/SKILL.md
dist/server.mjs
dist/roles/*.md
THIRD_PARTY_NOTICES.md
Installation
Release v0.5.2 includes both the prebuilt MCP server and its tested Pi SDK runtime, so Codex can install the plugin directly from the repository as a pinned Git marketplace. Node.js 22.19 or newer and an already configured Pi account/model catalog are required; cloning this repository, running npm install, or resolving a global @earendil-works/pi-coding-agent module is not required.
codex plugin marketplace add Labiey/expert-council-router --ref v0.8.3 --json
codex plugin marketplace list --json
codex plugin list --marketplace expert-council-router --available --json
codex plugin add expert-council@expert-council-router --json
codex plugin list --json
marketplace add is needed only once for this release. If the marketplace name is already registered from an older or local source, remove that source first or follow the upgrade procedure below. plugin list --json should show expert-council as installed from expert-council-router.
Codex Desktop on Windows includes the CLI, but it may not be on PATH. In PowerShell, resolve the running Desktop binary and use it for the same remote installation:
$ecCodex = (Get-Command codex.exe -ErrorAction SilentlyContinue).Source
if (-not $ecCodex) {
$ecCodex = Get-Process codex -ErrorAction SilentlyContinue |
Where-Object Path |
Select-Object -First 1 -ExpandProperty Path
}
if (-not $ecCodex) {
$ecCodex = Get-ChildItem (Join-Path $env:LOCALAPPDATA "OpenAI/Codex/bin") `
-Filter codex.exe -File -Recurse -ErrorAction SilentlyContinue |
Sort-Object LastWriteTime -Descending |
Select-Object -First 1 -ExpandProperty FullName
}
if (-not $ecCodex) { throw "Codex Desktop CLI was not found." }
& $ecCodex plugin marketplace add Labiey/expert-council-router --ref v0.8.3 --json
& $ecCodex plugin marketplace list --json
& $ecCodex plugin list --marketplace expert-council-router --available --json
& $ecCodex plugin add "expert-council@expert-council-router" --json
& $ecCodex plugin list --json
Fully quit Codex Desktop, wait for its backend process to exit, reopen it, and start a new task. Merely opening another task is not a reliable MCP reload boundary in every Desktop build.
For local plugin development, clone the repository, run npm ci && npm run build, and pass its absolute root to codex plugin marketplace add instead of the GitHub repository name. The pinned remote release is recommended for normal use.
A correct load exposes the expert-council Skill and all thirteen expert_* MCP tools. expert_inspect must return a real inventory rather than a "No compatible Pi SDK is installed" diagnostic. If the Skill is present but the tools are absent, or inspection reports that diagnostic, verify that the marketplace is pinned to v0.8.3 or newer, then restart or reinstall the plugin instead of launching dist/server.mjs manually or sending hand-written JSON-RPC.
To verify the installed workflow, use a new Codex task and ask:
Use Expert Council to inspect the currently available Pi models, providers, billing classification, and model-assessment status. Return only a compact summary; do not build a council or delegate experts.
The task should invoke expert_inspect. It should not ask for hook trust, manually launch the MCP server, or treat the plugin cache as the project workspace.
Upgrade
A marketplace pinned with --ref intentionally stays on that release. To upgrade, remove the installed plugin and old marketplace registration, then add the new tag and reinstall:
codex plugin remove expert-council@expert-council-router --json
codex plugin marketplace remove expert-council-router --json
codex plugin marketplace add Labiey/expert-council-router --ref vX.Y.Z --json
codex plugin add expert-council@expert-council-router --json
Replace vX.Y.Z with the intended release. Users who deliberately track the default branch can omit --ref and later run codex plugin marketplace upgrade expert-council-router --json, but pinned tags are safer for normal use. After reinstalling, fully restart Codex Desktop and test in a new task. Avoid installing multiple copies that all declare the expert_council MCP server.
Behavior highlights
- The bundled
.mcp.jsonraises the host tool-call ceiling to 3660 seconds so a single boundedexpert_waitcan block until completion; the Skill still requires explicit per-operation deadlines rather than treating that ceiling as a default budget. - On the first build of a conversation with neither
compositionnorcostPolicy,expert_buildreturns a composition menu (up to three saved rosters plus anautooption); choosingautoestablishes the cost policy (economy, balanced, or speed) for the session. Until a menu choice is made,expert_buildresponses keep asking. - Writable experts mutate inside a detached Git worktree under the trusted workspace; changes come back for Main Agent review and are never auto-merged.
Removal
Remove the plugin and its marketplace registration:
codex plugin remove expert-council@expert-council-router --json
codex plugin marketplace remove expert-council-router --json
codex plugin list --json
codex plugin marketplace list --json
If PowerShell cannot find codex, locate the Codex Desktop CLI first (while Codex Desktop is running its path can be taken from the process; otherwise fall back to the install directory):
$ecCodex = Get-Process codex -ErrorAction SilentlyContinue |
Where-Object Path |
Select-Object -First 1 -ExpandProperty Path
if (-not $ecCodex) {
$ecCodex = Get-ChildItem (Join-Path $env:LOCALAPPDATA "OpenAI\Codex") `
-Filter codex.exe -File -Recurse -ErrorAction SilentlyContinue |
Sort-Object LastWriteTime -Descending |
Select-Object -First 1 -ExpandProperty FullName
}
if (-not $ecCodex) {
throw "Codex Desktop's codex.exe was not found."
}
Then uninstall through the located CLI:
& $ecCodex plugin remove "expert-council@expert-council-router" --json
& $ecCodex plugin marketplace remove "expert-council-router" --json
& $ecCodex plugin list --json
& $ecCodex plugin marketplace list --json
If the plugin still shows after reopening Codex Desktop, close Codex and safely remove the stale Expert Council caches (only %USERPROFILE%\.codex\plugins\cache\expert-council-* is touched):
$ecCacheRoot = [IO.Path]::GetFullPath(
(Join-Path $env:USERPROFILE ".codex\plugins\cache")
)
$ecCachePrefix = $ecCacheRoot.TrimEnd("\") + "\"
$ecTargets = Get-ChildItem -LiteralPath $ecCacheRoot `
-Directory -ErrorAction SilentlyContinue |
Where-Object Name -Like "expert-council-*"
foreach ($ecTarget in $ecTargets) {
$ecResolved = [IO.Path]::GetFullPath($ecTarget.FullName)
if (
$ecResolved.StartsWith(
$ecCachePrefix,
[StringComparison]::OrdinalIgnoreCase
) -and
(Split-Path $ecResolved -Leaf) -like "expert-council-*"
) {
Write-Host "Removing cache: $ecResolved"
Remove-Item -LiteralPath $ecResolved -Recurse -Force
}
}
Fully quit Codex Desktop before starting new tasks.
Testing
npm test
npm run typecheck
npm run build
npm run pack:check
npm run validate
npm run validate builds first so a fresh clone has the workspace package entries generated before testing. Tests cover model normalization, published prices versus real policy billing, worker reliability, oracle scoring, reviewer diversity, hard constraints, unknown and missing models, team sizing, retries with per-attempt diagnostics, structured failure classification, escalation, retry limits, role permissions, compact host output, configuration validation, telemetry privacy/feedback/usage aggregation, Core host independence, mocked Pi discovery and execution, CLI JSON, MCP schemas, real Pi 0.84.4 extension loading/wrapping and async batch notifications, Pi extension registration, and real Git worktree isolation.
Ordinary tests only use the mock runtime and never call paid models. A real read-only Pi execution requires both an explicit model and an explicit cost acknowledgement:
$env:EXPERT_COUNCIL_LIVE_MODEL = "provider/model"
$env:EXPERT_COUNCIL_LIVE_CONFIRM = "YES"
npm run smoke:live:pi
Ordinary validation flows never execute that script.
Publishing
Run npm run validate first, inspect every npm pack --dry-run file list, then publish in dependency order:
@expert-council/core
@expert-council/pi-runtime
@expert-council/cli
@expert-council/mcp-server
@expert-council/pi-package
Known limitations
- Pi's API moves quickly. The current release was verified against the local 0.84.4 SDK; the runtime checks required SDK, model-runtime, resource-loader, and session methods and lists any missing contract explicitly on incompatibility.
- Pi has no unified real billing-type API. Runtime subscription signals and named Token Plans take priority; otherwise non-zero catalog prices are treated as metered, and providers without reliable evidence stay
unknownuntil an audit or explicit user configuration confirms them. - Expert Council does not infer subjective coding quality from model names, nor does it download benchmark presets automatically.
- Detached worktrees start from the committed
HEADand do not copy uncommitted changes from the main workspace. This is deliberate isolation; the runtime detects a dirty source workspace and surfaces the deviation through runtime limitations and mutation-council warnings before delegation. - Worktree changes are returned for Main Agent review and are never auto-merged or applied; call
expert_cleanupafter acceptance or rejection, or they will be cleaned up automatically after the retention window. - Writes to non-Git workspaces require explicit in-place mutation authorization.
- In-flight model calls do not resume after a server restart; persisted state closes them as explicit interrupted failures while preserving plans and completed results.
- Codex's own sandbox does not automatically contain the external Pi runtime, so Expert Council uses separate allowed roots and worktree boundaries.
- Expert Council provisions mutation worktrees from the repository's own committed lockfile only when
security.workspaceProvisioning.modeisauto(defaultnone, so nothing is installed unless opted in). Installs use the lockfile-pinned command with--ignore-scriptsand a scrubbed child environment; read-only workspaces are never provisioned and ecosystems without a supported lockfile are skipped. After provisioning, a verification gate runs the repository typecheck and test commands, and a failing gate downgrades an otherwise successful result topartialwithfailureType: "test_failure"so the corrected-retry path engages. Expert Council still contains no recursive expert trees, graphical interface, remote control plane, or remote telemetry.
License
MIT; see LICENSE.