Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions docs/network-configuration/network-configuration.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,14 @@ network:
message_queue_size: 1000 # Internal message queue size
message_timeout: 30.0 # Message processing timeout
message_routing_enabled: true # Enable intelligent message routing

# Agent lifecycle
auto_start_agents: false # Automatically start all discovered agents during network initialization
```

**Agent Lifecycle:**
- `auto_start_agents` - When `true`, automatically starts all agents discovered in `workspace/agents/` directory during network initialization. Useful for development and debugging workflows to avoid manual agent startup via admin interface.

## Transport Configuration

Networks support multiple transport protocols for agent communication.
Expand Down Expand Up @@ -445,6 +451,7 @@ network:
# Development-friendly settings
encryption_enabled: false
disable_agent_secret_verification: true
auto_start_agents: true # Auto-start agents for easier debugging

mods:
- name: "openagents.mods.workspace.messaging"
Expand Down
62 changes: 59 additions & 3 deletions src/openagents/core/agent_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,16 +48,18 @@ class AgentManager:
and log file management for all agents in workspace/agents/ directory.
"""

def __init__(self, workspace_path: Path):
def __init__(self, workspace_path: Path, auto_start_agents: bool = False):
"""Initialize agent manager.

Args:
workspace_path: Path to workspace directory
auto_start_agents: If True, automatically start all discovered agents during initialization
"""
self.workspace_path = Path(workspace_path)
self.agents_dir = self.workspace_path / "agents"
self.logs_dir = self.workspace_path / "logs" / "agents"
self.env_vars_dir = self.workspace_path / "config" / "agent_env"
self.auto_start_agents = auto_start_agents

# Ensure directories exist
self.logs_dir.mkdir(parents=True, exist_ok=True)
Expand All @@ -73,7 +75,7 @@ def __init__(self, workspace_path: Path):
# Reference to network for agent unregistration on stop
self._network = None

logger.info(f"AgentManager initialized for workspace: {self.workspace_path}")
logger.info(f"AgentManager initialized for workspace: {self.workspace_path} (auto_start_agents={auto_start_agents})")

def set_network(self, network) -> None:
"""Set the network reference for agent unregistration.
Expand Down Expand Up @@ -102,6 +104,11 @@ async def start(self) -> bool:

self.is_running = True
logger.info(f"AgentManager started with {len(self.agents)} discovered agents")

# Auto-start agents if configured
if self.auto_start_agents:
await self._auto_start_all_agents()

return True

except Exception as e:
Expand Down Expand Up @@ -950,7 +957,56 @@ async def _monitor_processes(self) -> None:
except Exception as e:
logger.error(f"Error in process monitor: {e}")
await asyncio.sleep(2.0)


async def _auto_start_all_agents(self) -> None:
"""Automatically start all discovered agents when auto_start_agents is enabled.

This method is called during AgentManager initialization if auto_start_agents is True.
It starts all agents that have been discovered.
"""
all_agents_status = self.get_all_agents_status()

if not all_agents_status:
logger.info("No agents discovered, skipping auto-start")
return

# Filter out agents that are already running
agents_to_start = [
agent_status
for agent_status in all_agents_status
if agent_status and agent_status.get("status") != "running"
]

if not agents_to_start:
logger.info("All discovered agents are already running")
return

logger.info(f"Auto-starting {len(agents_to_start)} agent(s)...")

# Start agents with a small delay between each to avoid overwhelming the system
for agent_status in agents_to_start:
agent_id = agent_status.get("agent_id")
if not agent_id:
continue

try:
logger.info(f"Auto-starting agent: {agent_id}")
result = await self.start_agent(agent_id)
if result.get("success"):
logger.info(f"✅ Successfully auto-started agent: {agent_id}")
else:
error_msg = result.get("message", "Unknown error")
logger.warning(
f"⚠️ Failed to auto-start agent '{agent_id}': {error_msg}"
)

# Small delay between starts to avoid overwhelming the system
await asyncio.sleep(0.5)
except Exception as e:
logger.error(f"Error auto-starting agent '{agent_id}': {e}")

logger.info(f"Auto-start completed for {len(agents_to_start)} agent(s)")

async def _stop_all_agents(self) -> None:
"""Stop all running agents."""
running_agents = [
Expand Down
6 changes: 5 additions & 1 deletion src/openagents/core/network.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,11 @@ def __init__(self, config: NetworkConfig, workspace_path: Optional[str]):
if self.workspace_manager:
from openagents.core.agent_manager import AgentManager

self.agent_manager = AgentManager(self.workspace_manager.workspace_path)
# Pass auto_start_agents config to AgentManager
self.agent_manager = AgentManager(
self.workspace_manager.workspace_path,
auto_start_agents=self.config.auto_start_agents
)
# Set network reference for agent unregistration on stop
self.agent_manager.set_network(self)

Expand Down
6 changes: 6 additions & 0 deletions src/openagents/models/network_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,12 @@ class NetworkConfig(BaseModel):
"When False, agents without password_hash are assigned to default_agent_group.",
)

# Agent lifecycle
auto_start_agents: bool = Field(
default=False,
description="When True, automatically start all discovered agents during network initialization.",
)

# Network initialization state
initialized: bool = Field(
default=False,
Expand Down
122 changes: 122 additions & 0 deletions tests/agents/test_agent_manager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
"""
Tests for AgentManager auto-start functionality.
"""

import pytest
from unittest.mock import AsyncMock, MagicMock

from openagents.core.agent_manager import AgentManager


@pytest.fixture
def tmp_workspace(tmp_path):
"""Create a temporary workspace directory."""
workspace = tmp_path / "workspace"
workspace.mkdir()
(workspace / "agents").mkdir()
(workspace / "logs").mkdir()
(workspace / "config").mkdir()
return workspace


@pytest.mark.asyncio
async def test_auto_start_enabled(tmp_workspace):
"""Test that agents are auto-started when auto_start_agents=True."""
manager = AgentManager(tmp_workspace, auto_start_agents=True)

# Mock the methods
manager.get_all_agents_status = MagicMock(return_value=[
{"agent_id": "agent1", "status": "stopped"},
{"agent_id": "agent2", "status": "stopped"},
])
manager.start_agent = AsyncMock(return_value={"success": True})

# Start the manager
await manager.start()

# Verify start_agent was called for each agent
assert manager.start_agent.call_count == 2
manager.start_agent.assert_any_call("agent1")
manager.start_agent.assert_any_call("agent2")


@pytest.mark.asyncio
async def test_auto_start_disabled(tmp_workspace):
"""Test that agents are not auto-started when auto_start_agents=False."""
manager = AgentManager(tmp_workspace, auto_start_agents=False)

# Mock the methods
manager.get_all_agents_status = MagicMock(return_value=[
{"agent_id": "agent1", "status": "stopped"},
])
manager.start_agent = AsyncMock(return_value={"success": True})

# Start the manager
await manager.start()

# Verify start_agent was never called
manager.start_agent.assert_not_called()


@pytest.mark.asyncio
async def test_auto_start_filters_running_agents(tmp_workspace):
"""Test that running agents are not restarted."""
manager = AgentManager(tmp_workspace, auto_start_agents=True)

# Mock agents with mixed status
manager.get_all_agents_status = MagicMock(return_value=[
{"agent_id": "agent1", "status": "running"},
{"agent_id": "agent2", "status": "stopped"},
{"agent_id": "agent3", "status": "running"},
])
manager.start_agent = AsyncMock(return_value={"success": True})

# Start the manager
await manager.start()

# Only agent2 should be started
assert manager.start_agent.call_count == 1
manager.start_agent.assert_called_with("agent2")


@pytest.mark.asyncio
async def test_auto_start_with_empty_agents(tmp_workspace):
"""Test auto-start handles empty agent list gracefully."""
manager = AgentManager(tmp_workspace, auto_start_agents=True)

# Mock empty agent list
manager.get_all_agents_status = MagicMock(return_value=[])
manager.start_agent = AsyncMock(return_value={"success": True})

# Start the manager - should not raise any errors
await manager.start()

# No agents to start
manager.start_agent.assert_not_called()


@pytest.mark.asyncio
async def test_auto_start_continues_on_failure(tmp_workspace):
"""Test that auto-start continues even if one agent fails."""
manager = AgentManager(tmp_workspace, auto_start_agents=True)

# Mock agents
manager.get_all_agents_status = MagicMock(return_value=[
{"agent_id": "agent1", "status": "stopped"},
{"agent_id": "agent2", "status": "stopped"},
{"agent_id": "agent3", "status": "stopped"},
])

# Make agent2 fail
async def mock_start_agent(agent_id):
if agent_id == "agent2":
return {"success": False, "message": "Start failed"}
return {"success": True}

manager.start_agent = AsyncMock(side_effect=mock_start_agent)

# Start the manager
await manager.start()

# All agents should be attempted
assert manager.start_agent.call_count == 3
Loading