Building Modular AI Agents: A Plugin Architecture
The Problem: Monolithic AI Systems
Most AI solutions start simple but quickly become unwieldy monoliths. Whether you’re building chatbots, RAG systems, or AI assistants, you’ve probably experienced this evolution (although this is a problem with non AI projects as well):
Day 1: “Let’s build a simple AI assistant that can answer questions about our docs”
Day 30: “Now it needs to search our database, generate reports, and handle customer queries”
Day 90: “The system is slow, hard to test, and any change breaks something else”
Why This Happens
- Knowledge Base Coupling: Everything gets thrown into one giant vector database
- Pipeline Rigidity: One retrieval system tries to handle all content types
- Prompt Bloat: A single “universal” prompt grows to handle every edge case
- Integration Mess: One API endpoint doing everything becomes impossible to maintain
The Real Cost
- Development slowdown: Teams can’t work independently
- Testing nightmare: Need to spin up everything to test one feature
- Deployment risk: Any change affects the entire system
- Scaling inefficiency: Resource usage driven by your heaviest component
The Solution: Plugin Architecture
Instead of one massive RAG solution, build modular agents that have unique capabilities:
from abc import ABC, abstractmethod
class BaseModularAgent(ABC):
"""Base class for all Agents"""
def __init__(self):
self.agent = None
@abstractmethod
def get_metadata(self) -> dict:
"""Return metadata this agent"""
return {
"name": "agent_name",
"description": "What this agent does"
}
@abstractmethod
def get_prompt(self) -> str:
"""System prompt for the agent"""
pass
@abstractmethod
async def initialize(self):
"""Initialize the agent"""
# Create agent with the prompt
pass
Core Principles
- Single Responsibility: Each plugin does one thing well
- Self-Contained: Plugins manage their own resources and dependencies
- Discoverable: System automatically finds and loads available plugin agents
- Composable: Plugins can work together or independently
Dynamic Discovery: Finding Your Plugin Agents
async def find_and_initialize_agents(directory: str):
""" Discover and initialize plugin agents"""
agents = []
for file in os.listdir(directory):
if file.endswith('_pluginagent.py'):
# Load the module
module = importlib.import_module(file[:-3])
# Find agent classes
for item in dir(module):
obj = getattr(module, item)
if (isinstance(obj, type) and
issubclass(obj, BaseModularAgent) and
obj != BaseModularAgent):
# Create instance and initialize the agent
agents = obj()
await agents.initialize()
agents.append(agents)
return agents
Modular Agent Example
Company Policy Agent
The below example omits the ‘retrieval’ part of RAG for simplicity. In real world this agent would most likely perform semantic search on a vector database which contains embeddings of company policy documents. It would define a function tool responsible for performing this search.
from agents import Agent
class CompanyPolicyAgent(BaseModularAgent):
def get_metadata(self) -> dict:
return {
"name": "document_search",
"description": "Search through company documents and policies"
}
def get_prompt(self) -> str:
return """You are a document search specialist. Help users find relevant
documents, policies, and information from the company knowledge base.
Always provide accurate, cited results."""
async def initialize(self):
metadata = self.get_metadata()
self.agent = Agent(
name=metadata["name"],
instructions=self.get_prompt(),
model="o3"
)
Putting It All Together with Agents SDK
from agents import Agent, Runner
import os
import importlib
async def main():
# Discover and initialize modular agents and use them as tools for orchestrator agent
agentModules = {}
for file in os.listdir('./plugin_agents'):
if file.endswith('_pluginagent.py'):
# Load the module
module = importlib.import_module(file[:-3])
# Find agent classes
for item in dir(module):
obj = getattr(module, item)
if (isinstance(obj, type) and
issubclass(obj, BaseModularAgent) and
obj != BaseModularAgent):
# Create instance and initialize the agent
pluginAgent = obj()
await pluginAgent.initialize()
metadata = pluginAgent.get_metadata()
agentModules[metadata["name"]] = pluginAgent
print(f"Loaded agent: {metadata['description']}")
# Convert each plugin agent into a tool
tools = []
for name, agent in agentModules.items():
metadata = agent.get_metadata()
tool = agent.agent.as_tool(
name=metadata["name"],
description=metadata["description"]
)
tools.append(tool)
# Create orchestrator agent with all plugin agents as tools
orchestrator = Agent(
name="AI Assistant",
instructions="""You are an intelligent assistant that can help with various tasks.
You have access to specialized agents through tools. Use the appropriate tool
based on what the user needs.""",
model="o3",
tools=tools
)
# Run the orchestrator agent with user prompt
user_input = "Find our company policy on remote work"
await Runner.run(orchestrator, user_input)
if __name__ == "__main__":
asyncio.run(main())
Why This Approach Works
For Developers:
- Build and test individual agents independently
- Add new features without touching existing code
- Debug issues in isolation
For Operations:
- Deploy updates to specific agents only
- Scale resources based on actual usage patterns
- Monitor and troubleshoot individual components
For Business:
- Faster feature development with parallel teams
- Reduced risk of system-wide failures
- Easy integration of new AI models and providers
Getting Started
- Identify your AI capabilities - What different things does your system need to do?
- Create separate plugins - Build one plugin agent per capability
- Use the discovery pattern - Let your system automatically find agents
- Start simple - Begin with basic implementations and evolve
The Bottom Line
Plugin architecture isn’t just about code organization - it’s about building AI solutions that can evolve with your business needs. Instead of rewriting everything when requirements change, you simply add, remove, or update individual capabilities.
Start small, think modular, and watch your AI solution become more maintainable, scalable, and powerful over time.