Skip to content

Commit 5d3b541

Browse files
authored
Merge pull request awslabs#45 from iprivit/main
feat: add claude-agent-sdk single-agent and multi-agent patterns with READMEs
2 parents cf55921 + e80f5ae commit 5d3b541

File tree

28 files changed

+1619
-3
lines changed

28 files changed

+1619
-3
lines changed

frontend/src/lib/agentcore-client/client.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,14 @@
44
import type { AgentCoreConfig, AgentPattern, ChunkParser, StreamCallback } from "./types"
55
import { parseStrandsChunk } from "./parsers/strands"
66
import { parseLanggraphChunk } from "./parsers/langgraph"
7+
import { parseClaudeAgentSdkChunk } from "./parsers/claude-agent-sdk"
78
import { readSSEStream } from "./utils/sse"
89

910
const PARSERS: Record<AgentPattern, ChunkParser> = {
1011
"strands-single-agent": parseStrandsChunk,
1112
"langgraph-single-agent": parseLanggraphChunk,
13+
"claude-agent-sdk-single-agent": parseClaudeAgentSdkChunk,
14+
"claude-agent-sdk-multi-agent": parseClaudeAgentSdkChunk,
1215
}
1316

1417
export class AgentCoreClient {
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import type { ChunkParser } from "../types";
5+
6+
/**
7+
* Parses SSE chunks from Claude Agent SDK agents.
8+
* Emits typed StreamEvents for text, tool use, and session lifecycle.
9+
*
10+
* Event shapes from the agent:
11+
* - {"data": "text"} → text content
12+
* - {"current_tool_use": {...}} → tool use (complete per event)
13+
* - {"claude_session_id": "..."} → session ID for resumption
14+
*/
15+
export const parseClaudeAgentSdkChunk: ChunkParser = (line, callback) => {
16+
if (!line.startsWith("data: ")) return;
17+
18+
const data = line.substring(6).trim();
19+
if (!data) return;
20+
21+
try {
22+
const json = JSON.parse(data);
23+
24+
// Text streaming
25+
if (typeof json.data === "string") {
26+
callback({ type: "text", content: json.data });
27+
return;
28+
}
29+
30+
// Tool use — claude-agent-sdk sends complete tool info per event
31+
if (json.current_tool_use) {
32+
const tool = json.current_tool_use;
33+
callback({
34+
type: "tool_use_start",
35+
toolUseId: tool.toolUseId,
36+
name: tool.name,
37+
});
38+
if (tool.input) {
39+
callback({
40+
type: "tool_use_delta",
41+
toolUseId: tool.toolUseId,
42+
input: JSON.stringify(tool.input),
43+
});
44+
}
45+
return;
46+
}
47+
48+
// Claude session ID for conversation resumption
49+
if (json.claude_session_id) {
50+
callback({ type: "lifecycle", event: "session_id" });
51+
return;
52+
}
53+
} catch {
54+
console.debug("Failed to parse claude-agent-sdk event:", data);
55+
}
56+
};

frontend/src/lib/agentcore-client/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// SPDX-License-Identifier: Apache-2.0
33

44
/** Supported agent framework patterns */
5-
export type AgentPattern = "strands-single-agent" | "langgraph-single-agent"
5+
export type AgentPattern = "strands-single-agent" | "langgraph-single-agent" | "claude-agent-sdk-single-agent" | "claude-agent-sdk-multi-agent"
66

77
/** Configuration for AgentCoreClient */
88
export interface AgentCoreConfig {

infra-cdk/config.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ stack_name_base: FAST-stack
55
admin_user_email: # Example: admin@example.com
66

77
backend:
8-
pattern: strands-single-agent # Available patterns: strands-single-agent, langgraph-single-agent
9-
deployment_type: docker # Available deployment types: docker (default), zip
8+
pattern: strands-single-agent # Available patterns: strands-single-agent, langgraph-single-agent, claude-agent-sdk
9+
deployment_type: docker # Available deployment types: docker (default), zip (not supported for claude-agent-sdk)
1010
network_mode: PUBLIC # Available network modes: PUBLIC (default), VPC
1111

1212
# VPC configuration - required when network_mode is VPC

infra-cdk/lib/backend-stack.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,13 @@ export class BackendStack extends cdk.NestedStack {
113113
let agentRuntimeArtifact: agentcore.AgentRuntimeArtifact
114114
let zipPackagerResource: cdk.CustomResource | undefined
115115

116+
if (deploymentType === "zip" && (pattern === "claude-agent-sdk-single-agent" || pattern === "claude-agent-sdk-multi-agent")) {
117+
throw new Error(
118+
"claude-agent-sdk patterns require Docker deployment (deployment_type: docker) " +
119+
"because they need Node.js and the claude-code CLI installed at build time."
120+
)
121+
}
122+
116123
if (deploymentType === "zip") {
117124
// ZIP DEPLOYMENT: Use Lambda to package and upload to S3 (no Docker required)
118125
const repoRoot = path.resolve(__dirname, "..", "..")
@@ -334,6 +341,11 @@ export class BackendStack extends cdk.NestedStack {
334341
GATEWAY_CREDENTIAL_PROVIDER_NAME: `${config.stack_name_base}-runtime-gateway-auth`, // Used by @requires_access_token decorator to look up the correct provider
335342
}
336343

344+
// Add claude-agent-sdk specific environment variable
345+
if (pattern === "claude-agent-sdk-single-agent" || pattern === "claude-agent-sdk-multi-agent") {
346+
envVars["CLAUDE_CODE_USE_BEDROCK"] = "1"
347+
}
348+
337349
// Create the runtime using L2 construct
338350
// requestHeaderConfiguration allows the agent to read the Authorization header
339351
// from RequestContext.request_headers, which is needed to securely extract the
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
FROM ghcr.io/astral-sh/uv:python3.11-bookworm-slim
5+
6+
WORKDIR /app
7+
8+
ENV UV_SYSTEM_PYTHON=1 \
9+
UV_COMPILE_BYTECODE=1 \
10+
DOCKER_CONTAINER=1 \
11+
OTEL_PYTHON_LOG_CORRELATION=true \
12+
PYTHONUNBUFFERED=1 \
13+
CLAUDE_CODE_USE_BEDROCK=1
14+
15+
# Install Node.js for claude-code CLI
16+
RUN apt-get update && \
17+
apt-get install -y curl && \
18+
curl -fsSL https://deb.nodesource.com/setup_lts.x | bash - && \
19+
apt-get install -y nodejs && \
20+
rm -rf /var/lib/apt/lists/*
21+
22+
# Install claude-code globally
23+
RUN npm install -g @anthropic-ai/claude-code@2.1.73
24+
25+
# Copy pyproject.toml and shared modules for FAST installation
26+
COPY pyproject.toml .
27+
COPY gateway/ gateway/
28+
COPY tools/ tools/
29+
30+
# Copy and install agent-specific requirements
31+
COPY patterns/claude-agent-sdk-multi-agent/requirements.txt requirements.txt
32+
RUN uv pip install --no-cache -r requirements.txt && \
33+
uv pip install --no-cache aws-opentelemetry-distro==0.10.1
34+
35+
# Install FAST package with only core dependencies
36+
RUN uv pip install --no-cache -e . --no-deps && \
37+
uv pip install --no-cache requests>=2.31.0
38+
39+
# Create non-root user and ensure claude config dir is writable
40+
RUN useradd -m -u 1000 bedrock_agentcore && \
41+
mkdir -p /home/bedrock_agentcore/.claude && \
42+
chown -R bedrock_agentcore:bedrock_agentcore /home/bedrock_agentcore/.claude
43+
USER bedrock_agentcore
44+
45+
ENV CLAUDE_CONFIG_DIR=/home/bedrock_agentcore/.claude
46+
47+
EXPOSE 8080
48+
49+
# Copy agent code files
50+
COPY patterns/claude-agent-sdk-multi-agent/agent.py .
51+
COPY patterns/claude-agent-sdk-multi-agent/code_int_mcp/ code_int_mcp/
52+
COPY patterns/claude-agent-sdk-multi-agent/agents/ agents/
53+
COPY patterns/utils/ utils/
54+
55+
# Healthcheck using Python
56+
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
57+
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8080/ping', timeout=2)" || exit 1
58+
59+
# Start agent with OpenTelemetry instrumentation
60+
CMD ["opentelemetry-instrument", "python", "-m", "agent"]
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
# Claude Agent SDK Multi-Agent Pattern
2+
3+
This pattern integrates Anthropic's Claude Agent SDK with Amazon Bedrock AgentCore, providing Code Interpreter access via an in-process MCP server, subagent delegation via the Task tool, and Gateway tool integration. For a simpler single-agent version without subagents, see the `claude-agent-sdk-single-agent` pattern.
4+
5+
## Features
6+
7+
- **Claude Agent SDK**: Uses Anthropic's official agent SDK (`ClaudeSDKClient`) for agentic workflows on Bedrock
8+
- **Code Interpreter**: Execute Python code, bash commands, and file operations via an in-process MCP server
9+
- **Subagent Spawning**: Delegate focused subtasks to a specialized `code-analyst` subagent via the Task tool
10+
- **Gateway Integration**: Access Lambda-based tools through AgentCore Gateway (MCP protocol with OAuth2 auth)
11+
- **Session Management**: Resume conversations across requests via `claude_session_id`
12+
- **Secure Identity**: User identity extracted from validated JWT token (`RequestContext`), not from payload
13+
14+
## Architecture
15+
16+
```
17+
User Request
18+
|
19+
BedrockAgentCoreApp (agent.py)
20+
|
21+
ClaudeSDKClient (Opus model via Bedrock)
22+
|
23+
+-- Code Interpreter MCP (in-process)
24+
| execute_code, execute_command, write_files, read_files
25+
|
26+
+-- Gateway MCP (HTTP, optional)
27+
| Lambda-based tools via AgentCore Gateway
28+
|
29+
+-- Task tool (subagent spawning)
30+
code-analyst (Sonnet) — analyze output, debug errors
31+
```
32+
33+
The main agent (Opus) orchestrates work and can delegate to a `code-analyst` subagent (Sonnet) that runs as a separate `claude-code` child process.
34+
35+
## File Structure
36+
37+
```
38+
patterns/claude-agent-sdk-multi-agent/
39+
├── agent.py # Main entrypoint (BedrockAgentCoreApp)
40+
├── agents/
41+
│ └── subagents.py # Subagent definitions (code-analyst)
42+
├── code_int_mcp/
43+
│ ├── server.py # MCP server with @tool definitions
44+
│ ├── client.py # boto3 wrapper for AgentCore Code Interpreter API
45+
│ └── models.py # Pydantic result model
46+
├── requirements.txt # Python dependencies
47+
├── Dockerfile # Container build (Python 3.11 + Node.js + claude-code CLI)
48+
└── README.md
49+
```
50+
51+
## Available Tools
52+
53+
| Tool | MCP Prefix | Description |
54+
|------|-----------|-------------|
55+
| `execute_code` | `mcp__codeint__` | Execute Python code snippets |
56+
| `execute_command` | `mcp__codeint__` | Run bash/shell commands |
57+
| `write_files` | `mcp__codeint__` | Write files in the Code Interpreter session |
58+
| `read_files` | `mcp__codeint__` | Read files from the Code Interpreter session |
59+
| `Task` || Spawn a subagent for focused subtasks |
60+
| Gateway tools | `mcp__gateway__*` | Lambda-based tools via AgentCore Gateway |
61+
62+
## Built-in Tool Configuration
63+
64+
The Claude Agent SDK includes built-in tools from claude-code (Bash, Read, Write, etc.). This pattern disables most of them so the agent operates exclusively through Code Interpreter and Gateway MCP tools.
65+
66+
**Disabled built-in tools** (`disallowed_tools` in `ClaudeAgentOptions`):
67+
68+
| Tool | Why disabled |
69+
|------|-------------|
70+
| `Bash` | Use `mcp__codeint__execute_command` instead (sandboxed Code Interpreter) |
71+
| `Write` | Use `mcp__codeint__write_files` instead (sandboxed Code Interpreter) |
72+
| `Read` | Use `mcp__codeint__read_files` instead (sandboxed Code Interpreter) |
73+
| `Edit` | Use Code Interpreter file operations instead |
74+
| `NotebookEdit` | Use Code Interpreter for notebook-style execution |
75+
| `WebFetch` | Not needed for this pattern |
76+
| `Glob` | Use Code Interpreter for file discovery |
77+
| `Grep` | Use Code Interpreter for content searching |
78+
| `EnterWorktree` | Not applicable in this deployment context |
79+
| `Skill` | Not applicable in this deployment context |
80+
| `TodoWrite` | Not applicable in this deployment context |
81+
| `CronCreate` | Not applicable in this deployment context |
82+
| `CronDelete` | Not applicable in this deployment context |
83+
| `CronList` | Not applicable in this deployment context |
84+
85+
**To re-enable a built-in tool**, remove it from the `disallowed_tools` list in the `_build_options()` function in `agent.py`:
86+
87+
```python
88+
# Before: tool is disabled
89+
disallowed_tools=["Bash", "Write", "NotebookEdit", "Edit", "WebFetch", "Read", "Glob", "Grep", "EnterWorktree", "Skill", "TodoWrite", "CronCreate", "CronDelete", "CronList"],
90+
91+
# After: Bash re-enabled
92+
disallowed_tools=["Write", "NotebookEdit", "Edit", "WebFetch", "Read", "Glob", "Grep", "EnterWorktree", "Skill", "TodoWrite", "CronCreate", "CronDelete", "CronList"],
93+
```
94+
95+
If you also want the agent to proactively use the re-enabled tool, add it to the `allowed_tools` list and mention it in the `system_prompt`.
96+
97+
**Note**: Subagents inherit the parent's MCP server configuration but have their own `tools` list defined in `agents/subagents.py`. The `disallowed_tools` setting on the parent does not automatically apply to subagents — update their tool lists separately if needed.
98+
99+
## Models
100+
101+
- **Main agent**: `us.anthropic.claude-opus-4-6-v1`
102+
- **Subagents**: `sonnet` (cost-efficient for focused analysis tasks)
103+
104+
## Streaming Events
105+
106+
The agent yields three event types as SSE `data: {json}` lines:
107+
108+
| Event | Format | Description |
109+
|-------|--------|-------------|
110+
| Text | `{"data": "text content"}` | Agent text response |
111+
| Tool use | `{"current_tool_use": {"name": "...", "input": {...}, "toolUseId": "..."}}` | Tool invocation |
112+
| Session ID | `{"claude_session_id": "..."}` | Session ID for conversation resumption |
113+
114+
A dedicated frontend parser at `frontend/src/lib/agentcore-client/parsers/claude-agent-sdk.ts` handles these events. Both the single-agent and multi-agent patterns share the same parser.
115+
116+
## Session Management
117+
118+
This pattern uses `claude_session_id` for conversation continuity — **not** AgentCoreMemory. The flow:
119+
120+
1. First request: no `claude_session_id` in payload — a fresh session is created
121+
2. Agent yields `{"claude_session_id": "..."}` at the end of the response
122+
3. Subsequent requests: include the returned `claude_session_id` in the payload
123+
4. The SDK resumes the conversation via the `resume` option in `ClaudeAgentOptions`
124+
5. If resumption fails (e.g., container replaced), the agent automatically starts a fresh session
125+
126+
## Code Interpreter Session Handling
127+
128+
Code Interpreter sessions are separate from Claude sessions:
129+
130+
1. First call: pass `code_int_session_id: ""` (empty string)
131+
2. The tool returns a valid session ID in the response
132+
3. Use the returned session ID for all subsequent Code Interpreter calls
133+
4. Never generate or fabricate session IDs
134+
135+
## Adding a Subagent
136+
137+
Edit `agents/subagents.py` and add an entry to the dictionary returned by `get_subagent_definitions()`:
138+
139+
```python
140+
"my-agent": AgentDefinition(
141+
description="When to use this agent (the main agent reads this to decide delegation)",
142+
prompt="System prompt defining the agent's role and behavior",
143+
tools=["mcp__codeint__execute_code", "mcp__gateway__*", "Read", "Grep", "Glob"],
144+
model="sonnet",
145+
),
146+
```
147+
148+
Constraints:
149+
- Subagents inherit MCP server configuration from the parent `ClaudeAgentOptions`
150+
- Subagents **cannot** spawn other subagents (don't include `Task` in their tools)
151+
- Keep descriptions clear — the main agent uses them to decide when to delegate
152+
153+
## Deployment
154+
155+
```bash
156+
cd infra-cdk
157+
# Set pattern in config.yaml:
158+
# backend:
159+
# pattern: claude-agent-sdk-multi-agent
160+
# deployment_type: docker
161+
cdk deploy
162+
```
163+
164+
**Note**: This pattern requires `deployment_type: docker` because it needs Node.js and the `@anthropic-ai/claude-code` npm package installed at build time. ZIP deployment is not supported.
165+
166+
## Security
167+
168+
- **User identity**: Extracted from the validated JWT token via `RequestContext`, not from the payload body
169+
- **STACK_NAME validation**: Validated for alphanumeric characters (plus `-` and `_`) before use in SSM parameter paths
170+
- **Payload validation**: Required fields (`prompt`, `runtimeSessionId`) are validated before processing
171+
- **Gateway auth**: OAuth2 client credentials flow via Cognito for machine-to-machine authentication
172+
- **Gateway resilience**: If Gateway is unavailable, the agent continues without Gateway tools
173+
174+
## Differences from Strands / LangGraph Patterns
175+
176+
| Feature | Claude Agent SDK (Multi-Agent) | Strands | LangGraph |
177+
|---------|-------------------------------|---------|-----------|
178+
| Framework | Anthropic Claude Agent SDK | Strands Agents | LangGraph + LangChain |
179+
| Model provider | Bedrock (via `CLAUDE_CODE_USE_BEDROCK`) | Bedrock (`BedrockModel`) | Bedrock (`ChatBedrock`) |
180+
| Memory | `claude_session_id` (SDK-managed) | AgentCoreMemory | AgentCoreMemory |
181+
| Token streaming | No (complete message blocks) | Yes | Yes |
182+
| Subagents | Yes (Task tool + `AgentDefinition`) | No (single agent) | No (single agent) |
183+
| Code Interpreter | In-process MCP server | `StrandsCodeInterpreterTools` | LangGraph tool wrapper |
184+
| Requires Node.js | Yes (claude-code CLI) | No | No |
185+
| ZIP deployment | Not supported | Supported | Supported |
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
# SPDX-License-Identifier: Apache-2.0

0 commit comments

Comments
 (0)