Home / Articles

Deploying a LangGraph Agent to AWS Bedrock AgentCore, Step by Step

Wrap an existing LangGraph agent for AgentCore, scaffold the project with the CLI, test it locally, deploy to AWS and invoke it with sessions and memory intact.

Publisher: REACTAPP.TOP8 min read1792 words

This article is published in English.

Sources: How to Deploy a LangGraph Agent on AWS Bedrock AgentCore

Plenty of LangGraph agents work well on a laptop and then stall at the step of reaching production. AWS Bedrock AgentCore targets that gap with serverless hosting that scales and doesn't ask for a redesign. The sections below cover wrapping an existing agent for AgentCore, generating a project, running it locally, shipping it to AWS and calling it once it's live.

What AgentCore is

AgentCore is a hosted runtime from AWS where agents are deployed, scaled and run without anyone managing servers. Frameworks like LangGraph, Strands, CrewAI and LlamaIndex all work, as do models from OpenAI, Google and Anthropic, so the agent code stays as it is. Session isolation, persistent memory, observability and identity management come built in.

Everything is driven by the AgentCore CLI. It's a Node.js program that generates the project skeleton, serves the agent locally and ships it to AWS with CDK running underneath.

Prerequisites

  • Python 3.12 or later
  • uv or pip to manage Python packages
  • Node.js 20 or later, since the CLI is distributed through npm
  • AWS CDK installed globally with npm install -g aws-cdk
  • An AWS account with credentials configured locally via aws configure
  • Existing LangGraph code that produces a compiled StateGraph

Step 1: install the CLI

One command-line tool drives everything: creating the project, running the app locally and deploying to AWS.

npm install -g @aws/agentcore

Confirm the install worked:

agentcore --help

Step 2: add dependencies

Two packages need to join the agent's dependencies. bedrock-agentcore is the Python SDK providing the BedrockAgentCoreApp wrapper class, and aws-opentelemetry-distro is AWS's build of the OpenTelemetry Python instrumentation.

# pyproject.toml
[project]
name = "my-agent"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = [
    "aws-opentelemetry-distro==0.17.0",
 "bedrock-agentcore>=1.6.3",
    "boto3>=1.42.0",
    "langgraph>=1.1.0",
    "langchain-core>=1.2.0",
    # ... your other existing dependencies
]

Install with whatever workflow is normal for the project:

uv sync
# or: pip install -e .

Step 3: write the entrypoint in main.py

AgentCore expects one Python file as the entrypoint, containing a BedrockAgentCoreApp instance and a function decorated with @app.entrypoint. The LangGraph logic barely changes. It just gets wrapped in a small runtime entrypoint.

# main.py
from langchain_core.messages import HumanMessage
from bedrock_agentcore.runtime import BedrockAgentCoreApp
from graphs.my_agent_graph import build_my_agent  # your existing graph builder

# 1. Instantiate the AgentCore app and logger
app = BedrockAgentCoreApp()
log = app.logger

# 2. Build your graph at module load time (startup)
#    AgentCore initialises the module once, then handles concurrent invocations.
#    Any failure here will prevent a broken agent from going live.
def create_agent():
    log.info("Initialising agent...")
    graph = build_my_agent()   # returns your compiled LangGraph StateGraph
    log.info("Agent ready")
    return graph
try:
    graph = create_agent()
except Exception as e:
    log.error(f"Critical failure during agent initialisation: {e}")
    raise  # fail fast - don't let a broken agent start serving requests

# 3. Async helper that drives the LangGraph streaming loop
async def run_agent(user_input: str, session_id: str = "default-session") -> str:
    responses = []
    config = {"configurable": {"thread_id": session_id}}
    async for chunk in graph.astream(
        {"messages": [HumanMessage(content=user_input)]},
        config=config,
        stream_mode="values",
    ):
        messages = chunk.get("messages", [])
        if messages:
            last = messages[-1]
            if getattr(last, "type", None) == "ai":
                responses.append(last)
    if not responses:
        return "No response"
    content = getattr(responses[-1], "content", "")
    return content if isinstance(content, str) else str(content)

# 4. The entrypoint - this is what AgentCore calls on every invocation
@app.entrypoint
async def invoke(payload, context):
    try:
        log.info("Invoke received")
        user_input = payload.get("prompt", "")
        session_id = payload.get("session_id", "default-session")
        if not user_input.strip():
            return {"error": "Prompt cannot be empty"}
        response = await run_agent(user_input, session_id)
        return {"response": response}
    except Exception as e:
        log.error(f"Error: {e}")
        return {"error": str(e)}

# 5. Run the app (only executed when running locally using agentcore dev)
if __name__ == "__main__":
    app.run()

What matters in that file

  • BedrockAgentCoreApp() gives the runtime its shell: an HTTP server, health-check routes and structured logs, all prewired.
  • The graph is constructed at module scope, once on import rather than on every call. Any startup problem therefore shows up immediately, and a faulty app never receives traffic.
  • @app.entrypoint marks the handler for incoming calls. It is given payload, the decoded JSON body, along with context for the AgentCore request. What it returns must be a plain dictionary that serializes to JSON. A bare string or a custom class won't do.
  • The incoming session_id becomes LangGraph's thread_id, so MemorySaver keeps a separate memory per session.

Step 4: create the project

Next comes the project layout. Run the initialization wizard from a clean root folder and let it build the skeleton:

agentcore create

The wizard asks for details like the project name, language, Python version and entrypoint file. The entrypoint and Python version must match the app. Afterward an agentcore.json file appears in the agentcore folder, telling AgentCore where the code lives and how to run it.

{
  "name": "MyAgentOnAgentcore",
    "runtimes": [
    {
      "name": "my-agent",
      "build": "CodeZip",
      "entrypoint": "main.py",
      "codeLocation": "app/my-agent/",
      "runtimeVersion": "PYTHON_3_12",
      "networkMode": "PUBLIC",
      "protocol": "HTTP",
	  "envVars": []
	}
  ]
}

Step 5: place the code where AgentCore looks

Move or copy the agent source into the directory named by codeLocation in agentcore.json. It's easy to get wrong, and a wrong location makes deployment needlessly painful. A common slip is dropping the code into the agentcore folder itself, which can cost a lot of time. The layout should look like this:

MyAgentOnAgentcore/
├──agentcore
	└── agentcore.json
├──app/
	└──my-agent/          			 ← codeLocation
		├── main.py                  ← entrypoint (matches agentcore.json)
		├── pyproject.toml           ← or requirements.txt
		├── src/
		│   └── my_agent_graph.py    ← your LangGraph graph builder
		│   └── tools.py             ← your tools, utilities, etc.
		└── .env					 ← your configuration variables

main.py has to sit at the top level of codeLocation and match the entrypoint in agentcore.json exactly.

Environment variables

AgentCore can hand environment variables to the runtime container. Locally, a .env file is the easiest route. In production, values can live in agentcore.json under envVars, though a .env file works too. Secrets such as passwords and API keys are better kept in AWS Secrets Manager and loaded at runtime, and in that case the AgentCore execution role needs permission to read them. Note that envVars is an array of JSON objects, each with name and value fields.

Step 6: validate

Before running anything, let the CLI check the configuration:

agentcore validate

Simple slips such as an absent entrypoint file or a bad path in the config get caught right here.

Step 7: run and test locally

Bring up the local runtime with:

agentcore dev

It launches a local HTTP server that closely mirrors production. When everything is wired correctly, the output shows the app ready and listening on a local port.

Calling the local server

An invocation can go through the CLI:

agentcore invoke "Summarise last month sales"

or through curl:

curl -X POST http://localhost:8080/invocations \
  -H "Content-Type: application/json" \
  -d '{"prompt": "Summarise last month sales", "session_id": "test-001"}'

A good response is JSON with the agent's answer inside:

{"response": "Last month, total sales were $1.2M across 3 regions..."}

Step 8: deploy to AWS

When local testing looks solid, deploy to AgentCore Runtime:

agentcore deploy

Behind the scenes, the CLI packages the code, provisions an S3 bucket for direct code deployment, creates an IAM execution role and deploys the AgentCore Runtime with CDK. The first deployment takes a few minutes, and later ones tend to be quicker.

Once it finishes, check the runtime status. The output shows the runtime ARN and the HTTP URL for invoking the deployed agent.

agentcore status

Step 9: invoke the deployed agent

The CLI is the easiest way to try the deployed version:

agentcore invoke --prompt "Who are the top 5 customers by revenue?" --session-id "session-id-with-length-greater-than-or-equal-33"

Calling over HTTP requires signing the request with AWS SigV4:

curl -X POST "https://bedrock-agentcore.us-east-1.amazonaws.com/runtimes/arn-of-MyAgentOnAgentcore-xxxx/invocations" \
  --user "$AWS_ACCESS_KEY_ID:$AWS_SECRET_ACCESS_KEY" \
  --aws-sigv4 "aws:amz:us-east-1:bedrock-agentcore" \
  -H "Content-Type: application/json" \
  -H "x-amzn-bedrock-agentcore-runtime-session-id: session-id-with-length-greater-than-or-equal-33" \
  -d '{"prompt": "Who are the top 5 customers by revenue?"}'

Use the HTTP URL from agentcore status.

For calls from other Python code, the SDK is generally the neatest option. Store the runtime ARN in an environment variable, pass the prompt, and send the previous session ID again with each follow-up.

# invoke_agent.py
import json
import boto3
import os
from dotenv import load_dotenv

load_dotenv()

client = boto3.client("bedrock-agentcore", region_name="us-east-1")
runtime_arn = os.getenv("AGENTCORE_RUNTIME_ARN")

def invoke(prompt: str, session_id: str) -> str:
    payload = json.dumps({"prompt": prompt, "session_id": session_id})
    response = client.invoke_agent_runtime(
        agentRuntimeArn=runtime_arn,
        payload=payload,
        contentType="application/json"
    )
    body = json.loads(response["response"].read().decode())
    return body["response"]

if __name__ == "__main__":
    session_id = ""
    print("Ready. Type 'exit' to quit.")

    while True:
        prompt = input("Your question: ").strip()
        if prompt == "exit":
            break

        if not prompt:
            continue

        params = {
            "agentRuntimeArn": runtime_arn,
            "payload": json.dumps({"prompt": prompt}),
            "contentType": "application/json"
        }
        if session_id:
            params["runtimeSessionId"] = session_id

        try:
            response = client.invoke_agent_runtime(**params)
            body = json.loads(response["response"].read().decode())
            session_id = response.get("runtimeSessionId", session_id)
            print(f"Agent: {body['response']}\n")
        except Exception as e:
            print(f"Error: {e}")

Set AGENTCORE_RUNTIME_ARN in the .env file:

AGENTCORE_RUNTIME_ARN=arn:aws:bedrock-agentcore:us-east-1:123456789012:agent-runtime/MyAgentOnAgentcore-xxxx

Keeping session state across calls

Look at runtimeSessionId in the boto3 example. AgentCore returns one with every response. Sending it back in the next request routes follow-ups to the same session context, and combined with LangGraph's MemorySaver the agent gains full conversation memory across separate HTTP calls.

Common gotchas

  • Startup failures are deliberate. When create_agent() throws at import time, AgentCore won't launch the runtime. That behavior protects against a misconfigured agent, such as one with absent credentials or a bad database URL, going live silently.
  • Permissions need adding. The execution role AgentCore creates needs explicit access to any AWS service the agent uses, Secrets Manager and S3 included. Add those permissions after the first deployment.
  • Versions must agree. Whatever Python version pyproject.toml declares has to line up with the runtime setting in agentcore.json.
  • Return plain dictionaries. If the entrypoint returns a string or custom object, the runtime boundary rejects it.

What actually changes

Getting from a local LangGraph agent to AgentCore takes only a handful of changes: install two extra packages, wrap the graph in BedrockAgentCoreApp, scaffold a project, test locally, then deploy and call it. The graph, the tools, the model calls and the memory configuration remain mostly untouched. AgentCore runs the infrastructure side, and LangGraph keeps owning the agent logic.