DEV Community

Cover image for Building Boardroom.exe: A Real-Time Multi-Agent Corporate Meeting Simulator
Harish Kotra (he/him)
Harish Kotra (he/him)

Posted on

Building Boardroom.exe: A Real-Time Multi-Agent Corporate Meeting Simulator

What happens when you put 9 AI personas in a room and ask them to decide on a simple feature like "Should we add dark mode?"

Chaos. Absolutely delightful, painfully accurate chaos.

I built Boardroom.exe - a real-time simulation of corporate dysfunction where AI agents debate, interrupt, scope-creep, and occasionally have breakthroughs (or breakdowns). This post is a technical deep-dive into how it was built.

The Concept

Every tech worker has sat through meetings that should have been 15 minutes but somehow became 2-hour debates about nothing. Boardroom.exe captures this experience in a browser-based simulation.

The goal isn't productivity - it's entertainment and recognition. When users see the simulation, they think: "This is exactly how our meetings feel."

Tech Stack

Technology Purpose
Next.js 15 (App Router) React framework
TypeScript Type safety
Tailwind CSS Styling
Zustand State management
React Flow (@xyflow/react) Graph visualization
Framer Motion Animations
Lucide React Icons

Architecture Overview

┌─────────────────────────────────────────────────────────────────┐
│                     Application Flow                            │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│   User Input ──► Zustand Store ──► React Components            │
│        │                │                │                      │
│        ▼                ▼                ▼                      │
│   Control Panel    Agent State      Meeting Room                │
│        │                │                │                      │
│        │                ▼                ▼                      │
│        │           Simulation ◄──── Timeline                    │
│        │              │                                         │
│        │              ▼                                         │
│        │         nextTurn() every 2.5s                         │
│        │                │                                         │
│        │                ▼                                         │
│        └──────►  UI Updates + Metrics                           │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

Core Components

1. State Management (Zustand)

The entire simulation state lives in a Zustand store. Here's the core interface:

// lib/types.ts
export interface MeetingState {
  isRunning: boolean;
  phase: 'idle' | 'opening' | 'discussion' | 'debate' | 'deadlock' | 'resolution' | 'ended';
  elapsedTime: number;
  topic: string;
  chaosLevel: number;
  agents: Agent[];
  currentSpeaker: string | null;
  transcript: Message[];
  internalThoughts: Message[];
  buzzwords: Record<string, number>;
  metrics: Metrics;
  scopeCreep: number;
  consensusLevel: number;
}
Enter fullscreen mode Exit fullscreen mode

The store handles all state mutations:

// lib/store.ts
export const useMeetingStore = create<MeetingStore>((set, get) => ({
  // ... initial state

  nextTurn: () => {
    const state = get();
    if (!state.isRunning) return;

    const { message, nextSpeaker, updatedAgents } = simulateTurn(
      state.agents,
      state.currentSpeaker,
      state.topic,
      { chaosLevel: state.chaosLevel, scopeCreep: state.scopeCreep, consensusLevel: state.consensusLevel }
    );

    // Update all state
    set({
      agents: updatedAgents,
      currentSpeaker: nextSpeaker,
      transcript: [...state.transcript, message],
      // ... more updates
    });
  },
}));
Enter fullscreen mode Exit fullscreen mode

2. Agent System

Each agent is a configuration object with traits that influence behavior:

// lib/agents.ts
export const createAgent = (role: AgentRole, id: string): Agent => {
  const baseConfig: Record<AgentRole, Partial<Agent>> = {
    ceo: {
      name: 'Marcus',
      traits: ['visionary', 'pivot-prone', 'Elon references', 'AI-obsessed'],
      speakingRate: 0.4,
      interruptChance: 0.35,
      buzzwordAffinity: 0.7,
    },
    // ... other agents
  };

  return {
    id,
    name: config.name!,
    role,
    roleLabel: AGENT_LABELS[role],
    color: AGENT_COLORS[role],
    influence: 50,
    emotionalState: 'neutral',
    traits: config.traits!,
    speakingRate: config.speakingRate!,
    interruptChance: config.interruptChance!,
    buzzwordAffinity: config.buzzwordAffinity!,
    isSpeaking: false,
    lastSpoke: 0,
    turnCount: 0,
  };
};
Enter fullscreen mode Exit fullscreen mode

3. Simulation Engine

The simulation engine generates responses based on agent personality:

// lib/simulation.ts
const AGENT_PERSONALITIES: Record<AgentRole, string[]> = {
  ceo: [
    "Let's think about this from first principles.",
    "I just read something about this. We need to pivot to AI.",
    "What would Elon do here?",
    // ... more phrases
  ],
  engineering: [
    "That sounds great, but the architecture can't support it.",
    "We already have 47 tech debt tickets.",
    "The migration will take 6 months.",
    // ... more phrases
  ],
  // ... other agents
};

export const simulateTurn = (agents, currentSpeaker, topic, state) => {
  // Select speaker based on speaking rates
  let speaker = availableAgents[Math.floor(Math.random() * availableAgents.length)];

  // Generate response based on personality + state
  const content = generateResponse(speaker, topic, agents, state);

  return {
    message,
    nextSpeaker: speaker.id,
    updatedAgents: agents.map(/* update speaker state */)
  };
};
Enter fullscreen mode Exit fullscreen mode

4. React Flow Visualization

The meeting room is visualized using React Flow:

// components/meeting-room/MeetingRoom.tsx
const nodes: Node[] = useMemo(() => {
  return agents.map((agent) => {
    const isSpeaking = currentSpeaker === agent.id;
    const influenceScale = 0.8 + (agent.influence / 100) * 0.4;

    return {
      id: agent.id,
      position: nodePositions[agent.id],
      data: {
        label: agent.name,
        role: agent.roleLabel,
        color: agent.color,
        isSpeaking,
        influence: agent.influence,
      },
      type: 'agentNode',
    };
  });
}, [agents, currentSpeaker, nodePositions]);

// Custom agent node component
function AgentNode({ data }) {
  return (
    <div className="flex flex-col items-center">
      {/* Speaking ring animation */}
      {data.isSpeaking && (
        <div className="absolute inset-0 rounded-full animate-ping"
             style={{ border: `3px solid ${data.color}` }} />
      )}

      {/* Agent circle with initials */}
      <div className="w-full h-full rounded-full border-2 flex items-center justify-center"
           style={{ backgroundColor: '#12121a', borderColor: data.color }}>
        <span className="text-xl font-bold" style={{ color: data.color }}>
          {data.label[0]}
        </span>
      </div>

      {/* Name and role badges */}
      <div className="mt-2 text-center">
        <span className="text-sm text-[#e4e4e7] font-medium block">{data.label}</span>
        <span className="text-xs px-2 py-0.5 rounded mt-1 inline-block"
              style={{ backgroundColor: `${data.color}20`, color: data.color }}>
          {data.role}
        </span>
      </div>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

5. Metrics System

Real-time KPIs track meeting health:

// lib/metrics.ts
export const calculateMetrics = (
  currentMetrics: Metrics,
  agents: Agent[],
  buzzwordCount: number,
  scopeCreep: number,
  chaosLevel: number,
  events: number
): Metrics => {
  const agentInfluence = agents.reduce((sum, a) => sum + a.influence, 0) / agents.length;
  const moraleImpact = agents.filter(a => a.emotionalState === 'frustrated' || a.emotionalState === 'angry').length * 5;

  return {
    productivity: Math.max(0, Math.min(100, currentMetrics.productivity + (-1 - events * 2 + agentInfluence / 20))),
    burnRate: Math.max(0, Math.min(100, currentMetrics.burnRate + (chaosLevel / 50) + (events * 3))),
    morale: Math.max(0, Math.min(100, currentMetrics.morale + (-moraleImpact - events * 5))),
    technicalDebt: Math.max(0, Math.min(100, currentMetrics.technicalDebt + scopeCreep / 15)),
    buzzwordDensity: Math.max(0, Math.min(100, currentMetrics.buzzwordDensity + buzzwordCount * 0.1)),
    shippingProbability: Math.max(0, Math.min(100, 80 - scopeCreep / 10)),
    pivotLikelihood: Math.max(0, Math.min(100, currentMetrics.pivotLikelihood + chaosLevel / 20 + events * 5)),
    reorgRisk: Math.max(0, Math.min(100, currentMetrics.reorgRisk + chaosLevel / 15)),
    investorSatisfaction: Math.max(0, Math.min(100, currentMetrics.investorSatisfaction - events * 5)),
  };
};
Enter fullscreen mode Exit fullscreen mode

Key Implementation Details

1. Deterministic Edge Generation (Fixing Hydration Errors)

The React Flow edges use a hash-based approach to ensure consistent rendering:

const edges: Edge[] = useMemo(() => {
  return agents.map((source, i) => agents.map((target, j) => {
    if (i < j) {
      // Use deterministic hash instead of Math.random()
      const hash = (source.id.charCodeAt(5) + target.id.charCodeAt(5)) % 10;
      if (hash < 2) {
        return {
          id: `edge-${source.id}-${target.id}`,
          source: source.id,
          target: target.id,
          // ... edge config
        };
      }
    }
  })).flat();
}, [agents]);
Enter fullscreen mode Exit fullscreen mode

This prevents hydration mismatches between server and client rendering.

2. Simulation Timing

The simulation runs on an interval:

// app/page.tsx
useEffect(() => {
  if (isRunning) {
    intervalRef.current = setInterval(() => {
      nextTurn();
    }, 2500); // Every 2.5 seconds
  }
  return () => clearInterval(intervalRef.current);
}, [isRunning, nextTurn]);
Enter fullscreen mode Exit fullscreen mode

3. Phase Transitions

Meeting phases transition based on metrics:

let newPhase = state.phase;
if (newMetrics.reorgRisk > 80) newPhase = 'deadlock';
else if (newScopeCreep > 80) newPhase = 'debate';
else if (state.elapsedTime > 120000 && newConsensusLevel < 30) newPhase = 'deadlock';
else if (newConsensusLevel > 70) newPhase = 'resolution';
Enter fullscreen mode Exit fullscreen mode

Adding Custom AI Integration

To replace rule-based responses with real AI:

// lib/simulation.ts
async function generateResponseWithAI(agent: Agent, topic: string, apiKey: string) {
  const response = await fetch('https://api.openai.com/v1/chat/completions', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${apiKey}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      model: 'gpt-4',
      messages: [
        {
          role: 'system',
          content: `You are ${agent.name}, ${agent.roleLabel}. ${AGENT_PERSONALITIES[agent.role].join(' ')}`
        },
        { role: 'user', content: `Respond to: ${topic}` }
      ],
      temperature: 0.8,
    })
  });
  return (await response.json()).choices[0].message.content;
}
Enter fullscreen mode Exit fullscreen mode

Running the Project

# Install dependencies
npm install

# Development
npm run dev

# Production build
npm run build
npm start
Enter fullscreen mode Exit fullscreen mode

Future Enhancements

  1. Real AI Integration - Connect to OpenAI/Anthropic for authentic responses
  2. Meeting Export - Generate PDF summaries, Slack threads, Jira tickets
  3. Custom Agent Builder - UI to create new agent personas
  4. Multi-Room Support - Run multiple simultaneous meetings
  5. Meeting Replay - Record and playback entire meeting sessions

Boardroom.exe demonstrates how emergent behavior can arise from simple rule-based agents. The simulation captures the essence of corporate dysfunction through carefully crafted personalities, real-time metrics, and dramatic visual feedback.

The project shows that you don't need complex AI to create engaging simulations - just well-designed rules and thoughtful UX.

How it works

Code & more: https://www.dailybuild.xyz/project/131-boardroomexe

Top comments (0)