blue/.blue/docs/rfcs/0048-alignment-expert-pools.impl.md
Eric Garcia d7db9c667d feat: RFC 0048 expert pool implementation and documentation batch
## 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>
2026-02-01 19:26:41 -05:00

376 lines
12 KiB
Markdown

# 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:
1. Keyword matching against topic title (e.g., "security" → "Security Architect")
2. 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:
```json
{
"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
```rust
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`
```json
{
"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: true` but no `expert_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:
```json
{
"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
```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
```rust
#[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`
1. **Remove**: `ROLE_KEYWORDS`, `GENERAL_ROLES`, `select_role_for_topic`
2. **Add**: `ExpertPool`, `PoolExpert`, `ExpertTier`, `RotationMode` structs
3. **Add**: `sample_panel_from_pool`, `weighted_sample` functions
4. **Modify**: `handle_create` to parse `expert_pool` and require it for alignment mode
5. **Modify**: `assign_pastry_agents` to accept sampled experts instead of generating roles
6. **Add**: `handle_sample_panel` for manual round control
### handle_create Changes
```rust
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:
1. **Reads** the topic/RFC thoroughly
2. **Identifies** the domain (e.g., "Investment Analysis", "System Architecture")
3. **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
4. **Assigns** relevance scores (0.20-0.95) based on expected contribution
5. **Creates** the dialogue with `expert_pool`
### Phase 1+: Round Execution
The MCP server:
1. Samples `panel_size` experts using weighted random selection
2. Higher relevance = higher selection probability
3. Core experts almost always selected; Wildcards provide variety
4. 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
```markdown
## 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_create` requires `expert_pool` for alignment mode
- [ ] Error returned when `expert_pool` missing in alignment mode
- [ ] Pool persisted to `expert-pool.json` in output directory
- [ ] Weighted sampling: higher relevance = higher selection probability
- [ ] Tier distribution respected: ~33% Core, ~42% Adjacent, ~25% Wildcard
- [ ] `rotation: "none"` uses same panel all rounds
- [ ] `rotation: "wildcards"` keeps Core/Adjacent, rotates Wildcards
- [ ] `rotation: "full"` resamples completely each round
- [ ] `blue_dialogue_sample_panel` respects retain/exclude
- [ ] `blue_dialogue_round_prompt` returns 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_KEYWORDS` constant
- Delete `GENERAL_ROLES` constant
- Delete `select_role_for_topic` function
- Alignment dialogues require `expert_pool` parameter
- Non-alignment dialogues unaffected
---
*"The Judge sees the elephant. The Judge summons the right blind men. The sampling ensures no single perspective dominates."*
— Blue