<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Fazalul Haque</title>
    <description>The latest articles on DEV Community by Fazalul Haque (@vfazal).</description>
    <link>https://dev.to/vfazal</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3940071%2F6f71ed19-adcf-456b-87d2-facddcae01e2.png</url>
      <title>DEV Community: Fazalul Haque</title>
      <link>https://dev.to/vfazal</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/vfazal"/>
    <language>en</language>
    <item>
      <title>Airflow 3.x Architecture Explained: A Practical Guide for Data Engineers</title>
      <dc:creator>Fazalul Haque</dc:creator>
      <pubDate>Mon, 22 Jun 2026 12:53:58 +0000</pubDate>
      <link>https://dev.to/vfazal/airflow-3x-architecture-explained-a-practical-guide-for-data-engineers-3261</link>
      <guid>https://dev.to/vfazal/airflow-3x-architecture-explained-a-practical-guide-for-data-engineers-3261</guid>
      <description>&lt;h2&gt;
  
  
  What Is Airflow?
&lt;/h2&gt;

&lt;p&gt;Let me be honest about how I first encountered Airflow. My team had a bunch of Python and SQL scripts running on cron jobs. One script would download data, another would load it into a staging database, a third one would transform it, and a fourth one would ingest it into our data warehouse. The scripts were chained together with some fragile shell glue. When things worked, nobody noticed. When things broke — and they broke often — figuring out &lt;em&gt;where&lt;/em&gt; it broke and &lt;em&gt;why&lt;/em&gt; was a nightmare.&lt;/p&gt;

&lt;p&gt;Someone on the team said "we should use Airflow." I nodded like I understood, then spent the next three evenings reading documentation.&lt;/p&gt;

&lt;p&gt;Here's what I wish someone had told me then: &lt;strong&gt;Airflow is a platform for authoring, scheduling, and monitoring workflows — workflows that you define as Python code.&lt;/strong&gt; That's it. No magic. You write a Python file describing what tasks to run and in what order, and Airflow takes care of the scheduling, retries, logging, and visibility.&lt;/p&gt;

&lt;p&gt;The thing that took me a while to internalize is that Airflow is not a data processing engine. It doesn't move your data around. It &lt;em&gt;orchestrates&lt;/em&gt; the things that do. It's the conductor, not the orchestra.&lt;/p&gt;




&lt;h2&gt;
  
  
  The DAG — Everything Starts Here
&lt;/h2&gt;

&lt;p&gt;Before diving into the architecture, you need to understand what a DAG is, because the whole system revolves around it.&lt;/p&gt;

&lt;p&gt;DAG stands for &lt;strong&gt;Directed Acyclic Graph&lt;/strong&gt;. Sounds fancy. In practice it just means: a set of tasks with dependencies between them, where you can't have circular dependencies (task A depends on task B depends on task A — that would be a cycle, and Airflow won't allow it).&lt;/p&gt;

&lt;p&gt;A simple DAG might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;extract_data → transform_data → load_to_warehouse
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You define this in a Python file. Each step is a &lt;strong&gt;Task&lt;/strong&gt; – a single unit of work, like running a Python function, executing a SQL query, or calling an external API. The arrows between them are &lt;strong&gt;dependencies&lt;/strong&gt;. Airflow reads your Python file, figures out the task ordering, and then knows how and when to execute things.&lt;/p&gt;

&lt;p&gt;In Airflow 3.x, DAG files still live in a &lt;code&gt;dags/&lt;/code&gt; folder, but there have been some meaningful changes to how they're parsed and distributed — we'll get to that when we talk about the DAG Processor.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Architecture: A Bird's Eye View
&lt;/h2&gt;

&lt;p&gt;Before we look at each component individually, here's a high-level view of how they fit together:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fili4w3mlog6gpsoyc77a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fili4w3mlog6gpsoyc77a.png" alt=" " width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Every component reads from or writes to the &lt;strong&gt;Metadata Database&lt;/strong&gt; in some way. That database is the single source of truth for the entire system. Let's now go through each piece.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Understanding the Architecture Matters
&lt;/h2&gt;

&lt;p&gt;If you're running Airflow locally, many of these components may live on the same machine and feel invisible.&lt;/p&gt;

&lt;p&gt;So why spend time learning the architecture?&lt;/p&gt;

&lt;p&gt;Because most real-world Airflow problems come down to understanding which component is responsible for what.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;DAG not showing up in the UI? Look at DAG parsing.&lt;/li&gt;
&lt;li&gt;Tasks stuck in queued state? Check the executor and workers.&lt;/li&gt;
&lt;li&gt;No new tasks are starting? Investigate the scheduler.&lt;/li&gt;
&lt;li&gt;UI errors or failed API requests? Check the API Server.&lt;/li&gt;
&lt;li&gt;Everything seems broken? The metadata database is often the first place to look.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You don't need to memorize every component. But having a mental model of how they fit together makes troubleshooting much easier as you move beyond simple local deployments.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Metadata Database
&lt;/h2&gt;

&lt;p&gt;I'm starting here because once you understand this, everything else makes sense.&lt;/p&gt;

&lt;p&gt;The metadata database is a relational database — Postgres in production (please use Postgres, not SQLite outside of local dev). It stores:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;All your DAG definitions and their current state&lt;/li&gt;
&lt;li&gt;Every DAG Run — every time a DAG has executed or is scheduled to execute&lt;/li&gt;
&lt;li&gt;Every Task Instance — every individual task run, its state (queued, running, success, failed), pointers to logs, and timing&lt;/li&gt;
&lt;li&gt;User information, connections, variables, and more&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every other component in Airflow talks to this database. The Scheduler reads from it to decide what to run. Workers write to it when tasks finish. The API Server and UI read from it to show you that nice UI. The database is the glue that holds the distributed system together.&lt;/p&gt;

&lt;p&gt;One practical implication: if your database is slow or down, your entire Airflow cluster is effectively crippled. Invest in your database setup.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Scheduler
&lt;/h2&gt;

&lt;p&gt;The Scheduler is the brain of the operation. It's a long-running process that continuously checks the metadata database, figures out which tasks are ready to run, and sends them off to be executed.&lt;/p&gt;

&lt;p&gt;Here's a rough version of what it does in a loop:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Check all DAG schedules — is there a DAG that's supposed to have run in the last interval and hasn't started yet?&lt;/li&gt;
&lt;li&gt;Create &lt;strong&gt;DAG Runs&lt;/strong&gt; and &lt;strong&gt;Task Instances&lt;/strong&gt; in the metadata database for anything that needs to run&lt;/li&gt;
&lt;li&gt;Look at all Task Instances that are in a "scheduled" state and evaluate their dependencies — are the upstream tasks done?&lt;/li&gt;
&lt;li&gt;Move eligible tasks to "queued" state and submit them to the executor&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The Scheduler doesn't run tasks itself. It &lt;em&gt;decides&lt;/em&gt; that tasks should run and tells the executor to handle the actual execution. That's an important distinction.&lt;/p&gt;

&lt;p&gt;In Airflow 3.x, the Scheduler has become more reliable and scalable. You can run multiple schedulers (active-active setup) for high availability. If one scheduler process dies, another picks up the work.&lt;/p&gt;

&lt;p&gt;One gotcha with the Scheduler: it needs up-to-date DAG metadata in order to schedule work. In Airflow 3.x, DAG parsing responsibilities can be delegated to a dedicated DAG Processor component. More on that below.&lt;/p&gt;




&lt;h2&gt;
  
  
  The DAG Processor (New in Airflow 3.x)
&lt;/h2&gt;

&lt;p&gt;This is one of the more significant architectural changes in Airflow 3.x, and one that beginners often don't realize exists.&lt;/p&gt;

&lt;p&gt;In older versions of Airflow, the Scheduler also handled parsing your DAG files. This was problematic. If a DAG file had a slow import or a bug that caused it to hang, it could slow down the Scheduler itself. Not great.&lt;/p&gt;

&lt;p&gt;In Airflow 3.x, DAG parsing has been extracted into its own &lt;strong&gt;DAG Processor&lt;/strong&gt; component. It runs as a separate process, continuously scanning your &lt;code&gt;dags/&lt;/code&gt; directory, importing the Python files, and extracting DAG metadata (schedule, tasks, dependencies) into the metadata database.&lt;/p&gt;

&lt;p&gt;The Scheduler then just reads from the database — it doesn't need to parse Python files directly anymore. Cleaner separation of concerns.&lt;/p&gt;

&lt;p&gt;To be precise, in a basic single‑machine setup the scheduler still spawns subprocesses to parse DAGs for you; the dedicated DAG Processor is mainly a production pattern. The important idea is that, in 3.x, you can keep user DAG code out of the core scheduler process and scale DAG parsing independently when you need to.&lt;/p&gt;




&lt;h2&gt;
  
  
  The API Server
&lt;/h2&gt;

&lt;p&gt;Airflow 3.x introduced a proper standalone API Server. In earlier versions, the REST API was bundled into the Webserver. &lt;/p&gt;

&lt;p&gt;The API Server exposes Airflow's REST API — it's what the UI uses to fetch DAG information, trigger runs, clear tasks, etc. It's also what you'd use if you want to trigger DAG runs programmatically from another system. CI/CD pipelines kicking off DAGs, external services triggering workflows — all of that goes through the API Server.&lt;/p&gt;

&lt;p&gt;What changed from 2.x is that the REST API is now a first-class interface rather than a secondary feature attached to the Webserver. This separation gives Airflow a cleaner architecture and makes it easier to evolve the UI and API independently over time.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Webserver (Airflow UI)
&lt;/h2&gt;

&lt;p&gt;In Airflow 3.x, the traditional standalone webserver role has largely been replaced by a UI that communicates with the API Server.&lt;/p&gt;

&lt;p&gt;You use Airflow UI to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;See all your DAGs and their last run status&lt;/li&gt;
&lt;li&gt;View the graph of a DAG — which tasks ran, which failed, which are running right now&lt;/li&gt;
&lt;li&gt;Look at logs for individual task runs&lt;/li&gt;
&lt;li&gt;Manually trigger DAG runs or clear failed tasks to retry them&lt;/li&gt;
&lt;li&gt;Manage connections and variables&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In Airflow 3.x, the UI has been significantly revamped. It's faster, cleaner, and the new &lt;strong&gt;Grid View&lt;/strong&gt; is much more useful than the old tree view for understanding the history of a DAG across many runs.&lt;/p&gt;

&lt;p&gt;The Airflow UI is stateless with respect to actual scheduling. It doesn't schedule anything. It's purely a read/write interface to the metadata database (via the API Server). If the API Server or the UI crashes, your DAGs keep running. Your visibility goes away, but the work continues.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Executor
&lt;/h2&gt;

&lt;p&gt;The Executor is one of the most misunderstood Airflow concepts.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;Executor&lt;/strong&gt; is not a separate process you run — it's a component &lt;em&gt;within&lt;/em&gt; the Scheduler that determines how tasks actually get executed. Think of it as the strategy the Scheduler uses for dispatching work, not something you run independently.&lt;/p&gt;

&lt;p&gt;There are a few executor types:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;LocalExecutor&lt;/strong&gt; — Tasks run as subprocesses on the same machine as the Scheduler. Simple, works well for small setups. Not suitable for real scale because everything shares one machine.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CeleryExecutor&lt;/strong&gt; — Tasks are sent to a Celery task queue (backed by Redis or RabbitMQ), and picked up by Worker processes that can run on different machines. This is the classic horizontally scalable setup.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;KubernetesExecutor&lt;/strong&gt; — Each task spins up a new Kubernetes Pod to run in, and the pod is destroyed when the task finishes. Very clean isolation, great for containerized environments. More overhead per task but excellent for bursty workloads.&lt;/p&gt;

&lt;p&gt;Some deployments use hybrid execution models that combine Celery workers with Kubernetes-based execution.&lt;/p&gt;

&lt;p&gt;The choice of executor has massive implications for how you deploy and scale Airflow. Start with LocalExecutor if you're just learning. Move to CeleryExecutor or KubernetesExecutor as you grow.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Workers
&lt;/h2&gt;

&lt;p&gt;Workers are the things that actually &lt;em&gt;execute&lt;/em&gt; your task code. They're separate processes (or pods, in the Kubernetes case) that pull tasks from the queue and run them. They only exist as separate processes if you're using CeleryExecutor or KubernetesExecutor. With LocalExecutor, there isn’t a separate worker service: tasks run as subprocesses inside the scheduler machine. &lt;/p&gt;

&lt;p&gt;When a task runs on a Worker:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The Worker picks up the task from the queue&lt;/li&gt;
&lt;li&gt;It executes your Python function (or Bash command, or Spark job, or whatever the operator does)&lt;/li&gt;
&lt;li&gt;It writes the result back to the metadata database — success or failure&lt;/li&gt;
&lt;li&gt;It ships logs to wherever logs are configured to go (local filesystem, S3, GCS, etc.)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Workers don't need to know about your full DAG structure. They just need to know "run this task." All the DAG context they need is passed along with the task message.&lt;/p&gt;

&lt;p&gt;One important thing: in a multi-worker setup, every worker machine needs access to the same DAG files. Otherwise, the worker won't be able to find the code it's supposed to run. This is commonly solved by mounting a shared filesystem or using Git Sync to pull DAGs onto every worker.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Triggerer
&lt;/h2&gt;

&lt;p&gt;One component I haven't mentioned yet: the Triggerer. It's an optional process that handles deferred tasks — tasks that are waiting on some external event (like a file landing in S3, or a sensor waiting for a condition) without occupying a worker slot while they wait. It runs deferred tasks in an asyncio event loop, which is far more efficient than a worker sitting idle. If you're not using deferrable operators, you don't strictly need it — but most production setups run it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Connections and Variables
&lt;/h2&gt;

&lt;p&gt;These aren't really "architecture" in the strict sense, but they're part of the runtime infrastructure and worth understanding early.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Connections&lt;/strong&gt; are how Airflow stores credentials and connection info for external systems — your database host/port/user/password, your AWS credentials, your Snowflake account info. When you use an operator (like &lt;code&gt;PostgresOperator&lt;/code&gt; or &lt;code&gt;S3Hook&lt;/code&gt;), it looks up a named connection from the metadata database rather than you hardcoding credentials in your DAG.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Variables&lt;/strong&gt; are just key-value pairs stored in the metadata database. Useful for config values you want to change without editing DAG code.&lt;/p&gt;

&lt;p&gt;Both connections and variables can be managed through the UI, the API, or environment variables (the latter being preferred in production from a secrets management perspective).&lt;/p&gt;




&lt;h2&gt;
  
  
  Putting It All Together
&lt;/h2&gt;

&lt;p&gt;Let's see what actually happens when a scheduled DAG runs, so all these pieces connect:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Your DAG file lives in the &lt;code&gt;dags/&lt;/code&gt; folder. The &lt;strong&gt;DAG Processor&lt;/strong&gt; picks it up, parses it, and writes the DAG structure to the &lt;strong&gt;Metadata Database&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The &lt;strong&gt;Scheduler&lt;/strong&gt; wakes up (it's running in a loop, typically every few seconds). It checks the database and sees that your DAG is scheduled to run at 2:00 AM. It's now 2:00 AM. The Scheduler creates a &lt;strong&gt;DAG Run&lt;/strong&gt; record and individual &lt;strong&gt;Task Instance&lt;/strong&gt; records in the database.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The Scheduler evaluates task dependencies. Task A has no upstream dependencies, so it becomes eligible. The Scheduler flips its state to "queued" and tells the &lt;strong&gt;Executor&lt;/strong&gt; to execute it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The Executor places Task A into the &lt;strong&gt;task queue&lt;/strong&gt; (Redis/RabbitMQ when using CeleryExecutor).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;An available &lt;strong&gt;Worker&lt;/strong&gt; picks up Task A from the queue. It executes your Python function. If it succeeds, the Worker marks the Task Instance as "success" in the database.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Back to the Scheduler — on the next loop, it sees Task A is done. Task B was waiting on Task A. Now Task B's dependencies are satisfied, so it becomes eligible. The cycle repeats.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Throughout all of this, the &lt;strong&gt;API Server&lt;/strong&gt; is answering requests from your browser, showing you the DAG Run and its task states in real time.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Everything is written to the &lt;strong&gt;Metadata Database&lt;/strong&gt; along the way.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  What if one of the components is down?
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;What breaks if it's down?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Scheduler&lt;/td&gt;
&lt;td&gt;No new tasks start&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Workers&lt;/td&gt;
&lt;td&gt;Tasks stop executing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Metadata DB&lt;/td&gt;
&lt;td&gt;Entire platform affected&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;API Server&lt;/td&gt;
&lt;td&gt;UI/API operations fail&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Triggerer&lt;/td&gt;
&lt;td&gt;Deferrable tasks stop progressing&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  A Note on Airflow 3.x Specifically
&lt;/h2&gt;

&lt;p&gt;Airflow 3.x brought a bunch of changes, and if you're reading older tutorials, some things will look different. The key differences to be aware of:&lt;/p&gt;

&lt;p&gt;The DAG authoring syntax got cleaner — the &lt;code&gt;@dag&lt;/code&gt; and &lt;code&gt;@task&lt;/code&gt; decorators (from the TaskFlow API) are now first-class citizens and the recommended way to write most DAGs. The old Operator-heavy style still works but new code should prefer TaskFlow.&lt;/p&gt;

&lt;p&gt;The UI was rebuilt from scratch — it's a modern React app now, not the old Flask/Jinja UI. Grid view is the default and it's significantly better.&lt;/p&gt;

&lt;p&gt;The Scheduler and DAG Processor are now clearly separated — as described above. Relevant if you're configuring a production deployment.&lt;/p&gt;

&lt;p&gt;The REST API is now the proper interface for programmatic access — no more hacking around with CLI commands for automation.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where to Go From Here
&lt;/h2&gt;

&lt;p&gt;If you've followed this far, you understand the fundamental architecture. The next things worth digging into:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Write your first DAG using the TaskFlow API (&lt;code&gt;@dag&lt;/code&gt;, &lt;code&gt;@task&lt;/code&gt; decorators)&lt;/li&gt;
&lt;li&gt;Understand what Operators are — they're the building blocks of tasks (PythonOperator, BashOperator, and so on)&lt;/li&gt;
&lt;li&gt;Set up a local Airflow environment with Docker Compose (the official &lt;code&gt;docker-compose.yaml&lt;/code&gt; from the Airflow docs is a good start)&lt;/li&gt;
&lt;li&gt;Learn about XComs — that's how tasks pass data to each other&lt;/li&gt;
&lt;li&gt;Understand task retries and the &lt;code&gt;on_failure_callback&lt;/code&gt; — essential for production use&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The architecture seems complex at first, but once you've run Airflow a few times, you start to develop intuition for which component is causing problems when things go wrong. And things will go wrong. That's part of the fun. Good luck.&lt;/p&gt;




</description>
      <category>airflow</category>
      <category>dataengineering</category>
      <category>etl</category>
      <category>datapipeline</category>
    </item>
    <item>
      <title>How to Deploy a LangGraph Agent on AWS Bedrock AgentCore</title>
      <dc:creator>Fazalul Haque</dc:creator>
      <pubDate>Mon, 25 May 2026 15:54:19 +0000</pubDate>
      <link>https://dev.to/vfazal/how-to-deploy-a-langgraph-agent-on-aws-bedrock-agentcore-2a9p</link>
      <guid>https://dev.to/vfazal/how-to-deploy-a-langgraph-agent-on-aws-bedrock-agentcore-2a9p</guid>
      <description>&lt;p&gt;You’ve built a LangGraph agent that works fine on your laptop. The next challenge is getting it running in a scalable, serverless production infrastructure without having to redesign the whole thing.&lt;/p&gt;

&lt;p&gt;That’s where AWS Bedrock AgentCore comes in. In this guide, I’ll show you how to put a wrapper for your existing agent to make it run on AgentCore, set up an AgentCore project, test it locally, deploy it to AWS, and invoke it after deployment.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Is AWS Bedrock AgentCore?
&lt;/h2&gt;

&lt;p&gt;AgentCore is a serverless hosting platform designed by AWS to deploy, scale, and operate your AI agents securely without you managing the infrastructure. It works with any open-source framework like LangGraph, Strands, CrewAI, or LlamaIndex and supports Large Language Models like OpenAI's GPT, Google's Gemini, or Anthropic's Claude. So you don’t have to rewrite the agent logic. It also provides session isolation, persistent memory, observability and identity management.&lt;/p&gt;

&lt;p&gt;The deployment is managed through the &lt;strong&gt;AgentCore CLI&lt;/strong&gt;, a Node.js tool that scaffolds projects, runs a local dev server, and deploys to AWS using CDK under the hood.&lt;/p&gt;




&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before you start, make sure you have the following in place:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Python 3.12+&lt;/strong&gt; &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;uv&lt;/strong&gt; or &lt;strong&gt;pip&lt;/strong&gt; for Python dependency management&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Node.js 20+&lt;/strong&gt; — the AgentCore CLI is an npm package&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS CDK&lt;/strong&gt; installed globally (&lt;code&gt;npm install -g aws-cdk&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;An &lt;strong&gt;AWS account&lt;/strong&gt; with credentials configured locally (&lt;code&gt;aws configure&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Your existing LangGraph agent code that creates a compiled StateGraph object&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Step 1 — Install the AgentCore CLI
&lt;/h2&gt;

&lt;p&gt;The AgentCore workflow starts with a single command-line tool. You’ll use it to create the project, run the app locally, and deploy it to AWS cloud.&lt;/p&gt;

&lt;p&gt;Install the CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; @aws/agentcore
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify it after the installation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;agentcore &lt;span class="nt"&gt;--help&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 2 — Add Additional Dependencies
&lt;/h2&gt;

&lt;p&gt;Add the following packages to your agent's dependencies:&lt;br&gt;
    - &lt;code&gt;bedrock-agentcore&lt;/code&gt; - the Python SDK that provides the &lt;code&gt;BedrockAgentCoreApp&lt;/code&gt; wrapper class.&lt;br&gt;
    - &lt;code&gt;aws-opentelemetry-distro&lt;/code&gt; - AWS-supported distribution of the OpenTelemetry Python Instrumentation package.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="c"&gt;# pyproject.toml&lt;/span&gt;
&lt;span class="nn"&gt;[project]&lt;/span&gt;
&lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"my-agent"&lt;/span&gt;
&lt;span class="py"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0.1.0"&lt;/span&gt;
&lt;span class="py"&gt;requires-python&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="py"&gt;"&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;3.12&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;
&lt;span class="py"&gt;dependencies&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="py"&gt;"aws-opentelemetry-distro=&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.17&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="s"&gt;",&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;    &lt;span class="py"&gt;"bedrock-agentcore&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.6&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="s"&gt;",&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;    &lt;span class="py"&gt;"boto3&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.42&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="s"&gt;",&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;    &lt;span class="py"&gt;"langgraph&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.1&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="s"&gt;",&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;    &lt;span class="py"&gt;"langchain-core&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.2&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="s"&gt;",&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;    &lt;span class="c"&gt;# ... your other existing dependencies&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Install all dependencies with your usual workflow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv &lt;span class="nb"&gt;sync&lt;/span&gt;
&lt;span class="c"&gt;# or: pip install -e .&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 3 — Write the AgentCore Entrypoint (&lt;code&gt;main.py&lt;/code&gt;)
&lt;/h2&gt;

&lt;p&gt;AgentCore expects a single Python file as the entrypoint with a &lt;code&gt;BedrockAgentCoreApp&lt;/code&gt; instance and a function decorated with &lt;code&gt;@app.entrypoint&lt;/code&gt;. The important part is that your LangGraph logic does not need to change much; you’re just wrapping it in a small runtime entrypoint. &lt;/p&gt;

&lt;p&gt;Here is the basic structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# main.py
&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;langchain_core.messages&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;HumanMessage&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;bedrock_agentcore.runtime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BedrockAgentCoreApp&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;graphs.my_agent_graph&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;build_my_agent&lt;/span&gt;  &lt;span class="c1"&gt;# your existing graph builder
&lt;/span&gt;
&lt;span class="c1"&gt;# 1. Instantiate the AgentCore app and logger
&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BedrockAgentCoreApp&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;log&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt;

&lt;span class="c1"&gt;# 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.
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_agent&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Initialising agent...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;graph&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;build_my_agent&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;   &lt;span class="c1"&gt;# returns your compiled LangGraph StateGraph
&lt;/span&gt;    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Agent ready&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;graph&lt;/span&gt;

&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;graph&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;create_agent&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Critical failure during agent initialisation: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt;  &lt;span class="c1"&gt;# fail fast — don't let a broken agent start serving requests
&lt;/span&gt;
&lt;span class="c1"&gt;# 3. Async helper that drives the LangGraph streaming loop
&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run_agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;default-session&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;responses&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;configurable&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;thread_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;graph&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;astream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;messages&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;HumanMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;user_input&lt;/span&gt;&lt;span class="p"&gt;)]},&lt;/span&gt;
        &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;stream_mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;values&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;messages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;messages&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;last&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;last&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ai&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;responses&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;last&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;responses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;No response&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;responses&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# 4. The entrypoint — this is what AgentCore calls on every invocation
&lt;/span&gt;&lt;span class="nd"&gt;@app.entrypoint&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Invoke received&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;user_input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prompt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;session_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;session_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;default-session&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;user_input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Prompt cannot be empty&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;run_agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;response&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Error: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;

&lt;span class="c1"&gt;# 5. Run the app (only executed when running locally using agentcore dev)
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  A few things that matter
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;BedrockAgentCoreApp()&lt;/code&gt;&lt;/strong&gt; gives you the AgentCore runtime wrapper. It sets up the HTTP server, health check endpoints, and structured logging for you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;Module-level graph initialisation&lt;/code&gt;&lt;/strong&gt; — the graph is created once when the module loads, not on every request. That catches startup failures early, which is good because it prevents a broken app from going live.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;@app.entrypoint&lt;/code&gt;&lt;/strong&gt; — this decorator registers the function as the handler for incoming invocations. It receives &lt;code&gt;payload&lt;/code&gt; (the parsed JSON body) and &lt;code&gt;context&lt;/code&gt; (AgentCore request context). It should return a plain JSON-serializable dictionary, not a raw string or a custom object.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;session_id&lt;/code&gt; → &lt;code&gt;thread_id&lt;/code&gt;&lt;/strong&gt; — The session_id is passed into LangGraph as thread_id, which enables per-session memory with &lt;code&gt;MemorySaver&lt;/code&gt;. &lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4 — Create an AgentCore Project
&lt;/h2&gt;

&lt;p&gt;Next, we need to generate our project layout. Run the initialization wizard inside a clean root folder and let it create the basic project layout for you.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;agentcore create
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The setup wizard will ask a few simple questions, like the project name, language, Python version, entrypoint file, etc. The important part is that the entrypoint and the Python version matches your app.&lt;/p&gt;

&lt;p&gt;After the project is created, you’ll get an agentcore.json file (in agentcore folder) that tells AgentCore where your code lives and how to run it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"MyAgentOnAgentcore"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"runtimes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"my-agent"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"build"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"CodeZip"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"entrypoint"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"main.py"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"codeLocation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"app/my-agent/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"runtimeVersion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PYTHON_3_12"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"networkMode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PUBLIC"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"protocol"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"HTTP"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"envVars"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 5 — Put Your Source Code to the Code Location
&lt;/h2&gt;

&lt;p&gt;Copy or move your agent source into the directory specified by &lt;code&gt;codeLocation&lt;/code&gt; in &lt;code&gt;agentcore.json&lt;/code&gt;.  This part is easy to get wrong, and when it’s wrong, deployment becomes unnecessarily frustrating. I struggled for a while after accidentally putting it in the agentcore folder.&lt;/p&gt;

&lt;p&gt;A typical layout should look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;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
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make sure &lt;code&gt;main.py&lt;/code&gt; sits at the top level of &lt;code&gt;codeLocation&lt;/code&gt; and matches the entrypoint in &lt;code&gt;agentcore.json&lt;/code&gt; exactly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Environment variables
&lt;/h3&gt;

&lt;p&gt;AgentCore can pass environment variables into the runtime container. For local testing, a &lt;code&gt;.env&lt;/code&gt; file is usually the easiest option.&lt;/p&gt;

&lt;p&gt;For production, you can put values in &lt;code&gt;agentcore.json&lt;/code&gt; under &lt;code&gt;envVars&lt;/code&gt; (&lt;code&gt;.env&lt;/code&gt; file also works). But secrets like passwords and API keys are better stored in AWS Secrets Manager and loaded at runtime. If you do that, the AgentCore execution role needs permission to read those secrets.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; &lt;code&gt;envVars&lt;/code&gt; should be an array of JSON objects with &lt;code&gt;name&lt;/code&gt; and &lt;code&gt;value&lt;/code&gt; fields.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 6 — Validate the Project
&lt;/h2&gt;

&lt;p&gt;Before you try to run anything, use AgentCore CLI to validate the project configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;agentcore validate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This catches the common mistakes early, like a missing entrypoint file or a bad path in the config. &lt;/p&gt;




&lt;h2&gt;
  
  
  Step 7 — Run and Test Locally
&lt;/h2&gt;

&lt;p&gt;Start the local runtime to test the agent locally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;agentcore dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This spins up a local HTTP server that closely mirrors the production environment for testing. If everything is wired up properly, you should see that the app is ready and listening on a local port.&lt;/p&gt;

&lt;h3&gt;
  
  
  Test on Local Server
&lt;/h3&gt;

&lt;p&gt;You can test an invocation using either the AgentCore CLI or curl:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;agentcore invoke &lt;span class="s2"&gt;"Summarise last month sales"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:8080/invocations &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"prompt": "Summarise last month sales", "session_id": "test-001"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A successful response should come back as JSON with the agent’s answer in it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"response"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Last month, total sales were $1.2M across 3 regions..."&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 8 — Deploy to AWS
&lt;/h2&gt;

&lt;p&gt;Once local testing looks solid, deploy to AWS Bedrock AgentCore Runtime:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;agentcore deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Under the hood, the CLI:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Packages your code&lt;/li&gt;
&lt;li&gt;Provisions an S3 bucket for direct code deploy&lt;/li&gt;
&lt;li&gt;Creates an IAM execution role&lt;/li&gt;
&lt;li&gt;Deploys the AgentCore Runtime via CDK&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The first deploy takes a few minutes. Subsequent deploys tend to be faster.&lt;/p&gt;

&lt;p&gt;When it’s successfully completed, check the runtime status. It will display the runtime ARN and HTTP URL to invoke the deployed agent.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;agentcore status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 9 — Invoke the Deployed Agent
&lt;/h2&gt;

&lt;p&gt;The easiest way to test the deployed version is with the CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;agentcore invoke &lt;span class="nt"&gt;--prompt&lt;/span&gt; &lt;span class="s2"&gt;"Who are the top 5 customers by revenue?"&lt;/span&gt; &lt;span class="nt"&gt;--session-id&lt;/span&gt; &lt;span class="s2"&gt;"session-id-with-length-greater-than-or-equal-33"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you want to call it using HTTP, you’ll need to sign the request with AWS SigV4.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s2"&gt;"https://bedrock-agentcore.us-east-1.amazonaws.com/runtimes/arn-of-MyAgentOnAgentcore-xxxx/invocations"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--user&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$AWS_ACCESS_KEY_ID&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;$AWS_SECRET_ACCESS_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--aws-sigv4&lt;/span&gt; &lt;span class="s2"&gt;"aws:amz:us-east-1:bedrock-agentcore"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"x-amzn-bedrock-agentcore-runtime-session-id: session-id-with-length-greater-than-or-equal-33"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"prompt": "Who are the top 5 customers by revenue?"}'&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use the HTTP URL displayed by &lt;code&gt;agentcore status&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;When integrating your agent with other Python applications, using the SDK is usually the cleanest approach. You can keep the runtime ARN in an environment variable, send the prompt, and pass the same session ID back on follow-up requests.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# invoke_agent.py
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dotenv&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;load_dotenv&lt;/span&gt;

&lt;span class="nf"&gt;load_dotenv&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bedrock-agentcore&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;region_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;us-east-1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;runtime_arn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;AGENTCORE_RUNTIME_ARN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prompt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;session_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invoke_agent_runtime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;agentRuntimeArn&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;runtime_arn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;contentType&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;application/json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;response&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;response&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;session_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Ready. Type &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;exit&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; to quit.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Your question: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;exit&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;break&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;

        &lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;agentRuntimeArn&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;runtime_arn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;payload&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prompt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;}),&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;contentType&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;application/json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;runtimeSessionId&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;session_id&lt;/span&gt;

        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invoke_agent_runtime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;response&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
            &lt;span class="n"&gt;session_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;runtimeSessionId&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Agent: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;response&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Error: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set &lt;code&gt;AGENTCORE_RUNTIME_ARN&lt;/code&gt; in your &lt;code&gt;.env&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;AGENTCORE_RUNTIME_ARN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;arn:aws:bedrock-agentcore:us-east-1:123456789012:agent-runtime/MyAgentOnAgentcore-xxxx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Maintaining session state across invocations
&lt;/h3&gt;

&lt;p&gt;Notice &lt;code&gt;runtimeSessionId&lt;/code&gt; in the boto3 example. AgentCore returns a &lt;code&gt;runtimeSessionId&lt;/code&gt; in every response. Passing it back in the next request tells AgentCore to route subsequent calls to the same session context — which, combined with LangGraph's &lt;code&gt;MemorySaver&lt;/code&gt;, gives the agent full conversation memory across multiple HTTP calls.&lt;/p&gt;




&lt;h2&gt;
  
  
  Common Gotchas
&lt;/h2&gt;

&lt;p&gt;Startup failures are intentional. If &lt;code&gt;create_agent()&lt;/code&gt; raises at module load, AgentCore will refuse to start the runtime. This is a feature, not a bug — it prevents an incorrectly configured agent (missing credentials, wrong DB URL) from going live silently.&lt;/p&gt;

&lt;p&gt;The execution role AgentCore creates needs explicit permissions for any AWS service your agent touches, including Secrets Manager and S3. Add those permissions after the first deploy.&lt;/p&gt;

&lt;p&gt;The Python version in &lt;code&gt;pyproject.toml&lt;/code&gt; should match the runtime version in &lt;code&gt;agentcore.json&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The entrypoint should return a JSON-serialisable dictionary. If it returns a plain string or a custom object, the runtime boundary will reject it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The transition from a local LangGraph agent to an AgentCore deployment really comes down to a few practical changes: add the right dependencies, wrap the graph in BedrockAgentCoreApp, scaffold the project, test locally, then deploy and invoke it.&lt;/p&gt;

&lt;p&gt;Everything else — the graph, the tools, the model calls, and the memory setup — stays mostly the same. AgentCore handles the runtime side, and LangGraph handles the agent logic.&lt;/p&gt;

&lt;p&gt;A full working example is available at &lt;a href="https://github.com/thedataengr/data-agent-on-aws-agentcore" rel="noopener noreferrer"&gt;github.com/thedataengr/data-agent-on-aws-agentcore&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>agents</category>
      <category>aws</category>
      <category>serverless</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
