Bedrock AgentCore
What is AgentCore?
Section titled “What is AgentCore?”Bedrock AgentCore is AWS’s serverless infrastructure for agents — managed runtime with VM-level isolation, code interpreter, cloud browser, identity management, memory, and observability. It is framework-agnostic — it hosts agents built with any framework.
agentfootprint is the framework. AgentCore is the infrastructure. Together: explainable agents on serverless AWS.
┌─────────────────────────────────────────┐│ Bedrock AgentCore ││ (runtime, memory, observability, ││ code interpreter, browser, identity) ││ ││ ┌───────────────────────────────────┐ ││ │ agentfootprint │ ││ │ (6 patterns, narrative, │ ││ │ grounding, recorders) │ ││ │ │ ││ │ ┌─────────────────────────────┐ │ ││ │ │ Claude on Bedrock │ │ ││ │ │ (LLM provider) │ │ ││ │ └─────────────────────────────┘ │ ││ └───────────────────────────────────┘ │└─────────────────────────────────────────┘Install
Section titled “Install”npm install agentfootprint bedrock-agentcore @aws-sdk/client-bedrock-runtime zodDeploy on AgentCore Runtime
Section titled “Deploy on AgentCore Runtime”AgentCore Runtime hosts your agent as a serverless endpoint. The handler uses an async generator — responses are streamed to clients via SSE.
import { BedrockAgentCoreApp } from 'bedrock-agentcore/runtime';import { z } from 'zod';import { Agent, bedrock, defineTool } from 'agentfootprint';import { agentObservability } from 'agentfootprint/observe';
// 1. Build your agentconst provider = bedrock('anthropic.claude-sonnet-4-20250514-v1:0');
const lookupOrder = defineTool({ id: 'lookup_order', description: 'Look up an order by ID', inputSchema: { type: 'object', properties: { orderId: { type: 'string' } }, required: ['orderId'], }, handler: async ({ orderId }) => ({ content: JSON.stringify({ orderId, status: 'shipped', amount: 299 }), }),});
const obs = agentObservability();
const agent = Agent.create({ provider }) .system('You are a customer support agent for TechStore.') .tool(lookupOrder) .recorder(obs) .build();
// 2. Create AgentCore app with invocationHandlerconst app = new BedrockAgentCoreApp({ invocationHandler: { requestSchema: z.object({ prompt: z.string(), sessionId: z.string().optional(), }), process: async function* (request, context) { // context.sessionId available for session management const result = await agent.run(request.prompt);
// Stream the response (AgentCore uses SSE) yield { event: 'message', data: { text: result.content }, };
// Optionally yield metadata yield { event: 'metadata', data: { narrative: agent.getNarrative(), tokens: obs.tokens(), cost: obs.cost(), }, }; }, },});
// 3. Start the server (HTTP on port 8080, serves /invocations and /ping)app.run();The async generator pattern (async function* with yield) is fundamental to AgentCore — it enables incremental SSE streaming over /invocations and WebSocket via /ws. The /ping endpoint is served automatically for health checks.
AgentCore Code Interpreter
Section titled “AgentCore Code Interpreter”Use AgentCore’s sandboxed code execution as a tool. Sessions must be created and closed:
import { CodeInterpreterTools } from 'bedrock-agentcore/experimental/code-interpreter/strands';import { defineTool } from 'agentfootprint';
// Note: import path includes /experimental/ — API may change.// Check bedrock-agentcore docs for current paths.const codeInterpreter = new CodeInterpreterTools({ region: 'us-east-1' });
const runCode = defineTool({ id: 'run_code', description: 'Execute Python code in a sandboxed environment. Use for calculations, data analysis, plotting.', inputSchema: { type: 'object', properties: { code: { type: 'string', description: 'Python code to execute' } }, required: ['code'], }, handler: async ({ code }) => { // Create a session, execute, close const session = await codeInterpreter.createSession(); try { const result = await session.execute(code); return { content: JSON.stringify(result) }; } finally { await session.close(); } },});
const agent = Agent.create({ provider }) .system('You are a data analyst. Use run_code for calculations.') .tool(runCode) .build();AgentCore Cloud Browser
Section titled “AgentCore Cloud Browser”Use AgentCore’s managed cloud browser for web automation. The browser runs in AWS — you connect via CDP WebSocket:
import { PlaywrightBrowser } from 'bedrock-agentcore/browser/playwright';import { defineTool } from 'agentfootprint';
// Check bedrock-agentcore docs for current import paths and APIconst browser = new PlaywrightBrowser({ region: 'us-east-1' });
const browsePage = defineTool({ id: 'browse_page', description: 'Navigate to a URL and extract page content', inputSchema: { type: 'object', properties: { url: { type: 'string' } }, required: ['url'], }, handler: async ({ url }) => { // Connects to AWS-managed browser via CDP WebSocket const session = await browser.createSession(); try { const page = await session.newPage(); await page.goto(url); const text = await page.textContent('body'); return { content: text ?? 'No content found' }; } finally { await session.close(); } },});AgentCore Identity
Section titled “AgentCore Identity”AgentCore manages credentials so you don’t put API keys in code. withAccessToken and withApiKey are credential wrappers that inject auth into your functions:
import { withAccessToken } from 'bedrock-agentcore/identity';import { defineTool } from 'agentfootprint';
const callInternalApi = defineTool({ id: 'call_internal_api', description: 'Call an internal API with managed credentials', inputSchema: { type: 'object', properties: { endpoint: { type: 'string' } }, required: ['endpoint'], }, handler: withAccessToken('internal-api', async (token, { endpoint }) => { // AgentCore injects the token — no API keys in your code const res = await fetch(endpoint, { headers: { Authorization: `Bearer ${token}` }, }); return { content: await res.text() }; }),});Observability
Section titled “Observability”agentfootprint recorders → CloudWatch
Section titled “agentfootprint recorders → CloudWatch”Export agentfootprint’s structured recorder data to CloudWatch:
import { CloudWatch } from '@aws-sdk/client-cloudwatch';import { agentObservability } from 'agentfootprint/observe';
const cw = new CloudWatch({ region: 'us-east-1' });const obs = agentObservability();
// After each agent run:const tokens = obs.tokens();await cw.putMetricData({ Namespace: 'AgentFootprint', MetricData: [ { MetricName: 'LLMCalls', Value: tokens.totalCalls, Unit: 'Count' }, { MetricName: 'InputTokens', Value: tokens.totalInputTokens, Unit: 'Count' }, { MetricName: 'OutputTokens', Value: tokens.totalOutputTokens, Unit: 'Count' }, { MetricName: 'EstCostUSD', Value: obs.cost(), Unit: 'None' }, ],});agentfootprint events → OpenTelemetry spans
Section titled “agentfootprint events → OpenTelemetry spans”AgentCore provides built-in OpenTelemetry tracing to CloudWatch. You can also export agentfootprint’s lifecycle events as spans:
import { trace } from '@opentelemetry/api';
const tracer = trace.getTracer('agentfootprint');const spanMap = new Map<string, any>();
await agent.run(message, { onEvent: (event) => { if (event.type === 'llm_start') { spanMap.set(`llm-${event.iteration}`, tracer.startSpan(`llm.call.${event.iteration}`)); } if (event.type === 'llm_end') { const span = spanMap.get(`llm-${event.iteration}`); if (span) { span.setAttribute('llm.model', event.model ?? 'unknown'); span.setAttribute('llm.latency_ms', event.latencyMs); span.setAttribute('llm.tool_calls', event.toolCallCount); span.end(); } } if (event.type === 'tool_start') { const span = tracer.startSpan(`tool.${event.toolName}`); span.setAttribute('tool.name', event.toolName); spanMap.set(event.toolCallId, span); } if (event.type === 'tool_end') { const span = spanMap.get(event.toolCallId); if (span) { span.setAttribute('tool.latency_ms', event.latencyMs); span.setAttribute('tool.error', event.error ?? false); span.end(); } } },});AgentCore built-in observability
Section titled “AgentCore built-in observability”AgentCore Runtime automatically sends traces and metrics to CloudWatch when deployed. This works alongside agentfootprint’s recorders — AgentCore captures infrastructure metrics (request latency, error rate, cold starts), while agentfootprint captures agent-level metrics (tokens, tool usage, narrative).
Narrative → CloudWatch Logs
Section titled “Narrative → CloudWatch Logs”The connected narrative is the richest debugging data. Log it alongside AgentCore’s traces:
import { CloudWatchLogs } from '@aws-sdk/client-cloudwatch-logs';
const logs = new CloudWatchLogs({ region: 'us-east-1' });
await logs.putLogEvents({ logGroupName: '/agentfootprint/narrative', logStreamName: `session-${context.sessionId}`, logEvents: agent.getNarrative().map((line, i) => ({ timestamp: Date.now() + i, message: line, })),});Memory
Section titled “Memory”AgentCore Memory (managed)
Section titled “AgentCore Memory (managed)”AgentCore provides a managed memory service with short-term (session) and long-term (cross-session) memory. The TypeScript SDK memory module is in development — when available, it will provide AWS-managed persistence without running your own database.
agentfootprint memory (self-managed)
Section titled “agentfootprint memory (self-managed)”For now, use agentfootprint’s pluggable memory with any backend:
import { InMemoryStore } from 'agentfootprint';
// Development: in-memoryconst agent = Agent.create({ provider }) .memory({ store: new InMemoryStore(), conversationId: context.sessionId, // use AgentCore's session ID }) .build();For production, implement the store interface backed by DynamoDB:
import { DynamoDBClient, GetItemCommand, PutItemCommand } from '@aws-sdk/client-dynamodb';
class DynamoMemoryStore { private client = new DynamoDBClient({ region: 'us-east-1' });
async load(conversationId: string) { const result = await this.client.send(new GetItemCommand({ TableName: 'agent-conversations', Key: { id: { S: conversationId } }, })); return result.Item?.messages ? JSON.parse(result.Item.messages.S!) : []; }
async save(conversationId: string, messages: unknown[]) { await this.client.send(new PutItemCommand({ TableName: 'agent-conversations', Item: { id: { S: conversationId }, messages: { S: JSON.stringify(messages) }, updatedAt: { N: String(Date.now()) }, }, })); }}IAM Permissions
Section titled “IAM Permissions”The AgentCore execution role needs these policies:
{ "Version": "2012-10-17", "Statement": [ { "Sid": "BedrockConverse", "Effect": "Allow", "Action": [ "bedrock:Converse", "bedrock:ConverseStream" ], "Resource": "arn:aws:bedrock:*::foundation-model/anthropic.*" } ]}Add AgentCore permissions as needed for Code Interpreter, Browser, and Identity — check the AgentCore IAM documentation for current action names.
Why agentfootprint on AgentCore?
Section titled “Why agentfootprint on AgentCore?”| AgentCore provides | agentfootprint provides |
|---|---|
| Serverless runtime with VM isolation | 6 composable agent patterns |
| Auto-scaling and deployment | Connected narrative trace |
| Code interpreter (sandboxed Python) | Grounding analysis (hallucination detection) |
| Cloud browser (managed Playwright) | Conditional instructions (Decision Scope) |
| Identity and credential management | mock() for $0 testing |
| Managed memory (short + long term) | Human-in-the-loop (pause/resume) |
| Built-in OpenTelemetry observability | 6 passive recorders + narrative |
| Health checks and session management | Tool gating (defense in depth) |
| Provider failover (Claude → GPT → Ollama) |
AgentCore makes your agent production-ready. agentfootprint makes your agent explainable.