Inspired by Perplexity Computer: Model Council mode for Mixture of Agents

Adds a 'council' synthesis style to MoA (per preset via synthesis_style,
one-shot via the new /council command on CLI + gateway). Reference models
answer independently; the aggregator chairs the deliberation and produces
a user-facing report of consensus, per-model disagreements (with the
differing assumptions behind them), unique contributions, and a
recommendation with an explicit confidence level.

Inspired by Perplexity's Model Council rollout to Perplexity Computer
(changelog 08/04/26): pick a board of 2-8 models, run them independently,
synthesize where they agree/disagree and what each uniquely surfaces.
This commit is contained in:
Hermes Agent 2026-08-12 17:27:23 -07:00 committed by Teknium
parent 1965fde394
commit 8d9e18d40b
10 changed files with 407 additions and 24 deletions

View File

@ -2001,6 +2001,9 @@ def run_conversation(
degraded_reference_policy=str(
moa_config.get("degraded_reference_policy") or "loud"
),
synthesis_style=str(
moa_config.get("synthesis_style") or "guidance"
),
agent=agent,
)
if _moa_context:

View File

@ -1217,6 +1217,7 @@ def aggregate_moa_context(
reference_max_tokens: int | None = None,
reference_timeout: float | None = None,
degraded_reference_policy: str = "loud",
synthesis_style: str = "guidance",
agent: Any = None,
) -> str:
"""Run configured reference models and synthesize their advice.
@ -1240,6 +1241,14 @@ def aggregate_moa_context(
``agent``, when passed, lets the reference fan-out be aborted early on a
user interrupt see ``_run_references_parallel``'s docstring.
``synthesis_style`` selects how advisor output is synthesized (see
``hermes_cli.moa_config.coerce_synthesis_style``): ``"guidance"`` (the
default) produces private context for the acting model; ``"council"``
(inspired by Perplexity Computer's Model Council, Aug 2026) makes the
aggregator act as a council *chair*, producing a user-facing deliberation
report that surfaces agreement, disagreement, per-model unique
contributions, and a recommendation with confidence.
"""
reference_models = [slot for slot in reference_models if slot.get("enabled", True)]
reference_outputs: list[tuple[str, str, Any]] = []
@ -1297,15 +1306,36 @@ def aggregate_moa_context(
f"{notice}"
)
synth_prompt = (
"You are the aggregator in a Mixture of Agents process. Synthesize the "
"reference responses into concise, actionable guidance for the main "
"Hermes agent. Focus on next steps, tool-use strategy, risks, and any "
"disagreements. Do not answer the user directly unless that is all that "
"is needed; produce context the main agent should use in its normal loop.\n\n"
f"Original user prompt:\n{user_prompt}\n\n"
f"Reference responses:\n{joined}"
)
council = str(synthesis_style or "guidance").strip().lower() == "council"
if council:
synth_prompt = (
"You are the CHAIR of a model council. Several independent frontier "
"models were each asked the same question and answered without seeing "
"each other's responses. Chair the deliberation: produce a council "
"report with these sections —\n"
"1. Consensus: where the models agree (consensus supports acting with "
"confidence).\n"
"2. Disagreements: where they diverge, attributing each position to "
"its model by label, and identifying the differing starting "
"assumptions behind each divergence.\n"
"3. Unique contributions: what each model uniquely surfaced that the "
"others missed.\n"
"4. Chair's recommendation: your synthesized position, with an "
"explicit confidence level and what evidence would change it.\n"
"Be concrete and attribute positions to models by name.\n\n"
f"Original user prompt:\n{user_prompt}\n\n"
f"Council member responses:\n{joined}"
)
else:
synth_prompt = (
"You are the aggregator in a Mixture of Agents process. Synthesize the "
"reference responses into concise, actionable guidance for the main "
"Hermes agent. Focus on next steps, tool-use strategy, risks, and any "
"disagreements. Do not answer the user directly unless that is all that "
"is needed; produce context the main agent should use in its normal loop.\n\n"
f"Original user prompt:\n{user_prompt}\n\n"
f"Reference responses:\n{joined}"
)
agg_label = _slot_label(aggregator)
agg_runtime = _slot_runtime(aggregator)
@ -1351,6 +1381,18 @@ def aggregate_moa_context(
if not synthesis:
synthesis = joined
if council:
return (
"[Model Council report — a board of independent models deliberated "
"on the user's question and the chair synthesized their positions. "
"Present this deliberation to the user: preserve the "
"consensus/disagreement structure and the per-model attributions, "
"and add your own judgement where useful.]\n"
f"Chair: {agg_label}\n"
f"Council members: {', '.join(_slot_label(slot) for slot in reference_models)}\n\n"
f"{synthesis.strip()}"
)
return (
"[Mixture of Agents context — use this as private guidance for the "
"normal Hermes agent loop. You may call tools, continue reasoning, or "
@ -2227,15 +2269,39 @@ class MoAChatCompletions:
elif joined or degraded:
if degraded:
joined = f"{joined}\n\n{degraded}" if joined else degraded
guidance = (
"[Mixture of Agents reference context]\n"
f"Preset: {self.preset_name}\n"
f"Aggregator/acting model: {_slot_label(aggregator)}\n"
f"References: {', '.join(label for label, _, _ in _agg_refs)}\n\n"
"Use the reference responses below as private context. You are the aggregator and acting model: "
"answer the user directly or call tools as needed.\n\n"
f"{joined}"
)
if str(preset.get("synthesis_style") or "guidance").strip().lower() == "council":
# Council style (inspired by Perplexity Computer's Model
# Council, Aug 2026): the acting model chairs a deliberation
# instead of consuming private advice. Its user-facing answer
# must surface consensus, disagreements (with per-model
# attribution and the differing assumptions behind them),
# unique contributions, and a recommendation with confidence.
guidance = (
"[Model Council deliberation]\n"
f"Preset: {self.preset_name}\n"
f"Chair (you): {_slot_label(aggregator)}\n"
f"Council members: {', '.join(label for label, _, _ in _agg_refs)}\n\n"
"You are the chair of a model council. The council member "
"responses below are independent answers to the current "
"state of the task. Chair the deliberation in your reply: "
"surface where the members agree (consensus), where they "
"disagree (attribute positions to members by name and name "
"the differing assumptions), what each uniquely surfaced, "
"and close with your own recommendation and an explicit "
"confidence level. You may still call tools when the task "
"requires acting rather than deliberating.\n\n"
f"{joined}"
)
else:
guidance = (
"[Mixture of Agents reference context]\n"
f"Preset: {self.preset_name}\n"
f"Aggregator/acting model: {_slot_label(aggregator)}\n"
f"References: {', '.join(label for label, _, _ in _agg_refs)}\n\n"
"Use the reference responses below as private context. You are the aggregator and acting model: "
"answer the user directly or call tools as needed.\n\n"
f"{joined}"
)
_attach_reference_guidance(agg_messages, guidance)
prepared_request = {

25
cli.py
View File

@ -10678,6 +10678,31 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin, CLIBillingMixin):
self._pending_moa_disable_after_turn = True
self._pending_agent_seed = payload
_cprint(f" MoA one-shot queued with preset {preset}; previous model will be restored after this turn.")
elif canonical == "council":
# /council <question> — one-shot Model Council (inspired by
# Perplexity Computer's Model Council, Aug 2026): the default MoA
# preset's reference models answer the question independently, the
# preset's aggregator chairs the deliberation, and the council
# report (consensus / disagreements / unique contributions /
# recommendation with confidence) is handed to the current session
# model to present. The session model is never switched.
from hermes_cli.moa_config import build_moa_turn_prompt
parts = cmd_original.split(None, 1)
payload = parts[1].strip() if len(parts) > 1 else ""
if not payload:
_cprint(" Usage: /council <question> (runs the default MoA preset's models as an independent council and reports consensus vs disagreement)")
return True
try:
moa_cfg = self.config.get("moa") if isinstance(self.config, dict) else {}
encoded = build_moa_turn_prompt(
payload, moa_cfg, synthesis_style="council"
)
except Exception as exc:
_cprint(f" Council setup failed: {exc}")
return True
self._pending_input.put(encoded)
_cprint(" ⚖ Council convened — reference models are deliberating.")
elif canonical == "subgoal":
self._handle_subgoal_command(cmd_original)
elif canonical == "skin":

View File

@ -16132,6 +16132,35 @@ class GatewayRunner(GatewayAuthorizationMixin, GatewayKanbanWatchersMixin, Gatew
if canonical == "refine":
return await self._handle_refine_command(event)
if canonical == "council":
# /council <question> — one-shot Model Council (inspired by
# Perplexity Computer's Model Council, Aug 2026). Encodes the
# question as a hidden MoA one-shot marker with the council
# synthesis style forced on; run_conversation decodes it, the
# default preset's reference models answer independently, and
# the chair's consensus/disagreement report reaches the session
# model as context. No model switch, no agent eviction.
from hermes_cli.moa_config import build_moa_turn_prompt
from hermes_cli.config import load_config
council_payload = event.get_command_args().strip()
if not council_payload:
return (
"Usage: /council <question> (runs the default MoA preset's "
"models as an independent council and reports consensus vs "
"disagreement)"
)
try:
cfg = load_config()
event.text = build_moa_turn_prompt(
council_payload,
cfg.get("moa") if isinstance(cfg, dict) else {},
synthesis_style="council",
)
except Exception:
return "Failed to prepare council turn."
# Fall through to _handle_message_with_agent with the rewritten text.
if canonical == "moa":
# /moa is one-shot sugar only: run a single prompt through the
# default MoA preset, then restore the prior model. To *switch* to a

View File

@ -170,6 +170,8 @@ COMMAND_REGISTRY: list[CommandDef] = [
args_hint="[focus instructions]"),
CommandDef("moa", "Run one prompt through the default Mixture of Agents preset, then restore your model", "Session",
args_hint="<prompt>", busy_policy="reject", busy_handler="moa"),
CommandDef("council", "Convene a model council: reference models answer independently, a chair synthesizes consensus and disagreements", "Session",
args_hint="<question>", busy_policy="reject"),
CommandDef("subgoal", "Add or manage extra criteria on the active goal", "Session",
args_hint="[text | remove N | clear]", busy_policy="dispatch"),
CommandDef("status", "Show session, model, token, and context info", "Session",
@ -1277,7 +1279,7 @@ _SLACK_PRIORITY_ALIASES = ("btw", "bg")
# native slash.
# - pause: global emergency stop; reached via /hermes pause [off] on
# Slack. Added at the 50-cap — a native slot would clamp /platform.
_SLACK_VIA_HERMES_ONLY = frozenset({"topup", "moa", "debug", "egress", "init", "version", "diff", "update", "heartbeat", "refine", "pause"})
_SLACK_VIA_HERMES_ONLY = frozenset({"topup", "moa", "council", "debug", "egress", "init", "version", "diff", "update", "heartbeat", "refine", "pause"})
def _sanitize_slack_name(raw: str) -> str:

View File

@ -89,6 +89,9 @@ def _print_config(config: dict[str, Any]) -> None:
print(f" {idx}. {_format_slot(slot)}")
agg = preset["aggregator"]
print(f" Aggregator: {_format_slot(agg)}")
style = str(preset.get("synthesis_style") or "guidance")
if style != "guidance":
print(f" Synthesis style: {style}")
def cmd_moa(args) -> None:

View File

@ -71,6 +71,26 @@ def _coerce_degraded_reference_policy(value: Any) -> str:
return policy if policy in {"loud", "silent"} else "loud"
def coerce_synthesis_style(value: Any) -> str:
"""Normalize the MoA synthesis style; unknown values degrade to 'guidance'.
- ``'guidance'`` (default): the classic MoA shape advisor responses are
synthesized into PRIVATE context for the acting model, which then
answers the user normally. Advisor perspectives never surface as a
deliverable.
- ``'council'``: multi-model deliberation shape (inspired by Perplexity
Computer's Model Council, Aug 2026): each reference model weighs in
independently and the acting model becomes the council *chair*,
producing a USER-FACING report that explicitly surfaces where the
models agree, where they disagree (and why differing starting
assumptions), what each uniquely contributes, and a recommended
position with confidence. Built for ambiguous judgment calls where
disagreement between frontier models is itself the signal.
"""
style = str(value or "guidance").strip().lower()
return style if style in {"guidance", "council"} else "guidance"
def _coerce_int(value: Any, default: int) -> int:
if value is None or value == "":
return default
@ -306,6 +326,7 @@ def _default_preset() -> dict[str, Any]:
"max_tokens": 4096,
"reference_max_tokens": None,
"fanout": "user_turn",
"synthesis_style": "guidance",
"enabled": True,
}
@ -366,6 +387,11 @@ def _normalize_preset(raw: Any) -> dict[str, Any]:
# last advisor run. Also accepts the mapping form
# {mode: every_n, n: N}, normalized to the canonical string.
"fanout": _coerce_fanout(raw.get("fanout")),
# How advisor output is synthesized for the acting model. "guidance"
# (default) keeps advisor perspectives as private context; "council"
# turns the acting model into a council chair producing a user-facing
# agree/disagree report (see coerce_synthesis_style).
"synthesis_style": coerce_synthesis_style(raw.get("synthesis_style")),
}
@ -415,6 +441,7 @@ def normalize_moa_config(raw: Any) -> dict[str, Any]:
"max_tokens": active["max_tokens"],
"reference_max_tokens": active.get("reference_max_tokens"),
"fanout": active.get("fanout", "user_turn"),
"synthesis_style": active.get("synthesis_style", "guidance"),
"enabled": active["enabled"],
# MoA-level (not per-preset) toggles ride at the top level alongside
# save_traces. privacy_filter: '' (off, default) | 'display' | 'full'
@ -475,11 +502,24 @@ def set_active_moa_preset(config: Any, name: str | None) -> dict[str, Any]:
return cfg
def encode_moa_turn(prompt: str, config: Any = None, preset: str | None = None) -> str:
"""Encode a /moa one-shot turn for frontends that can only send text."""
def encode_moa_turn(
prompt: str,
config: Any = None,
preset: str | None = None,
synthesis_style: str | None = None,
) -> str:
"""Encode a /moa one-shot turn for frontends that can only send text.
``synthesis_style``, when given, overrides the resolved preset's style for
this one turn (used by ``/council`` to force a council deliberation over
the default preset without touching config).
"""
turn_config = resolve_moa_preset(config or {}, preset)
if synthesis_style is not None:
turn_config["synthesis_style"] = coerce_synthesis_style(synthesis_style)
payload = {
"prompt": str(prompt or ""),
"config": resolve_moa_preset(config or {}, preset),
"config": turn_config,
}
encoded = base64.urlsafe_b64encode(
json.dumps(payload, separators=(",", ":"), ensure_ascii=False).encode("utf-8")
@ -500,9 +540,16 @@ def decode_moa_turn(message: Any) -> tuple[str, dict[str, Any] | None]:
return prompt, _normalize_preset(payload.get("config") or {})
def build_moa_turn_prompt(user_prompt: str, config: Any = None, preset: str | None = None) -> str:
def build_moa_turn_prompt(
user_prompt: str,
config: Any = None,
preset: str | None = None,
synthesis_style: str | None = None,
) -> str:
"""Build the hidden one-shot payload used by TUI/gateway routing."""
return encode_moa_turn(user_prompt, config, preset=preset)
return encode_moa_turn(
user_prompt, config, preset=preset, synthesis_style=synthesis_style
)
def moa_usage() -> str:

View File

@ -0,0 +1,147 @@
"""Tests for the MoA 'council' synthesis style (Model Council).
Inspired by Perplexity Computer's Model Council (Aug 2026): reference models
answer independently, and the aggregator acts as a council chair producing a
user-facing consensus/disagreement report instead of private guidance.
"""
from __future__ import annotations
from types import SimpleNamespace
import pytest
from hermes_cli.moa_config import (
build_moa_turn_prompt,
coerce_synthesis_style,
decode_moa_turn,
normalize_moa_config,
)
# ── coerce_synthesis_style normalization ────────────────────────────────────
def test_coerce_synthesis_style_defaults_and_tolerance():
assert coerce_synthesis_style(None) == "guidance"
assert coerce_synthesis_style("") == "guidance"
assert coerce_synthesis_style("guidance") == "guidance"
assert coerce_synthesis_style("council") == "council"
assert coerce_synthesis_style("COUNCIL ") == "council"
# Unknown / bad types degrade to the default (tolerant-read contract).
assert coerce_synthesis_style("debate") == "guidance"
assert coerce_synthesis_style(42) == "guidance"
def test_normalize_preset_carries_synthesis_style():
cfg = normalize_moa_config(
{"presets": {"p": {
"reference_models": [{"provider": "openrouter", "model": "openai/gpt-5.5"}],
"aggregator": {"provider": "openrouter", "model": "anthropic/claude-opus-4.8"},
"synthesis_style": "council",
}}}
)
assert cfg["presets"]["p"]["synthesis_style"] == "council"
# Flattened compatibility view mirrors the default preset.
assert cfg["synthesis_style"] == "council"
def test_normalize_preset_synthesis_style_defaults_to_guidance():
cfg = normalize_moa_config({})
assert cfg["presets"]["default"]["synthesis_style"] == "guidance"
assert cfg["synthesis_style"] == "guidance"
# ── one-shot marker round-trip (/council) ───────────────────────────────────
def test_council_one_shot_marker_round_trip():
encoded = build_moa_turn_prompt(
"should we launch in Q4 or Q1?", {}, synthesis_style="council"
)
prompt, config = decode_moa_turn(encoded)
assert prompt == "should we launch in Q4 or Q1?"
assert config is not None
assert config["synthesis_style"] == "council"
def test_moa_one_shot_marker_keeps_guidance_default():
encoded = build_moa_turn_prompt("hello", {})
_prompt, config = decode_moa_turn(encoded)
assert config is not None
assert config["synthesis_style"] == "guidance"
# ── aggregate_moa_context council path ──────────────────────────────────────
def _response(content: str = "ok"):
message = SimpleNamespace(content=content, tool_calls=[])
choice = SimpleNamespace(message=message, finish_reason="stop")
return SimpleNamespace(choices=[choice], usage=None, model="fake")
@pytest.fixture
def hermes_home(tmp_path, monkeypatch):
home = tmp_path / ".hermes"
home.mkdir()
monkeypatch.setenv("HERMES_HOME", str(home))
return home
def _run_aggregate(monkeypatch, synthesis_style):
from agent.moa_loop import aggregate_moa_context
calls: list[dict] = []
def fake_call_llm(**kwargs):
calls.append(kwargs)
return _response(
"advice" if kwargs.get("task") == "moa_reference" else "synthesis"
)
monkeypatch.setattr("agent.moa_loop.call_llm", fake_call_llm)
result = aggregate_moa_context(
user_prompt="buy or lease?",
api_messages=[{"role": "user", "content": "buy or lease?"}],
reference_models=[{"provider": "openrouter", "model": "openai/gpt-5.5"}],
aggregator={"provider": "openrouter", "model": "anthropic/claude-opus-4.8"},
synthesis_style=synthesis_style,
)
return result, calls
def _flatten(content) -> str:
if isinstance(content, str):
return content
return "".join(
part.get("text", "") for part in content if isinstance(part, dict)
)
def test_council_style_uses_chair_prompt_and_report_framing(hermes_home, monkeypatch):
result, calls = _run_aggregate(monkeypatch, "council")
agg_calls = [c for c in calls if c.get("task") == "moa_aggregator"]
assert len(agg_calls) == 1
synth_prompt = _flatten(agg_calls[0]["messages"][-1]["content"])
assert "CHAIR of a model council" in synth_prompt
assert "Council member responses" in synth_prompt
# The returned context is a user-facing council report, not private
# guidance.
assert result.startswith("[Model Council report")
assert "Chair: openrouter:anthropic/claude-opus-4.8" in result
assert "Council members: openrouter:openai/gpt-5.5" in result
def test_guidance_style_unchanged(hermes_home, monkeypatch):
"""Default style must be byte-compatible with the classic MoA framing."""
result, calls = _run_aggregate(monkeypatch, "guidance")
agg_calls = [c for c in calls if c.get("task") == "moa_aggregator"]
synth_prompt = _flatten(agg_calls[0]["messages"][-1]["content"])
assert "aggregator in a Mixture of Agents process" in synth_prompt
assert result.startswith("[Mixture of Agents context")
assert "CHAIR" not in synth_prompt

View File

@ -56,6 +56,7 @@ Type `/` in the CLI to open the autocomplete menu. Built-in commands are case-in
| `/heartbeat every <interval> <prompt>` (alias: `/hb`) | Set a recurring prompt that re-enters **this session** as a normal user turn whenever it's idle and the interval has elapsed (min 60s; missed ticks coalesce). Subcommands: `/heartbeat status`, `/heartbeat pause`, `/heartbeat resume`, `/heartbeat clear`. Session-scoped and in-process — use `hermes cron` for durable isolated schedules. See [Session Heartbeats](/user-guide/features/heartbeat). |
| `/refine [focus]` | Run the background memory/skill self-improvement review **now** instead of waiting for the automatic post-turn trigger. Optional focus text steers the review (e.g. `/refine save the deploy workflow as a skill`). Runs in a background fork against a conversation snapshot — the live session and prompt cache are untouched; results are reported when done. |
| `/moa <prompt>` | Run a single prompt through the default [Mixture of Agents](/user-guide/features/mixture-of-agents) preset, then restore your current model. One-shot — does not change your session model. |
| `/council <question>` | Convene a **model council**: the default MoA preset's reference models answer the question independently, and the aggregator chairs the deliberation — reporting where the models agree, where they disagree (with per-model attribution and the differing assumptions behind each divergence), what each uniquely surfaced, and a recommendation with an explicit confidence level. Built for ambiguous judgment calls where model disagreement is itself the signal. One-shot — does not change your session model. See [Council mode](/user-guide/features/mixture-of-agents#council-mode). |
| `/resume [name]` | Resume a previously-named session |
| `/sessions` (TUI alias: `/switch`) | Classic CLI: browse and resume previous sessions in an interactive picker. TUI: open the live session switcher for currently open TUI sessions. Use `/sessions new` in the TUI to start another live session immediately. |
| `/egress [status]` | Show Docker egress proxy status — enabled/configured/running state, credential source, token mappings, uncovered providers, and next remediation step. Works in CLI, TUI, Desktop chat, and messaging gateway. |
@ -256,6 +257,7 @@ The messaging gateway supports the following built-in commands inside Telegram,
| `/heartbeat every <interval> <prompt>` (alias: `/hb`) | Set a recurring prompt that re-enters this session when idle. Subcommands: `status`, `pause`, `resume`, `clear`. On Slack use `/hermes heartbeat …`. |
| `/refine [focus]` | Run the memory/skill self-improvement review now, optionally with focus instructions. On Slack use `/hermes refine …`. |
| `/moa <prompt>` | Run one prompt through the default [Mixture of Agents](/user-guide/features/mixture-of-agents) preset, then restore the session model. |
| `/council <question>` | One-shot [model council](/user-guide/features/mixture-of-agents#council-mode): reference models answer independently; the chair reports consensus, disagreements, and a recommendation with confidence. |
| `/branch [name]` (alias: `/fork`) | Branch the current session (explore a different path). |
| `/agents` (alias: `/tasks`) | Show active agents and running tasks. |
| `/sessions` | Browse and resume previous sessions. |

View File

@ -230,6 +230,65 @@ moa:
Omit `reasoning_effort` to use the provider/Hermes default for that slot.
## Council mode
*Inspired by Perplexity Computer's Model Council (Aug 2026).*
By default, MoA advisor perspectives are **private context** — the acting model
consumes them silently and answers normally. Council mode flips that: the
reference models become an explicit **board of independent models**, and the
acting model becomes the council **chair**, producing a user-facing
deliberation report that surfaces:
1. **Consensus** — where the models agree (consensus supports acting with
confidence).
2. **Disagreements** — where they diverge, attributed per model, with the
differing starting assumptions behind each divergence.
3. **Unique contributions** — what each model surfaced that the others missed.
4. **Chair's recommendation** — a synthesized position with an explicit
confidence level and what evidence would change it.
Council mode is built for ambiguous questions: judgment calls, trade-offs,
risk assessments — situations where there is no single right answer and
disagreement between frontier models is itself the signal.
### One-shot: `/council`
```
/council should we launch this feature in Q4 or Q1?
```
Runs the default MoA preset's reference models independently against your
question, chairs the deliberation, and reports back — then your session
continues on your normal model. Works on the CLI, TUI, and every messaging
platform (on Slack: `/hermes council …`).
### Persistent: `synthesis_style: council`
Set it per preset to make every turn of a MoA session deliberate as a council:
```yaml
moa:
presets:
board:
reference_models:
- provider: openrouter
model: openai/gpt-5.5
- provider: openrouter
model: google/gemini-3.1-pro
aggregator:
provider: openrouter
model: anthropic/claude-opus-4.8
synthesis_style: council # default: guidance
```
`guidance` (the default) is the classic MoA shape — advisor output stays
private. Unknown values degrade to `guidance`.
Tips (they sharpen council output noticeably): ask for decisions rather than
summaries ("what would you bet on and why"), include numbers/timelines/budget,
and ask each model to state the assumptions behind its conclusion.
## Terminal preset management
```bash