## RFC 0048 Expert Pool Implementation - Added tiered expert pools (Core/Adjacent/Wildcard) to dialogue handlers - Implemented weighted random sampling for panel selection - Added blue_dialogue_sample_panel MCP tool for manual round control - Updated alignment-play skill with pool design instructions ## New RFCs - 0044: RFC matching and auto-status (draft) - 0045: MCP tool enforcement (draft) - 0046: Judge-defined expert panels (superseded) - 0047: Expert pool sampling architecture (superseded) - 0048: Alignment expert pools (implemented) - 0050: Graduated panel rotation (draft) ## Dialogues Recorded - 2026-02-01T2026Z: Test expert pool feature - 2026-02-01T2105Z: SQLite vs flat files - 2026-02-01T2214Z: Guard command architecture ## Other Changes - Added TODO.md for tracking work - Updated expert-pools.md knowledge doc - Removed deprecated alignment-expert agent - Added spikes for SQLite assets and SDLC workflow gaps Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
12 KiB
RFC 0048: Alignment Expert Pools
| Status | Implemented |
| Date | 2026-02-01 |
| ADRs | 0014 (Alignment Dialogue Agents) |
| Supersedes | RFC 0046, RFC 0047 |
Summary
The alignment dialogue system uses keyword matching to auto-select generic expert roles, producing inappropriate panels for domain-specific topics. This RFC introduces Judge-defined expert pools: the Judge designs a tiered pool of domain-appropriate experts, and the MCP server samples panels from this pool with optional per-round rotation.
Problem
When blue_dialogue_create is called for an alignment dialogue, expert roles are auto-selected via:
- Keyword matching against topic title (e.g., "security" → "Security Architect")
- Fallback to generic roles: Systems Thinker, Domain Expert, Devil's Advocate
This fails for:
- Domain-specific topics: Investment analysis gets "Systems Architect" instead of "Portfolio Manager"
- Cross-functional topics: A product launch might need Marketing, Legal, Finance perspectives
- Novel domains: Topics without keyword matches get only generic roles
- Perspective diversity: Fixed panels miss opportunities for rotation and fresh viewpoints
The Judge understands the problem space after reading the topic. The Judge should design the expert pool.
Design
Expert Pool Structure
The Judge creates a pool with three tiers:
{
"title": "NVIDIA Investment Decision",
"alignment": true,
"expert_pool": {
"domain": "Investment Analysis",
"question": "Should Acme Trust add NVIDIA by trimming NVAI?",
"experts": [
{ "role": "Value Analyst", "tier": "Core", "relevance": 0.95 },
{ "role": "Growth Analyst", "tier": "Core", "relevance": 0.90 },
{ "role": "Risk Manager", "tier": "Core", "relevance": 0.85 },
{ "role": "Portfolio Strategist", "tier": "Core", "relevance": 0.80 },
{ "role": "ESG Analyst", "tier": "Adjacent", "relevance": 0.70 },
{ "role": "Quant Strategist", "tier": "Adjacent", "relevance": 0.65 },
{ "role": "Technical Analyst", "tier": "Adjacent", "relevance": 0.60 },
{ "role": "Behavioral Analyst", "tier": "Adjacent", "relevance": 0.55 },
{ "role": "Income Analyst", "tier": "Adjacent", "relevance": 0.50 },
{ "role": "Macro Economist", "tier": "Wildcard", "relevance": 0.40 },
{ "role": "Contrarian", "tier": "Wildcard", "relevance": 0.35 },
{ "role": "Geopolitical Analyst", "tier": "Wildcard", "relevance": 0.30 },
{ "role": "Market Historian", "tier": "Wildcard", "relevance": 0.25 }
]
},
"panel_size": 7,
"rotation": "wildcards"
}
Tier Distribution
| Tier | Pool % | Panel % | Purpose |
|---|---|---|---|
| Core | ~30% | ~33% | Domain essentials, always selected |
| Adjacent | ~40% | ~42% | Related expertise, high selection probability |
| Wildcard | ~30% | ~25% | Fresh perspectives, rotation candidates |
Rotation Modes
| Mode | Behavior |
|---|---|
none |
Fixed panel for all rounds (default) |
wildcards |
Core/Adjacent persist, Wildcards resample each round |
full |
Complete resample each round |
Sampling Algorithm
fn sample_panel(pool: &ExpertPool, panel_size: usize, round: usize, rotation: RotationMode) -> Vec<PastryAgent> {
let (core_n, adj_n, wc_n) = tier_split(panel_size);
match rotation {
RotationMode::None => {
// Round 0 selection persists all rounds
if round == 0 {
weighted_sample(&pool.core, core_n)
.chain(weighted_sample(&pool.adjacent, adj_n))
.chain(weighted_sample(&pool.wildcard, wc_n))
} else {
load_round_0_panel()
}
}
RotationMode::Wildcards => {
// Core/Adjacent persist, Wildcards resample
let core = if round == 0 { weighted_sample(&pool.core, core_n) } else { load_core() };
let adjacent = if round == 0 { weighted_sample(&pool.adjacent, adj_n) } else { load_adjacent() };
let unused_wc = pool.wildcard.iter().filter(|e| !used_in_previous_rounds(e));
let wildcards = weighted_sample(&unused_wc, wc_n);
core.chain(adjacent).chain(wildcards)
}
RotationMode::Full => {
weighted_sample(&pool.all, panel_size)
}
}
}
fn weighted_sample(experts: &[Expert], n: usize) -> Vec<Expert> {
// Higher relevance = higher selection probability
let total: f64 = experts.iter().map(|e| e.relevance).sum();
let probs: Vec<f64> = experts.iter().map(|e| e.relevance / total).collect();
weighted_reservoir_sample(experts, probs, n)
}
API
blue_dialogue_create
{
"title": "...",
"alignment": true,
"expert_pool": {
"domain": "string",
"question": "string (optional)",
"experts": [
{ "role": "string", "tier": "Core|Adjacent|Wildcard", "relevance": 0.0-1.0 }
]
},
"panel_size": 12,
"rotation": "none|wildcards|full"
}
Required for alignment mode: expert_pool with at least 3 experts.
Validation:
- Error if
alignment: truebut noexpert_pool - Error if
panel_size> total experts in pool - Error if relevance not in 0.0-1.0 range
- Warning if no Wildcard tier experts (groupthink risk)
blue_dialogue_sample_panel (New)
Manual round-by-round control:
{
"dialogue_title": "nvidia-investment-decision",
"round": 1,
"retain": ["Muffin", "Cupcake"],
"exclude": ["Beignet"]
}
Output Structure
{output_dir}/
├── expert-pool.json # Full pool (Judge's design)
├── round-0/
│ ├── panel.json # Sampled panel for this round
│ └── *.md # Agent responses
├── round-1/
│ ├── panel.json # May differ if rotation enabled
│ └── *.md
└── scoreboard.md
Dialogue Markdown
## Expert Pool
**Domain**: Investment Analysis
**Question**: Should Acme Trust add NVIDIA by trimming NVAI?
| Tier | Experts |
|------|---------|
| Core | Value Analyst, Growth Analyst, Risk Manager, Portfolio Strategist |
| Adjacent | ESG Analyst, Quant Strategist, Technical Analyst, Behavioral Analyst, Income Analyst |
| Wildcard | Macro Economist, Contrarian, Geopolitical Analyst, Market Historian |
## Round 0 Panel
| Agent | Role | Tier | Relevance | Emoji |
|-------|------|------|-----------|-------|
| 🧁 Muffin | Value Analyst | Core | 0.95 | 🧁 |
| 🧁 Cupcake | Risk Manager | Core | 0.85 | 🧁 |
| 🧁 Scone | ESG Analyst | Adjacent | 0.70 | 🧁 |
| 🧁 Eclair | Technical Analyst | Adjacent | 0.60 | 🧁 |
| 🧁 Donut | Behavioral Analyst | Adjacent | 0.55 | 🧁 |
| 🧁 Brioche | Contrarian | Wildcard | 0.35 | 🧁 |
| 🧁 Croissant | Market Historian | Wildcard | 0.25 | 🧁 |
Implementation
Data Structures
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExpertPool {
pub domain: String,
pub question: Option<String>,
pub experts: Vec<PoolExpert>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PoolExpert {
pub role: String,
pub tier: ExpertTier,
pub relevance: f64,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub enum ExpertTier {
Core,
Adjacent,
Wildcard,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, Default)]
pub enum RotationMode {
#[default]
None,
Wildcards,
Full,
}
Changes to dialogue.rs
- Remove:
ROLE_KEYWORDS,GENERAL_ROLES,select_role_for_topic - Add:
ExpertPool,PoolExpert,ExpertTier,RotationModestructs - Add:
sample_panel_from_pool,weighted_samplefunctions - Modify:
handle_createto parseexpert_pooland require it for alignment mode - Modify:
assign_pastry_agentsto accept sampled experts instead of generating roles - Add:
handle_sample_panelfor manual round control
handle_create Changes
pub fn handle_create(state: &mut ProjectState, args: &Value) -> Result<Value, ServerError> {
// ... existing validation ...
if alignment {
let pool: ExpertPool = args.get("expert_pool")
.ok_or_else(|| ServerError::InvalidParams("alignment requires expert_pool".into()))?
.try_into()?;
let panel_size = args.get("panel_size")
.and_then(|v| v.as_u64())
.unwrap_or(pool.experts.len().min(12) as u64) as usize;
let rotation: RotationMode = args.get("rotation")
.and_then(|v| v.as_str())
.map(|s| match s {
"wildcards" => RotationMode::Wildcards,
"full" => RotationMode::Full,
_ => RotationMode::None,
})
.unwrap_or_default();
// Sample initial panel
let sampled = sample_panel_from_pool(&pool, panel_size, 0, rotation);
let agents = assign_pastry_names(sampled);
// Persist pool
let pool_path = format!("{}/expert-pool.json", output_dir);
fs::write(&pool_path, serde_json::to_string_pretty(&pool)?)?;
// ... continue with dialogue creation ...
}
}
Judge Workflow
Phase 0: Pool Design
Before creating the dialogue, the Judge:
- Reads the topic/RFC thoroughly
- Identifies the domain (e.g., "Investment Analysis", "System Architecture")
- Designs 8-24 experts appropriate to the domain:
- Core (3-8): Essential perspectives for this specific problem
- Adjacent (4-10): Related expertise that adds depth
- Wildcard (3-6): Fresh perspectives, contrarians, cross-domain insight
- Assigns relevance scores (0.20-0.95) based on expected contribution
- Creates the dialogue with
expert_pool
Phase 1+: Round Execution
The MCP server:
- Samples
panel_sizeexperts using weighted random selection - Higher relevance = higher selection probability
- Core experts almost always selected; Wildcards provide variety
- If rotation enabled, Wildcards resample each round
Convergence
Same as ADR 0014: velocity → 0 for 3 consecutive rounds, or tensions resolved.
Skill Update: alignment-play
## Parameters
| Parameter | Default | Description |
|-----------|---------|-------------|
| `--panel-size` | pool size or 12 | Number of experts per round |
| `--rotation` | `none` | Rotation mode: none, wildcards, full |
| `--max-rounds` | `12` | Maximum rounds before stopping |
| `--rfc` | none | Link dialogue to an RFC |
## Expert Pool Design
The Judge designs the pool before creating the dialogue:
1. Analyze the problem domain
2. Identify 8-24 relevant expert roles
3. Assign tiers (Core/Adjacent/Wildcard)
4. Assign relevance scores (0.20-0.95)
5. Call blue_dialogue_create with expert_pool
Example pool for "API Rate Limiting Strategy":
| Role | Tier | Relevance |
|------|------|-----------|
| API Architect | Core | 0.95 |
| Platform Engineer | Core | 0.90 |
| Security Engineer | Core | 0.85 |
| SRE Lead | Adjacent | 0.70 |
| Developer Advocate | Adjacent | 0.65 |
| Cost Analyst | Adjacent | 0.55 |
| Customer Success | Wildcard | 0.40 |
| Chaos Engineer | Wildcard | 0.30 |
Test Plan
blue_dialogue_createrequiresexpert_poolfor alignment mode- Error returned when
expert_poolmissing in alignment mode - Pool persisted to
expert-pool.jsonin output directory - Weighted sampling: higher relevance = higher selection probability
- Tier distribution respected: ~33% Core, ~42% Adjacent, ~25% Wildcard
rotation: "none"uses same panel all roundsrotation: "wildcards"keeps Core/Adjacent, rotates Wildcardsrotation: "full"resamples completely each roundblue_dialogue_sample_panelrespects retain/excludeblue_dialogue_round_promptreturns correct role from sampled panel- Pastry names assigned correctly to sampled experts
- End-to-end: alignment dialogue with custom pool runs successfully
Migration
Breaking change: Remove all keyword-based role selection.
- Delete
ROLE_KEYWORDSconstant - Delete
GENERAL_ROLESconstant - Delete
select_role_for_topicfunction - Alignment dialogues require
expert_poolparameter - Non-alignment dialogues unaffected
"The Judge sees the elephant. The Judge summons the right blind men. The sampling ensures no single perspective dominates."
— Blue