council
by openvecta
README.md
# Vecta Council
**Stop asking one model. Convene a council.** *(beta)*
Fan one prompt out to several models in parallel, then let a synthesizer fold their
drafts into a single, stronger answer. One command, one MCP tool — BYOK, and it runs
against **any** OpenAI-compatible endpoint.
[](https://www.npmjs.com/package/@openvecta/council)
[](https://www.npmjs.com/package/@openvecta/council)
[](./LICENSE)


One model gives you one perspective — and one model's blind spots. A council gives you
disagreement you can actually see, and an answer that survived it.
<!-- demo.gif — a CLI run: several labeled drafts collapsing into one synthesis. -->
---
## Try it in 30 seconds (no config, no restart)
If you already have an `OPENAI_API_KEY` in your environment, that's the whole setup:
```bash
export OPENAI_API_KEY=sk-... # the key you already use
npx @openvecta/council "Postgres vs. DynamoDB for a write-heavy event log — case each way, then a recommendation."
```
It prints the synthesized answer to your terminal (add `--drafts` to see each model's
draft first). Point it at a different provider with one more variable:
```bash
export OPENAI_BASE_URL=https://openrouter.ai/api/v1 # or http://localhost:11434/v1 for Ollama
```
Off OpenVecta you must name the panel (the default ids are OpenVecta's) — see below.
And every single-provider run comes with an honest nudge: you're running a
single-vendor council; for a **true cross-lab panel**, point `OPENAI_BASE_URL` at
OpenVecta. Here's why that matters.
---
## Why a cross-lab council beats one model
Sampling the *same* model N times gives you N variations of the same training, the same
priors, the same blind spots. Its mistakes are correlated — ask it twice and it's
confidently wrong the same way both times.
Models from **different labs** are trained on different data with different objectives, so
their errors are *less* correlated. Put them on a panel and:
- **Disagreement becomes visible.** Where the drafts diverge is exactly where a single
model would have hidden its uncertainty behind a confident tone.
- **Synthesis beats voting.** The synthesizer reads every draft, keeps the strongest
reasoning from each, and repairs the weak spots — instead of picking a majority.
- **No single vendor can assemble the panel.** OpenAI's API can't call Anthropic to
cross-check itself. A cross-lab council is orchestration *across* providers.
We don't ship a marketing win-rate, and we won't pretend to. We ship the eval instead —
see [Proof, not benchmarks](#proof-not-benchmarks).
---
## Add it to your agent (MCP)
Vecta Council is also an MCP tool your coding agent can call mid-task. Add the server to
any MCP client (Claude Desktop, Cursor, Windsurf, your own kit).
### Any OpenAI-compatible provider (BYOK)
```json
{
"mcpServers": {
"council": {
"command": "npx",
"args": ["-y", "@openvecta/council", "mcp"],
"env": {
"OPENAI_BASE_URL": "https://openrouter.ai/api/v1",
"OPENAI_API_KEY": "sk-or-..."
}
}
}
}
```
Because this isn't OpenVecta, name the panel — pass the tool args explicitly:
```json
{
"name": "council",
"arguments": {
"prompt": "Pressure-test this migration plan.",
"debaters": ["openai/gpt-4o", "anthropic/claude-3.7-sonnet", "google/gemini-2.5-pro"],
"synthesizer": "deepseek/deepseek-chat"
}
}
```
> Provider id formats differ. OpenRouter needs `openai/gpt-4o`, `anthropic/claude-3.7-sonnet`,
> etc.; a local Ollama uses whatever you've pulled (`llama3.1`, `qwen2.5`). Bare ids like
> `gpt-4o` won't resolve on OpenRouter.
### OpenVecta (true cross-lab, defaults just work)
```json
{
"mcpServers": {
"council": {
"command": "npx",
"args": ["-y", "@openvecta/council", "mcp"],
"env": { "OPENVECTA_API_KEY": "ov_sk_live_..." }
}
}
}
```
On OpenVecta the default panel already spans labs, so you can just ask:
> *"Use the council to pressure-test this go-to-market plan."*
---
## The honest part: a single-provider council is weaker
If you point this at **one vendor** — OpenAI only, or a single local model — every debater
comes from one lab. You get diversity of *sampling*, not diversity of *training*. That's a
real council and it still helps on open-ended work, but it's the diluted version:
correlated models checking correlated models.
A *true* cross-lab panel needs an endpoint that actually spans labs. Two honest ways:
- **Aggregators like OpenRouter** — cross-lab already; you assemble, price, and manage the panel yourself.
- **[OpenVecta](https://openvecta.com)** — every frontier lab (OpenAI, Anthropic, Google, xAI)
plus many open models behind one OpenAI-compatible endpoint. The default council preset
spans labs out of the box, and you can pay **per call in USDC over
[x402](https://x402.org) with no account and no stored key** — the settlement model an
autonomous agent can actually use. That last part is the thing an aggregator's
signup-and-API-key flow can't do.
Same tool, same code — a better council underneath. Not a paywall; just where the cross-lab
panel is one env var away.
---
## Usage
**CLI**
```bash
npx @openvecta/council "Red-team this database choice and give me a recommendation."
npx @openvecta/council --rounds 3 --file ./plan.md "Draft a migration plan; call out the risks."
npx @openvecta/council --drafts "Which caching strategy for a read-heavy API, and why?"
```
**Custom panel + a peer-critique round (MCP args)**
```json
{
"name": "council",
"arguments": {
"prompt": "Draft a migration plan from a monolith to event-driven services. Call out the risks.",
"debaters": ["openai/gpt-4o", "anthropic/claude-3.7-sonnet", "google/gemini-2.5-pro"],
"synthesizer": "deepseek/deepseek-chat",
"rounds": 3,
"max_tokens": 4000
}
}
```
`rounds: 3` inserts a peer-critique pass — each debater sees the others' drafts and revises
before the synthesizer merges. Slower, sometimes sharper.
**Import the engine**
```ts
import { runCouncil } from "@openvecta/council/core";
const out = await runCouncil({
prompt: "Pressure-test this plan.",
baseUrl: "https://api.openvecta.com/v1",
apiKey: process.env.OPENVECTA_API_KEY,
debaters: ["gpt-oss-120b", "llama-4-maverick", "mimo-v2.5"],
synthesizer: "deepseek-v4-pro",
});
console.log(out.answer, "\n", out.trailer);
```
---
## How it works
```mermaid
flowchart LR
P[Your prompt] --> A[Model A · Lab 1]
P --> B[Model B · Lab 2]
P --> C[Model C · Lab 3]
A -->|draft| S[Synthesizer]
B -->|draft| S
C -->|draft| S
S --> F[One stronger final answer]
```
1. **Draft — in parallel.** Your prompt fans out to 2–4 debaters at once; each writes independently.
2. **Peer-refine — optional (`rounds: 3`).** Each debater sees the others' drafts and rewrites.
3. **Synthesize.** A separate synthesizer (kept *out* of the debater pool, so one failure
can't sink both a draft and the merge) folds the surviving drafts into one answer.
It degrades gracefully: if a debater times out or returns empty, the council drops it and
reports `N/M debaters responded`; if everything fails, it surfaces the real reason. This is
**client-side orchestration** — the tool just calls `/chat/completions` several times —
which is exactly why it runs against any OpenAI-compatible endpoint.
**When *not* to use it.** A council makes one call per model, so it costs several times more
than a single call and typically runs ~1–2 minutes. For math, exact factual lookups, or long
single-artifact builds (complex code, whole websites), a single strong model is more reliable
*and* cheaper — the council only ties there. Reach for it on **hard, high-stakes, one-shot**
judgment work: analysis, strategy, explanation, red-teaming, "which of these is right and why."
It's a second opinion you convene deliberately — not something to leave in a hot loop.
---
## Proof, not benchmarks
We don't publish a win-rate number, because a number you can't reproduce isn't evidence.
What we ship instead is the harness that produced ours: a **blind A/B eval with randomized
position and an impartial judge**, in [`eval/`](./eval). Point it at your own hard prompts,
run council vs. your single-model baseline, and read the per-category result yourself.
---
## Configuration
### Council parameters
| Param | Default | Notes |
|-------|---------|-------|
| `prompt` | — | the task or question (required) |
| `debaters` | multi-lab preset (OpenVecta only) | 2–4 model ids that draft in parallel. **Required against non-OpenVecta endpoints.** |
| `synthesizer` | default (OpenVecta only) | model id that writes the final answer. **Required against non-OpenVecta endpoints.** |
| `rounds` | `2` | `2` = draft → synthesize · `3` = adds a peer-critique round |
| `max_tokens` | drafts 2000 / synth 4000 | ⚠️ **one value caps *both* legs.** Setting it small can truncate the synthesis, and can empty a reasoning model that spends the budget on hidden thinking. Leave unset unless you know you need it. |
### Environment
| Var | Default | Notes |
|-----|---------|-------|
| `OPENAI_BASE_URL` / `OPENVECTA_BASE_URL` | `https://api.openvecta.com/v1` | any OpenAI-compatible endpoint |
| `OPENAI_API_KEY` / `OPENVECTA_API_KEY` | — | your key (start here) |
`OPENAI_*` and `OPENVECTA_*` are aliases; `OPENVECTA_*` wins if both are set.
---
## Install from source
```bash
git clone https://github.com/openvecta/council
cd council && npm install && npm run build # -> dist/index.js
node dist/index.js "your question"
```
> **Timeouts:** a council call runs ~1–2 min, past the 60s default in some MCP clients. The
> tool emits progress throughout, so clients that reset on progress stay connected; if yours
> hard-caps at 60s, raise its request timeout to ~180s for this server.
>
> **Security:** never commit an API key. Load secrets from your environment or a manager.
---
## License
MIT — see [LICENSE](./LICENSE). Built by [OpenVecta](https://openvecta.com). Issues and PRs
welcome. *The cross-lab council is one env var away.*
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues