Fix inbox creation issues in agent coordinator

- Fixed Task.new/3 to handle both maps and keyword lists
- Added robust inbox existence checking in find_available_agent
- Ensure inbox creation during agent registration and task assignment
- Add helper function ensure_inbox_exists to avoid crashes
This commit is contained in:
Ra
2025-08-23 14:46:28 -07:00
parent 5048db99c7
commit 943d8ad4d7
40 changed files with 7798 additions and 404 deletions

View File

@@ -0,0 +1,226 @@
#!/usr/bin/env elixir
# Auto-heartbeat demo script
# This demonstrates the enhanced coordination system with automatic heartbeats
Mix.install([
{:jason, "~> 1.4"},
{:uuid, "~> 1.1"}
])
# Load the agent coordinator modules
Code.require_file("lib/agent_coordinator.ex")
Code.require_file("lib/agent_coordinator/agent.ex")
Code.require_file("lib/agent_coordinator/task.ex")
Code.require_file("lib/agent_coordinator/inbox.ex")
Code.require_file("lib/agent_coordinator/task_registry.ex")
Code.require_file("lib/agent_coordinator/mcp_server.ex")
Code.require_file("lib/agent_coordinator/auto_heartbeat.ex")
Code.require_file("lib/agent_coordinator/enhanced_mcp_server.ex")
Code.require_file("lib/agent_coordinator/client.ex")
defmodule AutoHeartbeatDemo do
@moduledoc """
Demonstrates the automatic heartbeat functionality
"""
def run do
IO.puts("🚀 Starting Auto-Heartbeat Demo")
IO.puts("================================")
# Start the core services
start_services()
# Demo 1: Basic client with auto-heartbeat
demo_basic_client()
# Demo 2: Multiple agents with coordination
demo_multiple_agents()
# Demo 3: Task creation and completion with heartbeats
demo_task_workflow()
IO.puts("\n✅ Demo completed!")
end
defp start_services do
IO.puts("\n📡 Starting coordination services...")
# Start registry for inboxes
Registry.start_link(keys: :unique, name: AgentCoordinator.InboxRegistry)
# Start dynamic supervisor
DynamicSupervisor.start_link(name: AgentCoordinator.InboxSupervisor, strategy: :one_for_one)
# Start task registry (without NATS for demo)
AgentCoordinator.TaskRegistry.start_link()
# Start MCP servers
AgentCoordinator.MCPServer.start_link()
AgentCoordinator.AutoHeartbeat.start_link()
AgentCoordinator.EnhancedMCPServer.start_link()
Process.sleep(500) # Let services initialize
IO.puts("✅ Services started")
end
defp demo_basic_client do
IO.puts("\n🤖 Demo 1: Basic Client with Auto-Heartbeat")
IO.puts("-------------------------------------------")
# Start a client session
{:ok, client} = AgentCoordinator.Client.start_session(
"DemoAgent1",
[:coding, :analysis],
auto_heartbeat: true,
heartbeat_interval: 3000 # 3 seconds for demo
)
# Get session info
{:ok, info} = AgentCoordinator.Client.get_session_info(client)
IO.puts("Agent registered: #{info.agent_name} (ID: #{info.agent_id})")
IO.puts("Auto-heartbeat enabled: #{info.auto_heartbeat_enabled}")
# Check task board to see the agent
{:ok, board} = AgentCoordinator.Client.get_task_board(client)
agent = Enum.find(board.agents, fn a -> a["agent_id"] == info.agent_id end)
IO.puts("Agent status: #{agent["status"]}")
IO.puts("Agent online: #{agent["online"]}")
IO.puts("Session active: #{agent["session_active"]}")
# Wait and check heartbeat activity
IO.puts("\n⏱️ Waiting 8 seconds to observe automatic heartbeats...")
Process.sleep(8000)
# Check board again
{:ok, updated_board} = AgentCoordinator.Client.get_task_board(client)
updated_agent = Enum.find(updated_board.agents, fn a -> a["agent_id"] == info.agent_id end)
IO.puts("Agent still online: #{updated_agent["online"]}")
IO.puts("Active sessions: #{updated_board.active_sessions}")
# Stop the client
AgentCoordinator.Client.stop_session(client)
IO.puts("✅ Client session stopped")
end
defp demo_multiple_agents do
IO.puts("\n👥 Demo 2: Multiple Agents Coordination")
IO.puts("--------------------------------------")
# Start multiple agents
agents = []
{:ok, agent1} = AgentCoordinator.Client.start_session("CodingAgent", [:coding, :testing])
{:ok, agent2} = AgentCoordinator.Client.start_session("AnalysisAgent", [:analysis, :documentation])
{:ok, agent3} = AgentCoordinator.Client.start_session("ReviewAgent", [:review, :analysis])
agents = [agent1, agent2, agent3]
# Check the task board
{:ok, board} = AgentCoordinator.Client.get_task_board(agent1)
IO.puts("Total agents: #{length(board.agents)}")
IO.puts("Active sessions: #{board.active_sessions}")
Enum.each(board.agents, fn agent ->
if agent["online"] do
IO.puts(" - #{agent["name"]}: #{Enum.join(agent["capabilities"], ", ")} (ONLINE)")
else
IO.puts(" - #{agent["name"]}: #{Enum.join(agent["capabilities"], ", ")} (offline)")
end
end)
# Demonstrate heartbeat coordination
IO.puts("\n💓 All agents sending heartbeats...")
# Each agent does some activity
Enum.each(agents, fn agent ->
AgentCoordinator.Client.heartbeat(agent)
end)
Process.sleep(1000)
# Check board after activity
{:ok, updated_board} = AgentCoordinator.Client.get_task_board(agent1)
online_count = Enum.count(updated_board.agents, fn a -> a["online"] end)
IO.puts("Agents online after heartbeat activity: #{online_count}/#{length(updated_board.agents)}")
# Cleanup
Enum.each(agents, &AgentCoordinator.Client.stop_session/1)
IO.puts("✅ All agents disconnected")
end
defp demo_task_workflow do
IO.puts("\n📋 Demo 3: Task Workflow with Heartbeats")
IO.puts("---------------------------------------")
# Start an agent
{:ok, agent} = AgentCoordinator.Client.start_session("WorkflowAgent", [:coding, :testing])
# Create a task
task_result = AgentCoordinator.Client.create_task(
agent,
"Fix Bug #123",
"Fix the authentication bug in user login",
%{
"priority" => "high",
"file_paths" => ["lib/auth.ex", "test/auth_test.exs"],
"required_capabilities" => ["coding", "testing"]
}
)
case task_result do
{:ok, task_data} ->
IO.puts("✅ Task created: #{task_data["task_id"]}")
# Check heartbeat metadata
if Map.has_key?(task_data, "_heartbeat_metadata") do
metadata = task_data["_heartbeat_metadata"]
IO.puts(" Heartbeat metadata: Agent #{metadata["agent_id"]} at #{metadata["timestamp"]}")
end
{:error, reason} ->
IO.puts("❌ Task creation failed: #{reason}")
end
# Try to get next task
case AgentCoordinator.Client.get_next_task(agent) do
{:ok, task} ->
if Map.has_key?(task, "task_id") do
IO.puts("📝 Got task: #{task["title"]}")
# Simulate some work
IO.puts("⚙️ Working on task...")
Process.sleep(2000)
# Complete the task
case AgentCoordinator.Client.complete_task(agent) do
{:ok, result} ->
IO.puts("✅ Task completed: #{result["task_id"]}")
{:error, reason} ->
IO.puts("❌ Task completion failed: #{reason}")
end
else
IO.puts("📝 No tasks available: #{task["message"]}")
end
{:error, reason} ->
IO.puts("❌ Failed to get task: #{reason}")
end
# Final status check
{:ok, final_info} = AgentCoordinator.Client.get_session_info(agent)
IO.puts("Final session info:")
IO.puts(" - Last heartbeat: #{final_info.last_heartbeat}")
IO.puts(" - Session duration: #{final_info.session_duration} seconds")
# Cleanup
AgentCoordinator.Client.stop_session(agent)
IO.puts("✅ Workflow demo completed")
end
end
# Run the demo
AutoHeartbeatDemo.run()

View File

@@ -0,0 +1,150 @@
defmodule MCPServerDemo do
@moduledoc """
Demonstration script showing MCP server functionality
"""
alias AgentCoordinator.MCPServer
def run do
IO.puts("🚀 Testing Agent Coordinator MCP Server")
IO.puts("=" |> String.duplicate(50))
# Test 1: Get tools list
IO.puts("\n📋 Getting available tools...")
tools_request = %{"method" => "tools/list", "jsonrpc" => "2.0", "id" => 1}
tools_response = MCPServer.handle_mcp_request(tools_request)
case tools_response do
%{"result" => %{"tools" => tools}} ->
IO.puts("✅ Found #{length(tools)} tools:")
Enum.each(tools, fn tool ->
IO.puts(" - #{tool["name"]}: #{tool["description"]}")
end)
error ->
IO.puts("❌ Error getting tools: #{inspect(error)}")
end
# Test 2: Register an agent
IO.puts("\n👤 Registering test agent...")
register_request = %{
"method" => "tools/call",
"params" => %{
"name" => "register_agent",
"arguments" => %{
"name" => "DemoAgent",
"capabilities" => ["coding", "testing"]
}
},
"jsonrpc" => "2.0",
"id" => 2
}
register_response = MCPServer.handle_mcp_request(register_request)
agent_id = case register_response do
%{"result" => %{"content" => [%{"text" => text}]}} ->
data = Jason.decode!(text)
IO.puts("✅ Agent registered: #{data["agent_id"]}")
data["agent_id"]
error ->
IO.puts("❌ Error registering agent: #{inspect(error)}")
nil
end
if agent_id do
# Test 3: Create a task
IO.puts("\n📝 Creating a test task...")
task_request = %{
"method" => "tools/call",
"params" => %{
"name" => "create_task",
"arguments" => %{
"title" => "Demo Task",
"description" => "A demonstration task for the MCP server",
"priority" => "high",
"required_capabilities" => ["coding"]
}
},
"jsonrpc" => "2.0",
"id" => 3
}
task_response = MCPServer.handle_mcp_request(task_request)
case task_response do
%{"result" => %{"content" => [%{"text" => text}]}} ->
data = Jason.decode!(text)
IO.puts("✅ Task created: #{data["task_id"]}")
if data["assigned_to"] do
IO.puts(" Assigned to: #{data["assigned_to"]}")
end
error ->
IO.puts("❌ Error creating task: #{inspect(error)}")
end
# Test 4: Get task board
IO.puts("\n📊 Getting task board...")
board_request = %{
"method" => "tools/call",
"params" => %{
"name" => "get_task_board",
"arguments" => %{}
},
"jsonrpc" => "2.0",
"id" => 4
}
board_response = MCPServer.handle_mcp_request(board_request)
case board_response do
%{"result" => %{"content" => [%{"text" => text}]}} ->
data = Jason.decode!(text)
IO.puts("✅ Task board retrieved:")
Enum.each(data["agents"], fn agent ->
IO.puts(" Agent: #{agent["name"]} (#{agent["agent_id"]})")
IO.puts(" Capabilities: #{Enum.join(agent["capabilities"], ", ")}")
IO.puts(" Status: #{agent["status"]}")
if agent["current_task"] do
IO.puts(" Current Task: #{agent["current_task"]["title"]}")
else
IO.puts(" Current Task: None")
end
IO.puts(" Pending: #{agent["pending_tasks"]} | Completed: #{agent["completed_tasks"]}")
IO.puts("")
end)
error ->
IO.puts("❌ Error getting task board: #{inspect(error)}")
end
# Test 5: Send heartbeat
IO.puts("\n💓 Sending heartbeat...")
heartbeat_request = %{
"method" => "tools/call",
"params" => %{
"name" => "heartbeat",
"arguments" => %{
"agent_id" => agent_id
}
},
"jsonrpc" => "2.0",
"id" => 5
}
heartbeat_response = MCPServer.handle_mcp_request(heartbeat_request)
case heartbeat_response do
%{"result" => %{"content" => [%{"text" => text}]}} ->
data = Jason.decode!(text)
IO.puts("✅ Heartbeat sent: #{data["status"]}")
error ->
IO.puts("❌ Error sending heartbeat: #{inspect(error)}")
end
end
IO.puts("\n🎉 MCP Server testing completed!")
IO.puts("=" |> String.duplicate(50))
end
end
# Run the demo
MCPServerDemo.run()

View File

@@ -0,0 +1,172 @@
defmodule FullWorkflowDemo do
@moduledoc """
Demonstration of the complete task workflow
"""
alias AgentCoordinator.MCPServer
def run do
IO.puts("🚀 Complete Agent Coordinator Workflow Demo")
IO.puts("=" |> String.duplicate(50))
# Register multiple agents
IO.puts("\n👥 Registering multiple agents...")
agents = [
%{"name" => "CodingAgent", "capabilities" => ["coding", "debugging"]},
%{"name" => "TestingAgent", "capabilities" => ["testing", "qa"]},
%{"name" => "FullStackAgent", "capabilities" => ["coding", "testing", "ui"]}
]
agent_ids = Enum.map(agents, fn agent ->
register_request = %{
"method" => "tools/call",
"params" => %{
"name" => "register_agent",
"arguments" => agent
},
"jsonrpc" => "2.0",
"id" => :rand.uniform(1000)
}
case MCPServer.handle_mcp_request(register_request) do
%{"result" => %{"content" => [%{"text" => text}]}} ->
data = Jason.decode!(text)
IO.puts("#{agent["name"]} registered: #{data["agent_id"]}")
data["agent_id"]
error ->
IO.puts("❌ Error registering #{agent["name"]}: #{inspect(error)}")
nil
end
end)
# Create tasks with different requirements
IO.puts("\n📝 Creating various tasks...")
tasks = [
%{"title" => "Fix Bug #123", "description" => "Debug authentication issue", "priority" => "high", "required_capabilities" => ["coding", "debugging"]},
%{"title" => "Write Unit Tests", "description" => "Create comprehensive test suite", "priority" => "medium", "required_capabilities" => ["testing"]},
%{"title" => "UI Enhancement", "description" => "Improve user interface", "priority" => "low", "required_capabilities" => ["ui", "coding"]},
%{"title" => "Code Review", "description" => "Review pull request #456", "priority" => "medium", "required_capabilities" => ["coding"]}
]
task_ids = Enum.map(tasks, fn task ->
task_request = %{
"method" => "tools/call",
"params" => %{
"name" => "create_task",
"arguments" => task
},
"jsonrpc" => "2.0",
"id" => :rand.uniform(1000)
}
case MCPServer.handle_mcp_request(task_request) do
%{"result" => %{"content" => [%{"text" => text}]}} ->
data = Jason.decode!(text)
IO.puts("✅ Task '#{task["title"]}' created: #{data["task_id"]}")
if data["assigned_to"] do
IO.puts(" → Assigned to: #{data["assigned_to"]}")
end
data["task_id"]
error ->
IO.puts("❌ Error creating task '#{task["title"]}': #{inspect(error)}")
nil
end
end)
# Show current task board
IO.puts("\n📊 Current Task Board:")
show_task_board()
# Test getting next task for first agent
if agent_id = Enum.at(agent_ids, 0) do
IO.puts("\n🎯 Getting next task for CodingAgent...")
next_task_request = %{
"method" => "tools/call",
"params" => %{
"name" => "get_next_task",
"arguments" => %{
"agent_id" => agent_id
}
},
"jsonrpc" => "2.0",
"id" => :rand.uniform(1000)
}
case MCPServer.handle_mcp_request(next_task_request) do
%{"result" => %{"content" => [%{"text" => text}]}} ->
data = Jason.decode!(text)
if data["task"] do
IO.puts("✅ Got task: #{data["task"]["title"]}")
# Complete the task
IO.puts("\n✅ Completing the task...")
complete_request = %{
"method" => "tools/call",
"params" => %{
"name" => "complete_task",
"arguments" => %{
"agent_id" => agent_id,
"result" => "Task completed successfully!"
}
},
"jsonrpc" => "2.0",
"id" => :rand.uniform(1000)
}
case MCPServer.handle_mcp_request(complete_request) do
%{"result" => %{"content" => [%{"text" => text}]}} ->
completion_data = Jason.decode!(text)
IO.puts("✅ Task completed: #{completion_data["message"]}")
error ->
IO.puts("❌ Error completing task: #{inspect(error)}")
end
else
IO.puts(" No tasks available: #{data["message"]}")
end
error ->
IO.puts("❌ Error getting next task: #{inspect(error)}")
end
end
# Final task board
IO.puts("\n📊 Final Task Board:")
show_task_board()
IO.puts("\n🎉 Complete workflow demonstration finished!")
IO.puts("=" |> String.duplicate(50))
end
defp show_task_board do
board_request = %{
"method" => "tools/call",
"params" => %{
"name" => "get_task_board",
"arguments" => %{}
},
"jsonrpc" => "2.0",
"id" => :rand.uniform(1000)
}
case MCPServer.handle_mcp_request(board_request) do
%{"result" => %{"content" => [%{"text" => text}]}} ->
data = Jason.decode!(text)
Enum.each(data["agents"], fn agent ->
IO.puts(" 📱 #{agent["name"]} (#{String.slice(agent["agent_id"], 0, 8)}...)")
IO.puts(" Capabilities: #{Enum.join(agent["capabilities"], ", ")}")
IO.puts(" Status: #{agent["status"]}")
if agent["current_task"] do
IO.puts(" 🎯 Current: #{agent["current_task"]["title"]}")
end
IO.puts(" 📈 Stats: #{agent["pending_tasks"]} pending | #{agent["completed_tasks"]} completed")
IO.puts("")
end)
error ->
IO.puts("❌ Error getting task board: #{inspect(error)}")
end
end
end
# Run the demo
FullWorkflowDemo.run()

193
examples/mcp_client_example.py Executable file
View File

@@ -0,0 +1,193 @@
#!/usr/bin/env python3
"""
AgentCoordinator MCP Client Example
This script demonstrates how to connect to and interact with the
AgentCoordinator MCP server programmatically.
"""
import json
import subprocess
import sys
import uuid
from typing import Dict, Any, Optional
class AgentCoordinatorMCP:
def __init__(self, launcher_path: str = "./scripts/mcp_launcher.sh"):
self.launcher_path = launcher_path
self.process = None
def start(self):
"""Start the MCP server process"""
try:
self.process = subprocess.Popen(
[self.launcher_path],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
bufsize=0
)
print("🚀 MCP server started")
return True
except Exception as e:
print(f"❌ Failed to start MCP server: {e}")
return False
def stop(self):
"""Stop the MCP server process"""
if self.process:
self.process.terminate()
self.process.wait()
print("🛑 MCP server stopped")
def send_request(self, method: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
"""Send a JSON-RPC request to the MCP server"""
if not self.process:
raise RuntimeError("MCP server not started")
request = {
"jsonrpc": "2.0",
"id": str(uuid.uuid4()),
"method": method
}
if params:
request["params"] = params
# Send request
request_json = json.dumps(request) + "\n"
self.process.stdin.write(request_json)
self.process.stdin.flush()
# Read response
response_line = self.process.stdout.readline()
if not response_line:
raise RuntimeError("No response from MCP server")
return json.loads(response_line.strip())
def get_tools(self) -> Dict[str, Any]:
"""Get list of available tools"""
return self.send_request("tools/list")
def register_agent(self, name: str, capabilities: list) -> Dict[str, Any]:
"""Register a new agent"""
return self.send_request("tools/call", {
"name": "register_agent",
"arguments": {
"name": name,
"capabilities": capabilities
}
})
def create_task(self, title: str, description: str, priority: str = "normal",
required_capabilities: list = None) -> Dict[str, Any]:
"""Create a new task"""
args = {
"title": title,
"description": description,
"priority": priority
}
if required_capabilities:
args["required_capabilities"] = required_capabilities
return self.send_request("tools/call", {
"name": "create_task",
"arguments": args
})
def get_next_task(self, agent_id: str) -> Dict[str, Any]:
"""Get next task for an agent"""
return self.send_request("tools/call", {
"name": "get_next_task",
"arguments": {"agent_id": agent_id}
})
def complete_task(self, agent_id: str, result: str) -> Dict[str, Any]:
"""Complete current task"""
return self.send_request("tools/call", {
"name": "complete_task",
"arguments": {
"agent_id": agent_id,
"result": result
}
})
def get_task_board(self) -> Dict[str, Any]:
"""Get task board overview"""
return self.send_request("tools/call", {
"name": "get_task_board",
"arguments": {}
})
def heartbeat(self, agent_id: str) -> Dict[str, Any]:
"""Send agent heartbeat"""
return self.send_request("tools/call", {
"name": "heartbeat",
"arguments": {"agent_id": agent_id}
})
def demo():
"""Demonstrate MCP client functionality"""
print("🎯 AgentCoordinator MCP Client Demo")
print("=" * 50)
client = AgentCoordinatorMCP()
try:
# Start server
if not client.start():
return
# Wait for server to be ready
import time
time.sleep(2)
# Get tools
print("\n📋 Available tools:")
tools_response = client.get_tools()
if "result" in tools_response:
for tool in tools_response["result"]["tools"]:
print(f" - {tool['name']}: {tool['description']}")
# Register agent
print("\n👤 Registering agent...")
register_response = client.register_agent("PythonAgent", ["coding", "testing"])
if "result" in register_response:
content = register_response["result"]["content"][0]["text"]
agent_data = json.loads(content)
agent_id = agent_data["agent_id"]
print(f"✅ Agent registered: {agent_id}")
# Create task
print("\n📝 Creating task...")
task_response = client.create_task(
"Python Script",
"Write a Python script for data processing",
"high",
["coding"]
)
if "result" in task_response:
content = task_response["result"]["content"][0]["text"]
task_data = json.loads(content)
print(f"✅ Task created: {task_data['task_id']}")
# Get task board
print("\n📊 Task board:")
board_response = client.get_task_board()
if "result" in board_response:
content = board_response["result"]["content"][0]["text"]
board_data = json.loads(content)
for agent in board_data["agents"]:
print(f" 📱 {agent['name']}: {agent['status']}")
print(f" Capabilities: {', '.join(agent['capabilities'])}")
print(f" Pending: {agent['pending_tasks']}, Completed: {agent['completed_tasks']}")
except Exception as e:
print(f"❌ Error: {e}")
finally:
client.stop()
if __name__ == "__main__":
demo()

235
examples/unified_demo.exs Normal file
View File

@@ -0,0 +1,235 @@
#!/usr/bin/env elixir
# Unified MCP Server Demo
# This demo shows how the unified MCP server provides automatic task tracking
# for all external MCP server operations
Mix.install([
{:agent_coordinator, path: "."},
{:jason, "~> 1.4"}
])
defmodule UnifiedDemo do
@moduledoc """
Demo showing the unified MCP server with automatic task tracking
"""
def run do
IO.puts("🚀 Starting Unified MCP Server Demo...")
IO.puts("=" * 60)
# Start the unified system
{:ok, _} = AgentCoordinator.TaskRegistry.start_link()
{:ok, _} = AgentCoordinator.MCPServerManager.start_link(config_file: "mcp_servers.json")
{:ok, _} = AgentCoordinator.UnifiedMCPServer.start_link()
IO.puts("✅ Unified MCP server started successfully")
# Demonstrate automatic tool aggregation
demonstrate_tool_aggregation()
# Demonstrate automatic task tracking
demonstrate_automatic_task_tracking()
# Demonstrate coordination features
demonstrate_coordination_features()
IO.puts("\n🎉 Demo completed successfully!")
IO.puts("📋 Key Points:")
IO.puts(" • All external MCP servers are managed internally")
IO.puts(" • Every tool call automatically creates/updates tasks")
IO.puts(" • GitHub Copilot sees only one MCP server")
IO.puts(" • Coordination tools are still available for planning")
end
defp demonstrate_tool_aggregation do
IO.puts("\n📊 Testing Tool Aggregation...")
# Get all available tools from the unified server
request = %{
"jsonrpc" => "2.0",
"id" => 1,
"method" => "tools/list"
}
response = AgentCoordinator.UnifiedMCPServer.handle_mcp_request(request)
case response do
%{"result" => %{"tools" => tools}} ->
IO.puts("✅ Found #{length(tools)} total tools from all servers:")
# Group tools by server origin
coordinator_tools =
Enum.filter(tools, fn tool ->
tool["name"] in ~w[register_agent create_task get_next_task complete_task get_task_board heartbeat]
end)
external_tools = tools -- coordinator_tools
IO.puts(" • Agent Coordinator: #{length(coordinator_tools)} tools")
IO.puts(" • External Servers: #{length(external_tools)} tools")
# Show sample tools
IO.puts("\n📝 Sample Agent Coordinator tools:")
Enum.take(coordinator_tools, 3)
|> Enum.each(fn tool ->
IO.puts(" - #{tool["name"]}: #{tool["description"]}")
end)
if length(external_tools) > 0 do
IO.puts("\n📝 Sample External tools:")
Enum.take(external_tools, 3)
|> Enum.each(fn tool ->
IO.puts(
" - #{tool["name"]}: #{String.slice(tool["description"] || "External tool", 0, 50)}"
)
end)
end
error ->
IO.puts("❌ Error getting tools: #{inspect(error)}")
end
end
defp demonstrate_automatic_task_tracking do
IO.puts("\n🎯 Testing Automatic Task Tracking...")
# First, register an agent (this creates an agent context)
register_request = %{
"jsonrpc" => "2.0",
"id" => 2,
"method" => "tools/call",
"params" => %{
"name" => "register_agent",
"arguments" => %{
"name" => "Demo Agent",
"capabilities" => ["coding", "analysis"]
}
}
}
response = AgentCoordinator.UnifiedMCPServer.handle_mcp_request(register_request)
IO.puts("✅ Agent registered: #{inspect(response["result"])}")
# Now simulate using an external tool - this should automatically create a task
# Note: In a real scenario, external servers would be running
external_tool_request = %{
"jsonrpc" => "2.0",
"id" => 3,
"method" => "tools/call",
"params" => %{
"name" => "mcp_filesystem_read_file",
"arguments" => %{
"path" => "/home/ra/agent_coordinator/README.md"
}
}
}
IO.puts("🔄 Simulating external tool call: mcp_filesystem_read_file")
external_response =
AgentCoordinator.UnifiedMCPServer.handle_mcp_request(external_tool_request)
case external_response do
%{"result" => result} ->
IO.puts("✅ Tool call succeeded with automatic task tracking")
if metadata = result["_metadata"] do
IO.puts("📊 Automatic metadata:")
IO.puts(" - Tool: #{metadata["tool_name"]}")
IO.puts(" - Agent: #{metadata["agent_id"]}")
IO.puts(" - Auto-tracked: #{metadata["auto_tracked"]}")
end
%{"error" => error} ->
IO.puts(" External server not available (expected in demo): #{error["message"]}")
IO.puts(" In real usage, this would automatically create a task")
end
# Check the task board to see auto-created tasks
IO.puts("\n📋 Checking Task Board...")
task_board_request = %{
"jsonrpc" => "2.0",
"id" => 4,
"method" => "tools/call",
"params" => %{
"name" => "get_task_board",
"arguments" => %{}
}
}
board_response = AgentCoordinator.UnifiedMCPServer.handle_mcp_request(task_board_request)
case board_response do
%{"result" => %{"content" => [%{"text" => board_json}]}} ->
case Jason.decode(board_json) do
{:ok, board} ->
IO.puts("✅ Task Board Status:")
IO.puts(" - Total Agents: #{board["total_agents"]}")
IO.puts(" - Active Tasks: #{board["active_tasks"]}")
IO.puts(" - Pending Tasks: #{board["pending_count"]}")
if length(board["agents"]) > 0 do
agent = List.first(board["agents"])
IO.puts(" - Agent '#{agent["name"]}' is #{agent["status"]}")
end
{:error, _} ->
IO.puts("📊 Task board response: #{board_json}")
end
_ ->
IO.puts("📊 Task board response: #{inspect(board_response)}")
end
end
defp demonstrate_coordination_features do
IO.puts("\n🤝 Testing Coordination Features...")
# Create a manual task for coordination
create_task_request = %{
"jsonrpc" => "2.0",
"id" => 5,
"method" => "tools/call",
"params" => %{
"name" => "create_task",
"arguments" => %{
"title" => "Review Database Design",
"description" => "Review the database schema for the new feature",
"priority" => "high"
}
}
}
response = AgentCoordinator.UnifiedMCPServer.handle_mcp_request(create_task_request)
IO.puts("✅ Manual task created for coordination: #{inspect(response["result"])}")
# Send a heartbeat
heartbeat_request = %{
"jsonrpc" => "2.0",
"id" => 6,
"method" => "tools/call",
"params" => %{
"name" => "heartbeat",
"arguments" => %{
"agent_id" => "github_copilot_session"
}
}
}
heartbeat_response = AgentCoordinator.UnifiedMCPServer.handle_mcp_request(heartbeat_request)
IO.puts("✅ Heartbeat sent: #{inspect(heartbeat_response["result"])}")
IO.puts("\n💡 Coordination tools are seamlessly integrated:")
IO.puts(" • Agents can still create tasks manually for planning")
IO.puts(" • Heartbeats maintain agent liveness")
IO.puts(" • Task board shows both auto and manual tasks")
IO.puts(" • All operations work through the single unified interface")
end
end
# Run the demo
UnifiedDemo.run()