This is one of conclave's three core docs (per the 3-Core Documentation Rule). It shows the system context: how a user (or a downstream consumer) drives conclave, how config and environment-variable keys feed in, how requests reach the nine first-class providers through conclave's own provider highway (an httpx transport + per-provider adapter registry — no LLM-SDK dependency), how the v1.1 verdict pipeline turns the member answers into a structured, agreement-scored, execution-traceable verdict plus a redacted execution manifest, and how the v1.2 Elite Decision Protocol adds gated claim audit and revision before that verdict. It also shows where mcp-warden sits as a dev-time consumer. The DSE-708 edge includes offline replay plus an opt-in, capped paid exploratory path.
Authority note: behavioral details here are descriptive. The canonical spec is
docs/PRODUCT_DESIGN_DOCUMENT.md.
flowchart TB
user["Engineer / power user"]
warden["mcp-warden (sibling)<br/>DEV-TIME consumer only"]
subgraph inputs["Inputs (never contain key VALUES)"]
cfg["~/.conclave/config.yml<br/>models · councils · synthesizer · custom endpoints<br/>(provider names only)"]
env["Environment variables<br/>XAI_API_KEY · GEMINI_API_KEY / GOOGLE_API_KEY<br/>ANTHROPIC_API_KEY · PERPLEXITY_API_KEY · OPENAI_API_KEY"]
end
subgraph conclave["conclave (MIT, Python 3.11+) — no LLM-SDK dependency"]
cli["CLI · conclave ask / providers (cli.py)"]
lib["Library API · from conclave import Council (__init__.py)"]
council["Council orchestrator<br/>fan_out · synthesize_blocks · skip-no-key (council.py)"]
modes["Deliberation modes<br/>debate · adversarial · vote (modes.py + prompts.py)"]
elite["Elite (v1.2)<br/>initial → claim audit → revision<br/>fixed 3-success gate each phase"]
evalcli["Experimental eval CLI (DSE-708)<br/>offline replay · live dry-run<br/>gated paid exploratory execute"]
evalartifacts["Versioned eval artifacts<br/>manifest · run · grader set + separate map<br/>exploratory report"]
liveguard["Live guard<br/>exact USD 10.00 approval · owner-only seal key<br/>HMAC checkpoint · one in flight · no-repeat resume"]
registry["Registry · name to model-id<br/>key PRESENCE only, never values (registry.py)"]
config["Config loader · custom endpoints (config.py)"]
models["Result contract · CouncilResult v2<br/>answers · verdict · consensus · manifest (models.py)"]
provider["call_model<br/>resolve adapter · read key by name at call time<br/>output_contract · latency · usage · error redacted · never raises (providers.py)"]
subgraph highway["Provider highway (owned, extensible)"]
adreg["resolve_adapter (adapters/__init__.py)"]
oai["OpenAICompatAdapter<br/>openai · xai · perplexity (+ custom)<br/>response_format json_schema"]
anth["AnthropicAdapter<br/>/v1/messages · system-hoist · max_tokens<br/>tool input_schema"]
gem["GeminiAdapter<br/>generateContent · role-map · usageMetadata<br/>responseSchema"]
transport["transport.post_json<br/>single httpx async boundary (transport.py)"]
end
subgraph verdictpipe["Verdict pipeline (v1.1 · default-on)"]
applyverdict["_apply_verdict<br/>buffered + streaming (council.py)"]
extract["extract_verdict<br/>1 initial call + up to 1 repair · cluster stances · never raises (verdict_synthesis.py)"]
agree["agreement.position_cluster_ratio_v1<br/>DETERMINISTIC arithmetic · no difflib · never LLM-emitted (agreement.py)"]
verdictobj["CouncilVerdict<br/>positions + evidence_answer_ids · conflicts · provider_votes · minority_reports (verdict.py)"]
manifest["ModelHarnessManifest<br/>first-class · secret-free · phased receipts (manifest.py)"]
end
end
subgraph providers["Foundation model providers (9 first-class, BYO keys, no markup, no middleman)"]
grok["xAI · xai/grok-4.3"]
gemini["Google · gemini/gemini-2.5-pro"]
claude["Anthropic · anthropic/claude-sonnet-4-6"]
perplexity["Perplexity · perplexity/sonar-pro"]
openai["OpenAI · openai/gpt-4.1"]
groq["Groq · groq/llama-3.3-70b-versatile"]
deepseek["DeepSeek · deepseek/deepseek-chat"]
mistral["Mistral · mistral/mistral-large-latest"]
together["Together · together/Llama-3.3-70B-Instruct-Turbo"]
end
user -->|"prompt + council + mode"| cli
user -->|"public tasks + frozen artifacts"| evalcli
evalcli --> evalartifacts
evalcli -.->|"--execute + exact approval"| liveguard
user -->|"import"| lib
warden -.->|"imports at DEV time only · NOT a runtime dep"| lib
cfg --> config
config --> council
config -->|"custom endpoints"| adreg
registry --> council
env -.->|"key NAME presence check (value never read here)"| registry
cli --> council
lib --> council
council --> modes
modes -->|"reuse fan_out + synthesize_blocks"| council
council --> elite
elite -->|"3+ revisions: existing synthesis + verdict<br/>under 3: stop incomplete"| council
council --> provider
liveguard --> provider
provider --> adreg
adreg --> oai
adreg --> anth
adreg --> gem
oai --> transport
anth --> transport
gem --> transport
provider -.->|"reads key VALUE by name at call time<br/>(transient · never stored/logged · redacted from errors)"| env
transport --> grok
transport --> gemini
transport --> claude
transport --> perplexity
transport --> openai
transport --> groq
transport --> deepseek
transport --> mistral
transport --> together
provider --> models
council -->|"member answers or completed Elite revisions"| applyverdict
applyverdict -->|"synthesizer clusters stances<br/>(initial call + optional repair, output_contract)"| extract
extract -->|"clustering"| agree
agree -->|"consensus_score/label<br/>(arithmetic over the clustering)"| verdictobj
extract -->|"positions · conflicts · provider_votes"| verdictobj
verdictobj -->|"hoisted to result mirrors"| models
applyverdict -->|"records extractor model + prompt version<br/>(provenance) · re-runs secret-safety scan"| manifest
manifest -->|"rides on every result"| models
models --> council
council -->|"CouncilResult (answers + verdict + manifest · no secrets)"| cli
council -->|"CouncilResult (answers + verdict + manifest · no secrets)"| lib
cli -->|"VERDICT panel + rich panels, or --json"| user
lib -->|"CouncilResult"| warden
- Two entry points, one core. The CLI (
cli.py) and the library API (from conclave import Council) are both thin drivers over the sameCouncilorchestrator. There is no behavior in the CLI that the library can't reach. - The DSE-708 eval lane is paid exploratory only. Offline
eval runstill validates pre-recorded artifacts without provider calls.eval livedefaults to dry-run; execution requires--execute, exact USD 10.00 approval, and an owner-only checkpoint-seal key file. HMAC-SHA256 authenticates checkpoint state, and the price hash includes each model'smax_output_bytes_per_tokenestimate bound. One call is in flight, its reservation persists first, and resume skips an interrupted cell. The smoke checks correctness only—not efficiency or decision quality—and remains not decision eligible. - mcp-warden is dashed and dev-time. The dotted edge from
mcp-wardento the library is deliberate: warden imports conclave only at design/eval time. conclave is stochastic and must never sit in warden's deterministic runtime decision path. See PDD §10. - The provider highway is owned and extensible. conclave has no LLM-SDK dependency;
it talks to every provider through its own layer.
call_model(providers.py) callsresolve_adapter(adapters/__init__.py), which selects aProviderAdapterfor the model id:OpenAICompatAdapterserves openai/xai/perplexity and any user-declared OpenAI-compatible endpoint;AnthropicAdapterspeaks native/v1/messages(system prompt hoisted to the top-levelsystemfield,max_tokensrequired);GeminiAdapterspeaks nativegenerateContent(OpenAI roles mapped,systemInstructionhoisted,usageMetadataparsed). Every adapter builds a request and hands it to the single network boundary —transport.post_json(transport.py), one async httpx call site. - The verdict pipeline is default-on and execution-traceable (PDD §4a). Once the council has the
member answers,
_apply_verdict(council.py, run on both the buffered and streaming paths, after the manifest exists) drivesextract_verdict(verdict_synthesis.py): an initial extraction call asks the synthesizer model to cluster the members' stances — not to re-answer, and crucially not to emit a number. Invalid output permits one repair call. The validated clustering feedsagreement.position_cluster_ratio_v1(agreement.py), which computes theconsensus_scoreas pure deterministic arithmetic (largest cluster / positioned members; nodifflib, never model-emitted). The assembledCouncilVerdict(verdict.py) carries positions withevidence_answer_ids,conflicts,provider_votes, andminority_reports, and its values are hoisted to theCouncilResultv2 mirrors. The structured-output contract (output_contract→ each adapter's native surface: OpenAIresponse_format, Anthropic toolinput_schema, GeminiresponseSchema) enforces the extraction schema at decode time, with a prompt-level fallback for providers without strict support. TheModelHarnessManifest(manifest.py) rides on every result — first-class, not a debug flag — recording which model + prompt version produced the clustering (provenance) and stampingsecret_safetyonly after the serialized manifest is scanned clean. This is enforced as a true invariant at the single chokepointCouncil._cached_run→_ensure_manifest: all six modes (synthesize,raw,debate,adversarial,vote,elite) funnel through it, so a result can never escape without a manifest — including the zero-members early return and cache hits. A verdict is optional: open-ended prompts, fewer than two responding members, or extraction failure leaveverdict = Nonewith the synthesis and member answers intact and the reason recorded on the manifest. - Elite is quality-first and released in v1.2. Its three concurrent member phases are independent
answers, council-wide anonymized claim audits, and member revisions. Answer IDs trace outputs
within the run; they are not external citations. Each phase requires
three successful responders. Larger councils may lose members and continue while three remain;
a failed gate stops later calls and returns an incomplete result with artifacts and phased
receipts for attempted work, but no synthesis or verdict. Completion is separate from decision
readiness (
ready/not_ready/indeterminate). Completed runs feed revisions into the existing synthesis and verdict pipeline. This normally costs up to3N + 2calls (3N + 3with repair) and more wall-clock time than ordinary modes;--streamis rejected because Elite is buffered only. - Streaming shares the same boundary (PDD §9 #5). A
--streamrun (and the libraryCouncil.ask_streamasync generator) flows through a streaming sibling of the call path:call_model_stream(providers.py) →transport.stream_sse(transport.py, the single streaming httpx call site,client.stream(...)) → each adapter'sstream_request+parse_sse_event(OpenAI-compatdata:/[DONE]deltas; Anthropic named SSE events; GeministreamGenerateContent?alt=sse).streaming.pyinterleaves members concurrently and emitsStreamEvents, ending with adoneevent whoseCouncilResultmatches the non-streaming shape. Streaming coverssynthesize/rawonly; Elite has no streaming; the never-raises +redact()invariants hold identically, with partial text preserved on mid-stream failure. resolve_adapteris the extension seam. Adding a new provider family is one registration inadapters/__init__.py; adding an OpenAI-compatible endpoint is config-only — a~/.conclave/config.ymlendpoints:entry, no code. That is whyconfighas an edge into the adapter registry on the diagram.- Two distinct env-var edges (the key-handling boundary).
- The dotted edge from env to the registry is a presence check by name — conclave
asks "is
XAI_API_KEYset and non-empty?" and never reads the value. - The dotted edge from
call_modelto env is where the actual key value is read — bycall_modelitself, by name, at call time, then passed to the adapter to build the auth header and sent by the transport. The value is transient in-process: never stored on any object, never logged, never serialized, and scrubbed from error strings viaredact()(adapters/base.py). It never passes through a conclave data structure. This split is the core of conclave's "name-only" key posture (PDD §3).
- The dotted edge from env to the registry is a presence check by name — conclave
asks "is
- Config carries no secrets.
~/.conclave/config.ymlreferences providers by friendly name and model id only (and custom endpoints by URL + key-env-var name); it feeds names into the loader, never key values. - Results carry no secrets — and the manifest proves it.
CouncilResult(prompt, answers, model ids, latency, token usage, errors, the verdict, and the manifest) flows back to both the CLI and library consumers with no key material, so--jsonand downstream serialization are safe. The v1.1 manifest goes further:secret_safetyis stampedverified_no_secretsonly afterscan_for_secret_material()proves the serialized manifest free of forbidden substrings (sk-/bearer/authorization/api_key/x-api-key). - Partial-failure is structural.
call_modelconverts any provider error into aModelAnswer.errorrather than raising, so one failing provider never aborts the run.