Board Meetings
AI-facilitated board meetings where historical-figure personas (Stoic philosopher, investor, inventor, entrepreneur, TV host, president) deliberate on health and life decisions using Robert's Rules of Order. There is a pool of 7 personas, but a single meeting seats a smaller panel: the scheduled/direct path dynamically selects 5 attendees (chairman + 2 fixed members + 2 rotating members keyword-matched to the agenda). The system orchestrates parallel Claude CLI calls, manages voting, and produces formal resolutions with action items. Meetings run automatically on Sundays at 6AM PT via the scheduler, with a post-meeting secretary review emailed after completion.
Note: Two distinct entry paths set the attendee list differently. The scheduled cron path and the direct CLI runner (
run_meeting_direct.py) callselect_meeting_attendees()to seat 5. The interactive web-UI/API path defaults the meeting'sattendeesto all board members (get_all_board_members()) unless the create request supplies an explicit list. This page documents both.
Table of Contents
- How It Works
- Why This Design
- Agenda-Driven Attendee Selection
- Chairman DSL
- Trigger Points
- Meeting Flow
- Agent Personas
- Data Structures
- AI Facilitation Prompts
- File Locations
- Error Handling
- Known Limitations
- Improvement Opportunities
- Post-Meeting Secretary Review
How It Works
The board meeting system follows a structured lifecycle from creation to adjournment:
MEETING LIFECYCLE
=================
User creates meeting via web UI
|
v
+------------------+ +--------------------+
| POST /meetings | --> | Chairman drafts |
| (status:created) | | agenda from goals, |
+------------------+ | health data |
| +--------------------+
v |
+------------------+ v
| AGENDA_DRAFT | <-- ChatRoom conversation
| User reviews | with chairman (Marcus
| agenda with | can edit meeting.json
| chairman | directly via Edit tool)
+------------------+
|
v (approve)
+------------------+
| PREPARED |
| Ready to start |
+------------------+
|
v (start)
+------------------+
| IN_PROGRESS |
+------------------+
|
v
+------------------+ +------------------+ +------------------+
| OPENING | --> | QUORUM (Roll | --> | Check: quorum |
| Chairman calls | | Call via parallel| | (3) of seated |
| to order | | agent calls) | | members PRESENT |
+------------------+ +------------------+ +------------------+
|
v
+--------------------------------+
| |
v |
+-----------------------+ |
| FOR EACH AGENDA ITEM | <-----------------+
+-----------------------+
|
+---------------+---------------+
| |
v (decision item) v (non-decision)
+------------------+ +------------------+
| INTRO: Chairman | | INTRO + DISCUSS |
| introduces topic | | Round-robin |
+------------------+ | then summary |
| +------------------+
v
+------------------+
| DISCUSSION |
| All agents share |
| perspectives |
+------------------+
|
v
+------------------+
| MOTION phase |
| Chairman drafts |
| formal motion |
| "RESOLVED, That.."
+------------------+
|
v
+------------------+
| SECOND phase |
| Any member can |
| second (excludes |
| proposer) |
+------------------+
|
v (seconded)
+------------------+
| MOTION DISCUSSION|
| Focused on the |
| motion text, |
| amendments OK |
+------------------+
|
v
+------------------+
| VOTING |
| Parallel vote |
| FOR/AGAINST/ |
| ABSTAIN with |
| whisper mode |
+------------------+
|
v
+------------------+
| ACTION_ITEMS |
| Extract tasks |
| with owners and |
| deadlines |
+------------------+
|
v
+------------------+
| Save Resolution |
| RES-{mtg}-{item} |
+------------------+
|
v (next item or closing)
+------------------+
| CLOSING |
| Chairman summary |
+------------------+
|
v
+------------------+
| ADJOURNMENT |
| Formal close, |
| email summary, |
| Telegram notify |
+------------------+
Why This Design
Robert's Rules of Order: Formal procedure creates structured debate and clear decision records. The motion/second/vote cycle ensures every decision has a paper trail.
Distinct Perspectives, Dynamically Seated: The 7-persona pool spans complementary philosophies (discipline vs. creativity, action vs. patience, systems vs. intuition, ambition vs. virtue). Rather than convene all 7 on every agenda, the scheduled path seats 5: a fixed chairman + 2 fixed members + 2 rotating members chosen to match the agenda's themes while avoiding philosophical overlap. See Agenda-Driven Attendee Selection for the mechanism. This keeps debate relevant to the topic and reduces wall-clock time and token cost.
Batched Parallel Execution: Agent calls are batched (2 at a time, batch_size=2 default in cli_runner.py) to avoid Claude API rate limits while keeping meetings reasonably fast. A full meeting with several agents and 3-5 agenda items takes roughly 15-30 minutes of wall-clock time. (The direct runner overrides batch_size per phase β e.g. roll call uses 5 β since fewer seated members fit under the rate ceiling.)
Quorum System: Prevents decisions when too many agents fail. Quorum is 3 for a 5-seat meeting (board_config.json: quorum, mirrored by run_meeting_direct.py: MIN_QUORUM = 3). If quorum is lost during roll call, the meeting cannot proceed.
Atomic Checkpointing: State is checkpointed after every phase transition via _sync_and_checkpoint(). This enables:
- Pause/resume mid-meeting
- Crash recovery from server restart
- Session restoration with deterministic session IDs
Whisper Mode: Agents can include <thinking> tags for internal reasoning that isn't shown in the transcript. The <speaking> portion is what gets broadcast.
Direct File Editing: The chairman can edit meeting.json directly via the Edit tool during agenda review conversations, removing the need for special AGENDA_ACTION parsing.
Agenda-Driven Attendee Selection
Who sits on a given meeting is computed from the agenda. The mechanism lives in scripts/board/identity.py:select_meeting_attendees() and is driven by a small composition config.
Composition config
data/board/board_config.json is the single source of truth for who can attend and the quorum rules. identity.py falls back to a hard-coded _DEFAULT_CONFIG (identical to the shipped file) if it's missing.
{
"chairman": "aurelius",
"fixed_members": ["roosevelt", "dalio"],
"rotating_pool": ["davinci", "jobs", "musk", "rogers"],
"quorum": 3,
"max_attendees": 5
}
| Field | Meaning |
|---|---|
chairman |
Always seated; presides and votes. |
fixed_members |
Always seated alongside the chairman. |
rotating_pool |
Candidates for the 2 rotating seats, chosen per agenda. |
quorum |
Minimum members responding PRESENT for the meeting to proceed. |
max_attendees |
Seat cap (chairman + fixed + rotating). |
The loader tolerates an older single fixed_member key for backward compatibility.
Selection algorithm
select_meeting_attendees(agenda_items) always returns [chairman, *fixed_members, rotating_1, rotating_2]:
- Concatenate agenda text β
topic+descriptionof every agenda item, lowercased. - Score the pool β each rotating-pool member gets a point for every keyword in its
AGENT_THEMESentry (scripts/board/context_pool.py) that appears in the agenda text. - Pick the top scorer as the first rotating member (ties broken by member id).
- Apply a diversity penalty for the second pick: candidates that overlap philosophically with the first pick are penalized via
OVERLAP_PAIRS, then the highest adjusted score wins.
# scripts/board/identity.py β diversity penalties applied to the 2nd rotating seat
OVERLAP_PAIRS = {
frozenset({"musk", "roosevelt"}): 3, # both bias toward speed/action
frozenset({"aurelius", "rogers"}): 2, # both bias toward patience/care
frozenset({"dalio", "musk"}): 2, # both bias toward systems/engineering
frozenset({"jobs", "davinci"}): 2, # both bias toward creativity/design
}
AGENT_THEMES is the keyword map that powers scoring (excerpt):
# scripts/board/context_pool.py
AGENT_THEMES = {
"aurelius": ["virtue", "duty", "discipline", "stoic", "patience"],
"dalio": ["efficiency", "data", "principles", "systems", "metrics"],
"davinci": ["innovation", "curiosity", "exploration", "creativity"],
"jobs": ["experience", "design", "simplicity", "user", "taste"],
"musk": ["ambition", "engineering", "scale", "first-principles", "speed"],
"rogers": ["empathy", "kindness", "emotional", "relationships", "patience"],
"roosevelt": ["action", "courage", "vigor", "conservation", "boldness"],
}
Voting-eligibility model
Seating is config-driven; voting eligibility is read from each persona's JSON config:
Helper (scripts/board/identity.py) |
Reads | Returns |
|---|---|---|
get_chairman() |
board.role == "chairman" |
the chairman's member id |
get_voting_members() |
board.voting == true |
all voting member ids |
get_all_board_members() |
agents/board/*.json (skips _-prefixed) |
every persona id |
Note:
get_all_board_members()is what the interactive API path uses to defaultattendeesβ which is why an ad-hoc web meeting can seat all 7 while the scheduled path seats 5.
Chairman DSL
The chairman uses an embedded Domain-Specific Language (DSL) with structured commands wrapped in braces {}. Commands are embedded inline within natural language responses and parsed by scripts/board/dsl.py.
Core Concept
Instead of relying on unstructured LLM output, the chairman translates meeting actions into parseable commands:
"The motion has been seconded by Ray. Let us now vote on this resolution.
{SECOND: @dalio}
{PHASE: voting}
{DISCUSS}"
The natural language provides context for the transcript; the commands drive the meeting engine.
Command Reference
Meeting Opening
| Command | Syntax | Description |
|---|---|---|
ROLL |
{ROLL: @elon=present, @fred=present, @teddy=absent} |
Record attendance for all members |
QUORUM |
{QUORUM} |
Announce that quorum has been established |
Discussion Control
| Command | Syntax | Description |
|---|---|---|
DISCUSS |
{DISCUSS} |
Trigger full discussion round - all members speak or respond with "PASS" |
CALL |
{CALL: @steve @elon} |
Call on specific members to speak |
Motion Handling
| Command | Syntax | Description |
|---|---|---|
MOTION |
{MOTION: "RESOLVED, That..."} |
Propose a formal motion (must use "RESOLVED, That..." format) |
SECOND |
{SECOND: @steve} |
Record who seconded the motion |
AMENDMENT |
{AMENDMENT: @member "full amended text" "what changed"} |
Record an amendment with the proposer, new text, and summary |
TABLE |
{TABLE} |
Defer the motion to a future meeting |
WITHDRAW |
{WITHDRAW} |
Motion withdrawn by proposer |
Voting & Dissent
| Command | Syntax | Description |
|---|---|---|
VOTE |
{VOTE: @steve=FOR, @elon=AGAINST "reason", @fred=ABSTAIN} |
Record votes with optional reasons |
DISSENT |
{DISSENT: @elon "Formal dissent statement..."} |
Record a formal dissent for the record |
Procedural
| Command | Syntax | Description |
|---|---|---|
PHASE |
{PHASE: voting} |
Announce a phase transition |
RULING |
{RULING: "The point of order is well taken..."} |
Chairman's procedural ruling |
NEXT |
{NEXT} |
Advance to next agenda item |
OPEN |
{OPEN} |
Indicate floor is open for optional remarks (no action triggered) |
ADJOURN |
{ADJOURN} |
End the meeting |
Action Items
| Command | Syntax | Description |
|---|---|---|
ACTION |
{ACTION: "Task description" @owner "deadline"} |
Record an action item with owner and optional deadline |
Command Parsing
Location: scripts/board/dsl.py
The parser uses regex to extract commands from the chairman's response:
# Pattern: {COMMAND: body} or {COMMAND}
COMMAND_PATTERN = re.compile(r'\{(\w+):\s*([^}]+)\}|\{(\w+)\}')
# Vote entries: @name=VOTE or @name=VOTE "reason"
VOTE_ENTRY = re.compile(r'@(\w+)=(\w+)(?:\s+"([^"]*)")?')
# Attendance entries: @name=present/absent
ATTENDANCE_ENTRY = re.compile(r'@(\w+)=(\w+)')
Each command is parsed into a typed dataclass:
@dataclass
class VoteCommand:
votes: dict[str, tuple[str, str | None]] # member -> (vote, reason)
@dataclass
class MotionCommand:
text: str
@dataclass
class ActionCommand:
task: str
owner: str
deadline: str | None
Chairman System Prompt
The chairman receives the DSL documentation in its system prompt (scripts/board/prompts.py:CHAIRMAN_SYSTEM):
You are the Chairman of the Board. You run formal board meetings
following Robert's Rules of Order.
## Your Role
- Open meeting with roll call and quorum check
- Guide discussion, call on members, interpret their responses
- Translate natural language into structured commands
- Ensure proper procedure (motion β second β discussion β vote)
- Rule on points of order raised by members
## Command DSL
Use these commands inline in your responses:
{ROLL: @elon=present, @fred=present}
{DISCUSS}
{MOTION: "RESOLVED, That..."}
...
Meeting Flow with DSL
A typical agenda item flows like this:
- Introduction: Chairman introduces topic in natural language
- Discussion:
{DISCUSS}triggers parallel agent calls, responses collected - Motion: Chairman drafts motion using
{MOTION: "RESOLVED, That..."} - Seeking Second:
{CALL: @dalio @jobs}or{DISCUSS}to find a second - Recording Second:
{SECOND: @dalio}when someone seconds - Final Discussion: Another
{DISCUSS}for focused debate on the motion - Voting:
{PHASE: voting}then{VOTE: @steve=FOR, @elon=AGAINST "reason"} - Action Items:
{ACTION: "Task" @shareholder "2026-01-15"}
Why a DSL?
- Reliability: Structured commands are easier to parse than natural language
- Auditability: Commands create a clear log of procedural actions
- Flexibility: Chairman can combine natural language context with structured actions
- Consistency: Same command format across all meeting types
Trigger Points
| Entry Point | Location | Method |
|---|---|---|
| Create meeting | POST /api/v1/board/meetings |
Creates meeting with initial status |
| Request agenda | POST /api/v1/board/meetings/{id}/request-agenda |
Chairman drafts agenda from context |
| Chat with chairman | POST /api/v1/board/meetings/{id}/conversation |
Live chat during agenda review |
| Approve agenda | POST /api/v1/board/meetings/{id}/approve-agenda |
Locks agenda, status -> PREPARED |
| Edit agenda | POST /api/v1/board/meetings/{id}/edit-agenda |
Return to AGENDA_DRAFT status |
| Revise agenda | POST /api/v1/board/meetings/{id}/revise-agenda |
Chairman revises based on feedback |
| Start meeting | POST /api/v1/board/meetings/{id}/start |
Launches orchestrator task |
| Pause meeting | POST /api/v1/board/meetings/{id}/pause |
Pauses at next checkpoint |
| Resume meeting | POST /api/v1/board/meetings/{id}/resume |
Resumes from state.json |
| User input | POST /api/v1/board/meetings/{id}/user-input |
Queue input for next checkpoint |
| Get transcript | GET /api/v1/board/meetings/{id}/transcript |
Read meeting transcript |
| Get summary | GET /api/v1/board/meetings/{id}/summary |
Executive summary (generates on demand) |
| Regenerate summary | POST /api/v1/board/meetings/{id}/summary/regenerate |
Force regenerate, optionally email |
| Summary feedback | PUT /api/v1/board/meetings/{id}/summary/feedback |
Add feedback to summary |
| Delete meeting | DELETE /api/v1/board/meetings/{id} |
Delete meeting and all data |
| WebSocket updates | WS /api/v1/board/meetings/{id}/ws |
Real-time transcript & phase changes |
| Broadcast (internal) | POST /api/v1/board/meetings/{id}/broadcast |
CLI scripts send WS updates |
| List resolutions | GET /api/v1/board/resolutions |
All resolutions, filter pending_only |
| Get resolution | GET /api/v1/board/resolutions/{id} |
Single resolution detail |
| Delete resolution | DELETE /api/v1/board/resolutions/{id} |
Delete a resolution |
| Respond to resolution | POST /api/v1/board/resolutions/{id}/respond |
Accept/reject/defer resolution |
| List action items | GET /api/v1/board/actions |
Actions across all resolutions |
| List members | GET /api/v1/board/members |
All board member profiles (the 7-persona pool) |
| Get member | GET /api/v1/board/members/{id} |
Single board member profile |
| Agenda item commentary | PUT /api/v1/board/meetings/{id}/agenda/items/{item_id}/commentary |
Add user notes to item |
| Agenda feedback | PUT /api/v1/board/meetings/{id}/agenda/feedback |
Overall feedback, request revision |
| Clear conversation | DELETE /api/v1/board/meetings/{id}/conversation |
Reset chairman chat |
CLI Scripts: Two end-to-end runners exist.
scripts/board/run_meeting_direct.pyβ bypasses the API entirely, building the meeting in-process. It callsselect_meeting_attendees()to seat 5, runs all phases, sends Telegram updates, broadcasts to WebSocket clients, and finishes with a secretary review (see Post-Meeting Secretary Review). This is the path the scheduler uses.scripts/board/run_meeting.pyβ drives a meeting through the API (httpxcalls to create β request/approve agenda β start β poll), useful for exercising the real HTTP surface.
No /board CLI command or skill exists. Interactive meetings are initiated via the web UI at /board; automated meetings run via the scheduler.
Meeting Flow
Phase Sequence
For decision items (type: decision):
OPENING -> QUORUM -> [INTRO -> DISCUSSION -> MOTION -> SECOND -> MOTION_DISCUSSION -> VOTING -> ACTION_ITEMS]* -> CLOSING -> ADJOURNMENT
For non-decision items (type: review, discussion, information):
OPENING -> QUORUM -> [INTRO -> DISCUSSION (round-robin) -> summary]* -> CLOSING -> ADJOURNMENT
Phase Details
"All members" below means all seated attendees (5 on the scheduled path, up to 7 on an ad-hoc API meeting).
| Phase | What Happens | Agent Calls | Checkpoint |
|---|---|---|---|
OPENING |
Chairman calls meeting to order | 1 call to chairman | Yes |
QUORUM |
Roll call - all members respond "PRESENT" | Parallel call to all members (batched) | Yes |
INTRO |
Chairman introduces agenda item | 1 call to chairman | Yes |
DISCUSSION |
All members share perspectives (whisper mode) | Parallel call to all members (batched) | Yes |
MOTION |
Chairman drafts formal motion (with retry) | 1-2 calls | Yes |
SECOND |
Seek second from any voting member | Parallel call to non-proposers (excludes proposer) | Yes |
MOTION_DISCUSSION |
Focused discussion, amendments possible | Parallel call to all members + amendment prompt | Yes |
VOTING |
Formal vote collection (whisper mode) | Parallel call to all members (batched) | After each vote |
ACTION_ITEMS |
Extract tasks from adopted motion | 1 call to chairman | Yes |
CLOSING |
Chairman summarizes decisions | 1 call to chairman | Yes |
ADJOURNMENT |
Formal meeting close, notifications sent | 1 call to chairman | Yes |
State Machine
class BoardMeetingStatus(str, Enum):
CREATED = "created" # Initial state after POST /meetings
AGENDA_DRAFT = "agenda_draft" # Chairman drafting/reviewing agenda
PREPARED = "prepared" # Agenda approved, ready to start
IN_PROGRESS = "in_progress" # Meeting actively running
PAUSED = "paused" # Meeting paused at checkpoint
COMPLETED = "completed" # Meeting finished successfully
FAILED = "failed" # Meeting error (recoverable)
QUORUM_LOST = "quorum_lost" # Not enough agents responding
Agent Personas
The full pool is 7 personas, each with a distinct perspective loaded from agents/board/{member_id}.json. The chairman and the two fixed members are always seated; the other four form the rotating_pool that the selection algorithm draws two from per meeting.
| Agent ID | Display Name | Role | Perspective |
|---|---|---|---|
aurelius |
Marcus Aurelius | Chairman | Stoic philosophy - virtue, duty, long-term thinking, accepts what cannot be changed |
dalio |
Ray Dalio | Member | Principles-based decisions, radical transparency, systematic thinking, data-driven |
davinci |
Leonardo da Vinci | Member | Polymath thinking, curiosity, cross-domain synthesis, creative exploration |
jobs |
Steve Jobs | Member | Simplicity, user experience, design thinking, taste and intuition |
musk |
Elon Musk | Member | First principles, ambitious goals, engineering focus, speed over perfection |
rogers |
Fred Rogers | Member | Emotional intelligence, kindness, patience, relationship focus |
roosevelt |
Teddy Roosevelt | Member | Bold action, vigor, conservation, courage over caution |
Agent Configuration Structure
{
"v": 1,
"id": "board-aurelius",
"name": "Marcus Aurelius",
"description": "Chairman of the Board - Stoic philosophy and virtue ethics",
"extends": "base",
"launchable": false,
"tags": ["board"],
"board": {
"role": "chairman",
"display_name": "Marcus Aurelius",
"voting": true,
"perspective": "Stoic philosophy emphasizing virtue, duty..."
},
"claude_md": {
"sections": [
{
"heading": "## Your Identity",
"content": "You are Marcus Aurelius, Roman Emperor...",
"position": "start"
},
{
"heading": "## Parliamentary Procedure (Robert's Rules)",
"content": "This board follows Robert's Rules of Order...",
"position": "after:## Your Identity"
},
{
"heading": "### Chairman Duties",
"content": "As Chairman, you have specific responsibilities...",
"position": "after:## Parliamentary Procedure"
}
]
}
}
Facilitator Fallback Chain
If the chairman (aurelius) fails health check, the system falls back to:
1. dalio (systems thinker - procedural backup)
2. davinci (creative adapter - tertiary backup)
Each facilitator is tested with a 30-second health check before selection.
Data Structures
BoardMeeting
attendees is whatever the entry path computed: the 5 ids from select_meeting_attendees() on the scheduled/direct path, or the all-pool default below on an ad-hoc API meeting.
// data/board/meetings/mtg-{id}/meeting.json
{
"id": "mtg-9fed5d5a",
"name": "Weekly Health Review",
"scheduled_start": null,
"status": "agenda_draft",
"attendees": ["aurelius", "dalio", "davinci", "jobs", "musk", "rogers", "roosevelt"],
"agenda": {
"meeting_id": "mtg-9fed5d5a",
"status": "draft",
"items": [
{
"id": "agenda-1",
"topic": "Goal Structure and Early Momentum",
"type": "decision",
"time_minutes": 15,
"description": "We enter 2026 with 12 active goals...",
"desired_outcome": "Decide on a framework for tracking...",
"user_commentary": ""
}
],
"standing_items": ["Opening remarks", "Closing remarks"],
"user_feedback": "",
"revision_requested": false
},
"created_at": "2026-01-03T22:17:39.174118",
"started_at": null,
"completed_at": null
}
MeetingState (Checkpoint)
// data/board/meetings/mtg-{id}/state.json
{
"meeting_id": "mtg-abc123",
"status": "in_progress",
"current_agenda_index": 2,
"current_phase": "discussion",
"votes_collected": {
"aurelius": "FOR",
"dalio": "FOR",
"musk": "AGAINST"
},
"vote_reasons": {
"aurelius": "Aligns with our long-term health goals",
"dalio": "The data supports this intervention",
"musk": "Timeline is too conservative"
},
"vote_dissents": {
"musk": "We should move faster on implementation"
},
"vote_announced": false,
"resolutions_saved": ["RES-mtg-abc123-1"],
"agent_sessions": {
"aurelius": "a1b2c3d4e5f67890",
"dalio": "b2c3d4e5f6789012"
},
"present_members": ["aurelius", "dalio", "davinci", "jobs", "musk", "rogers", "roosevelt"],
"transcript": [
["Marcus Aurelius", "This meeting is now called to order.", "2026-01-03T10:00:00"],
["Ray Dalio", "PRESENT", "2026-01-03T10:00:15"]
],
"last_checkpoint_ts": "2026-01-03T10:15:30.123456",
"error_count": 0,
"user_input_pending": []
}
Resolution
// data/board/resolutions/RES-mtg-abc123-1.json
{
"id": "RES-mtg-abc123-1",
"meeting_id": "mtg-abc123",
"agenda_item_id": "agenda-1",
"topic": "Increase Weekly Cardio",
"motion": {
"id": "MOT-mtg-abc123-agenda-1",
"text": "RESOLVED, That the shareholder shall increase weekly cardio sessions from 2 to 3, targeting a minimum of 30 minutes per session, effective immediately.",
"proposed_by": "aurelius",
"seconded_by": "dalio",
"status": "adopted",
"original_text": null,
"created_at": "2026-01-03T10:30:00"
},
"vote": {
"for": ["aurelius", "dalio", "rogers", "roosevelt"],
"against": ["musk"],
"abstain": ["jobs", "davinci"]
},
"dissents": [
{
"member_id": "musk",
"member_name": "Elon Musk",
"statement": "Three sessions is not ambitious enough.",
"recorded_at": "2026-01-03T10:45:00"
}
],
"action_items": [
{
"id": "ACT-12345678",
"task": "Schedule 3 cardio sessions per week on calendar",
"owner": "Shareholder",
"deadline": "2026-01-10",
"resolution_id": "RES-mtg-abc123-1",
"status": "pending"
}
],
"user_response": null,
"created_at": "2026-01-03T10:50:00"
}
ConversationHistory
// data/board/meetings/mtg-{id}/conversation.jsonl (one JSON per line)
{"id": "msg-abc123", "role": "user", "content": "Can we add a sleep goal?", "timestamp": "..."}
{"id": "msg-def456", "role": "assistant", "content": "Certainly. I've added...", "timestamp": "...", "agenda_action": null}
AI Facilitation Prompts
Chairman System Prompt (with DSL)
Location: scripts/board/prompts.py:CHAIRMAN_SYSTEM
The chairman receives a system prompt that defines: - Their role as meeting facilitator - The complete DSL command reference (see Chairman DSL) - Meeting flow expectations - Procedural guidelines
This is the core prompt that enables structured meeting control. See the Chairman DSL section for the full command reference.
Agenda Generation Prompt
Location: scripts/board/agenda.py:build_agenda_prompt()
The agenda prompt combines multiple data sources:
Draft an agenda for the upcoming board meeting.
**Available Context:**
## Active Goals (Raw)
{loaded from goals/active/*.json with progress percentages}
## Goal Progress Analysis (Memory View)
{from memory/views/goal-progress.json - status breakdown, attention needed}
## Recent Garmin Data (past 3 days)
{sleep hours, HRV, steps, body battery ranges}
## Recent Check-ins (past 7 days)
{energy, mood, key responses}
## Health Snapshot
{from memory/views/health-snapshot.json}
## Training Status
{from memory/views/training-status.json}
## Week Summary
{from memory/views/week-summary.json}
## Pending Resolutions (awaiting user response)
{resolutions without user_response}
**Instructions:**
Create 3-5 agenda items covering the most important topics.
Consider: goal progress reviews, health patterns, training compliance.
**Format each item as:**
1. [TOPIC] (type: review|discussion|decision)
Description: What we need to discuss
Desired outcome: What decision or action we want
Motion Generation Prompt
Location: api/src/services/board.py:_generate_motion()
The board needs a motion on: {topic}
{description}
Draft a formal motion. Your response must include:
RESOLVED, That <specific person/role> shall <concrete action> by <specific date or "immediately">.
Example: "RESOLVED, That the user shall establish a 7-hour minimum sleep target effective immediately."
Be SPECIFIC - use real names, concrete actions, and actual deadlines. Do NOT use placeholder brackets.
If the first attempt fails format validation (no RESOLVED, contains [brackets], too short), a retry prompt is sent with explicit format requirements.
Vote Collection Prompt
Location: api/src/services/board.py:_collect_formal_vote()
The vote has been called on:
{motion in box format}
Cast your vote in this EXACT format:
VOTE: FOR (or AGAINST or ABSTAIN)
REASON: Your 1-2 sentence explanation for the record
DISSENT: If voting AGAINST, state your formal objection for the record
Remember: Your vote is on the motion as written above.
{WHISPER_WRAPPER - allows <thinking> tags for internal reasoning}
Chairman Conversation Prompt
Location: scripts/board/chairman_conversation.py:_build_conversation_prompt()
You are Marcus Aurelius, Chairman of the Personal Board of Directors.
You are having a conversation with the user about the meeting agenda. Your role is to:
1. Explain your reasoning for agenda items when asked
2. Answer questions about priorities and timing
3. Make changes to the agenda based on feedback
4. Suggest improvements when appropriate
## Current Agenda
{agenda_json}
## Context Summary
{goals and health summary from memory views}
## Conversation History
{last 10 messages}
## User's Message
{user_message}
## Instructions
Respond conversationally. Be concise and stoic.
## Modifying the Agenda
You have full authority to modify the agenda. The meeting file is at:
{meeting_file_path}
When you need to modify the agenda, use the Edit tool to update the meeting.json file directly.
File Locations
| Purpose | Path | Format |
|---|---|---|
| API Router | api/src/routers/board.py |
FastAPI endpoints (prefix /api/v1/board) |
| Orchestrator | api/src/services/board.py |
Meeting execution logic (~2577 lines) |
| Schemas | api/src/schemas/board.py |
Pydantic models (~449 lines) |
| Agent Configs | agents/board/*.json |
7 persona definitions (the pool) |
| Composition Config | data/board/board_config.json |
Chairman, fixed/rotating members, quorum, max attendees |
| CLI Runner | scripts/board/cli_runner.py |
Claude subprocess execution, MIN_QUORUM, batching |
| Context Pool | scripts/board/context_pool.py |
Agent context assembly, AGENT_THEMES, dissenter selection |
| Agenda Builder | scripts/board/agenda.py |
Agenda prompts and parsing |
| Chairman DSL | scripts/board/dsl.py |
Command parser (VOTE, MOTION, DISCUSS, etc.) |
| Resolution Parser | scripts/board/resolution.py |
Vote parsing logic |
| Chairman Chat | scripts/board/chairman_conversation.py |
Agenda review conversation |
| Session Manager | scripts/board/session_manager.py |
Persistent session tracking |
| Turn Manager | scripts/board/turn_manager.py |
@mention routing |
| Identity Loader | scripts/board/identity.py |
Load agent configs, attendee selection, voting model |
| Context Generator | scripts/board/context.py |
Generate board book |
| Summary Generator | scripts/board/summary.py |
Executive summary + email |
| Secretary Review | scripts/board/secretary_review.py |
Post-meeting triage + emailed summary |
| Action-Item Ledger | scripts/board/action_items.py |
Silicon-zack action-item tracker (consumed by /sleep) |
| Resolution Tracking | scripts/board/resolution_tracking.py |
Cross-meeting resolution/action status |
| Data Validation | scripts/board/validation.py |
Pre-meeting state vs. raw-data validation |
| Dashboard Builder | scripts/board/build_dashboard.py |
Resolution-tracking HTML dashboard |
| Prompts | scripts/board/prompts.py |
WHISPER_WRAPPER and others |
| Direct Runner | scripts/board/run_meeting_direct.py |
Bypass API, run meeting (scheduler path) |
| API Runner | scripts/board/run_meeting.py |
Drive a meeting through the HTTP API |
| Scheduled Runner (legacy) | scripts/board/scheduled_meeting.sh |
Shell wrapper; no longer the scheduler's path |
| Meeting Data | data/board/meetings/mtg-{id}/ |
Per-meeting files |
| Resolutions | data/board/resolutions/RES-*.json |
Saved resolutions |
| Action Items | data/board/action_items.json |
Action-item ledger store |
| Resolution Tracking | data/board/resolution_tracking.json |
Cross-meeting tracking store |
| Chairman Chat UI | web/src/components/board/ChairmanChatPanel.tsx |
React chat component |
Error Handling
Quorum System
# scripts/board/cli_runner.py
MIN_QUORUM = 2 # minimum agents for valid meeting decisions (small-board floor)
def check_quorum(results: dict[str, str | Exception]) -> bool:
successful = sum(1 for r in results.values() if isinstance(r, str))
return successful >= MIN_QUORUM
If quorum is lost during roll call, meeting status becomes QUORUM_LOST and cannot proceed. A QuorumError exception is raised.
Note: There are several quorum knobs and they don't all agree β verify against the path you're running.
cli_runner.py:MIN_QUORUM = 2(the low-level check),run_meeting_direct.py:MIN_QUORUM = 3, andboard_config.json:quorum = 3(the config the scheduled path honors). The unit testscripts/board/test_board.py::test_min_quorum_valuestill asserts== 4and is stale against the shipped value β treat 3 as the effective quorum for a 5-seat meeting.
Agent Retry Logic
Location: api/src/services/board.py:_agent_call_with_retry()
- Up to 2 retries per agent call
- Exponential backoff between retries (2^attempt seconds)
- Uses persistent sessions when available (
run_board_agent_with_session) - Falls back to stateless calls if session unavailable
Vote Parsing
Location: scripts/board/resolution.py:parse_vote_response()
- Looks for exact
VOTE: FOR|AGAINST|ABSTAINformat first - Falls back to heuristic keyword matching if exact format not found
- Tracks
heuristic_countto warn if too many votes required fallback parsing - Failed agents during voting default to
ABSTAIN
Motion Parsing
- Looks for
RESOLVED, That...orI move that...formats - Rejects motions with unfilled template brackets
[like this] - Retry prompt sent if first attempt fails validation
- If second attempt fails, agenda item is skipped gracefully
Crash Recovery
- State checkpointed after every phase transition via
_sync_and_checkpoint() - Atomic writes: tmp file +
os.replace()pattern - On API startup,
recover_interrupted_meetings()scans forIN_PROGRESSorPAUSEDmeetings - Restores context pool with transcript from
state.json - Session IDs are deterministic (
MD5(meeting_id:member_id)) for resumption - Resume starts from
current_agenda_index, skipping already-processed items
Known Limitations
Performance Bottlenecks
-
Batch Size = 2: Rate limiting means parallel calls default to 2 agents at a time (
cli_runner.py). A full meeting with several seated agents and 3-5 agenda items can take 15-30 minutes of wall-clock time. -
120-Second Timeout: Per-agent call timeout. Long-winded responses may get truncated.
-
No Streaming to UI: Responses are collected in full before display. WebSocket infrastructure exists but streaming isn't implemented.
Prompt Issues
-
Motion Format Compliance: Agents sometimes ignore the exact
RESOLVED, That...format, requiring retry logic. Sometimes include[placeholder]brackets. -
Vote Parsing Heuristics: When agents don't use exact
VOTE: FORformat, fallback parsing uses keyword matching which can misinterpret nuanced responses. High heuristic parse rates (>50%) trigger warnings. -
Whisper Mode Parsing: The
<thinking>and<speaking>tag extraction is regex-based and can fail on malformed output.
Missing Features
-
No CLI Command: Must use the web UI or
run_meeting_direct.pyto create meetings. A/boardslash command would be useful. -
No Amendment Voting: Friendly amendments are accepted by chairman discretion only. Contested amendments could use a formal amendment process.
-
Vote-Format Compliance: Agents still sometimes ignore the exact
VOTE: FOR/RESOLVED, That...formats, leaning on retry logic and heuristic parsing.
Resolution & Action-Item Follow-up
Action items and resolutions are tracked across meetings β the older "not synced / no historical analysis" limitations are resolved:
- Action-item ledger (
scripts/board/action_items.py): a silicon-zack action-item tracker (list/add/complete/extract <meeting_id>/scopesubcommands) stored atdata/board/action_items.json.extract_from_meeting()pulls{ACTION: "task" @owner "deadline"}commands out of a meeting transcript;scope_for_sleep()buckets pending items into overdue / upcoming-week / open-ended for the executive/sleepcycle to scope work and schedule proactive messages. - Cross-meeting resolution tracking (
scripts/board/resolution_tracking.py):sync_from_resolutions()scans resolution files + the action ledger and maintainsdata/board/resolution_tracking.json, surfacing execution status for follow-up in later agendas (stale afterSTALE_DAYS = 30). - Dashboard (
scripts/board/build_dashboard.py): renders an HTML resolution-tracking dashboard from the ledger, resolutions, and sleep-gate status.
Scheduling
Weekly meetings run automatically on Sundays at 6AM PT (api/src/scheduler.py: BOARD_MEETING_DAY = 6, BOARD_MEETING_HOUR = 6, BOARD_MEETING_MINUTE = 0, scheduled in _board_meeting_loop()). The loop's callback calls api/src/services/scheduler_followups.py:run_board_meeting(), which subprocess-execs python scripts/board/run_meeting_direct.py with a 1800-second (30-minute) timeout and reports completed / failed / timeout. The scheduler sends a Telegram notification on failure. The scripts/board/scheduled_meeting.sh shell wrapper still exists on disk but is no longer the scheduler's execution path. Ad-hoc meetings can still be created via the web UI.
Improvement Opportunities
High Value
-
Streaming Responses: WebSocket infrastructure already exists via
ConnectionManager. Could stream agent output as it arrives rather than waiting for full response. -
External Task-System Sync: Extracted action items already land in the silicon-zack ledger (
action_items.py) and feed/sleep. A further step would push items owned by bio-zack into Precedent automatically using theirowneranddeadline. -
Goal-Drift Meeting Triggers: Weekly meetings already run on schedule. Could add ad-hoc meetings triggered by goal drift detection from
/insightswhen multiple goals are off track.
Medium Value
-
Designated Dissenter:
context_pool.pyhasselect_dissenter()function that picks an agent to play devil's advocate based on motion themes - currently unused. Could create more balanced debate. -
Turn Management:
turn_manager.pysupports @mention routing and speaker queues but isn't fully integrated into meeting flow. Would enable more dynamic conversation. -
Parallel Session Init: Could initialize all agent sessions in parallel during OPENING phase instead of on-demand. Would reduce latency in later phases.
-
Resolution Templates: Common motion patterns (increase/decrease something, establish routine, review progress) could have templates to reduce format compliance issues.
Technical Debt
-
Vote Parsing Robustness: High heuristic parse rates suggest the vote format instructions aren't clear enough. Could add examples or structured output.
-
Consistent Whisper Mode: Not all phases use whisper mode. Standardizing would give more insight into agent reasoning.
-
Meeting Duration Tracking: Currently no tracking of actual meeting duration vs. estimated time. Would help calibrate agenda time estimates.
Post-Meeting Secretary Review
After a meeting completes (run_meeting_direct.py Phase 5), silicon-zack acts as secretary: instead of emailing the raw transcript, it reads the transcript + resolutions and triages every action item into three buckets, then sends a structured summary. Entry point: scripts/board/secretary_review.py:review_and_send(meeting_id).
| Bucket | Meaning |
|---|---|
did_it |
Reversible, < ~15 min, no judgment needed β already handled. |
recommending |
Low-stakes judgment call β proposed, awaiting a nod. |
need_your_call |
Irreversible / high stakes β explicitly kicked to bio-zack. |
The review is persisted to data/board/meetings/mtg-{id}/review.json and emailed (subject Board Review: {meeting_name}) to bio-zack via mail.fastmail.send_email (recipient falls through to the configured default β not hardcoded). If the email fails, the runner falls back to a plain summary. This bucket-based triage is the mechanism by which board output becomes actionable without dumping raw deliberation on the reader.
Quick Reference: Running a Board Meeting
Via Web UI
- Navigate to
/board - Click "New Meeting"
- Enter meeting name
- Wait for chairman to draft agenda
- Review agenda, chat with chairman to modify
- Click "Approve Agenda"
- Click "Start Meeting"
- Monitor transcript via WebSocket
- Respond to resolutions after meeting
Via Direct Script
cd /path/to/vita
PYTHONPATH=/path/to/vita uv run python scripts/board/run_meeting_direct.py
This runs a "Weekly Health & Productivity Review"-style meeting: it builds an agenda, calls select_meeting_attendees() to seat 5, runs all phases, sends Telegram updates at key moments, broadcasts to any connected WebSocket clients, and ends with the secretary review email.
# inspect / manage the action-item ledger produced by meetings
uv run python scripts/board/action_items.py list
uv run python scripts/board/action_items.py scope # buckets for /sleep
uv run python scripts/board/action_items.py extract mtg-xxx # pull {ACTION:} from a transcript
# refresh cross-meeting tracking + the HTML dashboard
uv run python scripts/board/build_dashboard.py
Where to go next
- Executive Loop β the
/sleepcycle that consumes the board's action-item ledger. - Precedent β the task/habit system action items could sync into.
- Scheduling β how the Sunday board-meeting cron is registered.
- Telegram β where in-meeting updates and failure notifications land.
- Model Guidance β
board_meetingruns unpinned (no fixed model); routing is defined there.
For technical architecture details, see the inline comments in api/src/services/board.py (~2577 lines of orchestration logic).