I Built 9 AI Advisors That Argue About My Life Data
A multi-persona LLM pattern with programmatic trigger conditions, agreement detection, and memory distillation, for personal dashboards that interpret data, not just display it.
My personal dashboard has 22 data collectors, 51 database models, and 240 MCP tools. It could tell me my sleep was bad, my mood was down, and my habits were slipping. What it couldn't do was interpret any of that. Until I gave it nine different personalities to argue with each other.
The Advisory Council pattern is a multi-persona LLM architecture where each AI advisor has its own personality, domain expertise, trigger conditions, cooldown rules, and persistent memory. It’s not “ask ChatGPT to pretend to be 9 people.” Each advisor fires independently based on real data thresholds, and their disagreements are often more valuable than any single alert.
The Perspective Problem
Most dashboards are single-voice systems. One algorithm, one interpretation, one set of alerts. A sleep score of 65 means the same thing whether you’re optimizing for athletic performance, emotional wellbeing, or career output. The problem isn’t data collection. It’s interpretation diversity.
Consider: your sleep dropped 20% this week. A wellness coach sees a health risk. A strategist sees a goal-stall predictor. A devil’s advocate asks whether your obsession with sleep metrics is itself the problem. Each interpretation is valid. A single-perspective dashboard picks one and discards the rest.
The advisory council pattern gives you all of them.
9 Advisors, 9 Perspectives
Each advisor is a frozen Python dataclass with an ID, name, title, role description, voice sample, accent color, and list of watched domains:
@dataclass(frozen=True)
class AdvisorProfile:
id: str # "alistair-vance"
name: str # "Alistair Vance"
title: str # "The Strategist"
role: str # One-sentence role description
voice_sample: str # Example quote for LLM voice calibration
accent_color: str # For UI avatar circles
domains: list[str] # ["goals", "mood", "sleep"]
trigger_descriptions: list[str]
The voice_sample field is the key design choice. It’s not just metadata. It’s injected into the LLM system prompt when generating commentary. Each advisor literally speaks differently. Here are the personalities:
| Advisor | Title | Voice | Watches |
|---|---|---|---|
| Alistair Vance | The Strategist | “The data suggests a rather compelling pattern here. Shall we examine it?” | Goals, cross-domain correlations |
| Sister Miriam | The Moral Compass | “Before we look at what’s missing, let’s acknowledge what you’ve built.” | Low mood, social isolation, gratitude |
| Jax Thorne | The Fixer | “Three things you can do right now. Pick one. Go.” | Habit decline, stalled goals |
| Coach Miller | The Wellness Coach | “Your body is keeping score even when you’re not. Let’s check in.” | Sleep, health metrics, streaks |
| Bramwell Finch | The Devil’s Advocate | “I see everyone agrees. Excellent. Let me offer the counterpoint.” | Complacency, journal rationalization |
| Birdie Malone | The Wildcard | “You’ve been staring at this dashboard for a while. Maybe go outside?” | Hyperfocus, audiobook binges |
| Professor Penhaligon | The Historian | “Interesting. This time last month, the trajectory looked quite different.” | Trends, seasonal patterns |
| Sloane Sterling | The Perception Architect | “The way you frame this matters more than the data itself.” | Career, professional positioning |
| Vivienne LeClair | The Flame Keeper | “Romance isn’t spontaneous. It’s intentional.” | Date nights, intimacy, love languages |
These aren’t categories. They’re characters. Alistair speaks in measured, analytical prose. Jax gives you a numbered list and tells you to move. Bramwell asks the uncomfortable question nobody else will. The voice differences aren’t cosmetic. They change how the same data gets interpreted.
How It Works: Trigger → Commentary → Rating → Memory
The critical design choice: advisors don’t fire because you asked them to. They fire because programmatic trigger conditions detected something worth commenting on.
Trigger Conditions Are Data-Driven
Here’s what actual trigger evaluation looks like (simplified from production code):
# Alistair Vance: Goal stalled 7+ days
stalled = [g for g in goals if g.days_since_update > 7]
if stalled and ("alistair-vance", "goal_stalled") not in recent_set:
triggered.append({
"advisor_id": "alistair-vance",
"trigger_type": "goal_stalled",
"context_summary": f"Goals stalled 7+ days: {names}",
"commentary": _alistair_stalled(stalled),
})
# Bramwell Finch: Complacency check (everything too good)
if (all(s >= mood_p75 for s in mood_scores[-3:])
and all(s.hours >= sleep_p75 for s in sleep_7d[-3:])):
triggered.append({
"advisor_id": "bramwell-finch",
"trigger_type": "complacency_check",
"commentary": "I see everyone agrees. Everything is wonderful. "
"Shall I be the one to ask what you're avoiding?",
})
# Birdie Malone: Hyperfocus (50+ commits, barely any music)
if commit_count >= 50 and music_count + reading_count < 10:
triggered.append({
"advisor_id": "birdie-malone",
"trigger_type": "hyperfocus_detected",
"commentary": f"{commit_count} commits and barely a song. "
"When was the last time you looked up?",
})
A crucial detail: triggers use personal baselines (30-day rolling percentiles), not hardcoded thresholds. “Low mood” means below your p25, not some universal number.
baselines = await get_baselines(session)
mood_low_threshold = mood_bl.p25 if mood_bl and not mood_bl.is_default else 4
Cooldowns Prevent Advisor Spam
Without cooldowns, the advisors would be insufferable. Three scope levels control how often each advisor can fire:
SCOPE_COOLDOWNS = {
"daily": (timedelta(hours=20), timedelta(hours=48)),
"weekly": (timedelta(days=6), timedelta(days=10)),
"monthly": (timedelta(days=25), timedelta(days=35)),
}
Each advisor+trigger combination has an independent cooldown. The result: each comment feels like a considered observation, not a nagging alert.
The Novel Parts
Agreement Detection
This is the highest-signal feature. When 2+ advisors independently flag overlapping domains within a time window, that’s a consensus signal:
# Group recent comments by domain
domain_map: dict[str, list] = {}
for comment in recent_comments:
for domain in comment.domains:
domain_map.setdefault(domain, []).append(comment)
# Find domains where 3+ different advisors commented
for domain, comments in domain_map.items():
unique_advisors = {c.advisor_id for c in comments}
if len(unique_advisors) >= 3:
agreements.append({
"type": "domain_consensus",
"key": domain,
"advisors": list(unique_advisors),
})
Bramwell Finch (the Devil’s Advocate) is specifically excluded from agreements: he’s the contrarian by design, and including him would dilute the signal.
When agreement is detected, priority escalates automatically:
ESCALATION_PRIORITY = {
1: {"P1": "P1", "P2": "P2", "P3": "P3"}, # single advisor
2: {"P1": "P1", "P2": "P1", "P3": "P2"}, # 2 advisors agree
3: {"P1": "P1", "P2": "P1", "P3": "P1"}, # 3+ advisors agree
}
If The Strategist, The Fixer, and The Wellness Coach all flag “goals” in the same week, a routine P3 observation becomes a P1 action item. Created automatically, with a webhook to my AI agent team.
Memory Distillation
When you rate an advisor comment ≥ 4/5 or mark it as “acted on,” it becomes a memory candidate. An LLM call extracts a reusable insight:
# Memory types:
# - observation: "User responds well to data-backed advice"
# - preference: "Prefers morning routines over evening"
# - effective_strategy: "3-item action lists get acted on"
# - ineffective_strategy: "Generic encouragement rated poorly"
# Injected into future prompts:
"""
--- Your Memories (learned from past interactions) ---
- [Effective Strategy] Concrete 3-item action lists get acted on
- [Observation] Mood dips correlate with low social contact
- [Preference] Prefers direct language over gentle suggestions
--- End Memories ---
"""
The loop closes: advisor speaks → user rates → memory extracted → injected into future prompts → advisor speaks better. This is reinforcement learning through human feedback, but for persona calibration rather than model weights.
After a few months, Jax learned that 3-item action lists get acted on, so he always gives exactly 3 items now. Miriam learned that gratitude framing lands better than “you should be grateful.” These aren’t hallucinations. They’re genuine adaptations.
The Full Trigger Catalog
The system currently has 34 trigger conditions across 9 advisors:
| Advisor | Trigger | Condition |
|---|---|---|
| Alistair Vance | goal_stalled | No progress in 7+ days |
| Alistair Vance | output_input_imbalance | 30+ commits but < 5 mood/sleep logs |
| Sister Miriam | mood_drop | Below personal p25 for 3+ days |
| Sister Miriam | social_isolation | No interactions in 5+ days |
| Sister Miriam | relationship_neglect | Past contact cadence deadline |
| Jax Thorne | habit_decline | Completion below personal p25 |
| Jax Thorne | keystone_missed | Keystone habit missed, cascade risk |
| Coach Miller | sleep_decline | > 1 std dev below baseline |
| Coach Miller | baby_sleep_disruption | 3+ baby wake-ups AND parent < 6h |
| Coach Miller | overcommitment | 6+ meeting hours, score ≥ 7 |
| Coach Miller | burnout_risk | Trajectory predicts threshold in <10 days |
| Bramwell Finch | complacency_check | All metrics above p75 for 3+ days |
| Bramwell Finch | pillar_recession | Below target for 3+ consecutive days |
| Birdie Malone | hyperfocus_detected | 50+ commits with < 10 songs + articles |
| Vivienne LeClair | romantic triggers | Date gaps, intimacy frequency, love language balance |
Triggers respect adoption: social isolation only fires if you’ve actually logged social events before.
What It’s Like to Live With
I’ve been running this daily for three months. The advisors catch patterns raw dashboards miss:
- Bramwell’s complacency check fired during a week where every metric looked great. Turned out I was avoiding a hard conversation and the “great metrics” were just distraction-driven productivity.
- Birdie’s hyperfocus detection caught a 3-day coding sprint where I hadn’t logged mood, sleep, or eaten a real meal. 87 commits, zero songs played.
- Sister Miriam’s relationship neglect surfaced a friendship that had gone silent for two months, not because of conflict, just drift.
- Coach Miller’s baby_sleep_disruption combined my son’s wake-up data with my own sleep quality to say “this isn’t just bad sleep. It’s unsustainable parenting load. Ask for help.”
The agreement system is the highest-signal feature. One week, Coach Miller flagged sleep decline, Sister Miriam flagged low mood, and Alistair flagged goal stall. Independently, on different days. The system detected domain consensus on “health,” escalated to P1, and created an action item: “Protect recovery time.” I rated it 5/5. The distilled memory: “When sleep, mood, and goals all decline together, the root cause is usually overcommitment, not any single domain.”
That’s the kind of insight you don’t get from a chart.
Build Your Own (Starter Pattern)
You don’t need 9 advisors and 34 triggers. Here’s a minimal version:
# advisors.py — Minimal 3-advisor setup
ADVISORS = {
"optimist": {
"name": "The Optimist",
"voice": "Focus on what's working. Amplify the positive signal.",
"domains": ["mood", "habits", "goals"],
},
"realist": {
"name": "The Realist",
"voice": "Look at the numbers. What do they actually say?",
"domains": ["sleep", "health", "habits"],
},
"contrarian": {
"name": "The Contrarian",
"voice": "Everyone agrees? Then someone isn't thinking hard enough.",
"domains": ["goals", "mood"],
},
}
# triggers.py — 5 starter triggers
async def evaluate(data: dict) -> list[dict]:
triggered = []
if data["days_since_goal_update"] > 7:
triggered.append({
"advisor": "realist",
"trigger": "goal_stalled",
"context": f"No progress in {data['days_since_goal_update']} days",
})
if data["mood_avg"] > 7 and data["sleep_avg"] > 7:
triggered.append({
"advisor": "contrarian",
"trigger": "complacency",
"context": "All metrics above threshold for 3+ days",
})
if data["mood_avg"] < 4:
triggered.append({
"advisor": "optimist",
"trigger": "mood_low",
"context": f"Mood averaging {data['mood_avg']:.1f} over 3 days",
})
return triggered
The architecture scales linearly: add more advisors, add more triggers, and the cooldown + agreement systems handle coordination automatically.
Dashboards That Think in Stereo
The advisory council pattern works anywhere you have data that needs interpretation, not just alerting. Team health dashboards, infrastructure monitoring, financial tracking: anywhere a single perspective flattens nuance, multiple personas restore it.
The memory distillation loop is what separates this from a rule engine. Over months, the advisors genuinely learn your preferences, your patterns, and your blind spots. It’s not AGI. It’s 9 frozen dataclasses, 34 threshold checks, and a feedback loop. But it caught a burnout trajectory I missed, surfaced relationships I was neglecting, and asked uncomfortable questions about weeks I thought were going great.
The best advice I’ve gotten this year didn’t come from a person. It came from nine fictional characters arguing about my sleep data.