argentic-framework-development

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

16 stars

Best use case

argentic-framework-development is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

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

Teams using argentic-framework-development should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/argentic-framework-development/SKILL.md --create-dirs "https://raw.githubusercontent.com/diegosouzapw/awesome-omni-skill/main/skills/development/argentic-framework-development/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/argentic-framework-development/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How argentic-framework-development Compares

Feature / Agentargentic-framework-developmentStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

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

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

# 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

```bash
# From PyPI
pip install argentic

# From source
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

```python
import asyncio
from argentic import Agent, Messager, LLMFactory
from argentic.core.tools import ToolManager
import yaml

async def main():
    # Load configuration
    with open("config.yaml") as f:
        config = yaml.safe_load(f)
    
    # Initialize LLM
    llm = LLMFactory.create_from_config(config["llm"])
    
    # Setup MQTT messaging
    messager = Messager(
        broker_address=config["messaging"]["broker_address"],
        port=config["messaging"]["port"]
    )
    await messager.connect()
    
    # Initialize ToolManager
    tool_manager = ToolManager(messager)
    await tool_manager.async_init()
    
    # Create Agent
    agent = Agent(
        llm=llm,
        messager=messager,
        tool_manager=tool_manager,
        role="assistant",
        system_prompt="You are a helpful AI assistant.",
        enable_dialogue_logging=True  # For debugging
    )
    await agent.async_init()
    
    # Use the agent
    response = await agent.query("What is the capital of France?")
    print(response)
    
    # Cleanup
    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

```python
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

# Step 1: Define input schema with Pydantic
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")

# Step 2: Implement tool class
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", "")
        
        # Your implementation here
        try:
            result = f"Processed {param1} with {param2}"
            return result
        except Exception as e:
            raise RuntimeError(f"Tool execution failed: {e}")

# Step 3: Register tool
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

# Step 4: Use with agent
async def main():
    messager = Messager(broker_address="localhost", port=1883)
    await messager.connect()
    
    tool_manager = ToolManager(messager)
    await tool_manager.async_init()
    
    # Register tool BEFORE creating agent
    tool = await setup_tool(messager)
    await asyncio.sleep(1)  # Wait for registration to complete
    
    # Create agent (will auto-discover registered tools)
    agent = Agent(llm, messager, tool_manager, role="assistant")
    await agent.async_init()
    
    # Agent can now use the tool
    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

```python
from argentic.core.graph.supervisor import Supervisor
from argentic import Agent, Messager, LLMFactory
from argentic.core.tools import ToolManager
import asyncio

async def main():
    # Setup (same as single agent)
    llm = LLMFactory.create_from_config(config["llm"])
    messager = Messager(broker_address="localhost", port=1883)
    await messager.connect()
    
    # IMPORTANT: Use ONE shared ToolManager for all agents
    tool_manager = ToolManager(messager)
    await tool_manager.async_init()
    
    # Create specialized agents with separate MQTT topics
    researcher = Agent(
        llm=llm,
        messager=messager,
        tool_manager=tool_manager,  # Shared!
        role="researcher",
        description="Research and information gathering specialist",
        system_prompt="You are a researcher. Find and synthesize information.",
        expected_output_format="text",
        # Agent-specific topics to avoid conflicts
        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,  # Same shared instance!
        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()
    
    # Create supervisor
    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()
    
    # Execute workflow with callback
    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
    )
    
    # Wait for completion
    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

```yaml
llm:
  # Provider: google_gemini, ollama, llama_cpp_server, llama_cpp_cli
  provider: google_gemini
  
  # Google Gemini
  google_gemini_model_name: gemini-2.0-flash
  google_gemini_api_key: ${GOOGLE_GEMINI_API_KEY}  # From env
  google_gemini_parameters:
    temperature: 0.7
    top_p: 0.95
    top_k: 40
    max_output_tokens: 2048
  
  # Ollama
  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
  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: ""  # Auto-generated if empty

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

```bash
# LLM API Keys
GOOGLE_GEMINI_API_KEY=your_api_key_here

# MQTT (optional)
MQTT_USERNAME=username
MQTT_PASSWORD=password

# Logging
LOG_LEVEL=INFO
CONFIG_PATH=config.yaml
```

## Core API Reference

### Agent

```python
Agent(
    llm: ModelProvider,                    # Required
    messager: Messager,                    # Required
    tool_manager: ToolManager = None,      # Recommended
    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,
)

# Key Methods
await agent.async_init()                   # Initialize
response = await agent.query(question)     # Direct query
await agent.process_task(task)             # Process task message
agent.print_dialogue_summary()             # Debug info
```

### Messager

```python
Messager(
    broker_address: str = "localhost",
    port: int = 1883,
    client_id: str = "",
    username: str = None,
    password: str = None,
    keepalive: int = 60,
)

# Key Methods
await messager.connect()
await messager.disconnect()
await messager.subscribe(topic, handler, message_cls)
await messager.publish(topic, message_obj)
```

### ToolManager

```python
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,
)

# Key Methods
await tool_manager.async_init()
result = await tool_manager.execute_tool(name, args, timeout=30)
tools_desc = tool_manager.get_tools_description()
```

### Supervisor

```python
Supervisor(
    llm: ModelProvider,
    messager: Messager,
    role: str = "supervisor",
    system_prompt: str = None,
    enable_dialogue_logging: bool = False,
)

# Key Methods
supervisor.add_agent(agent)
await supervisor.async_init()
task_id = await supervisor.start_task(task, callback)
```

## Important Implementation Details

### Tool Registration Flow

1. **Tool → ToolManager**: Tool publishes `RegisterToolMessage` to `agent/tools/register`
2. **ToolManager**: Generates unique `tool_id` (UUID), stores metadata
3. **ToolManager → Tool**: Publishes `ToolRegisteredMessage` to `agent/status/info`
4. **Tool**: Receives confirmation, subscribes to `agent/tools/call/{tool_id}`

### Tool Execution Flow

1. Agent calls `tool_manager.execute_tool(name, args)`
2. ToolManager publishes `TaskMessage` to `agent/tools/call/{tool_id}`
3. Tool receives message, validates args, executes `_execute()`
4. Tool publishes `TaskResultMessage` to `agent/tools/response/{tool_id}`
5. ToolManager returns result to agent

### Message Protocol

All messages inherit from `BaseMessage` (Pydantic models):

```python
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
```python
# GOOD
await messager.connect()
result = await agent.query(question)
await messager.disconnect()

# BAD - will not work
messager.connect()  # Missing await
```

### 2. Shared ToolManager for Multi-Agent
```python
# GOOD - One shared instance
tool_manager = ToolManager(messager)
agent1 = Agent(llm, messager, tool_manager, ...)
agent2 = Agent(llm, messager, tool_manager, ...)

# BAD - Multiple instances cause conflicts
agent1 = Agent(llm, messager, ToolManager(messager), ...)
agent2 = Agent(llm, messager, ToolManager(messager), ...)
```

### 3. Proper Resource Cleanup
```python
# GOOD
try:
    await messager.connect()
    # Use agents
finally:
    await messager.disconnect()
```

### 4. Wait After Tool Registration
```python
# GOOD
await tool.register(...)
await asyncio.sleep(1)  # Give time for registration
agent = Agent(...)
```

### 5. Separate Topics for Multi-Agent
```python
# GOOD - Each agent has own namespace
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
```python
# GOOD
system_prompt = "You are a researcher. Your job is to find and synthesize information from various sources. Be thorough and cite sources."

# BAD
system_prompt = "You are helpful."
```

### 7. Enable Logging for Debug
```python
agent = Agent(
    ...,
    enable_dialogue_logging=True,  # Shows all interactions
)
# Later
agent.print_dialogue_summary()
```

## Common Patterns

### Running Argentic Components

```bash
# CLI (after installation)
argentic agent --config-path config.yaml --log-level INFO
argentic rag --config-path config.yaml
argentic cli --config-path config.yaml

# Python module
python -m argentic agent --config-path config.yaml
```

### Import Patterns

```python
# Top-level imports
from argentic import Agent, Messager, LLMFactory

# Core modules
from argentic.core.tools import ToolManager, BaseTool
from argentic.core.graph.supervisor import Supervisor
from argentic.core.protocol.message import AgentTaskMessage

# LLM providers
from argentic.core.llm.providers.google_gemini import GoogleGeminiProvider
from argentic.core.llm.providers.ollama import OllamaProvider
```

### Testing Tools Independently

```python
# Test without full agent setup
async def test_tool():
    messager = Messager(broker_address="localhost")
    await messager.connect()
    
    tool = MyTool(messager)
    
    # Direct execution (bypass MQTT)
    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**:
```bash
# Check if mosquitto is running
docker ps | grep mosquitto
# Or
sudo systemctl status mosquitto

# Start 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:
```python
agent = Agent(
    ...,
    adaptive_max_iterations=True,
    max_consecutive_tool_calls=3,
    tool_call_window_size=5,
    enable_completion_analysis=True,
)
```

### State Management

```python
from argentic.core.agent.agent import AgentStateMode

# Stateful (default) - maintains conversation
agent = Agent(..., state_mode=AgentStateMode.STATEFUL)

# Stateless - each query independent
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

1. **Always async/await** - Everything is asynchronous
2. **One ToolManager** - Share across all agents
3. **Separate topics** - Each agent needs its own namespace
4. **Wait after registration** - Tools need time to register
5. **Clean up connections** - Always disconnect messager
6. **Enable logging** - Use `enable_dialogue_logging=True` for debug
7. **Pydantic validation** - Use for all tool inputs
8. **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

Related Skills

athena-framework

16
from diegosouzapw/awesome-omni-skill

Use this skill when working with Athena Framework for Crystal. Athena is a modular ecosystem of independent, reusable components including: Framework (ATH) for web apps, DependencyInjection (ADI) for IoC containers, Routing (ART) for HTTP routing, Serializer (ASR) for object serialization, Validator (AVD) for validation, Console (ACON) for CLI tools, EventDispatcher (AED) for event-driven architecture, and more. Use for building Crystal web applications, REST APIs, CLI tools, or integrating individual components.

ash-framework

16
from diegosouzapw/awesome-omni-skill

Comprehensive Ash framework guidelines for Elixir applications. Use when working with Ash resources, domains, actions, queries, changesets, policies, calculations, or aggregates. Covers code interfaces, error handling, validations, changes, relationships, and authorization. Read documentation before using Ash features - do not assume prior knowledge.

api-framework-express

16
from diegosouzapw/awesome-omni-skill

Express.js routes, middleware, error handling, request/response patterns

API Development

16
from diegosouzapw/awesome-omni-skill

Meta-skill that orchestrates the full API development lifecycle — from design through documentation — by coordinating specialized skills, agents, and commands into a seamless build workflow.

api-development-expert

16
from diegosouzapw/awesome-omni-skill

API development expert including REST design, OpenAPI, and documentation

android-kotlin-development

16
from diegosouzapw/awesome-omni-skill

Develop native Android apps with Kotlin. Covers MVVM with Jetpack, Compose for modern UI, Retrofit for API calls, Room for local storage, and navigation architecture.

android-development

16
from diegosouzapw/awesome-omni-skill

Android development patterns for Kotlin/Java including MediaProjection, Accessibility Service, Socket.IO, and foreground services. Use when working on TitanMirror or other Android projects.

ai-development-guide

16
from diegosouzapw/awesome-omni-skill

Technical decision criteria, anti-pattern detection, debugging techniques, and quality check workflow. Use when making technical decisions, detecting code smells, or performing quality assurance.

ai-assisted-development

16
from diegosouzapw/awesome-omni-skill

Leveraging AI coding assistants and tools to boost development productivity, while maintaining oversight to ensure quality results.

agent-framework-azure-ai-py

16
from diegosouzapw/awesome-omni-skill

Build Azure AI Foundry agents using the Microsoft Agent Framework Python SDK (agent-framework-azure-ai). Use when creating persistent agents with AzureAIAgentsProvider, using hosted tools (code int...

AEM Development Workflow

16
from diegosouzapw/awesome-omni-skill

Required workflow for AEM EDS development - linting, testing, preview validation, and PRs with demo links. Use when developing blocks or features for AEM Edge Delivery Services projects, before doing anything else.

Advanced Playwright E2E Framework

16
from diegosouzapw/awesome-omni-skill

Enterprise-grade Playwright test automation framework using 8-layer architecture with Page Object Model, Module Pattern, custom fixtures, API testing layer, structured logging, data generators, multi-browser support, Docker, CI/CD pipelines, and custom HTML reporting.