« Phase 20 README · Warmup · Hitchhiker's · Deep Dive · Principal Deep Dive · Staff Notes

Phase 20 — Core Contributor Notes: Amazon Bedrock AgentCore

This is the maintainer's-eye view: how the real systems under the miniature — Firecracker, the bedrock-agentcore SDK and starter toolkit, Cedar, and the Memory/Gateway services — actually implement these primitives, the non-obvious decisions in them, why the API evolved from Bedrock Agents, and what our stdlib versions deliberately fake. Where an exact constant or header name isn't something to assert from memory, this describes the pattern rather than inventing a value.

The Runtime container contract is an HTTP server, not a magic decorator

Our lab makes Runtime call app._entrypoint(payload, ctx) in-process. The real thing is a container contract. When you agentcore configure, the starter toolkit containerizes your app; when you agentcore launch, it pushes the image and stands up the managed Runtime. Inside, BedrockAgentCoreApp is running a small HTTP server (Starlette/uvicorn-class) that exposes the two endpoints the Runtime contract requires: an invocation endpoint (conventionally POST /invocations) and a health endpoint (GET /ping) on a fixed port. The @app.entrypoint decorator does not "become an agent" — it registers the function the invocation route dispatches to, adapting the request body to payload and request metadata (session id, etc.) to context. @app.ping overrides the default health response; the real service distinguishes Healthy from HealthyBusy so it knows whether async work is still pending before it reclaims capacity — which is why our ping() returns "Healthy" and the docstrings mention HealthyBusy.

The session id is the load-bearing input, and in production it arrives as a request header, not as a field the caller puts in the body. The Runtime routes by that id to the session's microVM. Streaming is real SSE: a generator entrypoint's chunks are flushed to the client as server-sent events, which is why our stream() distinguishes a generator (yield from) from a plain return (yield once). Two sharp edges a committer internalizes: (1) the container is typically expected to be built for the Runtime's architecture, so a locally-built image on the wrong arch fails at launch in a way that looks like a runtime error; (2) session lifetime is long — sessions can persist for a substantial duration (hours) to support long-running and async agents, so "warm" is a much longer-lived concept than a Lambda's few-minute reuse window.

What the miniature fakes: in-process call instead of HTTP+container; an integer tick instead of wall-clock TTL eviction; a dict store instead of a microVM's real filesystem and memory; synchronous consumption of the generator in invoke().

Firecracker is why isolation is a boundary, not a promise

The microVM claim rests on Firecracker, AWS's open-source VMM (the same technology under Lambda and Fargate). The details that make it the right tool: it uses KVM to run each guest with its own kernel, exposes a deliberately minimal device model (a handful of virtio devices — net, block — and little else) to shrink the attack surface, runs behind a jailer process for defense-in-depth, and boots in the low-hundreds-of-milliseconds range with only a few MB of memory overhead per microVM. That combination — real hardware-virtualization isolation and millisecond-class boot and tiny per-VM overhead — is what makes "one microVM per session" economically viable at fleet scale. A container cannot make the same claim: it shares the host kernel, so a kernel-level escape crosses the boundary. This is the entire security argument, and it is why the WARMUP is emphatic that "microVM ≠ container." Snapshotting (restore from a pre-booted snapshot) is the technique that pushes cold-start latency down further; our lab's cold_start flag is the observable shadow of it.

What the miniature fakes: there is no VM at all — isolation is enforced by simply not handing the entrypoint a reference to other sessions' stores. Structurally faithful (no capability to cross), physically nothing like a hypervisor boundary.

Cedar: our two-pass scan is the real evaluation shape, minus the type system

Policy's real engine is Cedar, AWS's open-source Rust authorization language, and the same engine behind Amazon Verified Permissions and Verified Access. Our PolicyEngine reproduces Cedar's decision procedure accurately: gather forbids, deny if any match; else gather permits, allow if any match; else default-deny. That two-pass, forbid-overrides, default-deny semantics is exactly Cedar's, and getting it right is the point of the lab.

What Cedar has that our miniature omits is the reason it scales to real authorization: it is a typed policy language over an entity model. Real Cedar policies reference typed entities (Agent::"support", Tool::"refund"), support hierarchy and group membership via the in operator, validate against a schema so a typo in an attribute name is a compile-time error rather than a silent mismatch, and evaluate when/unless conditions over structured entity attributes and a context record. Our version collapses all of that to exact-string-or-wildcard matching on three fields plus an injected Python when predicate. Sharp edges a Cedar user learns: forbid-overrides means you cannot "grant back" something a broad forbid denies (by design — it is the safety property); and because evaluation is over an explicit entity store, getting the entities and their attributes into the request correctly is where real bugs live, not in the policy text.

What the miniature fakes: no schema, no entity hierarchy, no in/groups, no policy parser — Python callables stand in for the compiled condition. The algebra is exact; the type system is absent.

Gateway is itself an MCP server; the factory is real code-gen

The real Gateway is not a proxy — it is an MCP server that materializes tools from targets. You register a target (a Lambda, an OpenAPI/Smithy spec, an existing MCP server, a function) and the Gateway synthesizes MCP tool descriptors and the invocation glue, then serves the standard MCP surface (tools/list, tools/call) so any MCP client — any framework — consumes them identically. Our FunctionTarget/LambdaTarget/OpenAPITargetGatewayTool is a faithful shrink of that: OpenAPITarget really does emit one tool per operation and derive the input schema from the operation's parameters, which is what the real spec importer does. The production Gateway adds semantic tool search — an embedding index over tool names/descriptions so an agent with hundreds of tools searches for the relevant few instead of paying to put every schema in context — and it fronts ingress auth via Identity. Our Identity is a {principal: token} map or an injected verify; the real one brokers OAuth against Cognito/Okta/Entra ID/Auth0 for inbound and manages credential providers (OAuth flows, API keys) for outbound, so the agent authenticates downstream as a workload without secrets in prompts.

What the miniature fakes: in-process registry vs a hosted MCP server; no semantic search; token-map identity vs real OAuth/credential brokering; the LambdaTarget proxy-response unwrap is a stylized version of API-Gateway-style {statusCode, body}.

Memory: real consolidation is asynchronous and model-driven

Our MemoryStore runs strategies synchronously inside consolidate(). The real service decouples them: you configure strategies (summary, semantic, user_preference, or custom) on the memory resource, write events into short-term, and the long-term extraction happens asynchronously — a model reads the event log and emits records into namespaces on its own cadence, not blocking the agent's turn. Retrieval is vector similarity over learned embeddings in a managed store, filtered by namespace. Our regex extractors and bag-of-words cosine make the mechanism visible and deterministic; the shape — events in, strategy-extracted namespaced records out, retrieve by namespace + relevance — is the real one. The idempotent dedup-on-content in _add_record mirrors a genuine production concern: a model re-run on an overlapping window will re-emit facts, and the store must not accumulate duplicates.

What the miniature fakes: synchronous instead of async extraction; regex/BoW instead of an LLM extractor and embedding retriever; a Python dict instead of a managed vector store.

Why the API evolved: Bedrock Agents → AgentCore

Bedrock Agents (2023-era, "Agents for Amazon Bedrock") is a single fully-managed agent: you configure a model, an instruction prompt, action groups (tools, usually Lambda-backed), and knowledge bases for RAG, and AWS runs its orchestration loop for you. It is genuinely good for low-code assistants inside AWS — and it is a ceiling. You adopt AWS's agent shape; you cannot drop in a LangGraph graph with custom branching, or run a CrewAI crew, or bring your own model host. Teams that outgrew the managed loop had no incremental path. AgentCore (GA 2025) is the unbundling: the same capabilities decomposed into independently adoptable primitives — Runtime, Gateway, Memory, Identity, Policy, Observability, Code Interpreter, Browser, Harness — with control inverted so you own the loop in any framework. The through-line of the whole industry is the same move: from "here is our agent" to "here is the runtime and plumbing for your agent." Knowing that Bedrock Agents and AgentCore are different products — not a rename — is the fastest credibility signal in an AWS interview. The Staff Notes turn all of this into the judgment calls an owner is trusted to make.