diff --git a/claude-agent-sdk-dynamic-skills-on-agentcore/.bedrock_agentcore.yaml.template b/claude-agent-sdk-dynamic-skills-on-agentcore/.bedrock_agentcore.yaml.template new file mode 100644 index 0000000..9b66230 --- /dev/null +++ b/claude-agent-sdk-dynamic-skills-on-agentcore/.bedrock_agentcore.yaml.template @@ -0,0 +1,93 @@ +# AgentCore Configuration Template for Claude Agent with Dynamic Skills +# +# This file will be generated by `agentcore configure` command. +# The values below show the recommended configuration options. +# +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: MIT-0 + +default_agent: claude_dynamic_skills + +agents: + # Simple implementation using Claude SDK with prompt injection + claude_simple: + name: claude_simple + entrypoint: claude_sdk_bedrock.py + deployment_type: container + platform: linux/amd64 # or linux/arm64 based on your preference + container_runtime: docker + aws: + execution_role_auto_create: true + region: ${AWS_REGION} + ecr_auto_create: true + network_configuration: + network_mode: PUBLIC + protocol_configuration: + server_protocol: HTTP + observability: + enabled: true + memory: + mode: NO_MEMORY + + # Advanced implementation using Claude Agent SDK with native skill discovery + claude_dynamic_skills: + name: claude_dynamic_skills + entrypoint: agent/agent.py + deployment_type: container + platform: linux/amd64 # or linux/arm64 based on your preference + container_runtime: docker + aws: + execution_role_auto_create: true + region: ${AWS_REGION} + ecr_auto_create: true + network_configuration: + network_mode: PUBLIC + protocol_configuration: + server_protocol: HTTP + observability: + enabled: true + memory: + mode: NO_MEMORY + +# Configuration Notes: +# +# 1. execution_role_auto_create: true +# AgentCore will create an IAM role with basic permissions. +# You'll need to add S3 permissions manually via AWS Console. +# +# 2. ecr_auto_create: true +# AgentCore will create an ECR repository for your container images. +# +# 3. platform: Choose based on your development/deployment environment: +# - linux/amd64: Standard x86_64 architecture +# - linux/arm64: ARM-based systems (Apple Silicon, Graviton) +# +# 4. observability: enabled +# Enables CloudWatch logging and AWS X-Ray tracing. +# +# 5. memory: NO_MEMORY +# No persistent memory between invocations. +# Each container startup loads skills fresh from S3. + +# Required IAM Permissions (add to execution role): +# { +# "Version": "2012-10-17", +# "Statement": [ +# { +# "Effect": "Allow", +# "Action": [ +# "s3:GetObject", +# "s3:ListBucket" +# ], +# "Resource": [ +# "arn:aws:s3:::YOUR_SKILLS_BUCKET_NAME", +# "arn:aws:s3:::YOUR_SKILLS_BUCKET_NAME/*" +# ] +# }, +# { +# "Effect": "Allow", +# "Action": "bedrock:InvokeModel", +# "Resource": "arn:aws:bedrock:*:*:model/us.anthropic.claude-*" +# } +# ] +# } \ No newline at end of file diff --git a/claude-agent-sdk-dynamic-skills-on-agentcore/.dockerignore b/claude-agent-sdk-dynamic-skills-on-agentcore/.dockerignore new file mode 100644 index 0000000..0440f1b --- /dev/null +++ b/claude-agent-sdk-dynamic-skills-on-agentcore/.dockerignore @@ -0,0 +1,67 @@ +# Build artifacts +build/ +dist/ +*.egg-info/ +*.egg + +# Python cache +__pycache__/ +__pycache__* +*.py[cod] +*$py.class +*.so +.Python + +# Virtual environments +.venv/ +.env +venv/ +env/ +ENV/ + +# Testing +.pytest_cache/ +.coverage +.coverage* +htmlcov/ +.tox/ +*.cover +.hypothesis/ +.mypy_cache/ +.ruff_cache/ + +# Development +*.log +*.bak +*.swp +*.swo +*~ +.DS_Store + +# IDEs +.vscode/ +.idea/ + +# Version control +.git/ +.gitignore +.gitattributes + +# Documentation +docs/ + +# CI/CD +.github/ +.gitlab-ci.yml +.travis.yml + +# Project specific +tests/ + +# Bedrock AgentCore specific - keep config but exclude runtime files +.bedrock_agentcore.yaml +.dockerignore +.bedrock_agentcore/ + +# Keep wheelhouse for offline installations +# wheelhouse/ diff --git a/claude-agent-sdk-dynamic-skills-on-agentcore/.gitignore b/claude-agent-sdk-dynamic-skills-on-agentcore/.gitignore new file mode 100644 index 0000000..575e7a8 --- /dev/null +++ b/claude-agent-sdk-dynamic-skills-on-agentcore/.gitignore @@ -0,0 +1,39 @@ +# Environment variables +.env + +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +*.egg-info/ +*.egg +dist/ +build/ + +# Virtual environments +venv/ +env/ +ENV/ +.venv/ + +# IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# Testing +.pytest_cache/ +.coverage +htmlcov/ +.tox/ + +# AgentCore +.bedrock_agentcore.yaml + +# OS +.DS_Store +Thumbs.db diff --git a/claude-agent-sdk-dynamic-skills-on-agentcore/LICENSE b/claude-agent-sdk-dynamic-skills-on-agentcore/LICENSE new file mode 100644 index 0000000..4ec534e --- /dev/null +++ b/claude-agent-sdk-dynamic-skills-on-agentcore/LICENSE @@ -0,0 +1,16 @@ +MIT No Attribution + +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of this +software and associated documentation files (the "Software"), to deal in the Software +without restriction, including without limitation the rights to use, copy, modify, +merge, publish, distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/claude-agent-sdk-dynamic-skills-on-agentcore/README.md b/claude-agent-sdk-dynamic-skills-on-agentcore/README.md new file mode 100644 index 0000000..92b7b69 --- /dev/null +++ b/claude-agent-sdk-dynamic-skills-on-agentcore/README.md @@ -0,0 +1,487 @@ +# Claude Agent SDK with Dynamic Skills on Amazon Bedrock AgentCore + +This sample demonstrates **the architecture for dynamic skill management** using the Claude SDK with Amazon Bedrock on AgentCore Runtime. Skills are loaded dynamically from Amazon S3 at container startup, enabling centralized skill management and zero-downtime updates without container rebuilds. + +## What Problem Does This Solve? + +**AgentCore Runtime Challenge**: AgentCore does not provide persistent storage between container lifecycles. Traditional approaches require rebuilding containers to update agent capabilities. + +This sample solves these limitations by: + +- **Dynamic Skill Loading**: Skills loaded from S3 at container startup (once per container lifecycle, not per request) +- **Persistent Storage Workaround**: S3 acts as the persistent skill repository, bypassing AgentCore's ephemeral storage +- **Centralized Management**: Multiple agent instances share the same S3 skill repository +- **Zero-Downtime Updates**: Update skills in S3 → AgentCore spins up new containers → fresh skills loaded +- **Skill Modularity**: Reusable skills across different agent deployments + +## Architecture + +``` +┌──────────┐ ┌─────────────────────┐ ┌─────────────────┐ +│ User │────▶│ AgentCore Runtime │────▶│ Container │ +│ Request │ │ (Managed Service) │ │ (HTTP:8080) │ +└──────────┘ └─────────────────────┘ └────────┬────────┘ + │ + ┌────────────────────────────┼────────────┐ + │ Container Startup Process │ │ + │ ▼ │ + │ ┌─────────┐ ┌─────────────┐ │ + │ │ S3 │◀──────│ Skill Loader│ │ + │ │ Skills │ load │ downloads │ │ + │ │ Bucket │ │ to .claude/ │ │ + │ └─────────┘ └─────────────┘ │ + │ │ │ + │ ▼ │ + │ ┌─────────────┐ │ + │ │ Claude SDK │ │ + │ │ discovers │ │ + │ │ skills │ │ + │ └─────────────┘ │ + └─────────────────────────────────────────┘ +``` + +**What This Sample Demonstrates:** +- **S3-Based Skill Loading**: Complete system for downloading skills from S3 into `.claude/skills/` directory +- **Dynamic Capability Updates**: Change agent capabilities by updating S3 without rebuilding containers +- **Skill Repository Management**: Centralized skill storage shared across multiple agent instances +- **Claude SDK Integration**: Native skill discovery and assembly using Claude Agent SDK + +**Sample Skills Included:** +- **Code Analysis**: Demonstrates security analysis workflows (uses Claude's general knowledge + skill context) +- **Web Research**: Shows information synthesis patterns with citation tracking +- **Data Processing**: Illustrates multi-source data handling approaches + +**Note**: The included skills provide **conceptual frameworks and prompting strategies** that guide Claude's responses. The `implementation.py` files in the sample skills are structural placeholders showing where custom code would go - the Claude Agent SDK primarily uses the `SKILL.md` files as context. This architecture can be extended with custom implementations, APIs, and specialized tools to create production-ready agents. + +**Skill Loading Process**: +1. Container starts → `startup.sh` executes (once per container lifecycle) +2. S3 skill loader downloads skills from S3 to `.claude/skills/` +3. Claude Agent SDK discovers skills in standard directory structure +4. Container serves multiple requests using the same loaded skills +5. When container is replaced → new container repeats steps 1-3 with fresh S3 download + +**Important**: Skills are loaded **once per container lifecycle**, not per request. This means: +- ✅ Fast request handling (no S3 download per request) +- ✅ Minimal S3 API calls (one download per container startup) +- ✅ Skills persist across all requests handled by that container +- ✅ New containers automatically get updated skills from S3 + +## Prerequisites + +* [AWS account](https://signin.aws.amazon.com/signin) with credentials configured (`aws configure`) +* [Python 3.10+](https://www.python.org/downloads/) +* [Docker](https://www.docker.com/) installed and running +* [AWS CLI](https://aws.amazon.com/cli/) configured +* Model Access: Claude model enabled in [Amazon Bedrock console](https://docs.aws.amazon.com/bedrock/latest/userguide/model-access-modify.html) + +**Required AWS Permissions**: +* `BedrockAgentCoreFullAccess` managed policy +* `AmazonBedrockFullAccess` managed policy +* S3 access for skill repository (configured in setup steps) + +## Quick Start + +### Step 1: Create S3 Skills Repository + +Create an S3 bucket and upload sample skills: + +```bash +# Create S3 bucket (use a unique name) +export BUCKET_NAME="claude-agent-skills-$(date +%s)" +aws s3 mb s3://$BUCKET_NAME + +# Create sample skills directory structure +mkdir -p skills/code-analysis skills/web-research skills/data-fetcher + +# Create code-analysis skill +cat > skills/code-analysis/SKILL.md << 'EOF' +--- +description: "Advanced code analysis for security vulnerabilities and best practices" +tags: ["security", "analysis", "code-review"] +--- + +# Code Analysis Skill + +**What This Skill Does:** +- **Security Analysis**: Guides Claude to analyze code for OWASP Top 10 vulnerabilities (SQL injection, XSS, CSRF, insecure authentication, etc.) +- **Performance Review**: Guides Claude to identify N+1 queries, inefficient algorithms, memory leaks, and bottlenecks +- **Code Quality**: Guides Claude to check naming conventions, code complexity, maintainability metrics +- **Dependency Audit**: Guides Claude to consider known CVE risks in third-party libraries + +**Supported Languages:** Python, JavaScript/TypeScript, Java, Go, C/C++, Rust +EOF + +# Create web-research skill +cat > skills/web-research/SKILL.md << 'EOF' +--- +description: "Web research and information synthesis with citations" +tags: ["research", "web", "synthesis"] +--- + +# Web Research Skill + +**What This Skill Does:** +- **Multi-Source Research**: Guides Claude to synthesize information from documentation, forums, GitHub, Stack Overflow, and technical blogs +- **Fact Verification**: Guides Claude to cross-reference information across multiple sources to ensure accuracy +- **Synthesis**: Guides Claude to combine information from different sources into coherent, comprehensive summaries +- **Citation Tracking**: Guides Claude to maintain source links and attribution for all research findings +EOF + +# Create data-fetcher skill +cat > skills/data-fetcher/SKILL.md << 'EOF' +--- +description: "Multi-source data retrieval and processing" +tags: ["data", "api", "database", "files"] +--- + +# Data Fetcher Skill + +**What This Skill Does:** +- **API Integration**: Provides framework for REST API integration patterns with OAuth, API keys, or basic auth +- **Database Queries**: Guides Claude on data retrieval patterns from PostgreSQL, MySQL, MongoDB, DynamoDB +- **File Processing**: Guides Claude to parse CSV, JSON, XML, Excel files from local or cloud storage +- **Cloud Storage**: Provides patterns for accessing S3, Google Cloud Storage, Azure Blob for data files +EOF + +# Upload skills to S3 +aws s3 sync skills/ s3://$BUCKET_NAME/skills/ + +# Verify upload +aws s3 ls s3://$BUCKET_NAME/skills/ --recursive + +# Save bucket name for later use +echo "export SKILLS_S3_BUCKET=$BUCKET_NAME" > .env +echo "S3 bucket created: $BUCKET_NAME" +``` + +### Step 2: Configure IAM Permissions + +Add S3 permissions to your AgentCore execution role: + +```bash +# Create IAM policy for S3 access +cat > s3-skills-policy.json << EOF +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "s3:GetObject", + "s3:ListBucket" + ], + "Resource": [ + "arn:aws:s3:::$BUCKET_NAME", + "arn:aws:s3:::$BUCKET_NAME/*" + ] + } + ] +} +EOF + +# Add this policy to your AgentCore execution role via AWS Console +echo "Add the above policy to your AgentCore execution role" +``` + +### Step 3: Install AgentCore CLI + +```bash +pip install bedrock-agentcore anthropic boto3 pyyaml +``` + +### Step 4: Configure the Agent + +```bash +# Set environment variable for S3 bucket +source .env # Loads SKILLS_S3_BUCKET from Step 1 + +# Use the simple version (recommended for first deployment) +agentcore configure -e claude_sdk_bedrock.py -dt container -dm -ni + +# Or use the advanced Claude Agent SDK version +agentcore configure -e agent/agent.py -dt container -dm -ni +``` + +### Step 5: Deploy to AgentCore Runtime + +```bash +# Important: Use --local-build to avoid Lambda layer injection +agentcore launch --local-build +``` + +### Step 6: Test Your Agent + +```bash +# Check deployment status +agentcore status + +# Test 1: Verify Dynamic Skill Loading +agentcore invoke '{"prompt": "What skills do you have?"}' +# Expected: Agent lists code-analysis, web-research, and data-fetcher skills + +# Test 2: Skill-Guided Code Review +agentcore invoke '{"prompt": "Using your code-analysis skill, review this Python function: def login(user, password): if user == \"admin\" and password == \"admin123\": return True"}' +# Expected: Applies code-analysis skill context to identify hardcoded credentials and suggest improvements + +# Test 3: Research-Guided Response +agentcore invoke '{"prompt": "Using your web-research skill approach, explain best practices for API security"}' +# Expected: Structured response following web-research skill methodology with sources and citations +``` + +**Expected Behavior:** +- Agent references skills loaded from S3 in its responses +- Applies skill-specific methodologies and frameworks +- Uses skill context to provide structured, consistent guidance +- Demonstrates how skills influence response quality and approach + +## Implementation Options + +This sample provides **two implementation approaches** to demonstrate the concept at different complexity levels: + +### Option 1: Simple Claude SDK (Recommended for Learning) +**File**: `claude_sdk_bedrock.py` +- Direct Bedrock API integration +- Skills injected into system prompt +- ~60 lines of code, easy to understand +- Perfect for learning the concepts +- **Use when**: Prototyping, learning, or building simple internal tools + +**Why this exists**: Demonstrates the core concept without complexity. You can read the entire implementation in 5 minutes and understand exactly how S3 skills work. Great for proof-of-concepts and educational purposes. + +### Option 2: Advanced Claude Agent SDK (Recommended for Production) +**File**: `agent/agent.py` +- Native Claude Agent SDK integration +- True skill discovery and assembly +- Async/await patterns, production-ready +- Advanced error handling and deduplication +- **Use when**: Building production systems or need advanced SDK features + +**Why this exists**: Shows production-ready patterns with proper error handling, async operations, and native SDK integration. Use this as a template for real-world deployments where reliability and maintainability matter. + +**Both approaches achieve the same goal** (dynamic S3 skill loading), just at different complexity levels. Start with Option 1 to understand the concept, then move to Option 2 for production deployments. + +## Skills Repository Structure + +The setup process creates an S3 bucket with sample skills: + +``` +s3://your-skills-bucket/ +└── skills/ + ├── code-analysis/ + │ ├── SKILL.md # Skill description and instructions + │ └── implementation.py # Skill execution logic (advanced version) + ├── web-research/ + │ ├── SKILL.md + │ └── implementation.py + ├── data-fetcher/ + │ ├── SKILL.md + │ └── implementation.py + ├── document-generator/ + │ ├── SKILL.md + │ └── implementation.py + ├── report-generator/ + │ ├── SKILL.md + │ └── implementation.py + └── data-analysis-workflow/ + ├── SKILL.md # Orchestrator skill + └── orchestrator.py # Coordinates other skills +``` + +Each skill has: +- **SKILL.md**: YAML frontmatter + description (for Claude SDK discovery) +- **implementation.py**: Python execution logic (for advanced workflows) + +## Configuration + +### Environment Variables + +| Variable | Description | Default | +|----------|-------------|---------| +| `AWS_REGION` | AWS region | `us-east-1` | +| `SKILLS_S3_BUCKET` | S3 bucket name | Set in Step 1 setup | +| `ANTHROPIC_MODEL` | Bedrock model ID | `us.anthropic.claude-sonnet-4-20250514-v1:0` | + +### AgentCore Settings + +| Setting | Value | Reason | +|---------|-------|--------| +| Build | `--local-build` | Avoids Lambda layer injection issues | +| Protocol | HTTP | Standard for AgentCore containers | +| Port | 8080 | Default AgentCore container port | + +## Key Implementation Details + +### Dynamic Skill Loading + +```python +class ClaudeSkillLoader: + def load_skills_from_s3(self): + # List all skill directories in S3 + response = self.s3_client.list_objects_v2( + Bucket=self.skills_bucket, Prefix='skills/', Delimiter='/' + ) + + for prefix in response.get('CommonPrefixes', []): + skill_name = prefix['Prefix'].replace('skills/', '').rstrip('/') + self._download_skill(skill_name) + + # Skills now available in .claude/skills/ for Claude SDK discovery +``` + +### Startup Orchestration + +```bash +#!/bin/bash +# startup.sh - Container entry point + +# Step 1: Load skills from S3 into .claude/skills/ +python s3_skill_loader.py + +# Step 2: Verify skills loaded correctly +ls -la .claude/skills/ + +# Step 3: Start Claude agent (skills auto-discovered) +exec python agent.py +``` + +### Claude SDK Integration + +```python +# Simple version - skills in system prompt +skills_info = "\n".join([f"- {name}: {desc}" for name, desc in SKILLS.items()]) +system_prompt = f"Available skills:\n{skills_info}" + +response = claude_client.messages.create( + model=MODEL_ID, + system=system_prompt, + messages=[{"role": "user", "content": prompt}] +) + +# Advanced version - native skill discovery +options = ClaudeAgentOptions( + setting_sources=["project"], # Discovers .claude/skills/ directory + allowed_tools=["Skill", "Read", "Write", "Bash"] +) +async for message in query(prompt=prompt, options=options): + # Claude SDK automatically finds and uses skills +``` + +## Updating Skills + +### Zero-Downtime Skill Updates + +1. **Update skills in S3**: + ```bash + aws s3 cp new-skill.md s3://your-bucket/skills/new-skill/SKILL.md + ``` + +2. **Restart containers** (AgentCore handles this): + ```bash + agentcore restart + ``` + +3. **Skills automatically reload** from S3 on container startup + +### Adding New Skills + +1. Create skill directory in S3: + ```bash + aws s3 cp SKILL.md s3://your-bucket/skills/my-new-skill/SKILL.md + aws s3 cp implementation.py s3://your-bucket/skills/my-new-skill/implementation.py + ``` + +2. Restart agent - new skills auto-discovered + +## Cost Considerations + +- **AgentCore Runtime**: Pay per request/minute of execution +- **S3**: Minimal cost for skill storage and requests +- **Bedrock**: Pay per token (input/output) +- **ECR**: Container storage costs + +**Cost Optimization**: +- Use `--local-build` to avoid CodeBuild charges +- Skills cached in container memory during container lifetime +- S3 requests minimized (one-time loading per container startup) + +## Cleanup + +```bash +# Destroy AgentCore deployment +agentcore destroy + +# Remove S3 skills bucket (be careful - this deletes all skills!) +source .env # Load SKILLS_S3_BUCKET +aws s3 rm s3://$SKILLS_S3_BUCKET --recursive +aws s3 rb s3://$SKILLS_S3_BUCKET + +# Clean up local files +rm -rf skills/ .env +``` + +## Limitations and Considerations + +### Container Lifecycle +- **Automatic termination**: Containers terminate after 15 minutes idle or 8 hours max lifetime (configurable via lifecycle settings) +- **Predictable replacement**: AgentCore replaces containers when idle timeout or max lifetime is reached, triggering fresh S3 downloads +- **Cold start on replacement**: Each new container must download skills from S3 (time varies based on skill size and S3 latency) +- **Skills loaded once per container**: Not per request. To update skills, AgentCore must provision new containers + +### S3 Dependencies +- **S3 availability required**: Container startup fails if S3 is unavailable +- **Network latency**: S3 download time depends on region, skill size, and network conditions +- **S3 costs**: Minimal but consider GET request costs for frequent container replacements + +### Skill Management +- **No versioning**: Skills are overwritten in S3. Consider using S3 versioning for rollback capability +- **No validation**: Skills are not validated before loading. Malformed skills may cause runtime errors +- **Skill size limits**: Large skills increase container startup time + +### Resource Constraints +- **Ephemeral storage**: Skills stored in container filesystem, lost when container terminates +- **No persistent storage**: AgentCore sessions are ephemeral - data persists only for session duration +- **Container resources**: Subject to AgentCore Runtime resource limits (configurable per deployment) + +### Best Practices +- ✅ Keep skills small and focused (< 100KB per skill recommended) +- ✅ Use S3 versioning for skill rollback capability +- ✅ Monitor S3 GET request costs in high-traffic scenarios +- ✅ Test skill updates in non-production environments first +- ✅ Include skill validation in your CI/CD pipeline +- ✅ Configure lifecycle settings based on your use case (idle timeout, max lifetime) +- ✅ Verify model availability in your AWS region before deployment + +## Security Considerations + +- **IAM Least Privilege**: Execution role has minimal S3 and Bedrock permissions +- **VPC Isolation**: Can be deployed in private VPC (configure AgentCore network settings) +- **Skill Validation**: Skills downloaded from trusted S3 bucket only +- **Container Security**: Minimal base image (python:3.12-slim) with reduced attack surface + +## Comparison with Existing AgentCore Examples + +| Feature | This Sample | claude-agent-sdk-on-agentcore | claude-code-on-agentcore | +|---------|-------------|------------------------------|--------------------------| +| **Skill Updates** | S3-based dynamic loading | Container rebuild required | Fixed capabilities | +| **Skill Sharing** | Centralized S3 repository | Bundled per agent | No skill system | +| **Zero Downtime** | Update S3, restart containers | Rebuild + redeploy | Rebuild + redeploy | +| **Use Case** | Multi-skill agent framework | GitHub PR reviews | Claude Code CLI | + +## Related Resources + +* [Amazon Bedrock AgentCore Documentation](https://docs.aws.amazon.com/bedrock-agentcore/) +* [Claude SDK Documentation](https://docs.anthropic.com/en/api/claude-on-amazon-bedrock) +* [AWS CLI S3 Commands](https://docs.aws.amazon.com/cli/latest/reference/s3/) +* [Other AgentCore Samples](https://github.com/aws-samples/anthropic-on-aws) + +## Contributing + +This sample demonstrates an advanced pattern for production-scale agent deployments. Contributions welcome for: +- Additional skill examples +- Enhanced error handling patterns +- Multi-region deployment support +- Advanced skill dependency management + +## License + +This sample is licensed under the MIT-0 License. See the [LICENSE](LICENSE) file for details. \ No newline at end of file diff --git a/claude-agent-sdk-dynamic-skills-on-agentcore/agent/.claude/skills/code-analysis/SKILL.md b/claude-agent-sdk-dynamic-skills-on-agentcore/agent/.claude/skills/code-analysis/SKILL.md new file mode 100644 index 0000000..2927b99 --- /dev/null +++ b/claude-agent-sdk-dynamic-skills-on-agentcore/agent/.claude/skills/code-analysis/SKILL.md @@ -0,0 +1,28 @@ +--- +description: "Code Analysis Skill" +tags: ["s3-loaded", "dynamic"] +--- + +# Code Analysis Skill + +## Description +Analyzes codebases, identifies patterns, suggests improvements, and provides detailed code quality assessments across multiple programming languages. + +## Capabilities +- Static code analysis +- Security vulnerability detection +- Performance optimization suggestions +- Code quality metrics +- Architecture pattern recognition +- Dependency analysis + +## Usage +This skill is triggered when users need code review, analysis, or improvement suggestions. + +## Examples +- "Analyze this Python codebase for security issues" +- "Review my JavaScript code for performance improvements" +- "Check this repository for code quality issues" + +## Implementation +Uses multiple static analysis tools and pattern recognition to provide comprehensive code insights. diff --git a/claude-agent-sdk-dynamic-skills-on-agentcore/agent/.claude/skills/code-analysis/implementation.py b/claude-agent-sdk-dynamic-skills-on-agentcore/agent/.claude/skills/code-analysis/implementation.py new file mode 100644 index 0000000..87e5073 --- /dev/null +++ b/claude-agent-sdk-dynamic-skills-on-agentcore/agent/.claude/skills/code-analysis/implementation.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python3 +"""Code Analysis Skill Implementation""" + +import os +import json +from typing import Dict, Any + +class CodeAnalysisSkill: + def __init__(self): + self.name = "code-analysis" + self.description = "Comprehensive code analysis and review" + + def execute(self, params: Dict[str, Any]) -> str: + """Execute code analysis skill""" + message = params.get('message', 'analyze code') + + # Simulate code analysis process + analysis_steps = [ + "🔍 Scanning codebase structure...", + "🛡️ Running security analysis...", + "⚡ Checking performance patterns...", + "📊 Calculating quality metrics...", + "🏗️ Analyzing architecture patterns..." + ] + + # Simulate analysis results + results = { + "files_analyzed": 47, + "languages_detected": ["Python", "JavaScript", "TypeScript"], + "security_issues": 2, + "performance_issues": 5, + "code_quality_score": 8.5, + "architecture_pattern": "Microservices", + "recommendations": [ + "Add input validation in user authentication", + "Optimize database queries in data layer", + "Implement caching for frequently accessed data", + "Add unit tests for core business logic" + ] + } + + result = "\n".join(analysis_steps) + result += f"\n\n📊 Analysis Results:\n" + result += f"Files Analyzed: {results['files_analyzed']}\n" + result += f"Languages: {', '.join(results['languages_detected'])}\n" + result += f"Quality Score: {results['code_quality_score']}/10\n" + result += f"Security Issues: {results['security_issues']}\n" + result += f"Performance Issues: {results['performance_issues']}\n" + result += f"Architecture: {results['architecture_pattern']}\n\n" + result += f"🎯 Recommendations:\n" + for rec in results['recommendations']: + result += f" • {rec}\n" + + return result + +def execute(params: Dict[str, Any]) -> str: + """Skill entry point""" + skill = CodeAnalysisSkill() + return skill.execute(params) diff --git a/claude-agent-sdk-dynamic-skills-on-agentcore/agent/.claude/skills/data-analysis-workflow/SKILL.md b/claude-agent-sdk-dynamic-skills-on-agentcore/agent/.claude/skills/data-analysis-workflow/SKILL.md new file mode 100644 index 0000000..34d1111 --- /dev/null +++ b/claude-agent-sdk-dynamic-skills-on-agentcore/agent/.claude/skills/data-analysis-workflow/SKILL.md @@ -0,0 +1,32 @@ +--- +description: "Data Analysis Workflow" +tags: ["s3-loaded", "dynamic"] +--- + +# Data Analysis Workflow + +## Description +Orchestrates a complete data analysis workflow using multiple skills. + +## Dependencies +- `data-fetcher`: For retrieving source data +- `report-generator`: For creating final reports + +## Workflow Steps +1. Fetch data using data-fetcher skill +2. Process and analyze the data +3. Generate report using report-generator skill + +## Parameters +- `source`: Data source for fetching +- `query`: Query parameters for data fetching +- `report_format`: Final report format + +## Usage +This is a meta-skill that coordinates other skills to complete a full analysis workflow. + +## Example +``` +Input: source="api", query="sales_2024", report_format="html" +Output: Complete HTML report with analyzed sales data +``` diff --git a/claude-agent-sdk-dynamic-skills-on-agentcore/agent/.claude/skills/data-fetcher/SKILL.md b/claude-agent-sdk-dynamic-skills-on-agentcore/agent/.claude/skills/data-fetcher/SKILL.md new file mode 100644 index 0000000..f4b4ff1 --- /dev/null +++ b/claude-agent-sdk-dynamic-skills-on-agentcore/agent/.claude/skills/data-fetcher/SKILL.md @@ -0,0 +1,24 @@ +--- +description: "Data Fetcher Skill" +tags: ["s3-loaded", "dynamic"] +--- + +# Data Fetcher Skill + +## Description +Fetches data from various sources for analysis. + +## Parameters +- `source`: Data source (api, database, file) +- `query`: Query parameters + +## Dependencies +None + +## Usage +```python +result = fetch_data(source="api", query="sales_data") +``` + +## Output +Returns structured data ready for processing. diff --git a/claude-agent-sdk-dynamic-skills-on-agentcore/agent/.claude/skills/data-fetcher/implementation.py b/claude-agent-sdk-dynamic-skills-on-agentcore/agent/.claude/skills/data-fetcher/implementation.py new file mode 100644 index 0000000..a7d11b5 --- /dev/null +++ b/claude-agent-sdk-dynamic-skills-on-agentcore/agent/.claude/skills/data-fetcher/implementation.py @@ -0,0 +1,12 @@ +def fetch_data(source, query): + """Fetch data from specified source""" + if source == "api": + return {"data": f"API data for {query}", "status": "success"} + elif source == "database": + return {"data": f"DB data for {query}", "status": "success"} + else: + return {"data": f"File data for {query}", "status": "success"} + +def execute(params): + """Main execution function called by Claude SDK""" + return fetch_data(params.get("source", "api"), params.get("query", "default")) diff --git a/claude-agent-sdk-dynamic-skills-on-agentcore/agent/.claude/skills/document-generator/SKILL.md b/claude-agent-sdk-dynamic-skills-on-agentcore/agent/.claude/skills/document-generator/SKILL.md new file mode 100644 index 0000000..36a246d --- /dev/null +++ b/claude-agent-sdk-dynamic-skills-on-agentcore/agent/.claude/skills/document-generator/SKILL.md @@ -0,0 +1,28 @@ +--- +description: "Document Generator Skill" +tags: ["s3-loaded", "dynamic"] +--- + +# Document Generator Skill + +## Description +Generates professional documents, reports, and documentation in various formats including technical specifications, user guides, and business reports. + +## Capabilities +- Technical documentation generation +- API documentation creation +- User manual generation +- Business report creation +- Markdown, PDF, and HTML output +- Template-based document creation + +## Usage +This skill is triggered when users need to create structured documents or reports. + +## Examples +- "Generate API documentation for this service" +- "Create a user guide for this application" +- "Generate a technical specification document" + +## Implementation +Uses document templates and structured data to generate professional, well-formatted documents. diff --git a/claude-agent-sdk-dynamic-skills-on-agentcore/agent/.claude/skills/document-generator/implementation.py b/claude-agent-sdk-dynamic-skills-on-agentcore/agent/.claude/skills/document-generator/implementation.py new file mode 100644 index 0000000..9645a6d --- /dev/null +++ b/claude-agent-sdk-dynamic-skills-on-agentcore/agent/.claude/skills/document-generator/implementation.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +"""Document Generator Skill Implementation""" + +from datetime import datetime +from typing import Dict, Any + +class DocumentGeneratorSkill: + def __init__(self): + self.name = "document-generator" + self.description = "Professional document and report generation" + + def execute(self, params: Dict[str, Any]) -> str: + """Execute document generation skill""" + message = params.get('message', 'generate document') + + # Simulate document generation process + generation_steps = [ + "📝 Analyzing document requirements...", + "🎨 Selecting appropriate template...", + "📊 Gathering relevant data...", + "🔧 Formatting content structure...", + "📄 Generating final document..." + ] + + # Simulate document creation + doc_info = { + "document_type": "Technical Specification", + "format": "Markdown", + "sections": [ + "Executive Summary", + "Technical Requirements", + "Architecture Overview", + "Implementation Details", + "Testing Strategy", + "Deployment Guide" + ], + "pages_generated": 12, + "creation_time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), + "quality_score": 9.2 + } + + result = "\n".join(generation_steps) + result += f"\n\n📄 Document Generated:\n" + result += f"Type: {doc_info['document_type']}\n" + result += f"Format: {doc_info['format']}\n" + result += f"Pages: {doc_info['pages_generated']}\n" + result += f"Quality Score: {doc_info['quality_score']}/10\n" + result += f"Created: {doc_info['creation_time']}\n\n" + result += f"📋 Sections Included:\n" + for section in doc_info['sections']: + result += f" • {section}\n" + + result += f"\n✅ Document ready for review and distribution" + + return result + +def execute(params: Dict[str, Any]) -> str: + """Skill entry point""" + skill = DocumentGeneratorSkill() + return skill.execute(params) diff --git a/claude-agent-sdk-dynamic-skills-on-agentcore/agent/.claude/skills/report-generator/SKILL.md b/claude-agent-sdk-dynamic-skills-on-agentcore/agent/.claude/skills/report-generator/SKILL.md new file mode 100644 index 0000000..dabfcf5 --- /dev/null +++ b/claude-agent-sdk-dynamic-skills-on-agentcore/agent/.claude/skills/report-generator/SKILL.md @@ -0,0 +1,25 @@ +--- +description: "Report Generator Skill" +tags: ["s3-loaded", "dynamic"] +--- + +# Report Generator Skill + +## Description +Generates reports from processed data. + +## Parameters +- `data`: Input data to process +- `format`: Output format (pdf, html, json) +- `template`: Report template to use + +## Dependencies +None + +## Usage +```python +report = generate_report(data=input_data, format="html", template="standard") +``` + +## Output +Returns formatted report in specified format. diff --git a/claude-agent-sdk-dynamic-skills-on-agentcore/agent/.claude/skills/report-generator/implementation.py b/claude-agent-sdk-dynamic-skills-on-agentcore/agent/.claude/skills/report-generator/implementation.py new file mode 100644 index 0000000..a93df11 --- /dev/null +++ b/claude-agent-sdk-dynamic-skills-on-agentcore/agent/.claude/skills/report-generator/implementation.py @@ -0,0 +1,18 @@ +def generate_report(data, format="html", template="standard"): + """Generate report from data""" + report_content = f"Report using {template} template:\n{data}" + + if format == "html": + return f"
{report_content}
" + elif format == "pdf": + return f"PDF: {report_content}" + else: + return {"report": report_content, "format": format} + +def execute(params): + """Main execution function called by Claude SDK""" + return generate_report( + params.get("data", {}), + params.get("format", "html"), + params.get("template", "standard") + ) diff --git a/claude-agent-sdk-dynamic-skills-on-agentcore/agent/.claude/skills/web-research/SKILL.md b/claude-agent-sdk-dynamic-skills-on-agentcore/agent/.claude/skills/web-research/SKILL.md new file mode 100644 index 0000000..3164e7d --- /dev/null +++ b/claude-agent-sdk-dynamic-skills-on-agentcore/agent/.claude/skills/web-research/SKILL.md @@ -0,0 +1,27 @@ +--- +description: "Web Research Skill" +tags: ["s3-loaded", "dynamic"] +--- + +# Web Research Skill + +## Description +Performs comprehensive web research on any topic using search engines and analyzes multiple sources to provide detailed, well-sourced information. + +## Capabilities +- Search the web for current information +- Analyze multiple sources for accuracy +- Synthesize findings into comprehensive reports +- Provide source citations and links +- Handle complex research queries + +## Usage +This skill is triggered when users ask for research, information gathering, or current data on any topic. + +## Examples +- "Research the latest developments in AI safety" +- "Find information about renewable energy trends in 2024" +- "Research best practices for cloud security" + +## Implementation +Uses web search APIs and content analysis to gather and synthesize information from multiple reliable sources. diff --git a/claude-agent-sdk-dynamic-skills-on-agentcore/agent/.claude/skills/web-research/implementation.py b/claude-agent-sdk-dynamic-skills-on-agentcore/agent/.claude/skills/web-research/implementation.py new file mode 100644 index 0000000..2e87cf0 --- /dev/null +++ b/claude-agent-sdk-dynamic-skills-on-agentcore/agent/.claude/skills/web-research/implementation.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python3 +"""Web Research Skill Implementation""" + +import requests +import json +from typing import Dict, Any + +class WebResearchSkill: + def __init__(self): + self.name = "web-research" + self.description = "Comprehensive web research and analysis" + + def execute(self, params: Dict[str, Any]) -> str: + """Execute web research skill""" + query = params.get('message', 'general research') + + # Simulate web research process + research_steps = [ + f"🔍 Searching for: {query}", + "📊 Analyzing multiple sources...", + "📝 Synthesizing findings...", + "🔗 Gathering citations..." + ] + + # Simulate research results + findings = { + "query": query, + "sources_analyzed": 15, + "key_findings": [ + "Current market trends show significant growth", + "Expert consensus indicates positive outlook", + "Recent studies support main hypothesis" + ], + "confidence_level": "High", + "last_updated": "2024-12-15" + } + + result = "\n".join(research_steps) + result += f"\n\n📋 Research Summary:\n" + result += f"Query: {findings['query']}\n" + result += f"Sources: {findings['sources_analyzed']}\n" + result += f"Confidence: {findings['confidence_level']}\n" + result += f"Key Findings:\n" + for finding in findings['key_findings']: + result += f" • {finding}\n" + + return result + +def execute(params: Dict[str, Any]) -> str: + """Skill entry point""" + skill = WebResearchSkill() + return skill.execute(params) diff --git a/claude-agent-sdk-dynamic-skills-on-agentcore/agent/Dockerfile b/claude-agent-sdk-dynamic-skills-on-agentcore/agent/Dockerfile new file mode 100644 index 0000000..2dea37e --- /dev/null +++ b/claude-agent-sdk-dynamic-skills-on-agentcore/agent/Dockerfile @@ -0,0 +1,62 @@ +# Claude Agent SDK with Dynamic Skills on AgentCore Runtime +# Production-ready Docker build with minimal base image +# +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: MIT-0 + +FROM python:3.12-slim + +# Install system dependencies and Node.js for Claude Code CLI +RUN apt-get update && apt-get install -y \ + curl \ + git \ + && curl -fsSL https://deb.nodesource.com/setup_18.x | bash - \ + && apt-get install -y nodejs \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +# Install Claude Code CLI globally +RUN npm install -g @anthropic-ai/claude-code + +# Set working directory +WORKDIR /app + +# Copy requirements and install Python dependencies +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +# Set environment variables (can be overridden by AgentCore) +ENV CLAUDE_CODE_USE_BEDROCK=1 +ENV AWS_REGION=us-east-1 +ENV ANTHROPIC_MODEL=us.anthropic.claude-sonnet-4-20250514-v1:0 +# SKILLS_S3_BUCKET will be set by AgentCore environment configuration + +# Copy application files +COPY s3_skill_loader.py . +COPY startup.sh . +COPY agent.py . + +# Create .claude/skills directory for skill discovery +RUN mkdir -p .claude/skills + +# Make startup script executable +RUN chmod +x /app/startup.sh + +# Install AWS CLI for container debugging (optional) +RUN pip install awscli --no-cache-dir + +# Create non-root user for security +RUN useradd -m -u 1000 claude && \ + chown -R claude:claude /app +USER claude + +# Health check +HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \ + CMD python -c "import sys; sys.exit(0)" + +# Expose port for AgentCore Runtime +EXPOSE 8080 + +# Use startup script as entrypoint +# This will load skills from S3 then start the agent +ENTRYPOINT ["/app/startup.sh", "agent.py"] \ No newline at end of file diff --git a/claude-agent-sdk-dynamic-skills-on-agentcore/agent/agent.py b/claude-agent-sdk-dynamic-skills-on-agentcore/agent/agent.py new file mode 100644 index 0000000..c0438b9 --- /dev/null +++ b/claude-agent-sdk-dynamic-skills-on-agentcore/agent/agent.py @@ -0,0 +1,137 @@ +#!/usr/bin/env python3 +""" +Claude Agent with Dynamic Skills - Advanced Implementation + +This demonstrates the advanced approach using Claude Agent SDK with native skill discovery. +Skills are loaded from S3 into the .claude/skills/ directory for automatic discovery. + +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +SPDX-License-Identifier: MIT-0 +""" + +import os +import asyncio +from typing import Dict, Any +from bedrock_agentcore.runtime import BedrockAgentCoreApp +from claude_agent_sdk import query, ClaudeAgentOptions + +# Configuration from environment variables +AWS_REGION = os.getenv('AWS_REGION', 'us-east-1') +MODEL_ID = os.getenv('ANTHROPIC_MODEL', 'us.anthropic.claude-sonnet-4-20250514-v1:0') +SKILLS_S3_BUCKET = os.getenv('SKILLS_S3_BUCKET') + +# Initialize AgentCore app +app = BedrockAgentCoreApp() + +@app.entrypoint +def handler(event, context): + """ + AgentCore entrypoint with native Claude Agent SDK integration. + + Skills are loaded from S3 into .claude/skills/ directory during container startup, + then automatically discovered by Claude Agent SDK. + + Args: + event: Request event from AgentCore + context: Lambda-style context (not used) + + Returns: + dict: Response with agent output and metadata + """ + prompt = event.get('prompt', 'Hello') if event else 'Hello' + + # Run Claude Agent SDK with S3-loaded skills + result = asyncio.run(run_claude_with_dynamic_skills(prompt)) + + return { + "response": result.get("response", ""), + "status": result.get("status", "unknown"), + "implementation": "advanced_native_sdk", + "s3_skills_loaded": True, + "skills_source": "s3_to_filesystem_to_claude_discovery", + "s3_bucket": SKILLS_S3_BUCKET + } + +async def run_claude_with_dynamic_skills(prompt: str) -> Dict[str, Any]: + """ + Run Claude Agent SDK with skills loaded from S3. + + The startup.sh script has already loaded skills from S3 into .claude/skills/ + directory. Claude Agent SDK will automatically discover and use these skills. + + Args: + prompt: User input prompt + + Returns: + dict: Response data with status and content + """ + try: + # Configure Claude Agent SDK for skill discovery + options = ClaudeAgentOptions( + cwd=os.getcwd(), # Current directory contains .claude/skills/ + setting_sources=["project"], # Enable .claude/skills/ directory discovery + allowed_tools=[ + "Skill", # Enable Claude's native skill system + "Read", # File reading capabilities + "Write", # File writing capabilities + "Bash", # Shell command execution + "Grep", # Text search + "Glob" # File pattern matching + ], + model=MODEL_ID + ) + + # Collect response parts from streaming API + response_parts = [] + + print(f"🤖 Querying Claude with prompt: {prompt[:50]}...") + + # Stream response from Claude Agent SDK + async for message in query(prompt=prompt, options=options): + # Extract text content from various message types + if hasattr(message, 'content') and message.content: + if isinstance(message.content, list): + # Handle list of content blocks + for block in message.content: + if hasattr(block, 'text') and block.text.strip(): + response_parts.append(block.text.strip()) + elif hasattr(message.content, 'text') and message.content.text.strip(): + # Handle single text content + response_parts.append(message.content.text.strip()) + elif hasattr(message, 'result') and str(message.result).strip(): + # Handle result objects + response_parts.append(str(message.result).strip()) + + # Remove duplicates while preserving order + seen = set() + unique_parts = [] + for part in response_parts: + if part not in seen: + seen.add(part) + unique_parts.append(part) + + # Join response parts + response_text = "\n\n".join(unique_parts) if unique_parts else "No response generated" + + print(f"✅ Generated response: {response_text[:100]}...") + + return { + "response": response_text, + "status": "success", + "parts_processed": len(response_parts), + "unique_parts": len(unique_parts) + } + + except Exception as e: + error_message = f"Claude Agent SDK error: {str(e)}" + print(f"❌ {error_message}") + + return { + "response": error_message, + "status": "error", + "parts_processed": 0 + } + +if __name__ == "__main__": + # For local testing + app.run() \ No newline at end of file diff --git a/claude-agent-sdk-dynamic-skills-on-agentcore/agent/requirements.txt b/claude-agent-sdk-dynamic-skills-on-agentcore/agent/requirements.txt new file mode 100644 index 0000000..237e9bc --- /dev/null +++ b/claude-agent-sdk-dynamic-skills-on-agentcore/agent/requirements.txt @@ -0,0 +1,26 @@ +# Claude Agent SDK with Dynamic Skills - Python Dependencies +# +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: MIT-0 + +# Core AgentCore Runtime SDK +bedrock-agentcore>=0.1.0 + +# Claude SDK with Bedrock support +anthropic[bedrock]>=0.40.0 + +# AWS SDK for S3 access +boto3>=1.26.0 + +# YAML parsing for skill metadata +pyyaml>=6.0 + +# Claude Agent SDK (if available) +# Note: This may require specific installation instructions +# claude-agent-sdk + +# Optional: Enhanced error handling and logging +structlog>=23.1.0 + +# Optional: Type hints support +typing-extensions>=4.0.0 \ No newline at end of file diff --git a/claude-agent-sdk-dynamic-skills-on-agentcore/agent/s3_skill_loader.py b/claude-agent-sdk-dynamic-skills-on-agentcore/agent/s3_skill_loader.py new file mode 100644 index 0000000..b6eeeed --- /dev/null +++ b/claude-agent-sdk-dynamic-skills-on-agentcore/agent/s3_skill_loader.py @@ -0,0 +1,248 @@ +#!/usr/bin/env python3 +""" +S3 Skill Loader for Claude Agent SDK + +Downloads skills from S3 and creates Claude-compatible skill directory structure. +This enables dynamic skill loading for AgentCore deployments. + +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +SPDX-License-Identifier: MIT-0 +""" + +import os +import boto3 +import yaml +from typing import Dict, Any, Optional + +class ClaudeSkillLoader: + """ + Loads skills from S3 and creates Claude-compatible skill structure. + + Skills are downloaded from S3 into the local .claude/skills/ directory + where they can be automatically discovered by Claude Agent SDK. + """ + + def __init__(self, skills_bucket: Optional[str] = None, skills_dir: str = '.claude/skills'): + """ + Initialize the skill loader. + + Args: + skills_bucket: S3 bucket name (defaults to SKILLS_S3_BUCKET env var) + skills_dir: Local directory for skills (default: .claude/skills) + """ + self.s3_client = boto3.client('s3', region_name=os.getenv('AWS_REGION', 'us-east-1')) + self.skills_bucket = skills_bucket or os.getenv('SKILLS_S3_BUCKET') + self.skills_dir = skills_dir + + if not self.skills_bucket: + raise ValueError("Skills bucket must be provided or set in SKILLS_S3_BUCKET environment variable") + + def load_skills_from_s3(self) -> int: + """ + Load skills from S3 and create local .claude/skills/ structure. + + Returns: + int: Number of skills successfully loaded + """ + print(f"🔄 Loading skills from S3 bucket: {self.skills_bucket}") + + # Ensure skills directory exists + os.makedirs(self.skills_dir, exist_ok=True) + + try: + # List all skill directories in S3 + response = self.s3_client.list_objects_v2( + Bucket=self.skills_bucket, + Prefix='skills/', + Delimiter='/' + ) + + skills_loaded = 0 + + for prefix in response.get('CommonPrefixes', []): + skill_name = prefix['Prefix'].replace('skills/', '').rstrip('/') + if skill_name: + if self._download_skill(skill_name): + skills_loaded += 1 + + print(f"✅ Successfully loaded {skills_loaded} skills into {self.skills_dir}/") + + # List downloaded skills for verification + if skills_loaded > 0: + self._list_downloaded_skills() + + return skills_loaded + + except Exception as e: + print(f"❌ Error loading skills from S3: {e}") + print("🤖 Agent will continue without S3 skills") + return 0 + + def _download_skill(self, skill_name: str) -> bool: + """ + Download individual skill from S3 to local filesystem. + + Args: + skill_name: Name of the skill to download + + Returns: + bool: True if skill was successfully downloaded + """ + skill_dir = os.path.join(self.skills_dir, skill_name) + os.makedirs(skill_dir, exist_ok=True) + + try: + # Download main skill file (SKILL.md) + skill_key = f'skills/{skill_name}/SKILL.md' + local_skill_path = os.path.join(skill_dir, 'SKILL.md') + + self.s3_client.download_file( + self.skills_bucket, + skill_key, + local_skill_path + ) + + # Ensure proper YAML frontmatter format + self._ensure_skill_format(local_skill_path) + + # Download implementation file if it exists (implementation.py) + try: + impl_key = f'skills/{skill_name}/implementation.py' + local_impl_path = os.path.join(skill_dir, 'implementation.py') + + self.s3_client.download_file( + self.skills_bucket, + impl_key, + local_impl_path + ) + print(f" 📥 Downloaded skill: {skill_name} (with implementation)") + + except Exception: + # Implementation file is optional + print(f" 📥 Downloaded skill: {skill_name} (description only)") + + return True + + except Exception as e: + print(f" ❌ Failed to download skill {skill_name}: {e}") + return False + + def _ensure_skill_format(self, skill_path: str) -> None: + """ + Ensure skill file has proper YAML frontmatter format for Claude SDK. + + Claude Agent SDK expects skills to have YAML frontmatter with metadata. + + Args: + skill_path: Path to the skill file to validate/fix + """ + with open(skill_path, 'r', encoding='utf-8') as f: + content = f.read() + + # Check if YAML frontmatter already exists + if content.startswith('---'): + return # Already has proper format + + # Extract description from content + description = self._extract_skill_description(content) + + # Add YAML frontmatter + yaml_header = f"""--- +description: "{description}" +tags: ["s3-loaded", "dynamic"] +--- + +""" + content = yaml_header + content + + # Write back to file + with open(skill_path, 'w', encoding='utf-8') as f: + f.write(content) + + def _extract_skill_description(self, content: str) -> str: + """ + Extract a meaningful description from skill content. + + Args: + content: Raw skill content + + Returns: + str: Extracted or default description + """ + lines = content.split('\n') + + # Look for various description patterns + for i, line in enumerate(lines): + line = line.strip() + + # Check for markdown heading + if line.startswith('# '): + return line.replace('# ', '').strip() + + # Check for description section + if line.lower().startswith('## description'): + if i + 1 < len(lines) and lines[i + 1].strip(): + return lines[i + 1].strip() + + # Check for first non-empty line as description + if line and not line.startswith('#'): + # Truncate if too long + if len(line) > 100: + return line[:97] + "..." + return line + + return "Dynamically loaded skill from S3" + + def _list_downloaded_skills(self) -> None: + """List all downloaded skills for verification.""" + print(f"\n📋 Skills available in {self.skills_dir}:") + + try: + for skill_name in os.listdir(self.skills_dir): + skill_path = os.path.join(self.skills_dir, skill_name) + + if os.path.isdir(skill_path): + skill_file = os.path.join(skill_path, 'SKILL.md') + impl_file = os.path.join(skill_path, 'implementation.py') + + has_impl = os.path.exists(impl_file) + impl_indicator = " + implementation" if has_impl else "" + + if os.path.exists(skill_file): + # Extract first line for preview + with open(skill_file, 'r', encoding='utf-8') as f: + first_line = f.readline().strip() + if first_line.startswith('---'): + # Skip YAML frontmatter + for line in f: + if line.strip() and not line.startswith('---'): + first_line = line.strip() + break + + print(f" ✓ {skill_name}{impl_indicator}") + print(f" {first_line[:60]}...") + else: + print(f" ❌ {skill_name} (missing SKILL.md)") + + except Exception as e: + print(f" ❌ Error listing skills: {e}") + +def main(): + """Main entry point for skill loading.""" + try: + loader = ClaudeSkillLoader() + skills_count = loader.load_skills_from_s3() + + if skills_count > 0: + print(f"\n🎯 Skills ready for Claude Agent SDK discovery in {loader.skills_dir}/") + else: + print("\n⚠️ No skills loaded - Claude will run without specialized capabilities") + + return skills_count + + except Exception as e: + print(f"❌ Skill loader initialization failed: {e}") + return 0 + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/claude-agent-sdk-dynamic-skills-on-agentcore/agent/startup.sh b/claude-agent-sdk-dynamic-skills-on-agentcore/agent/startup.sh new file mode 100644 index 0000000..5abf20b --- /dev/null +++ b/claude-agent-sdk-dynamic-skills-on-agentcore/agent/startup.sh @@ -0,0 +1,95 @@ +#!/bin/bash +# +# Container Startup Script for Claude Agent with Dynamic Skills +# +# This script orchestrates the container startup process: +# 1. Load skills from S3 into .claude/skills/ directory +# 2. Verify skills are properly formatted +# 3. Start the Claude agent with loaded skills +# +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: MIT-0 +# + +set -e # Exit on any error + +echo "🚀 Starting Claude Agent with Dynamic S3 Skill Loading..." +echo "📍 Working directory: $(pwd)" +echo "🌍 Environment:" +echo " - AWS_REGION: ${AWS_REGION:-us-east-1}" +echo " - SKILLS_S3_BUCKET: ${SKILLS_S3_BUCKET:-not set}" +echo " - ANTHROPIC_MODEL: ${ANTHROPIC_MODEL:-default}" +echo "" + +# Step 1: Load skills from S3 into .claude/skills/ directory +echo "📦 Step 1: Loading skills from S3..." +python /app/s3_skill_loader.py +skill_load_status=$? + +if [ $skill_load_status -eq 0 ]; then + echo "✅ Skill loading completed successfully" +else + echo "⚠️ Skill loading completed with warnings (exit code: $skill_load_status)" +fi +echo "" + +# Step 2: Verify and list loaded skills +echo "📋 Step 2: Verifying loaded skills..." + +if [ -d ".claude/skills" ]; then + skill_count=$(find .claude/skills -maxdepth 1 -type d | tail -n +2 | wc -l) + echo "📁 Skills directory exists with $skill_count skill directories:" + + # List skill directories with details + for skill_dir in .claude/skills/*/; do + if [ -d "$skill_dir" ]; then + skill_name=$(basename "$skill_dir") + echo " 📂 $skill_name" + + # Check for SKILL.md file + if [ -f "$skill_dir/SKILL.md" ]; then + # Show first meaningful line (skip YAML frontmatter) + first_line=$(grep -v '^---' "$skill_dir/SKILL.md" | grep -v '^$' | head -n 1 | cut -c1-50) + echo " 📄 SKILL.md: ${first_line}..." + else + echo " ❌ Missing SKILL.md file" + fi + + # Check for implementation file + if [ -f "$skill_dir/implementation.py" ]; then + echo " 🐍 Has implementation.py" + fi + fi + done + + if [ $skill_count -eq 0 ]; then + echo "⚠️ No skill directories found in .claude/skills/" + fi +else + echo "❌ .claude/skills directory not found - creating empty directory" + mkdir -p .claude/skills +fi +echo "" + +# Step 3: Display environment info +echo "🔧 Step 3: Environment verification..." +echo "Python version: $(python --version)" +echo "Available packages:" +python -c "import anthropic, boto3, bedrock_agentcore; print(' ✓ All required packages available')" 2>/dev/null || echo " ❌ Missing required packages" + +# Check AWS credentials +if aws sts get-caller-identity > /dev/null 2>&1; then + echo " ✓ AWS credentials configured" +else + echo " ❌ AWS credentials not available" +fi +echo "" + +# Step 4: Start the Claude agent +echo "🤖 Step 4: Starting Claude Agent..." +echo "Entry point: $*" +echo "Starting agent at $(date)" +echo "" + +# Execute the provided command (agent.py) +exec python "$@" \ No newline at end of file diff --git a/claude-agent-sdk-dynamic-skills-on-agentcore/claude_sdk_bedrock.py b/claude-agent-sdk-dynamic-skills-on-agentcore/claude_sdk_bedrock.py new file mode 100644 index 0000000..9720690 --- /dev/null +++ b/claude-agent-sdk-dynamic-skills-on-agentcore/claude_sdk_bedrock.py @@ -0,0 +1,133 @@ +#!/usr/bin/env python3 +""" +Claude Agent with Dynamic Skills - Simple Implementation + +This demonstrates the simplest approach to dynamic skill loading with Claude SDK. +Skills are loaded from S3 at startup and injected into the system prompt. + +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +SPDX-License-Identifier: MIT-0 +""" + +import os +import boto3 +import anthropic +from bedrock_agentcore.runtime import BedrockAgentCoreApp + +# Configuration from environment variables +AWS_REGION = os.getenv('AWS_REGION', 'us-east-1') +SKILLS_S3_BUCKET = os.getenv('SKILLS_S3_BUCKET') # Set during setup process +MODEL_ID = os.getenv('ANTHROPIC_MODEL', 'us.anthropic.claude-sonnet-4-20250514-v1:0') + +# Initialize AWS and Claude clients +s3_client = boto3.client('s3', region_name=AWS_REGION) +claude_client = anthropic.AnthropicBedrock(aws_region=AWS_REGION) + +# Global skills registry +SKILLS = {} + +def load_skills(): + """Load skill definitions from S3 at startup.""" + global SKILLS + + if not SKILLS_S3_BUCKET: + print("⚠️ SKILLS_S3_BUCKET environment variable not set") + return + + try: + print(f"🔄 Loading skills from S3 bucket: {SKILLS_S3_BUCKET}") + + response = s3_client.list_objects_v2( + Bucket=SKILLS_S3_BUCKET, + Prefix='skills/', + Delimiter='/' + ) + + for prefix in response.get('CommonPrefixes', []): + skill_name = prefix['Prefix'].replace('skills/', '').rstrip('/') + if skill_name: + try: + # Download skill description + skill_obj = s3_client.get_object( + Bucket=SKILLS_S3_BUCKET, + Key=f'skills/{skill_name}/SKILL.md' + ) + skill_content = skill_obj['Body'].read().decode('utf-8') + + # Extract first 200 characters for system prompt + SKILLS[skill_name] = skill_content[:200] + print(f" ✅ Loaded skill: {skill_name}") + + except Exception as e: + print(f" ❌ Failed to load skill {skill_name}: {e}") + SKILLS[skill_name] = f"Skill: {skill_name} (description unavailable)" + + print(f"🎯 Successfully loaded {len(SKILLS)} skills from S3") + + except Exception as e: + print(f"❌ Error loading skills from S3: {e}") + print("🤖 Agent will run without skills") + +# Load skills at module import time +load_skills() + +# Initialize AgentCore app +app = BedrockAgentCoreApp() + +@app.entrypoint +def handler(event, context): + """ + AgentCore entrypoint handler using Claude SDK with S3-loaded skills. + + Args: + event: Request event from AgentCore + context: Lambda-style context (not used) + + Returns: + dict: Response with agent output and metadata + """ + prompt = event.get('prompt', 'Hello') if event else 'Hello' + + # Build system prompt with available skills + if SKILLS: + skills_info = "\n".join([ + f"- {name}: {desc.strip()}" + for name, desc in SKILLS.items() + ]) + system_prompt = f"""You are a helpful AI assistant with specialized skills loaded dynamically from S3. + +Available skills: +{skills_info} + +When users ask about your capabilities or request specific tasks, reference these skills appropriately. +You can perform analysis, research, data processing, and document generation based on the loaded skills.""" + else: + system_prompt = "You are a helpful AI assistant. No specialized skills are currently loaded." + + try: + # Call Claude via Bedrock + response = claude_client.messages.create( + model=MODEL_ID, + max_tokens=1024, + system=system_prompt, + messages=[{"role": "user", "content": prompt}] + ) + + return { + "response": response.content[0].text, + "skills_loaded": len(SKILLS), + "skills_available": list(SKILLS.keys()), + "implementation": "simple_s3_injection", + "s3_bucket": SKILLS_S3_BUCKET + } + + except Exception as e: + return { + "error": f"Claude API error: {str(e)}", + "skills_loaded": len(SKILLS), + "implementation": "simple_s3_injection" + } + +if __name__ == "__main__": + # For local testing + app.run() \ No newline at end of file diff --git a/claude-agent-sdk-dynamic-skills-on-agentcore/requirements.txt b/claude-agent-sdk-dynamic-skills-on-agentcore/requirements.txt new file mode 100644 index 0000000..bb48abf --- /dev/null +++ b/claude-agent-sdk-dynamic-skills-on-agentcore/requirements.txt @@ -0,0 +1,16 @@ +# Claude Agent SDK with Dynamic Skills - Python Dependencies +# +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: MIT-0 + +# Core AgentCore Runtime SDK +bedrock-agentcore>=0.1.0 + +# Claude SDK with Bedrock support +anthropic[bedrock]>=0.40.0 + +# AWS SDK for S3 access +boto3>=1.26.0 + +# YAML parsing for skill metadata +pyyaml>=6.0 \ No newline at end of file diff --git a/claude-agent-sdk-dynamic-skills-on-agentcore/s3-skills-policy.json b/claude-agent-sdk-dynamic-skills-on-agentcore/s3-skills-policy.json new file mode 100644 index 0000000..bd77b89 --- /dev/null +++ b/claude-agent-sdk-dynamic-skills-on-agentcore/s3-skills-policy.json @@ -0,0 +1,16 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "s3:GetObject", + "s3:ListBucket" + ], + "Resource": [ + "arn:aws:s3:::$BUCKET_NAME", + "arn:aws:s3:::$BUCKET_NAME/*" + ] + } + ] +}