Skip to content

Agent Class

The Agent class is the core component that processes messages using an LLM with support for tool calling and agent delegation.

Class Definition

python
class Agent:
    def __init__(
        self,
        name: str,
        model_api: ModelAPI,
        instructions: str = "You are a helpful agent",
        description: str = "Agent",
        memory: LocalMemory = None,
        mcp_clients: List[MCPClient] = None,
        sub_agents: List[RemoteAgent] = None,
        max_steps: int = 5,
        memory_context_limit: int = 6
    )

Parameters

ParameterTypeRequiredDefaultDescription
namestrYes-Unique identifier for the agent
model_apiModelAPIYes-LLM client for completions
instructionsstrNo"You are a helpful agent"System prompt instructions
descriptionstrNo"Agent"Human-readable description
memoryLocalMemoryNoNew instanceSession/event storage
mcp_clientsList[MCPClient]No[]Tool clients for MCP servers
sub_agentsList[RemoteAgent]No[]Remote agents for delegation
max_stepsintNo5Maximum agentic loop iterations
memory_context_limitintNo6Max conversation turns in context

Agentic Loop Configuration

The agentic loop behavior is controlled by two parameters:

  • max_steps: Maximum reasoning iterations before returning. Prevents infinite loops.
  • memory_context_limit: How many recent conversation turns to include in the context.
python
# High-complexity tasks with more steps
agent = Agent(
    name="complex-agent",
    model_api=model_api,
    max_steps=10,
    memory_context_limit=10
)

# Simple tasks with fewer steps
agent = Agent(
    name="simple-agent",
    model_api=model_api,
    max_steps=2,
    memory_context_limit=4
)

Core Methods

process_message

Main method for processing user messages with the agentic loop.

python
async def process_message(
    self,
    message: str,
    session_id: Optional[str] = None,
    stream: bool = False,
    mock_response: Optional[str] = None
) -> AsyncIterator[str]

Parameters:

  • message: User message to process
  • session_id: Optional session ID (created if not provided)
  • stream: Whether to stream response word-by-word
  • mock_response: Mock LLM response for testing

Returns: AsyncIterator yielding response chunks

Example:

python
async for chunk in agent.process_message("Hello!", session_id="my-session"):
    print(chunk, end="")

execute_tool

Execute a tool by name with arguments.

python
async def execute_tool(self, tool_name: str, args: Dict[str, Any]) -> Any

Raises: ValueError if tool not found in any MCP client.

delegate_to_sub_agent

Delegate a task to a sub-agent with memory logging.

python
async def delegate_to_sub_agent(
    self,
    agent_name: str,
    task: str,
    session_id: Optional[str] = None
) -> str

Returns: Response from the sub-agent.

Raises: ValueError if sub-agent not found.

get_agent_card

Generate an A2A agent card for discovery.

python
def get_agent_card(self, base_url: str) -> AgentCard

Returns: AgentCard with name, description, skills, and capabilities.

Usage Examples

Basic Agent

python
from agent.client import Agent
from modelapi.client import ModelAPI

model_api = ModelAPI(model="smollm2:135m", api_base="http://localhost:8000")
agent = Agent(name="basic", model_api=model_api)

async def main():
    async for response in agent.process_message("What is 2+2?"):
        print(response)

Agent with Tools

python
from mcptools.client import MCPClient, MCPClientSettings

# Connect to MCP server
mcp_settings = MCPClientSettings(
    mcp_client_host="http://localhost",
    mcp_client_port="8001"
)
mcp_client = MCPClient(mcp_settings)
await mcp_client.discover_tools()

# Create agent with tools
agent = Agent(
    name="with-tools",
    model_api=model_api,
    mcp_clients=[mcp_client],
    instructions="Use the echo tool when asked to echo something."
)

Agent with Sub-Agents

python
from agent.client import RemoteAgent

# Create remote agent references
worker1 = RemoteAgent(name="worker-1", card_url="http://worker-1:8000")
worker2 = RemoteAgent(name="worker-2", card_url="http://worker-2:8000")

# Create coordinator
coordinator = Agent(
    name="coordinator",
    model_api=model_api,
    sub_agents=[worker1, worker2],
    instructions="Delegate tasks to worker-1 or worker-2 as appropriate."
)

Custom Configuration

python
# High-complexity tasks with more steps
agent = Agent(
    name="complex-agent",
    model_api=model_api,
    max_steps=10,
    memory_context_limit=10
)

# Simple tasks with fewer steps
agent = Agent(
    name="simple-agent",
    model_api=model_api,
    max_steps=2,
    memory_context_limit=4
)

Memory Integration

The agent automatically logs events to memory:

Event TypeWhen Logged
user_messageWhen process_message is called
agent_responseWhen final response is generated
tool_callWhen a tool is invoked
tool_resultWhen tool execution completes
delegation_requestWhen delegating to sub-agent
delegation_responseWhen receiving sub-agent response
errorWhen an error occurs

Access events via the memory debug endpoints or directly:

python
events = await agent.memory.get_session_events(session_id)
for event in events:
    print(f"{event.event_type}: {event.content}")

Lifecycle

Cleanup

Close all connections when done:

python
await agent.close()

This closes:

  • ModelAPI HTTP client
  • All MCPClient connections
  • All RemoteAgent connections

Multi-Agent System Design

Sub-Agent Availability and Ordering

When deploying multi-agent systems (e.g., coordinator with workers), agents may start in any order. The framework handles this gracefully:

Design Principles:

  1. Graceful Degradation: If a sub-agent is unavailable during discovery, the coordinator continues to function. The model is informed which agents are unavailable.

  2. Retry on Each Request: Sub-agent availability is re-checked on each request, allowing recovery when agents become available.

  3. Short Discovery Timeout: Agent card discovery uses a 5-second timeout (vs 30s for task invocation) since agent cards should respond quickly.

  4. Informative Error Messages: When delegation fails, the model receives a message like [Delegation failed: Agent 'worker' is not reachable. Please try an alternative approach.]

Example: Coordinator Starts Before Workers

python
# Coordinator created with references to workers that don't exist yet
worker1 = RemoteAgent(name="worker-1", card_url="http://worker-1:8000")
worker2 = RemoteAgent(name="worker-2", card_url="http://worker-2:8000")

coordinator = Agent(
    name="coordinator",
    model_api=model_api,
    sub_agents=[worker1, worker2]
)

# First request - workers not available yet
# Model receives: "worker-1: (currently unavailable), worker-2: (currently unavailable)"
# Model can still respond, just can't delegate

# Later, after workers start...
# Next request - workers discovered and available
# Model receives: "worker-1: Worker agent, worker-2: Worker agent"
# Delegation now works

Memory Events for Delegation

Event TypeDescription
delegation_requestLogged when delegation is attempted
delegation_responseLogged when sub-agent responds successfully
delegation_errorLogged when delegation fails (agent unavailable or error)

RemoteAgent Properties

python
remote = RemoteAgent(name="worker", card_url="http://worker:8000")

# Check availability
if remote.available:
    response = await remote.invoke("Do something")
else:
    print(f"Agent unavailable: {remote.last_error}")

# Force re-discovery
await remote.discover(retry=True)

Released under the Apache 2.0 License.