Skip to content

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:

  1. Context gathering (gather_context) β€” runs scripts.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. --raw skips this entirely (for general questions not about vita/projects).
  2. Parallel fan-out (ask_council / _call_model) β€” builds one messages list 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 final content is kept; reasoning/reasoning_details fields are stripped.
  3. Cost estimate β€” a rough tokens_in Γ— 10 + tokens_out Γ— 40 per-million approximation is summed across responses into total_cost_usd.
  4. Synthesis β€” core.py returns a CouncilResult with synthesis=""; 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.py and a separate models.json override mean the source file can name models that are no longer the active panel. Trust data/council/models.json (or the API below), not the literal in core.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-council is the most elaborate variant. Its FLOW.yaml pipeline is setup-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 supports preset (quality / budget), per-lens dimensions, and a models override.

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)

  1. Context β€” same gather_context as council (--raw skips).
  2. 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, …).
  3. Anonymize (_anonymize_responses) β€” drop errored responses, shuffle the survivors with random.shuffle, and relabel them A, B, C, … so reviewers can't tell who wrote what.
  4. Peer review β€” review_count reviewers (default min(model_count, 3), overridable via --reviews N) each get all anonymized responses and the REVIEW_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 at temperature=0.3 (lower, for analytical work).
  5. Chair synthesis β€” _format_for_synthesis reassembles the advisor responses (personas now revealed) plus the anonymous reviews into the SYNTHESIS_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.py returns the prompt; the host (silicon-zack) writes the actual synthesis into result.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

  1. 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.
  2. Format the question β€” assemble a structured prompt (context + relevant data + the user's question + a request for analysis).
  3. 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.
  4. Send β€” drive the target's web UI. Per the browser policy (CLAUDE.md, 2026-05-12; mcp__claude-in-chrome is 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.
  5. 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