AWS Builder Center
AgentCore Runtime V2: 7 Snapshot-Safe Code Rules

AgentCore Runtime V2: 7 Snapshot-Safe Code Rules

Learn 7 practical code rules for Amazon Bedrock AgentCore V2, covering snapshots, credentials, request state, tools, networking, and identity.

Amazon Bedrock AgentCore Runtime V2 introduces faster, more consistent cold starts through snapshot-based execution—but that also changes how developers should think about initialization, caching, credentials, networking, and request-specific state.
The important question is no longer just whether your agent runs.
It is:
What should be captured in the snapshot, and what must remain fresh for every request?

TL;DR

AgentCore Runtime V2 starts new instances by restoring a prepared snapshot of the agent environment.
A practical rule is:
Stable and reusable → initialize at startup
Fresh, expiring, random, or request-specific → create during request handling
The most important rules are:
  1. Keep expensive reusable initialization at startup.
  2. Generate request IDs inside the request handler.
  3. Generate current timestamps inside the request handler.
  4. Refresh temporary or expiring credentials when needed.
  5. Avoid freezing dynamic tool catalogs or external data.
  6. Reuse clients, but design for network reconnection.
  7. Do not use hostname or PID as a unique instance identity.
These details are easy to overlook because an application may work perfectly on its first request while still behaving incorrectly after snapshot restoration.

Who This Is For

This guide is useful for developers who:
  • Build AI agents with Amazon Bedrock AgentCore
  • Are evaluating or migrating to Runtime V2
  • Use Python or containerized agent applications
  • Work with reusable service clients, credentials, tools, or external APIs
  • Want to understand the application-level effects of snapshot-based execution
This is not just about getting a faster startup.
It is about making sure the application remains correct after the runtime starts restoring snapshots.

Why AgentCore Runtime V2 Matters

AI agents are becoming more complex.
A single agent may:
  • Call foundation models
  • Invoke tools
  • Query databases
  • Access external APIs
  • Retrieve knowledge
  • Maintain session state
  • Perform multiple steps before returning a result
As these workloads grow, runtime performance becomes part of the overall agent architecture.
AgentCore Runtime V2 addresses this with a snapshot-based execution model.
The runtime prepares the agent environment once and takes a snapshot. New instances restore that prepared environment instead of repeating the complete startup process.
This makes cold-start behavior more consistent, especially for workloads with large container images.
AWS reports that in its testing, the new Runtime achieved P75 cold-start times of about 1.9–2.0 seconds for container images between 200 MB and 2 GB, compared with 5.4–30 seconds for V1.
V2 also introduces elastic memory management, allowing memory that is no longer actively used to be reclaimed during a session.
But the performance improvements create an important application-level consideration:
The state produced during startup becomes part of the snapshot.
That means developers need to distinguish between:
State that is safe to reuse
and
State that must be recreated or refreshed.

The Mental Model: Startup vs Request

The easiest way to design for Runtime V2 is to divide your application into two execution phases.

Phase 1: Startup

Startup is where reusable and relatively stable initialization should happen.
Typical examples include:
  • Loading dependencies
  • Reading static configuration
  • Loading model assets
  • Creating reusable service clients
  • Initializing immutable application objects
  • Performing expensive setup that does not change per request
For example:
1
2
3
4
5
6
7
8
9
import json
import pathlib

SETTINGS = json.loads(
(pathlib.Path(__file__).parent / "agent.json").read_text()
)

MODEL = load_model_weights()
CLIENT = build_client()
This code runs before the application starts accepting requests.
The important question is:
Would this value still be correct if the next request arrived much later?
If yes, startup may be appropriate.

Phase 2: Request Handling

Request handling is where state that must remain current should be created.
Examples include:
  • Request IDs
  • Current timestamps
  • User-specific information
  • Temporary credentials
  • Session-specific state
  • Dynamic tool information
  • Request-specific randomness
For example:
1
import osimport timedef invoke(payload): request_id = os.urandom(16).hex() current_time = time.time() # Process the current request
The distinction is simple:
1
2
3
4
5
6
7
8
9
Stable + reusable
↓
Startup
↓
Snapshot

Fresh + dynamic
↓
Request Handler
This separation is the foundation of correct Runtime V2 application design.

The Rule at a Glance

StateWhere it belongsExample
Stable and reusableStartupModel weights, static configuration
Request-specificHandlerRequest ID, current timestamp
ExpiringHandlerTemporary credentials
DynamicHandlerTool catalog, external state
Reusable clientStartupSDK client and connection-pool setup
Live socketReconnect after restoreNetwork connection

The Classic Snapshot Mistake

Consider this code:
1
import timeimport uuidREQUEST_ID = uuid.uuid4().hexSTART_TIME = time.time()def invoke(payload): return { "request_id": REQUEST_ID, "time": START_TIME }
In a conventional application, this may look harmless.
The problem is that both values are generated during startup.
If the startup state is captured in the snapshot, restored instances inherit those values.
That can result in:
  • Duplicate request IDs
  • Stale timestamps
  • Incorrect request tracing
  • Confusing logs
  • Incorrect latency measurements
A safer pattern is:
1
import timeimport uuiddef invoke(payload): request_id = uuid.uuid4().hex current_time = time.time() return { "request_id": request_id, "time": current_time }
The values are now generated when the request actually arrives.
This is the central idea behind everything that follows.

Step 1: Put Expensive Reusable Work at

Startup

The first optimization is to move expensive initialization into startup when the result is safe to reuse.
For example:
1
import jsonimport pathlibSETTINGS = json.loads( (pathlib.Path(__file__).parent / "agent.json").read_text())MODEL = load_model_weights()CLIENT = build_client()
Good candidates include:
  • Static configuration
  • Immutable application data
  • Expensive model loading
  • Reusable service clients
  • Precomputed lookup structures
  • Dependency initialization
The runtime can then capture the initialized state and restore it for new instances.
But there is one critical condition:
The value must remain valid after restoration.
A useful design question is:
Would this value still be correct if the next request arrived much later?
If the answer is yes, it is a candidate for startup initialization.
If the answer is no, it should be calculated closer to request handling.

Good startup state

1
2
3
4
5
Static configuration
Model initialization
Reusable client objects
Immutable application data
Dependency loading

Bad startup state

1
2
3
4
5
6
Current timestamp
Request ID
Expiring credentials
User-specific data
Dynamic tool catalog
Request-specific random values
The goal is not to move as much code as possible into startup.
The goal is to put the right code there.

Step 2: Generate Request IDs Inside the

Handler

A request ID should identify the request, not the snapshot.
Avoid:
1
REQUEST_ID = uuid.uuid4().hex
Prefer:
1
def invoke(payload): request_id = uuid.uuid4().hex
Another option is:
1
import osdef invoke(payload): request_id = os.urandom(16).hex()
Fresh request identifiers are useful for:
  • CloudWatch logs
  • Distributed tracing
  • Audit records
  • Metrics
  • Idempotency
  • Debugging
An execution environment may process many requests.
Each request should therefore receive an identifier generated for that request.
Do not confuse application startup identity with request identity.

Step 3: Never Cache the Current Time at

Startup

This is another subtle mistake:
1
CURRENT_TIME = time.time()
This timestamp represents the moment startup code executed.
It does not represent the time a future request arrives.
Instead:
1
def invoke(payload): request_time = time.time()
The same principle applies to latency measurement.
Avoid:
1
START = time.monotonic()def invoke(payload): elapsed = time.monotonic() - START
when START is intended to represent the beginning of an individual request.
Take the reference timestamp inside the request handler instead:
1
def invoke(payload): start = time.monotonic() # Process request elapsed = time.monotonic() - start
This keeps measurements associated with the request that is actually executing.

Step 4: Treat Credentials as Expiring

State

Credentials deserve particular attention.
Consider:
1
TOKEN = load_credentials()def invoke(payload): return call_service( token=TOKEN, payload=payload )
The token may be valid when the environment is initialized.
Later, after the snapshot is restored, that same credential may be expired.
A safer architecture separates reusable client setup from credential freshness:
1
CLIENT = build_client()def invoke(payload): credentials = get_current_credentials() return call_service( client=CLIENT, credentials=credentials, payload=payload )
The exact credential mechanism depends on your application.
The architectural principle is:
Reusable client → initialize at startup
Expiring credential → refresh when required
This is especially important when an agent interacts with external services using temporary credentials or short-lived tokens.
Do not assume that a value remains valid simply because it was valid when the snapshot was created.

Step 5: Do Not Freeze Dynamic Tool

Information

This is particularly important for AI agents.
Suppose an agent discovers its tools during startup:
1
TOOLS = fetch_tool_catalog()
Caching this can look attractive because tool discovery may be expensive.
But the tool catalog can change.
A tool may be:
  • Added
  • Removed
  • Disabled
  • Reconfigured
  • Updated
  • Subject to a changed authorization policy
If the catalog is captured in the snapshot, the agent may continue operating with stale tool information.
AWS specifically calls out this class of problem in its V2 guidance.
Where tool information is dynamic, retrieve it or validate it during request handling:
1
def invoke(payload): tools = get_current_tools() return run_agent( payload=payload, tools=tools )
The exact implementation depends on your architecture.
The general rule is:
Do not turn a performance optimization into stale application state.
This matters even more for agent systems where the available tools can change independently of the running application.

Step 6: Reuse Clients, but Expect

Network Reconnection

There is an important distinction between a reusable client object and a live network connection.
A useful pattern is:
1
CLIENT = build_client()warm_up(CLIENT)def invoke(payload): return CLIENT.process(payload)
Creating and exercising the client at startup can allow reusable setup to be captured in the snapshot.
However, a physical socket opened before the snapshot does not survive a restore.
The first call after restoration may therefore need to establish the connection again.
The correct mental model is:
Cache the client.
Do not assume the original socket is permanent.
Your client library and application should tolerate reconnect behavior.
Also avoid application logic that assumes a fixed source port will remain available forever.
This is a subtle distinction:
1
2
3
4
5
6
7
Reusable client configuration
↓
Can be captured

Physical socket
↓
Must be able to reconnect
That allows you to benefit from startup initialization without depending on stale connection state.

Step 7: Do Not Use Hostname or PID as

Unique Instance Identity

Traditional applications sometimes use hostname and process ID to identify workers:
1
INSTANCE_ID = f"{socket.gethostname()}-{os.getpid()}"
This can become misleading with snapshot-based execution.
Every restored instance starts from the same snapshot and can report the same hostname and process ID.
AWS's current V2 guidance specifically warns against using those values as unique instance identifiers.
This can cause problems with:
  • Metrics
  • Logging
  • Lock ownership
  • Distributed coordination
  • Debugging
Instead, generate identifiers at the level where uniqueness is actually required.
For request-level identity:
1
import osdef invoke(payload): request_id = os.urandom(16).hex()
The important question is:
What exactly am I trying to identify?
If you need a request identifier, create one per request.
Do not assume that the process identity uniquely represents a restored runtime instance.

What About Randomness?

Random values also require careful treatment.
Consider:
1
SESSION_ID = generate_random_value()
If the value is generated during startup, that value can become part of the snapshot.
A request-specific value should instead be created during request execution:
1
import osdef invoke(payload): session_id = os.urandom(16).hex()
This matters for:
  • Session identifiers
  • Request identifiers
  • Temporary tokens
  • Security-related random values
  • Cryptographic operations
The Python random module also deserves attention because random state initialized before snapshotting can be inherited by restored instances.
For applications that bring their own cryptographic libraries, verify that the libraries are appropriate for snapshot-based execution. AWS documents snapshot-safe cryptographic builds for this reason.
The principle is:
Generate fresh randomness in the execution phase where it is needed.

A Clean AgentCore V2 Pattern

The following pattern combines the startup and request boundaries:
The following example is a pattern to adapt to your application; helper functions such as load_model_weights() and get_current_credentials() represent your own implementation.
1
import jsonimport osimport pathlibimport timefrom bedrock_agentcore.runtime import BedrockAgentCoreApp# ---------------------------------------# STARTUP# ---------------------------------------SETTINGS = json.loads( (pathlib.Path(__file__).parent / "agent.json").read_text())MODEL = load_model_weights()CLIENT = build_client()# Exercise reusable client setup before snapshotting.warm_up(CLIENT)app = BedrockAgentCoreApp()# ---------------------------------------# REQUEST# ---------------------------------------@app.entrypointdef invoke(payload): request_id = os.urandom(16).hex() request_time = time.time() # Obtain current/valid credentials. credentials = get_current_credentials()
The structure is deliberately simple.

Startup

1
2
3
4
5
6
7
8
9
10
11
12
13
Load dependencies
↓
Load static configuration
↓
Load reusable model state
↓
Create reusable clients
↓
Warm up reusable clients
↓
Start application
↓
Snapshot

Request

1
2
3
4
5
6
7
8
9
10
11
12
13
Request arrives
↓
Generate fresh request ID
↓
Read current time
↓
Refresh/use valid credentials
↓
Obtain current dynamic state
↓
Run agent
↓
Return response
This separation makes the application behavior much easier to reason about.

Enabling Runtime V2

Runtime V2 is selected using the platformVersion configuration.
For example, the AWS CLI supports:
1
2
3
4
5
6
7
8
9
10
aws bedrock-agentcore-control create-agent-runtime \
--agent-runtime-name "my-agent" \
--role-arn "arn:aws:iam::111122223333:role/AgentExecutionRole" \
--agent-runtime-artifact '{
"containerConfiguration": {
"containerUri": "111122223333.dkr.ecr.us-west-2.amazonaws.com/my-agent:latest"
}
}' \
--network-configuration '{"networkMode": "PUBLIC"}' \
--platform-version V2
After creation, verify the platform version:
1
2
3
aws bedrock-agentcore-control get-agent-runtime \
--agent-runtime-id YOUR_RUNTIME_ID \
--query platformVersion
Expected result:
1
"V2"
If platformVersion is omitted during runtime creation, V1 is used by default.
Before migrating an existing application, verify the current AWS CLI/SDK version, supported region, runtime configuration, and deployment workflow.

V2 Is More Than a Performance Upgrade

It is easy to summarize V2 as:
Faster startup + better memory efficiency
That is only part of the story.
The deeper change is the execution boundary:
1
2
3
4
5
6
7
Application Startup
↓
Snapshot
↓
Restore
↓
Request Handling
Once the startup environment becomes reusable snapshot state, developers must explicitly decide what is safe to capture.
That is an application architecture decision.
It is not just an infrastructure setting.

Migration Checklist

Before moving an existing AgentCore workload to V2, review the application startup path.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[ ] Static configuration is initialized at startup
[ ] Expensive reusable objects are initialized once
[ ] Request IDs are generated during request handling
[ ] Current timestamps are generated during request handling
[ ] Per-request latency timers start inside the handler
[ ] Temporary credentials are refreshed when required
[ ] Dynamic tool catalogs are not accidentally frozen
[ ] External dynamic data is not unintentionally cached forever
[ ] Network clients can reconnect after restore
[ ] Hostname/PID are not treated as unique instance identity
[ ] Request-specific randomness is generated after restore
[ ] Cryptographic dependencies are reviewed for snapshot safety
[ ] Startup initialization completes within the required health window
[ ] Multiple requests have been tested
[ ] Logs and tracing produce unique request identifiers
[ ] Environment-variable size limits have been checked
[ ] Deployment tooling supports the selected platform version

A Practical Test Plan

Do not test only:
1
2
3
4
5
Deploy
↓
Send one request
↓
It works
That test does not exercise the most interesting behavior of a snapshot-based runtime.
Instead, test the following scenarios.

Test 1: Multiple Requests

Send multiple requests and verify:
  • Request IDs are different
  • Timestamps reflect the actual request time
  • Request-specific data does not leak between requests
  • Per-request metrics remain correct

Test 2: Delayed Request

Wait before sending another request.
Verify:
  • Credentials are still valid
  • Current timestamps are correct
  • Dynamic information is not stale
  • Cached resources still behave correctly

Test 3: Multiple Instances

Generate enough traffic to exercise multiple instances.
Check:
  • Request identities remain unique
  • Metrics remain distinguishable
  • Shared startup state is safe to reuse
  • Hostname/PID are not being used as unique identity

Test 4: Network Recovery

Exercise a client after a connection has become invalid or needs to be recreated.
Verify:
  • The client can establish a new connection
  • The application does not depend on the original socket remaining alive
  • Connection pooling behaves correctly after restoration

Test 5: Dynamic Tool Configuration

Change the available tool configuration.
Verify that the agent does not continue using obsolete tool information captured during startup.
This is particularly important for agents whose tool inventory can change independently of the application deployment.

Important Operational Details

Runtime V2 introduces several operational differences that are easy to overlook.

Region Availability

Current AgentCore Runtime V2 availability includes:
  • US East (N. Virginia) — us-east-1
  • US East (Ohio) — us-east-2
  • US West (Oregon) — us-west-2
  • Europe (Ireland) — eu-west-1
  • Asia Pacific (Tokyo) — ap-northeast-1
Always verify current regional availability before planning a production migration.

Startup Health Window

AWS documents a 120-second startup window for the agent to become healthy.
Heavy initialization should therefore be measured rather than assumed to be harmless.
If startup does not complete successfully within the required health window, runtime creation or update can fail.
With the AgentCore SDK, startup work should be completed before app.run() starts listening.

Environment Variables

Current V2 limits the total environment-variable size to:
  • 1.5 KB for direct code deployments
  • 2.5 KB for container agents
This is smaller than the V1 limit.
If your application passes a large configuration payload through environment variables, review the configuration design before migrating.
Consider using an appropriate configuration or secrets service for larger values instead of treating environment variables as a general data store.

Infrastructure as Code

Do not assume that an existing infrastructure workflow can automatically switch from V1 to V2.
AWS currently documents that CloudFormation and AWS CDK do not support setting platformVersion directly.
Check your current Terraform, SDK, CLI, CDK, and deployment tooling before automating the migration.

Common Mistakes

Mistake 1: Caching Everything

Snapshot-based execution makes caching powerful.
That does not mean every object should be cached.
Cache stable, reusable state.
Refresh dynamic state.

Mistake 2: Moving Everything Into the Handler

The opposite extreme is also a problem.
If every dependency, model, and client is rebuilt for every request, much of the benefit of reusable initialization disappears.
The goal is not:
Startup or request
The goal is:
Choose the correct boundary for each type of state.

Mistake 3: Treating Startup as Per-Instance Initialization

A developer may assume:
“This runs once for this instance.”
With V2, the more important question is:
“Could this state be captured and inherited by restored instances?”
That changes how startup code should be reviewed.

Mistake 4: Testing Only the First Request

A first request can succeed while later restored executions expose:
  • Stale credentials
  • Duplicate identifiers
  • Old configuration
  • Broken network assumptions
  • Stale tool information
Test multiple requests and realistic delays.

Mistake 5: Assuming Faster Startup Solves Everything

Cold-start performance is important.
But a faster agent that uses stale or incorrect state is not a successful optimization.
Correctness still comes first.

When Should You Use V2?

V2 is particularly interesting for workloads where startup overhead is meaningful.
Examples include:
  • Large containerized agents
  • AI workloads with expensive initialization
  • Agents with large dependency trees
  • Workloads with unpredictable scaling
  • Applications where consistent startup behavior matters
V2 should not be adopted only because the version number is newer.
Measure your workload.
Compare:
  • Startup behavior
  • Memory usage
  • Request latency
  • Error rates
  • Credential behavior
  • Network behavior
  • Application correctness
  • Cost
Then determine whether the new runtime improves the actual workload.

What I Would Review First

When reviewing an existing agent before moving it to V2, search the startup path for patterns like:
1
TOKEN = ...
1
NOW = time.time()
1
REQUEST_ID = uuid.uuid4()
1
DATA = fetch_dynamic_data()
1
TOOLS = discover_tools()
1
INSTANCE_ID = f"{socket.gethostname()}-{os.getpid()}"
These lines are not automatically wrong.
They are simply places where you should ask:
Should this value survive a snapshot, or should it be recalculated when the request actually runs?
That is the important design decision.

A Simple Decision Framework

For every startup variable, ask these questions:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Is it stable?
│
├── Yes → Can it be safely reused?
│ │
│ └── Yes → Startup candidate
│
└── No → Request-time value

Can it expire?
│
└── Yes → Refresh when needed

Must it be unique?
│
└── Yes → Generate at the appropriate request/runtime level

Can external state change?
│
└── Yes → Do not freeze it unintentionally

Does it depend on a network connection?
│
└── Yes → Expect reconnect after restore
This framework is often more useful than memorizing individual rules.

The Bigger Lesson

AI agents are usually discussed in terms of:
  • Models
  • Prompts
  • Tools
  • Retrieval
  • Orchestration
But production agents also depend heavily on the execution environment.
AgentCore Runtime V2 is a good example.
The runtime becomes more efficient by preparing execution state once and restoring that state for new instances.
That creates a new responsibility for developers:
Know what state you are freezing.
Stable configuration and expensive reusable initialization can benefit from being prepared once.
Credentials, timestamps, random identifiers, request-specific state, and dynamic information usually need different treatment.
The winning pattern is therefore not:
“Cache everything.”
It is:
Cache what is stable. Refresh what is dynamic.

Final V2 Checklist

Before deploying an agent on Runtime V2, ask these five questions:
  1. What can safely be initialized once?
  2. What changes between requests?
  3. What can expire?
  4. What must be unique?
  5. What external state can become stale?
If those questions have clear answers, the migration becomes much easier to reason about.
AgentCore Runtime V2 is not simply a faster startup mechanism.
It is a different execution model.
Understanding that model is what allows developers to take advantage of the performance improvements without sacrificing application correctness.

What Would You Move?

If you already have an AgentCore application, what is the first piece of startup state you would move into the request handler for V2?
I'd be particularly interested in examples involving:
  • Credentials
  • Tool discovery
  • Caching
  • Database connections
  • External API clients
Share your approach or any migration issue you have encountered.

Official References

Any opinions in this article are those of the individual author and may not reflect the opinions of AWS.
Enjoyed reading this content? Let the author know!

Your likes, comments, shares, and saves help creators reach more builders.

Loading recommendations

Loading article