4 Role Committee
4 independent LLM sessions, information isolation + cross-challenge multi-round debate + REGIME neutral probability framing (only crash/unknown retain constraints). This chapter explains what each sees, what constrains them, and how they challenge each other.
← 01-architecture · Wiki Index · 03-dreaming →
Role Matrix
| Role | Can See | Blocked From | Output Format | File |
|---|---|---|---|---|
| Macro Strategist | VIX / TNX / Global macro brief + Intraday event context event_brief (Macro only, event RAG strictly isolated) | User holdings, technical indicators | SIGNAL + STRENGTH + SCORE | agents/macro_strategist.py |
| Quant Analyst | Technical indicators (Wilder RSI / MA / True Percentile / True TR ATR / RVOL) + REGIME neutral probability framing + Deterministic fact blocks (valuation/sentiment) | User holdings, Dreaming insights | Bullish/Bearish + Signal Strength | agents/quant.py |
| Wealth Context Officer (Role 5, cross-asset shared) | wealth_context from user.md (family backup / emergency fund / account type) + portfolio cash | Technical indicators, Macro view, individual stock holding details | Real Liquidity Rating (for Risk + CIO) | agents/wealth_context_officer.py |
| Risk Officer | Position concentration / unrealized profit buffer / Dreaming insights + Wealth Context Officer's real liquidity perspective | Technical indicators, Macro view | Risk Level + Concentration Score | agents/risk_officer.py |
| CIO | Full transcript of all three + Wealth Context perspective + User risk_level (included in portfolio_summary text) | — | 5-choice verdict + confidence + alloc | agents/cio.py |
Purpose of information isolation: Prevent LLMs from contaminating each other's views within the same context. Quant doesn't know how much the user has lost (won't bias towards "buy the dip"), Risk Officer doesn't know the RSI (won't be swayed by technical signals). Note that this "physical isolation" strictly applies only in Round 1 — see the round description in Cross-Challenge Protocol below.
One Complete Run in 6 Steps (Direct Path Perspective)
The round division below corresponds to the Direct path (core/committee/ package, including cron / Web GUI / skill run_committee). The Coordinator path (Claude Code spawns 4 subagents) differs slightly — see the "Two Paths LLM Call Count Comparison" at the end of this section.
Round 0 ─ Macro 1 LLM call (cross-asset shared, only 1 per run)
↓
Round 1 ─ Quant + Risk 2 LLM calls (parallel, independent statements)
↓
Round 2 ─ Quant sees Risk's last round + Risk sees Quant's last round (cross-challenge)
↓
Round 3 ─ Same as above (if not converged)
↓
[Convergence detection SIGNAL+STRENGTH stable for two rounds → early exit]
↓
Round 4 ─ CIO sees full transcript, 1 LLM call for verdictMeasured time (DeepSeek-Chat + 2 assets parallel + convergence exit): ~16 seconds (journal log evidence).
File: core/committee/debate.py:run_committee is the entry point; core/runner/session.py:run_committee_for_symbol is the end-to-end wrapper.
Two Paths LLM Call Count Comparison (to avoid ambiguity)
Common point of confusion: 4 prompt templates (roles) ≠ LLM call count. Rounds 2..N re-invoke Quant and Risk (the same roles, new prompts).
| Path | Scenario | Macro | R1 | R2..N | CIO | Typical Total per Asset |
|---|---|---|---|---|---|---|
| Direct | Multi-asset parallel optimization / any agent single asset | 1 (cross-asset shared) | 2 (Quant+Risk) | 2/round | 1 | 5 (converged) ~ 9 (4 rounds) per asset |
| Coordinator | Claude Code single asset spawn subagent | 1 (spawned together in Round 1) | 3 (Macro+Quant+Risk) | 2/round | 1 | 6 (1 R2) ~ 10 (3 R2) |
Why the Coordinator path doesn't share Macro: The user asks about one asset at a time, there's no "cross-asset reuse" pool; spawning Macro together in Round 1 is actually more parallel (Claude Agent({...}) sends 3 agents in one message, they run concurrently).
Direct path multi-asset measurement: 2 assets converged = 1 + 5×2 = 11 calls; 2 assets max=4 not converged = 1 + 9×2 = 19 calls; journal evidence shows 16 calls, falling between the two.
Before referencing this diagram, clarify which path you're on — the Coordinator documentation (skills/invest/references/committee-protocol.md) is written for the Coordinator path, this article is written for the Direct path. Both are correct but under different premises.
REGIME Classification and Neutral Probability Framing
Evolution (2026-05-31 Direction Lock Removed): The old version translated regime into a directional preset fed to Quant ("uptrend forbids bearish / no bottom fishing / sell on rallies"). Ablation on the same data proved: the entire direction lock layer = 1 leverage knob (uptrend lock) + 2 duds (downtrend lock backtest no-op, crash lock never triggered), not alpha. The current version treats regime as factual context: STRATEGY_HINT neutrally references the (asset, regime)'s OHLC 30d forward return probability framing (median / probability of falling below current price / sample count), leaving directional judgment to the LLM + data. The uptrend leverage effect is preserved as an explicit verdict.risk_profile=aggressive toggle (default steady, off). Flash crash defense is independent of regime (VIX sentinel + ATR volatility mutation ratio → deterministic buy-side downgrade in parse_cio_memo).
6 Regime Types (uptrend / downtrend / range_bound / crash / recovery / unknown)
Source: core/regime.py:THRESHOLDS (default thresholds + ASSET_OVERRIDES per-asset overrides)
| Regime | Trigger Condition | Framing Provided to Quant |
|---|---|---|
uptrend | (MA20 − MA120) / MA120 ≥ +trend_ma_spread_pct% (default 3%, per-asset overrides: Gold 5% / Nasdaq 4% / Crypto 8%) | Neutral probability framing (30d median/drawdown probability/n), self-determined direction |
downtrend | (MA20 − MA120) / MA120 ≤ −trend_ma_spread_pct% | Neutral probability framing, self-determined direction |
range_bound | MA entanglement (spread within ±threshold) + normal volatility | Neutral probability framing + current percentile, self-determined direction |
crash | Dual triggers (either triggers): ① Sharp drop atr_pct ≥ crash_atr_pct_min (per-asset) and return_30d ≤ −20%; ② Deep drop return_30d ≤ −30% (regardless of volatility) | Forced neutral (executability constraint: any direction is hard to rationally execute during a crash) |
recovery | crash not triggered + rebound ≥ 10% from recent 30-day low + price still low (true percentile < 50%) | Neutral probability framing, self-determined direction |
Note (aligned with
core/regime.pyimplementation, 2026-05-26):
- Trend determination uses deviation of MA20 from MA120 (single spread threshold), not the three-line bullish alignment of MA20>MA50>MA200; the code only calculates MA20/MA120/MA250, no MA50/MA200.
crashuses dual triggers (OR): Path 1 sharp drop (volatility leg AND 20% drawdown leg) + Path 2 deep drop (30-day drop ≥ 30%, regardless of volatility). Path 2 specifically catches "low-volatility slow declines" — a deep drop itself constitutes a crash, no high ATR needed.recoveryis a stateless implementation: approximates "just emerged from crash" using "rebounded ≥ 10% from recent 30-day low and percentile < 50%".- Priority:
crash(highest) →recovery→uptrend/downtrend→range_bound→unknown(insufficient data).
The REGIME brief is presented at highest priority (first paragraph) in Quant's prompt.
CIO Sanity Check
After the CIO outputs a verdict, parse_cio_memo() automatically validates:
| Anomaly | Automatic Correction |
|---|---|
BUY and confidence ≥ buy_confidence_overdrive(0.95) | Downgrade to ACCUMULATE + conf 0.6 (prevents overconfidence / prompt injection) |
alloc_cny exceeds ceiling | Clamp (prevents LLM from reporting astronomical numbers) |
Brief contains [WORKER_UNAVAILABLE] | Force HOLD + conf capped (garbage in, no high-confidence verdict) |
| SOLVENCY=strong + TRIM(concentration) | Force HOLD (sufficient buffer, concentration doesn't trigger reduction) |
| TRIM but buyback point missing / ≥ current price | Downgrade to HOLD (selling without buying back lower = pure loss) |
| Flash crash defense triggered (VIX sentinel OR ATR mutation ratio) | BUY→ACCUMULATE / ACCUMULATE→HOLD (deterministic buy-side downgrade, independent of regime) |
risk_profile=aggressive + uptrend + HOLD | Upgrade to ACCUMULATE (explicit leverage gear, default steady unchanged) |
Source: core/committee/cio_parse.py:parse_cio_memo (Sanity 0-5 + Defense + risk_profile, all deterministic post-processing).
Cross-Challenge Protocol
Round 1 Input (Independent Statements)
Quant sees:
# Asset: BetaShares Nasdaq 100 ETF (NDQ.AX)
# Market Regime (factual context + historical probability reference):
uptrend · MA20 above MA120 +8.5% (≥ 4%) · Price percentile 78%
→ Historical 30d forward return for this regime: median +1.4%, probability of falling below current price 35%, n=1423
(Use percentile / RSI to determine direction independently, no preset direction)
# Market Data (technical indicators + multi-cycle):
RSI(14, Wilder): 67, MA120: 33.8, MA250: 30.1, Price percentile(2y): 78%, RVOL(20): 0.9x, ...
Please output technical signals in Quant Analyst format.Risk Officer sees:
# Asset: BetaShares Nasdaq 100 ETF (NDQ.AX)
# User's current holdings:
CNY: ¥50,000 / AUD: $1,000 / NDQ.AX 50 shares @ A$38.50 (unrealized profit +12%)
# Long-term patterns (Dreaming, learning from committee's own verdicts vs actual market):
⚠️ Repeated HOLD on NDQ.AX during downtrend, 100% missed rallies 30 days later (avg +12%, n=11) → Reduce HOLD tendency
Please output risk assessment in Risk Officer format.Note: Quant doesn't know user holdings, Risk doesn't know RSI. This is physical isolation.
⚠️ Round nuance (important): Physical isolation only holds in Round 1. Entering Round 2+ cross-challenge, each agent sees the other's full transcript from the previous round (see next section), so Risk's holding numbers enter Quant's context via Risk's statement, and Quant's technical numbers enter Risk's context via Quant's statement — R2+ is no longer physically isolated, relying on prompt constraints:
quant/SKILL_rebuttal.mdandrisk_officer/SKILL_rebuttal.mdexplicitly state "only reference the other's conclusions (SIGNAL/STRENGTH), do not recalculate using their raw numbers (RSI/percentile/concentration)".
Round 2..N Input (Cross-Challenge)
Each agent sees the other's previous round output and is asked to respond rather than restate independently:
# This is Round 2 of cross-challenge (max 4 rounds)
# Last round Quant said:
[Quant Round 1 Full Text]
# Last round Risk said:
[Risk Round 1 Full Text]
Please re-evaluate:
- Do you maintain your original judgment or are you convinced by the other? Why?
- What data is the other party ignoring?
- Output your new SIGNAL + STRENGTH (if changed, explain the basis for the change)This step makes the LLM truly discuss, not just talk past each other.
Convergence Detection
Implementation: core/committee/debate.py:_check_convergence
# Two consecutive rounds, both Quant and Risk's respective SIGNAL + STRENGTH are stable
if (quant_signal_N == quant_signal_N-1 and
abs(quant_strength_N - quant_strength_N-1) < 1.0 and
risk_signal_N == risk_signal_N-1 and
abs(risk_strength_N - risk_strength_N-1) < 1.0):
converged = True # Early exit, don't waste tokensMost committees converge by Round 2-3 in practice (evidence: final_round=3, converged=True is a common value).
CIO's 5 Verdict Options
The CIO cannot fabricate; must choose from these 5:
| Verdict | Meaning | Typical alloc Range |
|---|---|---|
BUY | Strongly recommend buying | +3000 ~ +50000 CNY |
ACCUMULATE | Slowly add positions (recommend in batches) | +1000 ~ +5000 CNY |
HOLD | Do nothing | 0 |
TRIM | Partially reduce positions (free up ammunition) | -3000 ~ -20000 CNY |
SELL | Sell everything (very rare) | -20000 ~ -100000 CNY |
The CIO also outputs:
confidence: 0.0~1.0(clamped by sanity check)alloc_cny: int(suggested amount)dominant_view: macro|quant|risk(which party convinced the CIO)execution_plan+risk_plan(detailed execution + stop-loss)
View Prompts Online
GUI /committee → "4 Roles + Rules" tab directly displays the complete system prompts for all 4 roles + REGIME threshold table. Anyone can see how the LLM is constrained, no black box.
Backend endpoint: GET /api/regime_rules → returns full prompts + REGIME thresholds + CIO sanity check checklist.
Dual Execution Paths
Note: The same 4 prompts + the same cross-challenge protocol, two execution implementations:
| Path | Coordinator | Worker Implementation | Model |
|---|---|---|---|
| Skill | User's Claude | Claude Agent({subagent_type}) truly spawns 4 subagents | Claude 4 |
| Web/Cron | core/committee/ | 4 SDKAgent + ThreadPoolExecutor same-process multi-threading | DeepSeek |
See 04-execution-paths.md and adr/001-dual-execution-paths.md for details.
Next Steps
→ 03-dreaming.md — How long-term memory is accumulated (where the "historical behavior patterns" seen by the Risk Officer come from)
→ adr/001-dual-execution-paths.md — Why keep dual paths without merging
→ 07-extending.md#adding-new-agent-roles — How to add an ESG analyst