Multi-model consensus for cases where you want more than one model's judgment.
The Council agent runs several councillors in parallel, then synthesizes their outputs into one answer.
Each councillor in a preset is registered as a dynamic subagent named
councillor-<name> (e.g. councillor-alpha, councillor-beta), each
with its own configured model. The orchestrator dispatches all councillors
in parallel via OpenCode's native task() tool at depth 1, and each
councillor appears as its own TUI pane.
User / Orchestrator
|
v
Council agent (@council, your configured synthesizer model)
|
+--> task(): councillor-alpha (configured model)
+--> task(): councillor-beta (configured model)
+--> task(): councillor-gamma (configured model)
|
v
Council agent synthesizes councillor results
|
v
Final answer
The council agent waits for all councillors to respond (or fail), then synthesizes their results into a single report.
Add a council model and at least one council preset to your plugin config:
~/.config/opencode/oh-my-opencode-slim.json
{
"preset": "openai",
"presets": {
"openai": {
"council": { "model": "openai/gpt-5.6" }
}
},
"council": {
"presets": {
"default": {
"alpha": { "model": "openai/gpt-5.6-luna" },
"beta": { "model": "google/gemini-3-pro" },
"gamma": { "model": "openai/gpt-5.3-codex" }
}
}
}
}
Then use it directly:
@council What is the safest migration strategy for this schema change?
{
"council": {
"default_preset": "default",
"presets": {
"default": {
"alpha": { "model": "openai/gpt-5.6-luna" }
}
}
}
}
| Setting | Type | Default | Description |
|---|---|---|---|
presets |
object | - | Required. Named councillor presets |
default_preset |
string | "default" |
Preset used when none is specified |
Each entry inside a preset is one councillor:
| Field | Type | Required | Description |
|---|---|---|---|
model |
string | array | Yes | A provider/model string, or an ordered fallback chain tried until one responds |
variant |
string | No | Optional variant/reasoning setting (applies to chain entries without their own) |
prompt |
string | No | Optional role guidance prepended to the user prompt |
The synthesizer model is not configured inside council.presets.
Configure it using the normal agent system:
{
"presets": {
"openai": {
"council": { "model": "openai/gpt-5.6", "variant": "high" }
}
}
}
Or with a global override:
{
"agents": {
"council": {
"temperature": 0.2
}
}
}
When model is a string, the councillor uses that single model.
When model is an array, the councillor walks the chain in order:
{
"council": {
"presets": {
"review": {
"reviewer": {
"model": [
"openai/gpt-5.6",
{ "id": "google/gemini-3-pro", "variant": "high" },
"anthropic/claude-opus-4-6"
],
"prompt": "Focus on bugs, edge cases, and failure modes."
}
}
}
}
}
Entries are provider/model strings or { "id", "variant" } objects. The
councillor tries each entry in order until one responds. Empty responses are
retried once per entry; other failures advance to the next entry. The
councillor only fails once every entry in the chain is exhausted.
There are two separate model layers:
@council itself, which
does the final synthesis.council.presets.<preset>.<councillor>.model.agents.councillor can change shared councillor settings such as temperature,
MCPs, and skills, but it does not choose the councillor model.
Councillor models always come from:
council.presets.<preset>.<councillor>.model
{
"presets": {
"openai": {
"council": { "model": "openai/gpt-5.6" }
}
},
"council": {
"presets": {
"second-opinion": {
"reviewer": { "model": "openai/gpt-5.6-luna" }
}
}
}
}
{
"presets": {
"openai": {
"council": { "model": "openai/gpt-5.6" }
}
},
"council": {
"default_preset": "balanced",
"presets": {
"balanced": {
"alpha": { "model": "openai/gpt-5.6-luna" },
"beta": { "model": "google/gemini-3-pro" },
"gamma": { "model": "anthropic/claude-opus-4-6" }
}
}
}
}
Each councillor can receive its own steering prompt:
{
"council": {
"presets": {
"review-board": {
"reviewer": {
"model": "openai/gpt-5.6-luna",
"prompt": "Focus on bugs, edge cases, and failure modes."
},
"architect": {
"model": "google/gemini-3-pro",
"prompt": "Focus on maintainability, boundaries, and long-term design."
},
"optimizer": {
"model": "openai/gpt-5.3-codex",
"prompt": "Focus on performance, latency, and resource usage."
}
}
}
}
}
The councillor sees:
<role prompt>
---
<user prompt>
@council Should we use a job queue or an outbox pattern here?
The orchestrator may also delegate to @council for high-stakes or
ambiguous decisions.
Each councillor appears as its own TUI pane, dispatched in parallel. As they complete, their responses stream into the panes. Once all councillors have responded (or failed), the council agent synthesizes their results.
Council responses include:
unanimous, majority,
or split.A footer tracks participation:
---
*Council: 2/3 councillors responded (alpha: gpt-5.6-luna, beta: gemini-3-pro)*
| Scenario | Behavior |
|---|---|
| Some councillors fail | Synthesize from the successful ones |
| All councillors fail | Return an error |
| Preset has zero councillors | Return an error |
master fieldsThe council.master field and other master-prefixed fields have been
removed. A deprecation warning is logged this release if a config still
contains them, but they no longer have any effect.
Prefer this instead:
{
"presets": {
"openai": {
"council": { "model": "openai/gpt-5.6" }
}
}
}
master is ignoredcouncillors objects are still accepted for backward
compatibility@council is missingCouncil is only available when config.council exists.
Make sure your config includes a council block with at least one preset.
Check:
council.presetsdefault_preset points to a real preset when omitted at runtimeVerify the configured model IDs exist in your OpenCode environment. Each
councillor model must be a valid provider/model identifier your OpenCode
setup can reach.