From 05fee423f2a11666741a8ac9c3b8ce2fefbab616 Mon Sep 17 00:00:00 2001 From: daniel Date: Fri, 15 May 2026 15:19:08 -0500 Subject: [PATCH] feat: dynamic subagent loading from SSM --- agentclaw/app/agent_claw_main/main.py | 45 ++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/agentclaw/app/agent_claw_main/main.py b/agentclaw/app/agent_claw_main/main.py index a1cb8dc..faefcc1 100644 --- a/agentclaw/app/agent_claw_main/main.py +++ b/agentclaw/app/agent_claw_main/main.py @@ -68,6 +68,7 @@ from bedrock_agentcore.memory.integrations.strands.config import AgentCoreMemory from bedrock_agentcore.memory.integrations.strands.session_manager import AgentCoreMemorySessionManager # code_interpreter removed — causes [Errno 98] port 8080 conflict on warm container re-init from tools.code_interpreter import run_code +from strands_tools import http_request, file_read app = BedrockAgentCoreApp() @@ -87,6 +88,43 @@ except Exception as _e: print(traceback.format_exc()) +# ── Subagent loading ────────────────────────────────────────────────────── + +TOOL_PRESETS = { + "aws": lambda: [MCPClient(lambda: aws_iam_streamablehttp_client(config.AWS_MCP_URL, aws_service="aws-mcp"))], + "coding": lambda: [MCPClient(lambda: aws_iam_streamablehttp_client(config.AWS_MCP_URL, aws_service="aws-mcp")), run_code], + "documents": lambda: [http_request, file_read], +} + + +def _load_subagents(ssm_client) -> list: + """Load subagent definitions from SSM and return as tools.""" + import json + try: + resp = ssm_client.get_parameter(Name='/agent-claw/subagents') + defs = json.loads(resp['Parameter']['Value']) + except Exception as e: + print(f'[main] Failed to load subagents from SSM: {type(e).__name__}: {e}') + return [] + tools = [] + for cfg in defs: + preset = cfg.get('tools', '') + if preset not in TOOL_PRESETS: + print(f'[main] Unknown tool preset "{preset}" for subagent "{cfg.get("name")}", skipping') + continue + try: + sub = Agent( + model=BedrockModel(model_id=cfg['model_id'], region_name='us-east-1'), + system_prompt=cfg['system_prompt'], + tools=TOOL_PRESETS[preset](), + ) + tools.append(sub.as_tool(name=cfg['name'], description=cfg['description'])) + except Exception as e: + print(f'[main] Failed to build subagent "{cfg.get("name")}": {type(e).__name__}: {e}') + print(f'[main] Loaded {len(tools)} subagent(s)') + return tools + + # ── Tool definitions ────────────────────────────────────────────────────── # NOTE: send_message tool removed — delivery handled by agent-runner streaming consumer @@ -497,6 +535,7 @@ async def main(payload: dict, context): _time_str = _now.strftime('%A, %B %d, %Y %I:%M %p %Z') system_prompt = system_prompt + f'\n\nCurrent date/time: {_time_str}' system_prompt += 'AWS tools available: call_aws (any AWS API via AWS MCP Server), aws_list_lambda_functions, aws_get_cost_and_usage, aws_describe_service. Use call_aws directly for AWS API calls — do NOT say you lack AWS access.' + system_prompt += '\n\nSubagents available — use them aggressively to save cost and improve quality:\n- aws_agent: all AWS infrastructure, cost, resource, IAM, CloudWatch queries\n- coding_agent: code writing, builds, deployments, CodeBuild/AppRunner/ECR\n- document_agent: summarize URLs, extract data from documents, process long text\nDefault to delegating; only answer directly for simple conversational responses or tasks that don\'t fit a subagent.' print(f'[main] System prompt time injection: {_time_str}') # Model: claude-sonnet-4-6 via cross-region inference @@ -519,7 +558,11 @@ async def main(payload: dict, context): mcp_connections = services.get('mcp_connections', []) mcp_clients, _mcp_to_close = mcp_loader.load_mcp_tools(mcp_connections, actor_id) - all_tools = base_tools + _aws_mcp_tools + mcp_clients + # Load subagents from SSM + ssm = boto3.client('ssm', region_name='us-east-1') + subagent_tools = _load_subagents(ssm) + + all_tools = base_tools + _aws_mcp_tools + mcp_clients + subagent_tools agent = Agent( model=model,