| name | argentic-framework-development |
| description | Expert knowledge for building AI agents with Argentic - a Python microframework for async MQTT-based agents with multi-LLM support, custom tools, and multi-agent orchestration |
Argentic Framework Development Skill
You are an expert in building AI agents using the Argentic framework. This skill provides comprehensive knowledge about Argentic's architecture, patterns, and best practices.
Framework Overview
Argentic is a Python 3.11+ microframework for building local AI agents with async MQTT messaging.
Core Architecture:
- Fully async/await based
- MQTT-driven messaging between components
- Multi-LLM support (Google Gemini, Ollama, Llama.cpp)
- Dynamic tool registration via messaging protocol
- Single-agent and multi-agent (Supervisor) patterns
Key Components:
Agent - Main AI agent with LLM integration
Messager - MQTT messaging layer
ToolManager - Tool registry and execution
Supervisor - Multi-agent coordinator
BaseTool - Base class for custom tools
Installation
pip install argentic
git clone https://github.com/angkira/argentic.git
cd argentic
pip install -e .
Prerequisites:
- Python 3.11+
- MQTT broker (mosquitto):
docker run -d -p 1883:1883 eclipse-mosquitto:2.0
- API keys for LLM providers (e.g.,
GOOGLE_GEMINI_API_KEY in .env)
Pattern 1: Single Agent Application
import asyncio
from argentic import Agent, Messager, LLMFactory
from argentic.core.tools import ToolManager
import yaml
async def main():
with open("config.yaml") as f:
config = yaml.safe_load(f)
llm = LLMFactory.create_from_config(config["llm"])
messager = Messager(
broker_address=config["messaging"]["broker_address"],
port=config["messaging"]["port"]
)
await messager.connect()
tool_manager = ToolManager(messager)
await tool_manager.async_init()
agent = Agent(
llm=llm,
messager=messager,
tool_manager=tool_manager,
role="assistant",
system_prompt="You are a helpful AI assistant.",
enable_dialogue_logging=True
)
await agent.async_init()
response = await agent.query("What is the capital of France?")
print(response)
await messager.disconnect()
if __name__ == "__main__":
asyncio.run(main())
Key Points:
- Always
await messager.connect() before using components
- Initialize
ToolManager before creating agents
- Always
await agent.async_init() after creation
- Clean up with
await messager.disconnect() in finally block
Pattern 2: Custom Tool Development
import json
from typing import Any
from pydantic import BaseModel, Field
from argentic.core.tools.tool_base import BaseTool
from argentic.core.messager.messager import Messager
class MyToolInput(BaseModel):
param1: str = Field(description="First parameter description")
param2: int = Field(default=10, ge=1, le=100, description="Integer between 1-100")
optional_param: str = Field(default="", description="Optional parameter")
class MyCustomTool(BaseTool):
def __init__(self, messager: Messager):
super().__init__(
name="my_custom_tool",
manual="Tool description that LLM will see. Explain what it does and how to use it.",
api=json.dumps(MyToolInput.model_json_schema()),
argument_schema=MyToolInput,
messager=messager,
)
async def _execute(self, **kwargs) -> Any:
"""
Implement tool logic here.
Arguments are validated by Pydantic before this is called.
"""
param1 = kwargs["param1"]
param2 = kwargs.get("param2", 10)
optional = kwargs.get("optional_param", "")
try:
result = f"Processed {param1} with {param2}"
return result
except Exception as e:
raise RuntimeError(f"Tool execution failed: {e}")
async def setup_tool(messager):
tool = MyCustomTool(messager)
await tool.register(
registration_topic="agent/tools/register",
status_topic="agent/status/info",
call_topic_base="agent/tools/call",
response_topic_base="agent/tools/response",
)
return tool
async def main():
messager = Messager(broker_address="localhost", port=1883)
await messager.connect()
tool_manager = ToolManager(messager)
await tool_manager.async_init()
tool = await setup_tool(messager)
await asyncio.sleep(1)
agent = Agent(llm, messager, tool_manager, role="assistant")
await agent.async_init()
response = await agent.query("Use my_custom_tool with param1='test'")
print(response)
await messager.disconnect()
Tool Development Best Practices:
- Use Pydantic for automatic validation
- Provide clear
manual description for LLM
- Include field descriptions in Pydantic schema
- Handle errors with specific exceptions
- Test tools independently before agent integration
Pattern 3: Multi-Agent System
from argentic.core.graph.supervisor import Supervisor
from argentic import Agent, Messager, LLMFactory
from argentic.core.tools import ToolManager
import asyncio
async def main():
llm = LLMFactory.create_from_config(config["llm"])
messager = Messager(broker_address="localhost", port=1883)
await messager.connect()
tool_manager = ToolManager(messager)
await tool_manager.async_init()
researcher = Agent(
llm=llm,
messager=messager,
tool_manager=tool_manager,
role="researcher",
description="Research and information gathering specialist",
system_prompt="You are a researcher. Find and synthesize information.",
expected_output_format="text",
register_topic="agent/researcher/tools/register",
tool_call_topic_base="agent/researcher/tools/call",
tool_response_topic_base="agent/researcher/tools/response",
status_topic="agent/researcher/status/info",
graph_id="multi_agent_system",
)
await researcher.async_init()
coder = Agent(
llm=llm,
messager=messager,
tool_manager=tool_manager,
role="coder",
description="Code writing and debugging specialist",
system_prompt="You are a coder. Write clean, efficient code.",
expected_output_format="code",
register_topic="agent/coder/tools/register",
tool_call_topic_base="agent/coder/tools/call",
tool_response_topic_base="agent/coder/tools/response",
status_topic="agent/coder/status/info",
graph_id="multi_agent_system",
)
await coder.async_init()
supervisor = Supervisor(
llm=llm,
messager=messager,
role="supervisor",
system_prompt="You are a supervisor. Analyze tasks and route them to appropriate agents.",
enable_dialogue_logging=True,
)
supervisor.add_agent(researcher)
supervisor.add_agent(coder)
await supervisor.async_init()
workflow_complete = asyncio.Event()
result_data = {}
def completion_callback(task_id, success, result="", error=""):
result_data["success"] = success
result_data["result"] = result
result_data["error"] = error
workflow_complete.set()
task_id = await supervisor.start_task(
"Research async patterns in Python and write example code",
completion_callback
)
await asyncio.wait_for(workflow_complete.wait(), timeout=180)
if result_data["success"]:
print(f"Success: {result_data['result']}")
else:
print(f"Failed: {result_data['error']}")
await messager.disconnect()
if __name__ == "__main__":
asyncio.run(main())
Multi-Agent Critical Points:
- Use ONE shared
ToolManager instance for all agents
- Give each agent separate MQTT topic namespaces
- Set unique
role for each agent
- Use clear
description for supervisor routing
- Set same
graph_id for all agents in the system
- Enable logging for debugging:
enable_dialogue_logging=True
Configuration
config.yaml Structure
llm:
provider: google_gemini
google_gemini_model_name: gemini-2.0-flash
google_gemini_api_key: ${GOOGLE_GEMINI_API_KEY}
google_gemini_parameters:
temperature: 0.7
top_p: 0.95
top_k: 40
max_output_tokens: 2048
ollama_model_name: llama3
ollama_base_url: http://localhost:11434
ollama_use_chat_model: true
ollama_parameters:
temperature: 0.7
num_predict: 128
llama_cpp_server_host: 127.0.0.1
llama_cpp_server_port: 5000
llama_cpp_server_auto_start: false
llama_cpp_server_binary: ~/llama.cpp/build/bin/llama-server
messaging:
protocol: mqtt
broker_address: localhost
port: 1883
keepalive: 60
client_id: ""
topics:
tools:
register: "agent/tools/register"
call: "agent/tools/call"
response_base: "agent/tools/response"
status: "agent/tools/status"
commands:
ask_question: "agent/command/ask_question"
responses:
answer: "agent/response/answer"
log: "agent/log"
.env File
GOOGLE_GEMINI_API_KEY=your_api_key_here
MQTT_USERNAME=username
MQTT_PASSWORD=password
LOG_LEVEL=INFO
CONFIG_PATH=config.yaml
Core API Reference
Agent
Agent(
llm: ModelProvider,
messager: Messager,
tool_manager: ToolManager = None,
role: str = "agent",
system_prompt: str = None,
description: str = "",
expected_output_format: Literal["json", "text", "code"] = "json",
enable_dialogue_logging: bool = False,
register_topic: str = "agent/tools/register",
tool_call_topic_base: str = "agent/tools/call",
tool_response_topic_base: str = "agent/tools/response",
status_topic: str = "agent/status/info",
graph_id: str = None,
state_mode: AgentStateMode = STATEFUL,
)
await agent.async_init()
response = await agent.query(question)
await agent.process_task(task)
agent.print_dialogue_summary()
Messager
Messager(
broker_address: str = "localhost",
port: int = 1883,
client_id: str = "",
username: str = None,
password: str = None,
keepalive: int = 60,
)
await messager.connect()
await messager.disconnect()
await messager.subscribe(topic, handler, message_cls)
await messager.publish(topic, message_obj)
ToolManager
ToolManager(
messager: Messager,
register_topic: str = "agent/tools/register",
tool_call_topic_base: str = "agent/tools/call",
tool_response_topic_base: str = "agent/tools/response",
status_topic: str = "agent/status/info",
default_timeout: int = 30,
)
await tool_manager.async_init()
result = await tool_manager.execute_tool(name, args, timeout=30)
tools_desc = tool_manager.get_tools_description()
Supervisor
Supervisor(
llm: ModelProvider,
messager: Messager,
role: str = "supervisor",
system_prompt: str = None,
enable_dialogue_logging: bool = False,
)
supervisor.add_agent(agent)
await supervisor.async_init()
task_id = await supervisor.start_task(task, callback)
Important Implementation Details
Tool Registration Flow
- Tool → ToolManager: Tool publishes
RegisterToolMessage to agent/tools/register
- ToolManager: Generates unique
tool_id (UUID), stores metadata
- ToolManager → Tool: Publishes
ToolRegisteredMessage to agent/status/info
- Tool: Receives confirmation, subscribes to
agent/tools/call/{tool_id}
Tool Execution Flow
- Agent calls
tool_manager.execute_tool(name, args)
- ToolManager publishes
TaskMessage to agent/tools/call/{tool_id}
- Tool receives message, validates args, executes
_execute()
- Tool publishes
TaskResultMessage to agent/tools/response/{tool_id}
- ToolManager returns result to agent
Message Protocol
All messages inherit from BaseMessage (Pydantic models):
from argentic.core.protocol.message import (
AgentTaskMessage,
AgentTaskResultMessage,
)
from argentic.core.protocol.task import (
TaskMessage,
TaskResultMessage,
TaskErrorMessage,
)
from argentic.core.protocol.tool import (
RegisterToolMessage,
ToolRegisteredMessage,
)
Best Practices
1. Always Use Async/Await
await messager.connect()
result = await agent.query(question)
await messager.disconnect()
messager.connect()
2. Shared ToolManager for Multi-Agent
tool_manager = ToolManager(messager)
agent1 = Agent(llm, messager, tool_manager, ...)
agent2 = Agent(llm, messager, tool_manager, ...)
agent1 = Agent(llm, messager, ToolManager(messager), ...)
agent2 = Agent(llm, messager, ToolManager(messager), ...)
3. Proper Resource Cleanup
try:
await messager.connect()
finally:
await messager.disconnect()
4. Wait After Tool Registration
await tool.register(...)
await asyncio.sleep(1)
agent = Agent(...)
5. Separate Topics for Multi-Agent
researcher = Agent(
...,
register_topic="agent/researcher/tools/register",
tool_call_topic_base="agent/researcher/tools/call",
)
coder = Agent(
...,
register_topic="agent/coder/tools/register",
tool_call_topic_base="agent/coder/tools/call",
)
6. Clear System Prompts
system_prompt = "You are a researcher. Your job is to find and synthesize information from various sources. Be thorough and cite sources."
system_prompt = "You are helpful."
7. Enable Logging for Debug
agent = Agent(
...,
enable_dialogue_logging=True,
)
agent.print_dialogue_summary()
Common Patterns
Running Argentic Components
argentic agent --config-path config.yaml --log-level INFO
argentic rag --config-path config.yaml
argentic cli --config-path config.yaml
python -m argentic agent --config-path config.yaml
Import Patterns
from argentic import Agent, Messager, LLMFactory
from argentic.core.tools import ToolManager, BaseTool
from argentic.core.graph.supervisor import Supervisor
from argentic.core.protocol.message import AgentTaskMessage
from argentic.core.llm.providers.google_gemini import GoogleGeminiProvider
from argentic.core.llm.providers.ollama import OllamaProvider
Testing Tools Independently
async def test_tool():
messager = Messager(broker_address="localhost")
await messager.connect()
tool = MyTool(messager)
result = await tool._execute(param="test")
print(result)
await messager.disconnect()
asyncio.run(test_tool())
Troubleshooting
MQTT Connection Failed
Error: Connection refused or No connection to MQTT broker
Solution:
docker ps | grep mosquitto
sudo systemctl status mosquitto
docker run -d -p 1883:1883 --name mosquitto eclipse-mosquitto:2.0
Tool Not Registered
Error: Tool doesn't appear in agent's tool list
Solution:
- Add delay after registration:
await asyncio.sleep(1)
- Verify topics match between tool and ToolManager
- Enable logging to see registration messages
- Check tool is registered BEFORE agent initialization
LLM API Error
Error: Invalid API key or Authentication failed
Solution:
- Verify
.env file exists with correct key
- Load environment:
from dotenv import load_dotenv; load_dotenv()
- Check environment variable:
os.getenv("GOOGLE_GEMINI_API_KEY")
Tool Timeout
Error: TimeoutError: Tool execution exceeded timeout
Solution:
- Increase timeout:
await tool_manager.execute_tool(..., timeout=60)
- Check tool implementation for blocking operations
- Ensure tool publishes result message
Multi-Agent Not Routing
Error: Supervisor doesn't route to agents
Solution:
- Verify supervisor
system_prompt includes routing instructions
- Check all agents have unique
role values
- Ensure agent topics don't overlap
- Enable
enable_dialogue_logging=True on supervisor
Advanced Features
Endless Cycle Support
For long-running agents:
agent = Agent(
...,
adaptive_max_iterations=True,
max_consecutive_tool_calls=3,
tool_call_window_size=5,
enable_completion_analysis=True,
)
State Management
from argentic.core.agent.agent import AgentStateMode
agent = Agent(..., state_mode=AgentStateMode.STATEFUL)
agent = Agent(..., state_mode=AgentStateMode.STATELESS)
When to Use This Skill
Use this skill when:
- Building AI agent applications
- Implementing custom tools for agents
- Creating multi-agent systems
- Debugging Argentic applications
- Configuring LLM providers
- Setting up MQTT messaging
- Working with async Python patterns in Argentic
Key Reminders
- Always async/await - Everything is asynchronous
- One ToolManager - Share across all agents
- Separate topics - Each agent needs its own namespace
- Wait after registration - Tools need time to register
- Clean up connections - Always disconnect messager
- Enable logging - Use
enable_dialogue_logging=True for debug
- Pydantic validation - Use for all tool inputs
- Clear prompts - Make system prompts specific and detailed
Resources
- Documentation: Check
docs/ directory or ARGENTIC_QUICKREF.md
- Examples: See
examples/single_agent_example.py, examples/multi_agent_example.py
- Source:
src/argentic/ for implementation details