Multi-Agent Debugger
Interactive Visual Debugging Tool for Agent Systems
Document ID: E4-MULTI-AGENT-DEBUGGER
Version: 1.0
Category: P5 - Interactive Learning
Format: React JSX Component
Component Overview
The Multi-Agent Debugger provides a visual interface for understanding and debugging multi-agent interactions. Users can step through agent communication, inspect message flows, and identify coordination issues.
JSX Component
import React, { useState, useEffect, useRef } from 'react';
import { Play, Pause, SkipForward, RotateCcw, Eye, AlertTriangle, CheckCircle, Clock, MessageSquare, Users, Zap, ChevronRight, ChevronDown } from 'lucide-react';
const MultiAgentDebugger = () => {
const [scenario, setScenario] = useState('hierarchical');
const [isPlaying, setIsPlaying] = useState(false);
const [currentStep, setCurrentStep] = useState(0);
const [speed, setSpeed] = useState(1000);
const [selectedAgent, setSelectedAgent] = useState(null);
const [expandedMessages, setExpandedMessages] = useState([]);
const intervalRef = useRef(null);
const scenarios = {
hierarchical: {
name: 'Hierarchical Delegation',
description: 'Orchestrator delegates to specialist agents',
agents: [
{ id: 'orchestrator', name: 'Orchestrator', role: 'coordinator', color: 'blue' },
{ id: 'researcher', name: 'Researcher', role: 'specialist', color: 'emerald' },
{ id: 'analyst', name: 'Analyst', role: 'specialist', color: 'purple' },
{ id: 'writer', name: 'Writer', role: 'specialist', color: 'amber' }
],
steps: [
{ from: 'user', to: 'orchestrator', type: 'task', message: 'Create market analysis report', status: 'success' },
{ from: 'orchestrator', to: 'orchestrator', type: 'planning', message: 'Decomposing task into subtasks...', status: 'success' },
{ from: 'orchestrator', to: 'researcher', type: 'delegate', message: 'Gather market data and competitor info', status: 'success' },
{ from: 'researcher', to: 'researcher', type: 'tool', message: 'web_search("market trends 2024")', status: 'success' },
{ from: 'researcher', to: 'researcher', type: 'tool', message: 'knowledge_search("competitor analysis")', status: 'success' },
{ from: 'researcher', to: 'orchestrator', type: 'result', message: 'Research complete: 15 sources gathered', status: 'success' },
{ from: 'orchestrator', to: 'analyst', type: 'delegate', message: 'Analyze data and identify trends', status: 'success' },
{ from: 'analyst', to: 'analyst', type: 'reasoning', message: 'Applying trend analysis...', status: 'success' },
{ from: 'analyst', to: 'orchestrator', type: 'result', message: 'Analysis complete: 5 key trends identified', status: 'success' },
{ from: 'orchestrator', to: 'writer', type: 'delegate', message: 'Draft report with findings', status: 'success' },
{ from: 'writer', to: 'writer', type: 'generation', message: 'Generating report structure...', status: 'success' },
{ from: 'writer', to: 'orchestrator', type: 'result', message: 'Draft report complete: 2500 words', status: 'success' },
{ from: 'orchestrator', to: 'orchestrator', type: 'synthesis', message: 'Synthesizing final report...', status: 'success' },
{ from: 'orchestrator', to: 'user', type: 'response', message: 'Market analysis report ready', status: 'success' }
]
},
debate: {
name: 'Distributed Debate',
description: 'Agents debate to reach consensus',
agents: [
{ id: 'agent1', name: 'Analyst A', role: 'debater', color: 'blue' },
{ id: 'agent2', name: 'Analyst B', role: 'debater', color: 'emerald' },
{ id: 'agent3', name: 'Analyst C', role: 'debater', color: 'purple' },
{ id: 'consensus', name: 'Consensus', role: 'moderator', color: 'amber' }
],
steps: [
{ from: 'user', to: 'all', type: 'task', message: 'Should we expand to European market?', status: 'success' },
{ from: 'agent1', to: 'all', type: 'position', message: 'Position: YES - High growth potential (confidence: 0.8)', status: 'success' },
{ from: 'agent2', to: 'all', type: 'position', message: 'Position: NO - Regulatory complexity (confidence: 0.7)', status: 'success' },
{ from: 'agent3', to: 'all', type: 'position', message: 'Position: YES with caution - Phased approach (confidence: 0.75)', status: 'success' },
{ from: 'agent2', to: 'agent1', type: 'critique', message: 'Challenge: How to handle GDPR compliance?', status: 'success' },
{ from: 'agent1', to: 'agent2', type: 'response', message: 'Response: Partner with local legal firm', status: 'success' },
{ from: 'agent3', to: 'all', type: 'synthesis', message: 'Proposing: Start with UK, then expand', status: 'success' },
{ from: 'agent1', to: 'agent3', type: 'support', message: 'Updated position: Agree with phased approach', status: 'success' },
{ from: 'agent2', to: 'agent3', type: 'support', message: 'Updated position: Accept with risk mitigation', status: 'success' },
{ from: 'consensus', to: 'consensus', type: 'calculation', message: 'Convergence: 0.85 - consensus reached', status: 'success' },
{ from: 'consensus', to: 'user', type: 'response', message: 'Recommendation: Phased expansion starting UK', status: 'success' }
]
},
errorRecovery: {
name: 'Error Recovery',
description: 'Demonstrating failure handling',
agents: [
{ id: 'orchestrator', name: 'Orchestrator', role: 'coordinator', color: 'blue' },
{ id: 'worker1', name: 'Worker 1', role: 'executor', color: 'emerald' },
{ id: 'worker2', name: 'Worker 2', role: 'executor', color: 'purple' }
],
steps: [
{ from: 'user', to: 'orchestrator', type: 'task', message: 'Process batch of 100 documents', status: 'success' },
{ from: 'orchestrator', to: 'worker1', type: 'delegate', message: 'Process documents 1-50', status: 'success' },
{ from: 'orchestrator', to: 'worker2', type: 'delegate', message: 'Process documents 51-100', status: 'success' },
{ from: 'worker1', to: 'worker1', type: 'processing', message: 'Processing batch 1... 25/50 complete', status: 'success' },
{ from: 'worker2', to: 'worker2', type: 'processing', message: 'Processing batch 2... 10/50 complete', status: 'success' },
{ from: 'worker2', to: 'orchestrator', type: 'error', message: 'ERROR: Rate limit exceeded on API call', status: 'error' },
{ from: 'orchestrator', to: 'orchestrator', type: 'checkpoint', message: 'Checkpoint saved: Worker2 at doc 60', status: 'warning' },
{ from: 'orchestrator', to: 'worker2', type: 'retry', message: 'Retry with exponential backoff (2s delay)', status: 'warning' },
{ from: 'worker2', to: 'worker2', type: 'processing', message: 'Resuming from checkpoint... 15/50', status: 'success' },
{ from: 'worker1', to: 'orchestrator', type: 'result', message: 'Batch 1 complete: 50/50 processed', status: 'success' },
{ from: 'worker2', to: 'orchestrator', type: 'result', message: 'Batch 2 complete: 50/50 processed', status: 'success' },
{ from: 'orchestrator', to: 'user', type: 'response', message: 'All 100 documents processed (1 retry)', status: 'success' }
]
}
};
const currentScenario = scenarios[scenario];
useEffect(() => {
if (isPlaying && currentStep < currentScenario.steps.length - 1) {
intervalRef.current = setTimeout(() => {
setCurrentStep(prev => prev + 1);
}, speed);
} else if (currentStep >= currentScenario.steps.length - 1) {
setIsPlaying(false);
}
return () => clearTimeout(intervalRef.current);
}, [isPlaying, currentStep, speed, currentScenario.steps.length]);
const handlePlay = () => setIsPlaying(true);
const handlePause = () => setIsPlaying(false);
const handleStep = () => {
if (currentStep < currentScenario.steps.length - 1) {
setCurrentStep(prev => prev + 1);
}
};
const handleReset = () => {
setIsPlaying(false);
setCurrentStep(0);
};
const toggleMessageExpand = (index) => {
setExpandedMessages(prev =>
prev.includes(index) ? prev.filter(i => i !== index) : [...prev, index]
);
};
const getStatusIcon = (status) => {
switch (status) {
case 'success': return <CheckCircle className="w-4 h-4 text-green-500" />;
case 'error': return <AlertTriangle className="w-4 h-4 text-red-500" />;
case 'warning': return <AlertTriangle className="w-4 h-4 text-amber-500" />;
default: return <Clock className="w-4 h-4 text-gray-400" />;
}
};
const getTypeColor = (type) => {
const colors = {
task: 'bg-blue-100 text-blue-700',
delegate: 'bg-purple-100 text-purple-700',
result: 'bg-green-100 text-green-700',
error: 'bg-red-100 text-red-700',
retry: 'bg-amber-100 text-amber-700',
tool: 'bg-gray-100 text-gray-700',
position: 'bg-blue-100 text-blue-700',
critique: 'bg-orange-100 text-orange-700',
support: 'bg-green-100 text-green-700',
planning: 'bg-indigo-100 text-indigo-700',
reasoning: 'bg-violet-100 text-violet-700',
synthesis: 'bg-cyan-100 text-cyan-700',
checkpoint: 'bg-amber-100 text-amber-700',
generation: 'bg-pink-100 text-pink-700',
response: 'bg-emerald-100 text-emerald-700',
calculation: 'bg-teal-100 text-teal-700',
processing: 'bg-slate-100 text-slate-700'
};
return colors[type] || 'bg-gray-100 text-gray-700';
};
const getAgentColor = (agentId) => {
const agent = currentScenario.agents.find(a => a.id === agentId);
if (!agent) return 'gray';
return agent.color;
};
const getAgentColorClass = (color) => {
const classes = {
blue: 'bg-blue-500',
emerald: 'bg-emerald-500',
purple: 'bg-purple-500',
amber: 'bg-amber-500',
gray: 'bg-gray-500'
};
return classes[color] || classes.gray;
};
const visibleSteps = currentScenario.steps.slice(0, currentStep + 1);
const getAgentStats = (agentId) => {
const agentSteps = visibleSteps.filter(s => s.from === agentId || s.to === agentId);
return {
messages: agentSteps.length,
errors: agentSteps.filter(s => s.status === 'error').length,
tools: agentSteps.filter(s => s.type === 'tool').length
};
};
return (
<div className="max-w-6xl mx-auto p-6 bg-gray-900 min-h-screen text-white">
{/* Header */}
<div className="bg-gray-800 rounded-lg p-6 mb-6">
<div className="flex items-center justify-between mb-4">
<div>
<h1 className="text-2xl font-bold">Multi-Agent Debugger</h1>
<p className="text-gray-400">Step through agent interactions and identify issues</p>
</div>
<select
value={scenario}
onChange={(e) => { setScenario(e.target.value); handleReset(); }}
className="bg-gray-700 border-gray-600 rounded-lg px-4 py-2 text-white"
>
{Object.entries(scenarios).map(([key, s]) => (
<option key={key} value={key}>{s.name}</option>
))}
</select>
</div>
{/* Playback Controls */}
<div className="flex items-center gap-4">
<div className="flex items-center gap-2">
<button
onClick={handleReset}
className="p-2 bg-gray-700 rounded-lg hover:bg-gray-600 transition-colors"
>
<RotateCcw className="w-5 h-5" />
</button>
{isPlaying ? (
<button
onClick={handlePause}
className="p-2 bg-amber-600 rounded-lg hover:bg-amber-500 transition-colors"
>
<Pause className="w-5 h-5" />
</button>
) : (
<button
onClick={handlePlay}
className="p-2 bg-green-600 rounded-lg hover:bg-green-500 transition-colors"
>
<Play className="w-5 h-5" />
</button>
)}
<button
onClick={handleStep}
disabled={currentStep >= currentScenario.steps.length - 1}
className="p-2 bg-gray-700 rounded-lg hover:bg-gray-600 transition-colors disabled:opacity-50"
>
<SkipForward className="w-5 h-5" />
</button>
</div>
{/* Progress Bar */}
<div className="flex-1">
<div className="w-full bg-gray-700 rounded-full h-2">
<div
className="bg-blue-500 h-2 rounded-full transition-all duration-300"
style={{ width: `${((currentStep + 1) / currentScenario.steps.length) * 100}%` }}
/>
</div>
<div className="flex justify-between text-xs text-gray-400 mt-1">
<span>Step {currentStep + 1} of {currentScenario.steps.length}</span>
<span>{currentScenario.description}</span>
</div>
</div>
{/* Speed Control */}
<div className="flex items-center gap-2">
<span className="text-sm text-gray-400">Speed:</span>
<select
value={speed}
onChange={(e) => setSpeed(Number(e.target.value))}
className="bg-gray-700 border-gray-600 rounded px-2 py-1 text-sm"
>
<option value={2000}>0.5x</option>
<option value={1000}>1x</option>
<option value={500}>2x</option>
<option value={250}>4x</option>
</select>
</div>
</div>
</div>
<div className="grid grid-cols-4 gap-6">
{/* Agent Panel */}
<div className="bg-gray-800 rounded-lg p-4">
<h3 className="text-lg font-medium mb-4 flex items-center gap-2">
<Users className="w-5 h-5" />
Agents
</h3>
<div className="space-y-3">
{currentScenario.agents.map(agent => {
const stats = getAgentStats(agent.id);
const isActive = visibleSteps.length > 0 &&
(visibleSteps[visibleSteps.length - 1].from === agent.id ||
visibleSteps[visibleSteps.length - 1].to === agent.id);
return (
<div
key={agent.id}
onClick={() => setSelectedAgent(selectedAgent === agent.id ? null : agent.id)}
className={`p-3 rounded-lg cursor-pointer transition-all ${
isActive ? 'ring-2 ring-blue-500' : ''
} ${selectedAgent === agent.id ? 'bg-gray-700' : 'bg-gray-750 hover:bg-gray-700'}`}
>
<div className="flex items-center gap-3">
<div className={`w-3 h-3 rounded-full ${getAgentColorClass(agent.color)} ${isActive ? 'animate-pulse' : ''}`} />
<div className="flex-1">
<p className="font-medium">{agent.name}</p>
<p className="text-xs text-gray-400">{agent.role}</p>
</div>
</div>
{selectedAgent === agent.id && (
<div className="mt-3 pt-3 border-t border-gray-600 text-sm">
<div className="flex justify-between text-gray-400">
<span>Messages:</span>
<span>{stats.messages}</span>
</div>
<div className="flex justify-between text-gray-400">
<span>Tool calls:</span>
<span>{stats.tools}</span>
</div>
{stats.errors > 0 && (
<div className="flex justify-between text-red-400">
<span>Errors:</span>
<span>{stats.errors}</span>
</div>
)}
</div>
)}
</div>
);
})}
</div>
</div>
{/* Message Timeline */}
<div className="col-span-3 bg-gray-800 rounded-lg p-4">
<h3 className="text-lg font-medium mb-4 flex items-center gap-2">
<MessageSquare className="w-5 h-5" />
Message Timeline
</h3>
<div className="space-y-2 max-h-[500px] overflow-y-auto">
{visibleSteps.map((step, index) => (
<div
key={index}
className={`p-3 rounded-lg bg-gray-750 transition-all ${
index === currentStep ? 'ring-2 ring-blue-500' : ''
} ${selectedAgent && step.from !== selectedAgent && step.to !== selectedAgent ? 'opacity-40' : ''}`}
>
<div
className="flex items-start gap-3 cursor-pointer"
onClick={() => toggleMessageExpand(index)}
>
<div className="flex items-center gap-2 min-w-[140px]">
<div className={`w-2 h-2 rounded-full ${getAgentColorClass(getAgentColor(step.from))}`} />
<span className="text-sm text-gray-300">{step.from}</span>
<ChevronRight className="w-4 h-4 text-gray-500" />
<div className={`w-2 h-2 rounded-full ${getAgentColorClass(getAgentColor(step.to))}`} />
<span className="text-sm text-gray-300">{step.to}</span>
</div>
<span className={`px-2 py-0.5 rounded text-xs ${getTypeColor(step.type)}`}>
{step.type}
</span>
<p className="flex-1 text-sm">{step.message}</p>
{getStatusIcon(step.status)}
{expandedMessages.includes(index) ? (
<ChevronDown className="w-4 h-4 text-gray-400" />
) : (
<ChevronRight className="w-4 h-4 text-gray-400" />
)}
</div>
{expandedMessages.includes(index) && (
<div className="mt-3 pt-3 border-t border-gray-600 text-sm">
<div className="grid grid-cols-2 gap-4 text-gray-400">
<div>
<p className="text-xs uppercase text-gray-500 mb-1">From Agent</p>
<p>{step.from}</p>
</div>
<div>
<p className="text-xs uppercase text-gray-500 mb-1">To Agent</p>
<p>{step.to}</p>
</div>
<div>
<p className="text-xs uppercase text-gray-500 mb-1">Message Type</p>
<p>{step.type}</p>
</div>
<div>
<p className="text-xs uppercase text-gray-500 mb-1">Status</p>
<p className={
step.status === 'success' ? 'text-green-400' :
step.status === 'error' ? 'text-red-400' : 'text-amber-400'
}>{step.status}</p>
</div>
</div>
<div className="mt-3">
<p className="text-xs uppercase text-gray-500 mb-1">Full Message</p>
<p className="bg-gray-900 p-2 rounded font-mono text-xs">{step.message}</p>
</div>
</div>
)}
</div>
))}
</div>
</div>
</div>
{/* Legend */}
<div className="mt-6 bg-gray-800 rounded-lg p-4">
<h3 className="text-sm font-medium text-gray-400 mb-3">Message Types</h3>
<div className="flex flex-wrap gap-2">
{['task', 'delegate', 'result', 'error', 'retry', 'tool', 'planning', 'reasoning', 'synthesis'].map(type => (
<span key={type} className={`px-2 py-1 rounded text-xs ${getTypeColor(type)}`}>
{type}
</span>
))}
</div>
</div>
</div>
);
};
export default MultiAgentDebugger;
Features
- Scenario Selection: Choose from hierarchical, debate, and error recovery scenarios
- Playback Controls: Play, pause, step-through, and reset
- Speed Control: Adjust simulation speed
- Agent Filtering: Click agents to filter their messages
- Message Expansion: Click messages for detailed view
- Visual Indicators: Color-coded agents, message types, and status
Component maintained by CODITECT Education Team. Feedback: education@coditect.com