This document explains the modular architecture approach that eliminates centralized code dependencies when adding new tools and agents.
# Bad: Every new tool requires updating centralized code
class AllTools:
TOOL_REGISTRY = {
"get_db_schema": ToolDefinition(...),
"execute_sql_query": ToolDefinition(...),
"new_tool": ToolDefinition(...) # ← Must update this for every new tool
}- Breaks Modularity: Adding a new tool requires modifying central files
- Deployment Dependencies: All agents depend on centralized tool definitions
- Version Conflicts: Updates to central registry affect all agents
- Team Conflicts: Multiple teams can't independently deploy tools
# Good: Tools resolved dynamically from DynamoDB at runtime
tool_configs = [
{
"tool_name": "any_tool_name", # Must exist in DynamoDB registry
"lambda_arn": "arn:aws:lambda:...",
"requires_activity": True,
"activity_type": "human_approval"
}
]class ModularBaseAgentStack(Stack):
"""
Truly modular agent deployment without centralized dependencies
- No centralized tool registry dependencies at deploy time
- Tools are resolved dynamically from DynamoDB at runtime
- Agents can be deployed independently without updating central code
"""
def __init__(self, ..., validate_tools: bool = False):
# validate_tools=False by default for full modularity
# Tools are validated at runtime from DynamoDB# Each tool stack registers itself in DynamoDB
MultiToolConstruct(
self,
"LocalAutomationToolsRegistry",
tool_groups=[{
"tool_specs": [
{
"tool_name": "local_agent_execute",
"description": "Execute automation scripts on remote local systems",
"requires_activity": True,
"activity_type": "remote_execution",
"activity_arn": self.remote_execution_activity_arn
}
],
"lambda_function": self.local_automation_lambda
}],
env_name=self.env_name
){% raw %}
# Step Functions loads tools dynamically from DynamoDB
"Load Tool Definitions": {
"Type": "Map",
"Items": "{% $parse($agent_config.tools.S) %}",
"ItemProcessor": {
"StartAt": "Get Tool Details",
"States": {
"Get Tool Details": {
"Type": "Task",
"Resource": "arn:aws:states:::dynamodb:getItem",
"Arguments": {
"TableName": tool_registry_table_name,
"Key": {
"tool_name": {"S": "{% $states.input.tool_name %}"}
}
}
}
}
}
}{% endraw %}
- Deploy tools without updating any central code
- Deploy agents without dependency conflicts
- Teams can work independently
- Tools are discovered dynamically at execution time
- No compile-time dependencies on tool definitions
- Support for any tool registered in DynamoDB
# Team A deploys their tool
cdk deploy CustomAnalyticsToolStack-prod
# Team B deploys their agent using Team A's tool
# (without needing to coordinate code changes)
cdk deploy CustomAgentStack-prod- Tool updates don't affect agent deployments
- Agent updates don't require tool redeployments
- Independent versioning and rollback capabilities
class SQLAgentStack(BaseAgentStack):
def __init__(self, ...):
# Validates against centralized AllTools registry
tool_names = [config["tool_name"] for config in tool_configs]
invalid_tools = AllTools.validate_tool_names(tool_names)
if invalid_tools:
raise ValueError(f"Invalid tools: {invalid_tools}")class SQLAgentStack(ModularBaseAgentStack):
def __init__(self, ...):
# No centralized validation - tools resolved from DynamoDB
print(f"Using tools: {[config['tool_name'] for config in tool_configs]}")
super().__init__(
# ... other parameters
validate_tools=False # Disable centralized validation
)# Each tool stack registers itself
class CustomToolStack(Stack):
def __init__(self, ...):
# 1. Create Lambda function
self.custom_lambda = lambda_.Function(...)
# 2. Create activity if needed
if self.requires_remote_execution:
self.remote_activity = sfn.Activity(...)
# 3. Self-register in DynamoDB
MultiToolConstruct(
self,
"CustomToolsRegistry",
tool_groups=[{
"tool_specs": [{
"tool_name": "custom_tool",
"description": "Custom tool functionality",
"input_schema": {...},
"requires_activity": True,
"activity_type": "remote_execution",
"activity_arn": self.remote_activity.activity_arn
}],
"lambda_function": self.custom_lambda
}]
)# Use descriptive, unique tool names
"tool_name": "financial_stock_analysis" # Good
"tool_name": "analyze" # Bad - too generic# Human approval: Agent-owned (one per agent)
approval_activity = sfn.Activity(
self, f"{agent_name}ApprovalActivity",
activity_name=f"{agent_name}-approval-activity-{env_name}"
)
# Remote execution: Tool-owned (one per remote tool)
remote_activity = sfn.Activity(
self, f"{tool_name}RemoteActivity",
activity_name=f"{tool_name}-remote-activity-{env_name}"
)# Graceful handling of missing tools
try:
# Tool execution
except ToolNotFoundError:
return {
"error": "Tool not available in registry",
"available_tools": "Check DynamoDB registry"
}# Optional validation for development
class TestAgentStack(ModularBaseAgentStack):
def __init__(self, ...):
super().__init__(
# ... other parameters
validate_tools=True # Enable for testing/development
)# Support for tool versions
"tool_name": "analyze_data",
"tool_version": "v2.1.0",
"compatibility": ["v2.x", "v1.5+"]# Runtime tool discovery and registration
"Discover Available Tools": {
"Type": "Task",
"Resource": "arn:aws:states:::dynamodb:scan",
"Arguments": {
"TableName": tool_registry_table_name,
"FilterExpression": "attribute_exists(tool_name)"
}
}{% raw %}
# Automatic tool selection based on capabilities
"Select Best Tool": {
"Type": "Choice",
"Choices": [
{
"Condition": "{% $states.input.requires_database %}",
"Next": "Use Database Tool"
},
{
"Condition": "{% $states.input.requires_computation %}",
"Next": "Use Compute Tool"
}
]
}{% endraw %}
This modular architecture provides the foundation for truly independent tool and agent development while maintaining the flexibility and power of the approval workflow system.