Multi-Model Deliberation
vita can ask questions of AI models other than the one driving the session. The thesis is triangulation: different frontier models have different blind spots and strengths, so for a genuinely hard call, polling several and synthesizing their answers beats trusting any single perspective. This page documents the four external-model deliberation surfaces β Council, Tribunal, Delphi, and ask-ai β how each works, where their state lives, and when each is the right tool.
Note: This is distinct from the internal Board. The Board convenes fixed persona agents that are all the same provider (parallel Claude CLI calls deliberating under Robert's Rules) for life/health decisions. The systems here reach outside β to other vendors' frontier models via OpenRouter, or to consumer chat UIs via a browser. External reach means real model diversity, but also that context leaves the machine; that trade-off shapes the whole page.
The four surfaces at a glance
| Surface | Mechanism | Models | Diversity source | Latency / calls | Entry point |
|---|---|---|---|---|---|
| Council | Parallel API fan-out, one synthesis | ~4 frontier, via OpenRouter | between-model | ~30s / 4 calls | /council, scripts/council |
| Tribunal | Personas + blind peer review + chair synthesis | ~4 frontier Γ 5 personas | between-model + assigned thinking-style | ~90s / ~8 calls | /tribunal, scripts/tribunal |
| Delphi | Iterative multi-round subpanel consensus | N simulated experts (subagents) | persona + multi-round revision | minutes / many subagents | /delphi (skill) |
| ask-ai | Browser automation to consumer chat UIs | one of GPT / Grok / Gemini | single external model | one chat turn | /ask-ai (command) |
All four converge to the same shape: gather vita context β frame the question β consult external model(s) β synthesize/interpret for the host. They differ in how many models, how the answers are cross-examined, and how the context leaves the host.
Warning β context leakage is the governing constraint. Council, Tribunal, and Delphi search vita for background and embed snippets into the prompt sent to external models. ask-ai sends compiled health context to a third-party chat UI. Anything routed through these surfaces leaves the host. Treat strategy/work questions and any sensitive personal content accordingly; ask-ai in particular shows an explicit privacy preview and requires confirmation before sending (see ask-ai).
Council (multi-model consensus)
Council is the baseline: fan a question out to a handful of top-tier models in parallel, then have silicon-zack read all of the raw answers and write a single synthesis. The asker only sees the synthesis; raw model outputs stay behind the scenes unless requested.
Control flow
Implemented in scripts/council/core.py:
- Context gathering (
gather_context) β runsscripts.search.search(question, limit=5), takes up to 5 snippets, and reframes the raw question into a prompt that prepends a "Background context about the person asking" block. If search is unavailable or returns nothing, the raw question is used unchanged.--rawskips this entirely (for general questions not about vita/projects). - Parallel fan-out (
ask_council/_call_model) β builds onemessageslist and posts it concurrently (asyncio.gather) to every selected model via the OpenRouter chat-completions endpoint (https://openrouter.ai/api/v1/chat/completions),temperature=0.7,max_tokens=65000(high to leave room for reasoning-model CoT tokens). Only the finalcontentis kept;reasoning/reasoning_detailsfields are stripped. - Cost estimate β a rough
tokens_in Γ 10 + tokens_out Γ 40per-million approximation is summed across responses intototal_cost_usd. - Synthesis β
core.pyreturns aCouncilResultwithsynthesis=""; the caller (silicon-zack) fills it in.format_for_synthesis()lays out the original question, the context used, and each model's labeled response for the host to read and synthesize.
from scripts.council import ask_council_sync, format_for_synthesis
result = ask_council_sync("should the proactive queue use sqlite or postgres?")
formatted = format_for_synthesis(result) # host then writes result.synthesis
Model selection and where it lives
The default roster is hardcoded in COUNCIL_DEFAULT_MODELS in core.py, but the active selection is configurable and persisted to data/council/models.json:
{ "model_keys": ["model-a", "model-b", "model-c", "model-d"] }
get_council_model_keys() reads that file (falling back to the hardcoded defaults), validates each key against the known/available model set, and caches the result. set_council_model_keys() validates, dedupes, persists, and refreshes the cache. The hardcoded defaults are a known staleness hazard β they list a specific frontier roster in source that drifts; the JSON override is how the live set is kept current. For the current model roster and routing logic, defer to Model Guidance rather than the hardcoded list.
Note: This is one of the recurring "instrument can lie" cases the system watches for β a hardcoded default roster in
core.pyand a separatemodels.jsonoverride mean the source file can name models that are no longer the active panel. Trustdata/council/models.json(or the API below), not the literal incore.py.
OpenRouter credentials
_get_openrouter_key() reads OPENROUTER_API_KEY from the environment first, then falls back to ~/.config/vita/openrouter.json ({"api_key": "sk-or-..."}). Missing both raises RuntimeError.
HTTP API for the model panel
The inference router (api/src/routers/inference.py, prefix /api/v1/inference) exposes the panel config so the web UI can edit it:
| Method | Endpoint | Purpose |
|---|---|---|
GET |
/api/v1/inference/council/models |
Returns available_model_keys + selected_model_keys |
PUT |
/api/v1/inference/council/models |
Sets the selection ({ "model_keys": [...] }), returns updated config |
(API served on port 33800; see Architecture.)
Stepwise flow variants
Beyond the in-session skill, scripts/council is wrapped by a family of stepwise flows under flows/ for batch/pipeline use:
| Flow | What it adds |
|---|---|
flows/council |
Baseline: fan to frontier models, synthesize. For judgment calls, not implementation. |
flows/simple-council |
Distribute a prompt to frontier models in parallel, synthesize with a fast model. |
flows/simple-fast-council |
Same idea through faster/cheaper models for quick turnaround. |
flows/deep-council |
Adds verbalized sampling (within-model diversity), custom analytical lenses, and anonymous peer review. |
flows/fusion-council |
Web-grounded panel + a structured judge + a second gap-fill research round (see below). |
flows/council-plan |
Council-reviews a spec, then an agent explores the codebase and writes a grounded plan. |
flows/council-review |
Council-reviews a plan/document, synthesizes feedback, then an agent revises it. |
Run any of them via:
stepwise run flows/fusion-council --name "fusion: queue migration" --wait
Tip:
flows/fusion-councilis the most elaborate variant. ItsFLOW.yamlpipeline issetup-models β resolve-lens β panel β anonymize β judge β fill β synthesize: each panelist researches the web in parallel (OpenRouter:online), responses are anonymized/shuffled, a judge produces a consensus / contradiction / blind-spot map (it compares rather than merges), a second round researches the flagged blind spots and adjudicates contradictions, and only then does a chair write the decision brief. It supportspreset(quality/budget), per-lens dimensions, and amodelsoverride.
When to use Council
Good for: architecture decisions, complex trade-offs with several valid approaches, triangulating on uncertain technical topics. Not for: simple factual lookups, quick obvious questions, or personal-life decisions (those go to the Board).
Tribunal (personas + anonymous peer review)
Tribunal is Council with two extra layers bolted on: assigned personas force cognitive diversity (so you don't get five similarly-smart-but-converging answers), and a blind peer-review round catches what individual advisors miss before a chairman synthesis writes the verdict. Implemented in scripts/tribunal/core.py, reusing council's gather_context, get_council_models, and OpenRouter key.
The personas
Five personas live in scripts/tribunal/personas.py as frozen Persona(name, system_prompt) records. Each is a system prompt that constrains a model to one thinking style:
| Persona | Mandate |
|---|---|
| Contrarian | Assume a fatal flaw exists and hunt for it; stress-test edge cases and second-order effects. |
| First Principles | Ignore the question as framed; ask what's actually being solved; flag if the wrong variable is being optimized. |
| Expansionist | Hunt for the upside being missed; show specifically what "bigger" looks like. |
| Outsider | Pretend zero context/jargon; catch the curse of knowledge and insider assumptions. |
| Executor | Only care about the concrete first step; reject vague plans; demand sequenced actions. |
assign_personas(model_count) maps the 5 personas onto the available models round-robin ([i % model_count for i in range(len(PERSONAS))]), so with 4 models the 5th persona reuses model 0.
Control flow (run_tribunal)
- Context β same
gather_contextas council (--rawskips). - Advisor round β 5 advisor calls fan out in parallel, each model carrying its assigned persona as the system prompt, all answering the framed question. Results become
AdvisorResponse(model_name, persona, content, β¦). - Anonymize (
_anonymize_responses) β drop errored responses, shuffle the survivors withrandom.shuffle, and relabel themA, B, C, β¦so reviewers can't tell who wrote what. - Peer review β
review_countreviewers (defaultmin(model_count, 3), overridable via--reviews N) each get all anonymized responses and theREVIEW_PROMPT, which asks three questions: strongest response and why, biggest blind spot, and β emphasized as most important β what did ALL of them miss? Reviewers run attemperature=0.3(lower, for analytical work). - Chair synthesis β
_format_for_synthesisreassembles the advisor responses (personas now revealed) plus the anonymous reviews into theSYNTHESIS_PROMPT, which asks the chair for consensus, key disagreements, blind spots caught by review, an emergent cross-cutting insight, a verdict, and one "Monday morning" next step. As with council,core.pyreturns the prompt; the host (silicon-zack) writes the actual synthesis intoresult.synthesis.
from scripts.tribunal import run_tribunal_sync, format_result_markdown
result = run_tribunal_sync("migrate the proactive queue from sqlite to postgres?")
print(format_result_markdown(result)) # result.synthesis = chair prompt for the host
Roughly ~8 OpenRouter calls (5 advisor + 3 review) and ~90s versus council's 4 calls / ~30s β use it when being wrong is expensive and Council's answer feels too agreeable or surface-level.
Delphi (rotational-subpanel expert consensus)
Delphi is a skill (.claude/skills/delphi/SKILL.md), not a standalone script. Where Council/Tribunal are single-shot API fan-outs to real distinct vendors, Delphi is a multi-round, iterative consensus process run entirely through host-spawned subagents simulating a panel of imagined experts. It implements a Modified Delphi Method with Custer's rotational-subpanel technique to cut the combinatorial review burden, and it surfaces consensus, structured disagreement, and novel insight through anonymous iterative revision.
Note: Delphi diversity comes from who the experts are and rounds of revision, not from different model vendors. The subagents are general-purpose host agents role-playing assembled personas β so unlike Council/Tribunal there is no OpenRouter call and no per-vendor diversity, but there is far more depth of deliberation.
How it runs
| Phase | What happens |
|---|---|
| 0 β Interview | AskUserQuestion asks 2-3 clarifying questions (scope, stakes, constraints) to sharpen the inquiry, then restates a refined inquiry statement. |
| 1 β Panel assembly | Generate N experts (default 9, --number-of-perspectives N) in three tiers: Domain (50%), Adjacent (30%), Wildcard outsiders (20%), each with a name/archetype and a one-line lens. |
| 2 β Subpanel formation | Split into 3 subpanels by stratified random assignment (each gets a proportional Domain/Adjacent/Wildcard mix). Per round, each subpanel reviews ~2/3 of all positions (the other two subpanels' positions) β Custer's rotation, so every position is reviewed by 2 of 3 subpanels and no expert reviews everything. |
| 3 β Delphi rounds | Round 1 launches N parallel subagents (one per expert) for initial positions (position/rationale/confidence/assumptions/blind-spots). Rounds 2-5 launch 3 subpanel subagents that simulate collective deliberation, deciding per-expert to maintain / revise / strengthen, then re-clustering positions and recomputing consensus metrics. |
| 4 β Final report | A ranked consensus table, consensus positions, a divergence report (split %, strongest arguments, why each side wasn't persuaded), outlier insights, wildcard contributions, convergence-curve process dynamics, and a meta-analysis. |
Consensus thresholds (enforced rigorously): strong = 75%+, emerging = 60-74%, active divergence = no position above 59%, polarized = two opposing positions each held by 30%+. The continuation gate stops when strong consensus is reached on all major aspects, when position shifts fall below 10% in two consecutive rounds (stability), or after a hard cap of round 5; on round 4/5 it reconvenes the full panel on non-consensus items only.
Anonymity is preserved throughout: experts never see each other's raw output (only the synthesizer's clustered summaries), and the final report attributes insights by expert type (domain/adjacent/wildcard), never by name. The skill credits the rotational-subpanel technique to an external source.
/delphi "is event sourcing worth it for the activity log?" --number-of-perspectives 12
Use Delphi over Council when the question is high-stakes and you want structured convergence and divergence mapping over multiple rounds, not a fast single-shot triangulation.
ask-ai (browser second opinions)
ask-ai (.claude/commands/ask-ai.md) is the odd one out: instead of API fan-out via OpenRouter, it drives a consumer AI chat UI (GPT / Grok / Gemini) through browser automation to get a second opinion β designed primarily for health questions. It is mechanically distinct from Council in two ways: one model at a time, and it goes through the web UI rather than an API.
Supported targets
| Alias | Interface |
|---|---|
gpt |
ChatGPT web UI |
grok |
Grok (via X) |
gemini |
Gemini web UI |
Control flow
- Gather context β read canonical state (e.g.
data/executive/identity-state.md,data/scratch/daily.md) and pull domain-specific aggregates via memory views (memory.view_loader.load_view, checking the freshness verdict). A topicβcontext table selects which slices to include based on the question's keywords. - Format the question β assemble a structured prompt (context + relevant data + the user's question + a request for analysis).
- Privacy preview β before sending, print an explicit notice listing exactly what will be shared with the external model, then offer three choices: proceed, edit/reduce context, or cancel. This is the consent gate.
- Send β drive the target's web UI. Per the browser policy (CLAUDE.md, 2026-05-12;
mcp__claude-in-chromeis banned), this uses the camofox skill (anti-detection Firefox in Docker, with handoff for auth). A copy/paste fallback is provided if no browser tool is available. - Interpret β present the model's analysis, note discrepancies with the host's own read, highlight actionable points, and append a caveat that AI health advice should be discussed with a healthcare provider.
/ask-ai gpt review my recent sleep trend
Warning β medical PII surface. Because ask-ai is built for health questions, its context compilation and privacy preview deliberately surface personal health metrics before sending them to a third-party chat UI. The privacy preview exists precisely so the operator can review and reduce that payload. This is the most PII-sensitive of the four surfaces; the mechanism (consent gate, anonymization best-practices, never-share list) is the safeguard. Real values are never shown in this documentation β only placeholders.
Choosing a surface
quick technical triangulation, fast .............. /council
expensive-to-be-wrong, want adversarial review ... /tribunal
high-stakes, want multi-round convergence map .... /delphi
one external opinion on a health question ........ /ask-ai
life / health / values decision, internal only ... the Board (board.md)
The first three (Council, Tribunal, Delphi) escalate in depth and cost at the price of latency. ask-ai is orthogonal: a single external opinion through a browser, with the strongest consent gate. The Board is the internal counterpart β same-provider persona deliberation that never leaves the host β and is the right call for personal/values questions where external context-sharing is undesirable. For the broader research harness (web fan-out, source fetching, adversarial verification), see Research.
File Locations
| Path | Purpose |
|---|---|
scripts/council/core.py |
Council engine: context, fan-out, synthesis formatting, model selection |
scripts/council/__init__.py |
Council package exports |
data/council/models.json |
Persisted active council/tribunal model panel ({"model_keys": [...]}) |
scripts/tribunal/core.py |
Tribunal engine: advisor round, anonymize, peer review, chair synthesis |
scripts/tribunal/personas.py |
The 5 thinking-style personas + assign_personas() |
scripts/tribunal/__init__.py |
Tribunal package exports |
.claude/skills/council/SKILL.md |
/council skill definition |
.claude/skills/tribunal/SKILL.md |
/tribunal skill definition |
.claude/skills/delphi/SKILL.md |
/delphi skill β full multi-round Delphi process |
.claude/commands/ask-ai.md |
/ask-ai command β browser-based external opinion |
flows/council, flows/simple-council, flows/simple-fast-council, flows/deep-council, flows/fusion-council, flows/council-plan, flows/council-review |
Stepwise flow variants |
api/src/routers/inference.py |
/api/v1/inference/council/models GET/PUT endpoints |
~/.config/vita/openrouter.json |
OpenRouter API key (or OPENROUTER_API_KEY env) |
Where to go next
- Board β internal same-provider persona deliberation (the contrast case)
- Research β web-fan-out research harness with adversarial verification
- Model Guidance β current model roster and routing (authoritative over hardcoded defaults)
- Browser β camofox automation used by ask-ai