First Agent
A complete step-by-step tutorial from an empty directory to a running agent on AgentCore Runtime.
You write: one YAML blueprint, one prompt builder, and one 5-line handler. The platform generates everything else.
What You Are Building
A general-purpose assistant agent that:
- Runs in an isolated microVM per session on AgentCore Runtime
- Routes tool calls through AgentCore Gateway to your domain MCP server
- Persists conversation history with short-term and long-term Memory
- Validates inbound requests with Cognito JWT
- Streams OTEL traces to CloudWatch automatically
Step 1: Write the Blueprint
The blueprint is the single source of truth for your agent. Every runtime behavior — model selection, tool routing, memory persistence, auth, observability — is declared here.
Create blueprints/agents/assistant.yaml:
id: assistant
name: General Purpose Assistant
version: "1.0.0"
description: "General-purpose assistant with tool access and memory"
# Required: references a versioned prompt in the Prompt Registry.
# The loader resolves this at startup — direct Lambda invoke first,
# then HTTP fallback, then local file (useful in dev).
prompt_ref: assistant-system-v1
# Runtime: hosts this agent in an isolated microVM per session.
# network_mode: PRIVATE means the container runs inside your VPC.
runtime:
type: agentcore
max_iterations: 15
idle_timeout_minutes: 15
network_mode: PRIVATE
protocol: HTTP
# Model: provider-agnostic. Switch provider by changing these two fields.
# model_id, temperature, and max_tokens are required — no platform defaults.
# provider defaults to "bedrock". Use ${VAR:-fallback} for env-template expansion.
model:
provider: bedrock
model_id: ${MODEL_ID}
temperature: 0.3
max_tokens: 4096
# Tools: MCP targets routed through Gateway.
# The platform wires the MCPClient; domain provides the Lambda or container.
tools:
- mcp: assistant-tools-mcp
tools: [search_knowledge_base, create_note, list_notes]
# Gateway inbound auth.
gateway:
auth_type: aws_iam
# Memory: two tiers wired automatically when strategies are declared.
# Short-term: last short_term_k turns injected into system prompt each session.
# Long-term: facts extracted asynchronously, retrieved by semantic similarity.
# There is no `mode` field — strategies activate memory automatically.
# SUMMARIZATION is accepted as an alias for SUMMARY.
memory:
strategies:
- type: USER_PREFERENCE
name: PreferenceLearner
namespace: "user/{actorId}/preferences/"
- type: SEMANTIC
name: FactExtractor
namespace: "user/{actorId}/facts/"
- type: SUMMARY
name: Summarizer
namespace: "user/{actorId}/{sessionId}/summaries/"
event_expiry_days: 30
short_term_k: 5
# Identity: Cognito JWT validates every inbound request before your code runs.
# Outbound credential types are api_key and oauth2 only.
identity:
authorizer:
type: cognito_jwt
user_pool_id: ${COGNITO_POOL_ID}
client_id: ${COGNITO_CLIENT_ID}
credentials:
- name: notes-api-key
type: api_key
provider: notes-apikey-provider
# Observability: all application-level hooks (Langfuse, audit, cost tracking)
# are gated by enabled: true. OTEL auto-instrumentation is separate — it is
# controlled by the Dockerfile generated at deploy time.
# audit_log writes to DynamoDB (not CloudWatch).
observability:
enabled: true
trace_attributes:
environment: development
agent.version: "1.0.0"
audit_log:
enabled: true
ttl_days: 1825 # 5 years
# Evaluation: online evaluation scores a percentage of live sessions.
# provider defaults to "agentcore"; set to "langfuse" for provider-agnostic eval.
evaluation:
provider: agentcore # agentcore | langfuse
online:
sampling_rate: 10
evaluators:
- Builtin.GoalSuccessRate
- Builtin.Correctness
- Builtin.ToolSelectionAccuracy
# Policy: Cedar access control attached to the Gateway.
# mode: LOG_ONLY during development; switch to ENFORCE in production.
policy:
engine: AssistantPolicies
mode: LOG_ONLY
rules:
- name: notes_write_limit
allow: create_note
when: "context.input.content.length <= 10000"
What Each Block Does
| YAML Key | What the Platform Wires Automatically |
|---|---|
runtime: | @app.entrypoint decorator, Dockerfile with OTEL layer, ECR push, Runtime registration |
model: | Strands BedrockModel / LiteLLMModel / AnthropicModel / GeminiModel — dispatched by provider |
tools: + gateway: | MCPClient connected to ${GATEWAY_URL}, MCP target registrations |
memory: | MemoryHookProvider — loads history on agent init, saves turns on each message |
identity: | Runtime JWT validation, credential provider registrations |
observability: | CompositeObservabilityHook (Langfuse, audit log, structured logger, cost tracker) |
evaluation: | Online evaluation config against the agent’s OTEL traces |
policy: | Cedar policies translated and attached to the Gateway |
Step 2: Write the System Prompt
Create prompts/assistant-system-v1.txt:
You are a helpful assistant with access to a knowledge base and note-taking tools.
Use search_knowledge_base to look up information before answering factual questions.
Use create_note and list_notes to help users manage their notes.
Be concise and accurate. When you are unsure, say so.
Push this to the Prompt Registry before deploying:
agentcli prompt push \
--id assistant-system-v1 \
--file prompts/assistant-system-v1.txt \
--env dev
Step 3: Write the Prompt Builder
The prompt builder is the only domain-specific code you write per agent. It constructs the per-invocation prompt from your input parameters.
Create src/myapp/agent_configs.py:
from agent_core import AgentConfig, AgentConfigRegistry
REGISTRY = AgentConfigRegistry()
REGISTRY.register(AgentConfig(
agent_id="assistant",
operation_name="assist",
required_fields=["user_request"],
build_prompt=lambda params, idem_key: (
f"[Task ID: {idem_key}]\n\n"
f"User request: {params['user_request']}\n\n"
"Respond helpfully and concisely. Use tools when needed."
),
))
AgentConfigRegistry is the complete interface between your domain logic and the platform. Everything else — wiring the prompt to the agent, injecting memory context, attaching tools — is handled by BlueprintLoader.
Step 4: Write the Handler
Every agent uses an identical 5-line handler. The blueprint, not the handler, determines all runtime behavior.
Create src/myapp/app.py:
from agent_core import AgentCoreApp, BlueprintLoader, GenericHandler
from myapp.agent_configs import REGISTRY
HANDLER = GenericHandler(
loader=BlueprintLoader(blueprints_dir="blueprints", config_registry=REGISTRY)
)
app = AgentCoreApp(handler=HANDLER)
BlueprintLoader reads blueprints/agents/assistant.yaml, resolves all declared blocks, and builds a Strands Agent with the correct model, Gateway tools, memory hooks, identity decorators, and OTEL attributes. AgentCoreApp exposes POST /invocations and GET /ping on port 8080.
For specialist agents in a multi-agent graph, BlueprintLoader.build_entrypoint() auto-mounts an A2A sidecar server when multi_agent.role: specialist is declared in the blueprint — no additional code needed.
Step 5: Validate the Blueprint
agentcli blueprint lint blueprints/agents/assistant.yaml
Expected output:
Validating blueprints/agents/assistant.yaml ... OK
runtime: agentcore, PRIVATE, HTTP
model: ${MODEL_ID} via bedrock
tools: 1 MCP target(s), 3 tool(s)
memory: 3 strategies, 5 short-term turns
identity: cognito_jwt, 1 credential provider(s)
observability: enabled, audit_log: enabled
evaluation: online @ 10%, 3 evaluator(s)
policy: LOG_ONLY, 1 rule(s)
All blueprints valid.
If validation fails, the CLI reports the field path and the expected format. Fix each reported issue before proceeding.
Step 6: Deploy
With infrastructure in place (see Infrastructure):
# Deploy to dev environment
agentcli deploy agent blueprints/agents/assistant.yaml --env dev
Under the hood, agentcli deploy agent:
- Reads the blueprint and generates a
Dockerfilewith the OTEL instrumentation layer - Builds the Docker image
- Authenticates to ECR and pushes the image
- Calls the AgentCore API to register or update the Runtime
- Reports the Runtime ARN and endpoint URL
Step 7: Invoke Your Agent
Once deployed, invoke via the AgentCore Runtime API:
aws bedrock-agentcore invoke-agent-runtime \
--agent-runtime-arn "${RUNTIME_ARN}" \
--payload '{"operation": "assist", "user_request": "What notes do I have for today?"}' \
--session-id "$(uuidgen)"
What You Did NOT Write
The platform generated all of this from the blueprint:
- The
@app.entrypointdecorator and AgentCore wiring - The
BedrockModel(orLiteLLMModel/AnthropicModel/GeminiModel) instantiation - The
MCPClientconnecting to Gateway at${GATEWAY_URL} - The
MemoryHookProviderthat loads history on agent init and saves turns on each message - The
@requires_access_token/@requires_api_keydecorators for outbound credentials - The Dockerfile with OTEL instrumentation
- The
trace_attributesdict on the StrandsAgent - The evaluation online config against the agent’s OTEL traces
- The Cedar policy attachment to the Gateway
Switching Providers
To switch from Bedrock to LiteLLM, change two lines in the blueprint:
model:
provider: litellm # was: bedrock
model_id: claude-sonnet-4-6 # name the proxy expects
temperature: 0.3
max_tokens: 4096
base_url: ${LITELLM_BASE_URL}
api_key_env: LITELLM_API_KEY
No handler or infrastructure changes required. Redeploy and the new provider is live.
Next Steps
- Agent Blueprint Spec — complete YAML field reference
- Inference Providers — Bedrock, LiteLLM, Anthropic, and Vertex in depth
- Concepts: How It Works — deep dive into the BlueprintLoader pipeline
- Infrastructure — Terraform modules and deployment patterns
- SDK Reference — Runtime —
AgentCoreApp,GenericHandler,BlueprintLoaderAPI