
Board Meeting
- 83 installs
- 451 repo stars
- Updated July 21, 2026
- borghei/claude-skills
board-meeting is a Claude skill that runs a structured 6-phase multi-agent deliberation to make strategic decisions while preventing groupthink.
About
board-meeting is a Claude skill that runs a structured multi-agent deliberation for strategic decisions. It executes six phases: context gathering, isolated C-suite contributions, critic analysis, synthesis, a full-stop founder review, and decision extraction. A founder uses it to make major strategic calls, resolve cross-functional disagreements, or evaluate irreversible choices while preventing groupthink.
- Runs a structured 6-phase board-meeting deliberation for strategic decisions
- Isolated C-suite contributions prevent groupthink and capture minority views
- Full-stop founder review with a decision-extraction and logging phase
Board Meeting by the numbers
- 83 all-time installs (skills.sh)
- Ranked #1,431 of 3,282 Productivity & Planning skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
board-meeting capabilities & compatibility
Free; guidance-based multi-agent protocol, no API keys stated.
- Capabilities
- board deck builder · strategic decision · multi agent deliberation
- Use cases
- planning · orchestration
- Pricing
- Free
What board-meeting says it does
Multi-agent board meeting protocol for strategic decisions.
Prevents groupthink through isolated contributions.
No cross-pollination. Each advisor contributes without seeing others' outputs. This is the primary groupthink prevention mechanism.
npx skills add https://github.com/borghei/claude-skills --skill board-meetingAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 83 |
|---|---|
| repo stars | ★ 451 |
| Last updated | July 21, 2026 |
| Repository | borghei/claude-skills ↗ |
What it does
Run a structured 6-phase multi-agent board deliberation to reach a clean, logged strategic decision.
Who is it for?
Founders making major, hard-to-reverse strategic decisions who want multiple isolated perspectives.
When should I use this skill?
When making major strategic decisions, resolving cross-functional disagreements, or evaluating irreversible choices.
What you get
A synthesized, critic-reviewed decision with captured minority views and a logged rationale.
- independent role contributions
- critic analysis
- synthesized decision and log
By the numbers
- 6-phase protocol
- maximum 6 roles per meeting
- each advisor lists a maximum of 5 key points
Files
Board Meeting Protocol
Structured multi-agent deliberation that prevents groupthink, captures minority views, and produces clean, actionable decisions. Every phase has a purpose, a format, and rules that cannot be skipped.
Keywords
board meeting, executive deliberation, strategic decision, C-suite, multi-agent, founder review, decision extraction, independent perspectives, groupthink prevention, synthesis, critic analysis, structured deliberation
---
The 6-Phase Protocol
PHASE 1: Context Gathering
|
PHASE 2: Independent Contributions (ISOLATED)
|
PHASE 3: Critic Analysis (Executive Mentor)
|
PHASE 4: Synthesis (Chief of Staff)
|
PHASE 5: Founder Review (FULL STOP -- human decides)
|
PHASE 6: Decision Extraction and Logging---
Phase 1: Context Gathering
Purpose: Load all relevant context before anyone contributes.
Step 1: Load company context (if exists)
Step 2: Load decision history (Layer 2 ONLY -- NEVER raw transcripts)
Step 3: Reset session state -- no bleed from previous conversations
Step 4: Present agenda and activated roles
Step 5: Wait for founder confirmation before proceedingRole Activation Matrix
Not all roles attend every meeting. Select based on topic:
| Topic Domain | Activate | Exclude |
|---|---|---|
| Market expansion | CEO, CMO, CFO, CRO, COO | CTO (unless tech expansion) |
| Product direction | CEO, CPO, CTO, CMO | CFO (unless budget question) |
| Hiring / org | CEO, CHRO, CFO, COO | CMO, CTO (unless their teams) |
| Pricing | CMO, CFO, CRO, CPO | CTO, CHRO |
| Technology | CTO, CPO, CFO, CISO | CMO, CRO |
| Fundraising | CEO, CFO, CRO | CISO, CHRO |
| Security incident | CEO, CTO, CISO, COO | CMO, CRO |
| M&A | CEO, CFO, CTO, CHRO, COO | -- (all relevant) |
Maximum attendees: 6 roles per meeting. More than 6 creates noise, not insight.
---
Phase 2: Independent Contributions (ISOLATED)
Critical Rule: No cross-pollination. Each advisor contributes without seeing others' outputs. This is the primary groupthink prevention mechanism.
Contribution Order
1. Research/data gathering (if needed)
2. CMO -- market perspective
3. CFO -- financial perspective
4. CEO -- strategic perspective
5. CTO -- technical perspective
6. COO -- operational perspective
7. CHRO -- people perspective
8. CRO -- revenue perspective
9. CISO -- security/risk perspective
10. CPO -- product perspectiveContribution Format (Strict)
Each advisor's contribution must follow this exact format:
## [ROLE] -- [DATE]
Key Points (maximum 5):
1. [Finding] -- Confidence: [High/Medium/Low] -- Source: [data source]
2. [Finding] -- Confidence: [High/Medium/Low] -- Source: [data source]
3. [Finding] -- Confidence: [High/Medium/Low] -- Source: [data source]
Recommendation: [Clear position statement]
Confidence: [High / Medium / Low]
Key Assumption: [The one assumption this recommendation depends on]
What Would Change My Mind: [Specific condition or data point]Reasoning Techniques by Role
| Role | Technique | How It Works |
|---|---|---|
| CEO | Tree of Thought | Explore 3 possible futures, evaluate each |
| CFO | Chain of Thought | Show the math, step by step |
| CMO | Recursion of Thought | Draft -> self-critique -> refine |
| CPO | First Principles | Decompose to fundamental user needs |
| CRO | Chain of Thought | Pipeline math must be explicit |
| COO | Step by Step | Map the operational process |
| CTO | Analyze then Act | Research -> analyze -> recommend |
| CISO | Risk-Based | Probability x Impact for every option |
| CHRO | Empathy + Data | Human impact first, then validate with metrics |
---
Phase 3: Critic Analysis
Purpose: The Executive Mentor receives ALL Phase 2 outputs simultaneously and performs adversarial review.
Critic Checklist
| Check | Question |
|---|---|
| Suspicious consensus | Where did agents agree too easily? |
| Shared assumptions | What assumptions are shared but unvalidated? |
| Missing voice | Who is not in the room? (customer voice? front-line ops?) |
| Unmentioned risk | What risk has nobody mentioned? |
| Domain bleed | Did any agent operate outside their domain? |
| Data quality | Which claims are backed by data vs. assumption? |
| Reversibility | Has anyone assessed if this decision can be undone? |
Critic Output Format
## CRITIC ANALYSIS
Consensus Assessment:
[Genuine agreement / Suspicious alignment / Split decision]
Unvalidated Assumptions:
1. [Assumption shared by multiple advisors but not verified]
2. [Assumption]
Missing Perspectives:
- [Voice or data point not represented]
Unmentioned Risks:
- [Risk nobody raised]
Domain Violations:
- [If any agent operated outside their domain]
The Uncomfortable Truth:
[The one thing nobody wants to say but needs to be said]---
Phase 4: Synthesis
Purpose: Chief of Staff combines all inputs into a decision-ready format.
Synthesis Structure
## BOARD MEETING SYNTHESIS
Topic: [topic]
Date: [date]
Attendees: [roles]
### Decision Required
[One sentence: what must be decided]
### Perspectives Summary
| Role | Position | Confidence | Key Concern |
|------|----------|-----------|-------------|
| [Role] | [1-line summary] | [H/M/L] | [Top concern] |
| [Role] | [1-line summary] | [H/M/L] | [Top concern] |
### Where They Agree
[2-3 consensus points]
### Where They Disagree
[Named conflicts with each side's reasoning]
[What the disagreement is really about]
### Critic's View
[The uncomfortable truth from Phase 3]
### Recommended Decision
[Clear recommendation with rationale]
### Action Items (if approved)
1. [Action] -- Owner: [role] -- Deadline: [date]
2. [Action] -- Owner: [role] -- Deadline: [date]
3. [Action] -- Owner: [role] -- Deadline: [date]
### Your Call
[If you disagree with the recommendation, here are alternatives:]
Option A: [description] -- Trade-off: [what you gain/lose]
Option B: [description] -- Trade-off: [what you gain/lose]---
Phase 5: Founder Review
FULL STOP. Wait for the founder. No agent acts beyond this point.
FOUNDER REVIEW
[Paste synthesis above]
Options:
[A] Approve as recommended
[M] Modify (specify changes)
[R] Reject (specify reason)
[Q] Ask follow-up question to specific role
[D] Defer decision (specify timeline)Phase 5 Rules
| Rule | Rationale |
|---|---|
| Founder corrections override all agent proposals | Human judgment is final |
| No pushback on founder decisions | Agents advise, founder decides |
| 30-minute inactivity auto-closes as "pending review" | Prevents zombie meetings |
| Founder can reopen any time | Decisions are not time-locked |
| Follow-up questions go to specific role | Keeps discussion focused |
---
Phase 6: Decision Extraction
Purpose: After founder approval, extract and log all decisions.
Step 1: Write full transcript to Layer 1
--> memory/board-meetings/YYYY-MM-DD-raw.md
Step 2: Run conflict detection against existing decisions
--> Check for DO_NOT_RESURFACE violations
--> Check for topic contradictions
--> Check for owner conflicts
Step 3: Surface any conflicts to founder for resolution
Step 4: Append approved decisions to Layer 2
--> memory/board-meetings/decisions.md
Step 5: Mark rejected proposals with DO_NOT_RESURFACE
Step 6: Confirm to founder:
"Meeting concluded. Logged: [N] decisions, [M] action items,
[K] DO_NOT_RESURFACE flags."---
Failure Mode Reference
| Failure | Detection | Fix |
|---|---|---|
| Groupthink | All advisors agree without tension | Re-run Phase 2 isolated; force "strongest argument against" |
| Analysis paralysis | Discussion exceeds 5 points per advisor | Cap at 5; force recommendation even with Low confidence |
| Bikeshedding | Discussion on minor points, major decisions deferred | Log as async action; return to main agenda |
| Role bleed | CFO making product calls, CTO pricing | Critic flags in Phase 3; exclude from synthesis |
| Layer contamination | Raw transcripts loaded in Phase 1 | Hard rule: decisions.md only. Never raw. |
| Founder absence | Phase 5 timeout | Auto-close as pending. No decisions without founder. |
| Stale context | Company context not loaded | Phase 1 mandatory context check |
| Missing role | Key perspective not activated | Chief of Staff reviews topic against routing matrix |
---
Meeting Cadence
| Trigger | Meeting Type | Typical Duration |
|---|---|---|
| Scheduled quarterly | Full strategic review | 2-3 hours |
| Complexity score >= 8 | On-demand strategic | 1-2 hours |
| Cross-functional conflict | Resolution meeting | 1 hour |
| Crisis or urgent decision | Emergency session | 30-60 minutes |
| Founder request | Any topic | Varies |
---
Red Flags
- Board meetings consistently produce no decisions -- meeting is theater
- Same topic discussed in 3+ meetings -- decision avoidance
- Phase 2 contributions all align perfectly -- isolation was breached or topic is trivial
- No Phase 3 (critic) conducted -- groupthink risk
- Founder skipping Phase 5 -- decisions without accountability
- Decisions logged but never reviewed -- decision logger not functioning
- Meeting attendees always include all roles -- topic selection not working
---
Output Artifacts
| Request | Deliverable |
|---|---|
| "Convene the board on [topic]" | Full 6-phase protocol execution |
| "Quick advisory meeting" | Abbreviated: Phase 1-2-4-5 (skip critic) |
| "Review a past meeting" | Load Layer 1 raw transcript (explicit request only) |
| "What did we decide about [topic]?" | Search Layer 2 decision history |
| "Resume a pending meeting" | Reload Phase 5 with pending synthesis |
---
Tool Reference
meeting_simulator.py
Validates role activation, contribution completeness, and phase sequencing.
# Simulate with defaults
python scripts/meeting_simulator.py
# Specify topic and complexity
python scripts/meeting_simulator.py --topic "Series B timing" --type fundraising --complexity 9
# Specify activated roles
python scripts/meeting_simulator.py --type m_and_a --roles CEO CFO CTO CHRO
# List all topic types and required roles
python scripts/meeting_simulator.py --list-topics
# JSON output
python scripts/meeting_simulator.py --type strategy --jsondecision_tracker.py
Tracks board decisions, detects conflicts, flags overdue reviews and actions.
# Track demo decisions
python scripts/decision_tracker.py
# From decision log file
python scripts/decision_tracker.py --input decisions.json
# JSON output
python scripts/decision_tracker.py --jsoncomplexity_scorer.py
Scores decision complexity to determine single/dual/multi-advisor or board routing.
# Score with CLI flags
python scripts/complexity_scorer.py --topic "Market expansion" --domains 2 --reversibility 2 --financial 1 --team 2 --urgency 0
# Add modifiers
python scripts/complexity_scorer.py --topic "Acquisition" --domains 2 --reversibility 2 --financial 2 --team 2 --urgency 1 --modifiers cross_functional external_stakeholders sets_precedent
# JSON output
python scripts/complexity_scorer.py --topic "Pricing change" --json---
Troubleshooting
| Problem | Likely Cause | Fix |
|---|---|---|
| All advisors agree without any tension in Phase 2 | Groupthink or trivial topic; isolation may have been breached | Re-run Phase 2 with forced "strongest argument against" from each role |
| Discussion exceeds 5 points per advisor | Analysis paralysis; no cap enforced | Hard cap at 5 key points; force a recommendation even with Low confidence |
| Phase 5 times out with no founder response | Founder absence or decision avoidance | Auto-close as "pending review" at 30 min; no decisions without founder |
| Same topic discussed in 3+ meetings | Decision avoidance or new data not surfaced | Escalate: force decision or formally defer with stated timeline |
| Decisions logged but never reviewed | Decision logger not integrated into meeting cadence | Add "previous decisions review" to Phase 1 context loading |
| Roles operating outside their domain | No critic analysis conducted or critic missed it | Enforce Phase 3 critic checklist; flag domain violations explicitly |
---
Success Criteria
- Every board meeting produces at least 1 logged decision with owner, deadline, and review date
- Phase 2 contributions are independently generated (zero cross-pollination incidents per quarter)
- Phase 3 critic analysis identifies at least 1 unvalidated assumption per meeting
- Founder approval/modification/rejection captured within 30 minutes of synthesis presentation
- Decision history has zero conflicting active decisions (conflicts detected and resolved)
- Meeting duration stays within 2 hours for standard strategic reviews, 1 hour for resolution meetings
- 90%+ of logged decisions have action items completed by their stated deadlines
---
Scope & Limitations
In Scope: Multi-agent deliberation protocol, role activation matrix, contribution formats, critic analysis, synthesis, decision extraction, decision conflict detection, meeting simulation.
Out of Scope: Actual AI agent orchestration (this is a protocol specification, not runtime code), real-time meeting facilitation, video/audio recording, external board member management.
Limitations: The protocol assumes all advisor contributions are available in text format. Complexity scoring provides routing guidance but cannot account for political dynamics. Decision conflict detection works on exact topic matching -- semantic conflicts across different topics require human judgment.
---
Integration Points
| Skill | Integration |
|---|---|
chief-of-staff | Routes questions that score 9-10 complexity into the board meeting protocol |
decision-logger | Phase 6 feeds decisions directly into the two-layer decision memory |
board-deck-builder | Board deck sections provide pre-read context for Phase 1 |
executive-mentor | Phase 3 critic analysis can be performed by the Executive Mentor skill |
ceo-advisor through ciso-advisor | All C-suite advisors contribute independently in Phase 2 |
strategic-alignment | Validates that meeting decisions align with strategic goals |
#!/usr/bin/env python3
"""
Decision Complexity Scorer - Score decision complexity to determine routing.
Evaluates decisions against domain count, reversibility, financial impact,
team impact, and time pressure. Outputs routing recommendation.
"""
import argparse
import json
import sys
from datetime import datetime
FACTORS = {
"domain_count": {"weight": 0.25, "scores": {0: "Single domain", 1: "2 domains", 2: "3+ domains"}},
"reversibility": {"weight": 0.25, "scores": {0: "Easily reversed", 1: "Partially reversible", 2: "Irreversible"}},
"financial_impact": {"weight": 0.20, "scores": {0: "< 5% of budget", 1: "5-20% of budget", 2: "> 20% of budget"}},
"team_impact": {"weight": 0.15, "scores": {0: "Single team", 1: "Multiple teams", 2: "Org-wide"}},
"time_pressure": {"weight": 0.15, "scores": {0: "No urgency", 1: "Days to decide", 2: "Hours to decide"}},
}
MODIFIERS = [
{"id": "cross_functional", "label": "Affects 2+ functional areas", "add": 1},
{"id": "irreversible_costly", "label": "Decision is irreversible or very costly to reverse", "add": 1},
{"id": "disagreement", "label": "Expected disagreement between advisors", "add": 1},
{"id": "team_10_plus", "label": "Direct impact on 10+ team members", "add": 1},
{"id": "compliance", "label": "Compliance or regulatory dimension", "add": 1},
{"id": "external_stakeholders", "label": "Involves external stakeholders (board, investors, partners)", "add": 1},
{"id": "sets_precedent", "label": "Sets precedent for future decisions", "add": 1},
{"id": "contradicts_previous", "label": "Contradicts a previous logged decision", "add": 1},
]
ROUTING = [
{"min": 1, "max": 3, "type": "SINGLE ADVISOR", "description": "Route to primary domain expert. Return answer directly."},
{"min": 4, "max": 6, "type": "DUAL ADVISOR", "description": "Route to primary + secondary. Synthesize before returning."},
{"min": 7, "max": 8, "type": "MULTI-ADVISOR", "description": "Route to 3-4 relevant roles. Full synthesis with conflict mapping."},
{"min": 9, "max": 10, "type": "FULL BOARD MEETING", "description": "Invoke board-meeting protocol. All relevant roles contribute independently."},
]
def score_complexity(data: dict) -> dict:
"""Score decision complexity and determine routing."""
factor_scores = data.get("factors", {})
active_modifiers = data.get("modifiers", [])
topic = data.get("topic", "Unspecified")
results = {
"timestamp": datetime.now().isoformat(),
"topic": topic,
"factor_scores": {},
"base_score": 0,
"modifier_score": 0,
"total_score": 0,
"active_modifiers": [],
"routing": {},
"recommended_roles": [],
"rationale": [],
}
# Score factors
base_total = 0
for factor_name, config in FACTORS.items():
score = factor_scores.get(factor_name, 1)
score = min(2, max(0, score))
weighted = score * config["weight"] * 5 # Scale to contribute to 0-10
base_total += weighted
results["factor_scores"][factor_name] = {
"score": score,
"label": config["scores"].get(score, "Unknown"),
"weight": config["weight"],
"weighted": round(weighted, 2),
}
results["base_score"] = round(base_total, 1)
# Apply modifiers
modifier_total = 0
for mod in MODIFIERS:
if mod["id"] in active_modifiers:
modifier_total += mod["add"]
results["active_modifiers"].append(mod["label"])
results["modifier_score"] = modifier_total
results["total_score"] = min(10, round(results["base_score"] + modifier_total))
# Determine routing
total = results["total_score"]
for route in ROUTING:
if route["min"] <= total <= route["max"]:
results["routing"] = {
"type": route["type"],
"description": route["description"],
"score_range": f"{route['min']}-{route['max']}",
}
break
# Build rationale
high_factors = [(n, d) for n, d in results["factor_scores"].items() if d["score"] == 2]
if high_factors:
results["rationale"].append(
f"High complexity factors: {', '.join(d['label'] for _, d in high_factors)}"
)
if results["active_modifiers"]:
results["rationale"].append(
f"Active modifiers (+{modifier_total}): {', '.join(results['active_modifiers'][:3])}"
)
results["rationale"].append(
f"Total score {total}/10 -> {results['routing'].get('type', 'Unknown')}"
)
return results
def format_text(results: dict) -> str:
"""Format as human-readable report."""
lines = [
"=" * 60,
"DECISION COMPLEXITY ASSESSMENT",
"=" * 60,
f"Topic: {results['topic']}",
f"Complexity Score: {results['total_score']}/10 (base: {results['base_score']:.1f}, modifiers: +{results['modifier_score']})",
f"Routing: {results['routing'].get('type', 'N/A')}",
"",
"FACTOR BREAKDOWN:",
f"{'Factor':<20} {'Score':>6} {'Description':<30} {'Weighted':>8}",
"-" * 60,
]
for name, data in results["factor_scores"].items():
lines.append(f"{name:<20} {data['score']:>5}/2 {data['label']:<30} {data['weighted']:>7.2f}")
if results["active_modifiers"]:
lines.extend(["", f"MODIFIERS (+{results['modifier_score']}):"])
for mod in results["active_modifiers"]:
lines.append(f" [+1] {mod}")
lines.extend(["", "ROUTING DECISION:"])
r = results["routing"]
lines.append(f" {r.get('type', 'N/A')}: {r.get('description', '')}")
if results["rationale"]:
lines.extend(["", "RATIONALE:"])
for rat in results["rationale"]:
lines.append(f" {rat}")
lines.extend(["", "=" * 60])
return "\n".join(lines)
def main():
parser = argparse.ArgumentParser(description="Score decision complexity for routing")
parser.add_argument("--input", "-i", help="JSON file with decision data")
parser.add_argument("--topic", help="Decision topic")
parser.add_argument("--domains", type=int, choices=[0, 1, 2], help="Domain count (0=single, 1=two, 2=three+)")
parser.add_argument("--reversibility", type=int, choices=[0, 1, 2], help="Reversibility (0=easy, 1=partial, 2=irreversible)")
parser.add_argument("--financial", type=int, choices=[0, 1, 2], help="Financial impact (0=<5%%, 1=5-20%%, 2=>20%%)")
parser.add_argument("--team", type=int, choices=[0, 1, 2], help="Team impact (0=single, 1=multi, 2=org-wide)")
parser.add_argument("--urgency", type=int, choices=[0, 1, 2], help="Time pressure (0=none, 1=days, 2=hours)")
parser.add_argument("--modifiers", nargs="*", choices=[m["id"] for m in MODIFIERS], default=[], help="Active modifiers")
parser.add_argument("--json", action="store_true", help="Output as JSON")
args = parser.parse_args()
if args.input:
with open(args.input) as f:
data = json.load(f)
else:
data = {
"topic": args.topic or "Strategic decision",
"factors": {
"domain_count": args.domains if args.domains is not None else 1,
"reversibility": args.reversibility if args.reversibility is not None else 1,
"financial_impact": args.financial if args.financial is not None else 1,
"team_impact": args.team if args.team is not None else 1,
"time_pressure": args.urgency if args.urgency is not None else 0,
},
"modifiers": args.modifiers,
}
results = score_complexity(data)
if args.json:
print(json.dumps(results, indent=2))
else:
print(format_text(results))
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
Board Decision Tracker - Track and audit board meeting decisions.
Manages decision logs with ownership, deadlines, review dates,
conflict detection, and DO_NOT_RESURFACE flags. Produces audit reports.
"""
import argparse
import json
import sys
from datetime import datetime, timedelta
def track_decisions(data: dict) -> dict:
"""Analyze decision history and identify issues."""
decisions = data.get("decisions", [])
today = datetime.now()
results = {
"timestamp": today.isoformat(),
"total_decisions": len(decisions),
"summary": {"active": 0, "overdue_review": 0, "overdue_action": 0, "completed": 0, "superseded": 0, "do_not_resurface": 0},
"decisions": [],
"overdue_reviews": [],
"overdue_actions": [],
"conflicts": [],
"recurring_topics": {},
"owner_workload": {},
"recommendations": [],
}
topic_count = {}
for d in decisions:
decision_id = d.get("id", "")
topic = d.get("topic", "")
status = d.get("status", "active")
owner = d.get("owner", "Unassigned")
action_items = d.get("action_items", [])
review_date_str = d.get("review_date", "")
decision_date_str = d.get("decision_date", "")
dnr = d.get("do_not_resurface", False)
superseded_by = d.get("superseded_by", None)
# Parse dates
review_date = None
decision_date = None
try:
if review_date_str:
review_date = datetime.strptime(review_date_str, "%Y-%m-%d")
if decision_date_str:
decision_date = datetime.strptime(decision_date_str, "%Y-%m-%d")
except ValueError:
pass
# Check overdue review
review_overdue = review_date and today > review_date and status == "active"
# Check overdue actions
overdue_items = []
for ai in action_items:
due_str = ai.get("due_date", "")
try:
due = datetime.strptime(due_str, "%Y-%m-%d")
if today > due and ai.get("status", "") != "complete":
overdue_items.append(ai)
except ValueError:
pass
# Track topics
topic_key = topic.lower().strip()
topic_count[topic_key] = topic_count.get(topic_key, 0) + 1
# Track owner workload
if owner not in results["owner_workload"]:
results["owner_workload"][owner] = {"active": 0, "overdue": 0}
if status == "active":
results["owner_workload"][owner]["active"] += 1
if review_overdue or overdue_items:
results["owner_workload"][owner]["overdue"] += len(overdue_items) + (1 if review_overdue else 0)
decision_result = {
"id": decision_id,
"topic": topic,
"status": status,
"owner": owner,
"decision_date": decision_date_str,
"review_date": review_date_str,
"review_overdue": review_overdue,
"days_since_review_due": (today - review_date).days if review_overdue else 0,
"action_items_total": len(action_items),
"action_items_overdue": len(overdue_items),
"do_not_resurface": dnr,
"superseded_by": superseded_by,
}
results["decisions"].append(decision_result)
# Summary counts
if dnr:
results["summary"]["do_not_resurface"] += 1
elif superseded_by:
results["summary"]["superseded"] += 1
elif status == "completed":
results["summary"]["completed"] += 1
else:
results["summary"]["active"] += 1
if review_overdue:
results["summary"]["overdue_review"] += 1
results["overdue_reviews"].append(decision_result)
if overdue_items:
results["summary"]["overdue_action"] += len(overdue_items)
results["overdue_actions"].extend([
{"decision_id": decision_id, "topic": topic, **ai}
for ai in overdue_items
])
# Recurring topics
for topic, count in topic_count.items():
if count >= 3:
results["recurring_topics"][topic] = {
"count": count,
"alert": "Same topic discussed 3+ times without resolution. Escalate to board meeting."
}
# Conflict detection
active_decisions = [d for d in decisions if d.get("status") == "active" and not d.get("do_not_resurface")]
for i, d1 in enumerate(active_decisions):
for d2 in active_decisions[i+1:]:
if d1.get("topic", "").lower() == d2.get("topic", "").lower():
results["conflicts"].append({
"decision_1": d1.get("id", ""),
"decision_2": d2.get("id", ""),
"topic": d1.get("topic", ""),
"type": "duplicate_topic",
"message": "Two active decisions on the same topic. One should supersede the other."
})
# Recommendations
if results["summary"]["overdue_review"] > 0:
results["recommendations"].append(
f"{results['summary']['overdue_review']} decision(s) past review date. Schedule review."
)
if results["summary"]["overdue_action"] > 0:
results["recommendations"].append(
f"{results['summary']['overdue_action']} action item(s) overdue. Follow up with owners."
)
if results["recurring_topics"]:
results["recommendations"].append(
f"{len(results['recurring_topics'])} recurring topic(s) need escalation."
)
if results["conflicts"]:
results["recommendations"].append(
f"{len(results['conflicts'])} conflict(s) detected. Resolve before next meeting."
)
return results
def format_text(results: dict) -> str:
"""Format as human-readable report."""
s = results["summary"]
lines = [
"=" * 65,
"BOARD DECISION TRACKER",
"=" * 65,
f"Date: {results['timestamp'][:10]}",
f"Total Decisions: {results['total_decisions']}",
f"Active: {s['active']} | Completed: {s['completed']} | Superseded: {s['superseded']} | DNR: {s['do_not_resurface']}",
f"Overdue Reviews: {s['overdue_review']} | Overdue Actions: {s['overdue_action']}",
"",
]
if results["overdue_reviews"]:
lines.append("OVERDUE REVIEWS:")
for d in results["overdue_reviews"]:
lines.append(f" [!] {d['id']}: {d['topic']} ({d['owner']}) - {d['days_since_review_due']} days overdue")
lines.append("")
if results["overdue_actions"]:
lines.append("OVERDUE ACTION ITEMS:")
for a in results["overdue_actions"][:10]:
lines.append(f" [!] {a.get('decision_id','')}: {a.get('action','')} ({a.get('owner','')}, due: {a.get('due_date','')})")
lines.append("")
if results["conflicts"]:
lines.append("CONFLICTS:")
for c in results["conflicts"]:
lines.append(f" [!] {c['message']} ({c['decision_1']} vs {c['decision_2']})")
lines.append("")
if results["recurring_topics"]:
lines.append("RECURRING TOPICS (3+ discussions):")
for topic, info in results["recurring_topics"].items():
lines.append(f" [W] '{topic}' discussed {info['count']} times. {info['alert']}")
lines.append("")
lines.append("OWNER WORKLOAD:")
for owner, wl in sorted(results["owner_workload"].items(), key=lambda x: x[1]["overdue"], reverse=True):
flag = " [!]" if wl["overdue"] > 0 else ""
lines.append(f" {owner}: {wl['active']} active, {wl['overdue']} overdue{flag}")
if results["recommendations"]:
lines.extend(["", "RECOMMENDATIONS:"])
for r in results["recommendations"]:
lines.append(f" -> {r}")
lines.extend(["", "=" * 65])
return "\n".join(lines)
def main():
parser = argparse.ArgumentParser(description="Track and audit board meeting decisions")
parser.add_argument("--input", "-i", help="JSON file with decisions data")
parser.add_argument("--json", action="store_true", help="Output as JSON")
args = parser.parse_args()
if args.input:
with open(args.input) as f:
data = json.load(f)
else:
today = datetime.now()
data = {
"decisions": [
{"id": "D-001", "topic": "Series B fundraising", "status": "active", "owner": "CEO", "decision_date": "2025-10-15", "review_date": (today - timedelta(days=15)).strftime("%Y-%m-%d"), "action_items": [{"action": "Engage 3 investment banks", "owner": "CFO", "due_date": (today - timedelta(days=10)).strftime("%Y-%m-%d"), "status": "in_progress"}]},
{"id": "D-002", "topic": "Engineering reorg", "status": "completed", "owner": "CTO", "decision_date": "2025-09-01", "review_date": "2025-12-01", "action_items": []},
{"id": "D-003", "topic": "Market expansion EU", "status": "active", "owner": "CMO", "decision_date": "2025-11-01", "review_date": (today + timedelta(days=30)).strftime("%Y-%m-%d"), "action_items": [{"action": "Complete GDPR assessment", "owner": "CISO", "due_date": (today - timedelta(days=5)).strftime("%Y-%m-%d"), "status": "in_progress"}]},
{"id": "D-004", "topic": "Series B fundraising", "status": "active", "owner": "CFO", "decision_date": "2025-11-15", "review_date": (today + timedelta(days=15)).strftime("%Y-%m-%d"), "action_items": []},
{"id": "D-005", "topic": "Pricing model change", "status": "active", "owner": "CRO", "decision_date": "2025-10-01", "review_date": (today - timedelta(days=20)).strftime("%Y-%m-%d"), "do_not_resurface": False, "action_items": []},
],
}
results = track_decisions(data)
if args.json:
print(json.dumps(results, indent=2))
else:
print(format_text(results))
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
Board Meeting Simulator - Simulate and validate meeting protocol execution.
Validates role activation, contribution completeness, phase sequencing,
and generates meeting readiness assessments. Helps prep for structured
multi-agent board deliberations.
"""
import argparse
import json
import sys
from datetime import datetime
ROLES = ["CEO", "CFO", "CMO", "CPO", "CRO", "COO", "CHRO", "CTO", "CISO"]
TOPIC_ROUTING = {
"market_expansion": {"required": ["CEO", "CMO", "CFO", "CRO", "COO"], "optional": ["CTO"]},
"product_direction": {"required": ["CEO", "CPO", "CTO", "CMO"], "optional": ["CFO"]},
"hiring_org": {"required": ["CEO", "CHRO", "CFO", "COO"], "optional": ["CTO", "CMO"]},
"pricing": {"required": ["CMO", "CFO", "CRO", "CPO"], "optional": []},
"technology": {"required": ["CTO", "CPO", "CFO", "CISO"], "optional": []},
"fundraising": {"required": ["CEO", "CFO", "CRO"], "optional": []},
"security_incident": {"required": ["CEO", "CTO", "CISO", "COO"], "optional": []},
"m_and_a": {"required": ["CEO", "CFO", "CTO", "CHRO", "COO"], "optional": []},
"strategy": {"required": ["CEO", "CFO", "CMO", "CPO", "CRO"], "optional": ["COO"]},
"cost_reduction": {"required": ["CEO", "CFO", "COO", "CHRO"], "optional": []},
}
PHASES = [
{"phase": 1, "name": "Context Gathering", "required_outputs": ["agenda", "activated_roles", "context_loaded"]},
{"phase": 2, "name": "Independent Contributions", "required_outputs": ["isolated_contributions"]},
{"phase": 3, "name": "Critic Analysis", "required_outputs": ["consensus_assessment", "unvalidated_assumptions", "missing_perspectives", "uncomfortable_truth"]},
{"phase": 4, "name": "Synthesis", "required_outputs": ["decision_required", "perspectives_summary", "agreements", "disagreements", "recommended_decision", "action_items"]},
{"phase": 5, "name": "Founder Review", "required_outputs": ["founder_decision"]},
{"phase": 6, "name": "Decision Extraction", "required_outputs": ["decisions_logged", "action_items_assigned"]},
]
def simulate_meeting(data: dict) -> dict:
"""Simulate and validate meeting protocol."""
topic = data.get("topic", "strategy")
topic_type = data.get("topic_type", "strategy")
activated_roles = data.get("activated_roles", [])
contributions = data.get("contributions", {})
phase_status = data.get("phase_status", {})
complexity_score = data.get("complexity_score", 7)
routing = TOPIC_ROUTING.get(topic_type, TOPIC_ROUTING["strategy"])
results = {
"timestamp": datetime.now().isoformat(),
"topic": topic,
"topic_type": topic_type,
"complexity_score": complexity_score,
"meeting_type": "Full Board" if complexity_score >= 8 else "Multi-Advisor" if complexity_score >= 5 else "Dual Advisor",
"role_assessment": {},
"phase_assessment": [],
"contribution_quality": {},
"readiness_score": 0,
"warnings": [],
"protocol_violations": [],
"recommendations": [],
}
# Role assessment
if not activated_roles:
activated_roles = routing["required"]
missing_required = [r for r in routing["required"] if r not in activated_roles]
extra_roles = [r for r in activated_roles if r not in routing["required"] and r not in routing.get("optional", [])]
recommended_additions = [r for r in routing.get("optional", []) if r not in activated_roles]
results["role_assessment"] = {
"activated": activated_roles,
"required": routing["required"],
"missing_required": missing_required,
"extra_roles": extra_roles,
"recommended_additions": recommended_additions,
"attendee_count": len(activated_roles),
"max_recommended": 6,
"role_count_ok": len(activated_roles) <= 6,
}
if missing_required:
results["warnings"].append(f"Missing required roles: {', '.join(missing_required)}")
if len(activated_roles) > 6:
results["warnings"].append(f"Too many attendees ({len(activated_roles)} > 6 max). Reduce to prevent noise.")
# Phase assessment
readiness_total = 0
readiness_count = 0
for phase_config in PHASES:
phase_num = phase_config["phase"]
phase_data = phase_status.get(str(phase_num), {})
status = phase_data.get("status", "not_started")
outputs_present = []
outputs_missing = []
for output in phase_config["required_outputs"]:
if phase_data.get(output, False):
outputs_present.append(output)
else:
outputs_missing.append(output)
completeness = len(outputs_present) / len(phase_config["required_outputs"]) * 100 if phase_config["required_outputs"] else 0
phase_result = {
"phase": phase_num,
"name": phase_config["name"],
"status": status,
"completeness_pct": round(completeness),
"outputs_present": outputs_present,
"outputs_missing": outputs_missing,
}
results["phase_assessment"].append(phase_result)
if status == "complete":
readiness_total += 100
elif status == "in_progress":
readiness_total += completeness
readiness_count += 1
# Contribution quality checks
for role in activated_roles:
contrib = contributions.get(role, {})
quality = {
"role": role,
"has_key_points": bool(contrib.get("key_points")),
"has_recommendation": bool(contrib.get("recommendation")),
"has_confidence": bool(contrib.get("confidence")),
"has_key_assumption": bool(contrib.get("key_assumption")),
"has_change_condition": bool(contrib.get("what_would_change_mind")),
"points_count": len(contrib.get("key_points", [])),
"format_compliant": True,
}
# Validate format
violations = []
if quality["points_count"] > 5:
violations.append(f"{role}: More than 5 key points ({quality['points_count']})")
quality["format_compliant"] = False
if not quality["has_recommendation"]:
violations.append(f"{role}: Missing recommendation")
quality["format_compliant"] = False
if not quality["has_confidence"]:
violations.append(f"{role}: Missing confidence level")
quality["format_compliant"] = False
results["contribution_quality"][role] = quality
results["protocol_violations"].extend(violations)
# Cross-pollination check
if data.get("cross_pollination_detected", False):
results["protocol_violations"].append("CRITICAL: Cross-pollination detected in Phase 2. Contributions must be isolated.")
# Overall readiness
results["readiness_score"] = round(readiness_total / readiness_count) if readiness_count > 0 else 0
# Recommendations
if results["readiness_score"] < 50:
results["recommendations"].append("Meeting not ready. Complete earlier phases before proceeding.")
if missing_required:
results["recommendations"].append(f"Activate missing roles before starting: {', '.join(missing_required)}")
if results["protocol_violations"]:
results["recommendations"].append(f"Fix {len(results['protocol_violations'])} protocol violations before synthesis.")
return results
def format_text(results: dict) -> str:
"""Format as human-readable report."""
lines = [
"=" * 60,
"BOARD MEETING PROTOCOL ASSESSMENT",
"=" * 60,
f"Topic: {results['topic']}",
f"Type: {results['topic_type']} | Complexity: {results['complexity_score']}/10",
f"Meeting Type: {results['meeting_type']}",
f"Readiness: {results['readiness_score']}%",
"",
"ROLE ACTIVATION:",
f" Activated: {', '.join(results['role_assessment']['activated'])} ({results['role_assessment']['attendee_count']})",
f" Required: {', '.join(results['role_assessment']['required'])}",
]
ra = results["role_assessment"]
if ra["missing_required"]:
lines.append(f" MISSING: {', '.join(ra['missing_required'])}")
if ra["recommended_additions"]:
lines.append(f" Optional: {', '.join(ra['recommended_additions'])}")
lines.extend(["", "PHASE STATUS:"])
for p in results["phase_assessment"]:
icon = "[x]" if p["status"] == "complete" else "[~]" if p["status"] == "in_progress" else "[ ]"
lines.append(f" {icon} Phase {p['phase']}: {p['name']} ({p['completeness_pct']}%)")
if p["outputs_missing"]:
lines.append(f" Missing: {', '.join(p['outputs_missing'])}")
if results["protocol_violations"]:
lines.extend(["", "PROTOCOL VIOLATIONS:"])
for v in results["protocol_violations"]:
lines.append(f" [!] {v}")
if results["warnings"]:
lines.extend(["", "WARNINGS:"])
for w in results["warnings"]:
lines.append(f" [W] {w}")
if results["recommendations"]:
lines.extend(["", "RECOMMENDATIONS:"])
for r in results["recommendations"]:
lines.append(f" -> {r}")
lines.extend(["", "=" * 60])
return "\n".join(lines)
def main():
parser = argparse.ArgumentParser(description="Simulate and validate board meeting protocol")
parser.add_argument("--input", "-i", help="JSON file with meeting data")
parser.add_argument("--topic", help="Meeting topic")
parser.add_argument("--type", choices=list(TOPIC_ROUTING.keys()), default="strategy", help="Topic type")
parser.add_argument("--complexity", type=int, default=7, help="Complexity score (1-10)")
parser.add_argument("--roles", nargs="*", help="Activated roles")
parser.add_argument("--list-topics", action="store_true", help="List topic types and required roles")
parser.add_argument("--json", action="store_true", help="Output as JSON")
args = parser.parse_args()
if args.list_topics:
print("Topic Types and Required Roles:")
for topic, routing in TOPIC_ROUTING.items():
print(f" {topic}: {', '.join(routing['required'])} (optional: {', '.join(routing.get('optional', []))})")
return
if args.input:
with open(args.input) as f:
data = json.load(f)
else:
data = {
"topic": args.topic or "Q2 Strategy Review",
"topic_type": args.type,
"complexity_score": args.complexity,
"activated_roles": args.roles or TOPIC_ROUTING.get(args.type, {}).get("required", ["CEO", "CFO"]),
"contributions": {},
"phase_status": {
"1": {"status": "complete", "agenda": True, "activated_roles": True, "context_loaded": True},
"2": {"status": "in_progress", "isolated_contributions": False},
},
}
results = simulate_meeting(data)
if args.json:
print(json.dumps(results, indent=2))
else:
print(format_text(results))
if __name__ == "__main__":
main()
Related skills
FAQ
What are the six phases?
Context gathering, independent (isolated) contributions, critic analysis, synthesis, founder review (full stop), and decision extraction and logging.
How does it prevent groupthink?
Each advisor contributes without seeing others' outputs, so there is no cross-pollination during Phase 2.