{"id":94763,"date":"2026-10-08T16:32:30","date_gmt":"2026-10-08T14:32:30","guid":{"rendered":"https:\/\/www.skaylink.com\/?p=94763"},"modified":"2026-10-08T16:32:30","modified_gmt":"2026-10-08T14:32:30","slug":"getting-started-with-aws-agentcore","status":"publish","type":"post","link":"https:\/\/www.skaylink.com\/en\/insights\/blog\/getting-started-with-aws-agentcore\/","title":{"rendered":"Getting started with AWS AgentCore"},"content":{"rendered":"\t\t<div data-elementor-type=\"wp-post\" data-elementor-id=\"94763\" class=\"elementor elementor-94763\" data-elementor-post-type=\"post\">\n\t\t\t\t<div class=\"elementor-element elementor-element-407c07e5 header-keyvisual-container e-flex e-con-boxed e-con e-parent\" data-id=\"407c07e5\" data-element_type=\"container\" data-e-type=\"container\" data-settings=\"{&quot;background_background&quot;:&quot;classic&quot;}\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-5d886fd5 elementor-align-left elementor-widget elementor-widget-breadcrumbs\" data-id=\"5d886fd5\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"breadcrumbs.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<p id=\"breadcrumbs\"><span><span><a href=\"https:\/\/www.skaylink.com\/en\/\">Home<\/a><\/span><\/span><\/p>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-169f44b e-con-full e-flex e-con e-child\" data-id=\"169f44b\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t<div class=\"elementor-element elementor-element-12cca5bd header-keyvisual-content e-con-full e-flex e-con e-child\" data-id=\"12cca5bd\" data-element_type=\"container\" data-e-type=\"container\" data-settings=\"{&quot;background_background&quot;:&quot;classic&quot;}\">\n\t\t\t\t<div class=\"elementor-element elementor-element-2cfe5f4a elementor-widget elementor-widget-image\" data-id=\"2cfe5f4a\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"image.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<img decoding=\"async\" width=\"2560\" height=\"1707\" src=\"https:\/\/www.skaylink.com\/wp-content\/uploads\/2026\/10\/MG_4866-johanna-lohr-fotografie-skaylink-scaled.jpg\" class=\"attachment-full size-full wp-image-94772\" alt=\"\" srcset=\"https:\/\/www.skaylink.com\/wp-content\/uploads\/2026\/10\/MG_4866-johanna-lohr-fotografie-skaylink-scaled.jpg 2560w, https:\/\/www.skaylink.com\/wp-content\/uploads\/2026\/10\/MG_4866-johanna-lohr-fotografie-skaylink-300x200.jpg 300w, https:\/\/www.skaylink.com\/wp-content\/uploads\/2026\/10\/MG_4866-johanna-lohr-fotografie-skaylink-1024x683.jpg 1024w, https:\/\/www.skaylink.com\/wp-content\/uploads\/2026\/10\/MG_4866-johanna-lohr-fotografie-skaylink-768x512.jpg 768w, https:\/\/www.skaylink.com\/wp-content\/uploads\/2026\/10\/MG_4866-johanna-lohr-fotografie-skaylink-1536x1024.jpg 1536w, https:\/\/www.skaylink.com\/wp-content\/uploads\/2026\/10\/MG_4866-johanna-lohr-fotografie-skaylink-2048x1365.jpg 2048w\" sizes=\"(max-width: 2560px) 100vw, 2560px\" \/>\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-67ee9e2e e-con-full e-flex e-con e-child\" data-id=\"67ee9e2e\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-69caeba8 elementor-widget elementor-widget-text-editor\" data-id=\"69caeba8\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p>Blog<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-78855362 elementor-widget elementor-widget-heading\" data-id=\"78855362\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<h1 class=\"elementor-heading-title elementor-size-default\">Getting started with AWS AgentCore<\/h1>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-33749e53 elementor-widget elementor-widget-text-editor\" data-id=\"33749e53\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\tLearn how to develop, deploy, and test a scalable AI agent using AWS AgentCore and Terraform.\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-767b246a e-flex e-con-boxed e-con e-parent\" data-id=\"767b246a\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t<div class=\"elementor-element elementor-element-4943555c e-flex e-con-boxed e-con e-child\" data-id=\"4943555c\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t<div class=\"elementor-element elementor-element-16e29517 e-flex e-con-boxed e-con e-child\" data-id=\"16e29517\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-6aac6ab6 elementor-widget elementor-widget-text-editor\" data-id=\"6aac6ab6\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\tOctober 8, 2026\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-36a3e580 e-flex e-con-boxed e-con e-child\" data-id=\"36a3e580\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t<div class=\"elementor-element elementor-element-7de1387d e-flex e-con-boxed e-con e-child\" data-id=\"7de1387d\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-59006115 elementor-author-box--image-valign-middle elementor-widget elementor-widget-author-box\" data-id=\"59006115\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"author-box.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t<div class=\"elementor-author-box\">\n\t\t\t\t\t\t\t<div  class=\"elementor-author-box__avatar\">\n\t\t\t\t\t<img decoding=\"async\" src=\"https:\/\/www.skaylink.com\/wp-content\/uploads\/2025\/09\/Screenshot_20240710_101648-219x300.png\" alt=\"Picture of Claudia M\u00fcller\" loading=\"lazy\">\n\t\t\t\t<\/div>\n\t\t\t\n\t\t\t<div class=\"elementor-author-box__text\">\n\t\t\t\t\t\t\t\t\t<div >\n\t\t\t\t\t\t<span class=\"elementor-author-box__name\">\n\t\t\t\t\t\t\tClaudia M\u00fcller\t\t\t\t\t\t<\/span>\n\t\t\t\t\t<\/div>\n\t\t\t\t\n\t\t\t\t\t\t\t\t\t<div class=\"elementor-author-box__bio\">\n\t\t\t\t\t\t<p>Senior AWS Consultant<\/p>\n\t\t\t\t\t<\/div>\n\t\t\t\t\n\t\t\t\t\t\t\t<\/div>\n\t\t<\/div>\n\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-540b7c46 e-flex e-con-boxed e-con e-child\" data-id=\"540b7c46\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-117f44f7 elementor-widget elementor-widget-author-box\" data-id=\"117f44f7\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"author-box.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t<div class=\"elementor-author-box\">\n\t\t\t\n\t\t\t<div class=\"elementor-author-box__text\">\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\t\t\t<\/div>\n\t\t<\/div>\n\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-7be3aae5 e-flex e-con-boxed e-con e-parent\" data-id=\"7be3aae5\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t<div class=\"elementor-element elementor-element-21794327 e-con-full e-flex e-con e-child\" data-id=\"21794327\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-3afe5a39 elementor-widget elementor-widget-heading\" data-id=\"3afe5a39\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<h2 class=\"elementor-heading-title elementor-size-default\">What is an AI Agent?<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-6ed6b542 elementor-widget elementor-widget-text-editor\" data-id=\"6ed6b542\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p>An AI agent is a program that uses a large language model (LLM) not just to answer questions, but to <strong>take actions<\/strong>. Instead of generating text and stopping, an agent can decide to call external functions \u2014 query a database, fetch a file, call an API \u2014 and then use the results to formulate its final answer.<\/p><p>The basic loop looks like this:<\/p><ol><li>User sends a question.<\/li><li>The LLM reads the question and the list of available tools.<\/li><li>The LLM decides: can I answer directly, or do I need to call a tool first?<\/li><li>If it calls a tool, it receives the tool&#8217;s output and continues reasoning.<\/li><li>Steps 3\u20134 repeat until the LLM has enough information to respond.<\/li><li>The LLM produces a final answer for the user.<\/li><\/ol><p>This &#8220;reason \u2192 act \u2192 observe \u2192 repeat&#8221; loop is what separates an agent from a simple chatbot. The LLM becomes the decision-maker that orchestrates tool calls autonomously.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-c6867a1 elementor-widget elementor-widget-heading\" data-id=\"c6867a1\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<h2 class=\"elementor-heading-title elementor-size-default\">AgentCore vs Bedrock Agents: Why a New Service?<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-33dd20d4 elementor-widget elementor-widget-text-editor\" data-id=\"33dd20d4\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p>AWS is retiring <strong>Bedrock Agents<\/strong>, which was already a fully managed agent service where you configure tools via the console, attach action groups, and AWS handles orchestration. So why a new service? Bedrock Agents works well for standard use cases, but the orchestration logic is a black box. You can&#8217;t change how the agent loops, which framework it uses, or how tools are routed.<\/p><p><strong>Bedrock AgentCore<\/strong> is the infrastructure layer underneath. It gives you:<\/p><ul><li><strong>Your own container<\/strong> \u2014 run any agent framework (Strands, LangChain, CrewAI, or custom code)<\/li><li><strong>Full orchestration control<\/strong> \u2014 custom hooks, iteration limits, short-circuit logic, anything<\/li><li><strong>MCP protocol for tools<\/strong> \u2014 the open Model Context Protocol standard, not a proprietary format<\/li><li><strong>Managed compute<\/strong> \u2014 you don&#8217;t run servers; AWS scales the containers for you<\/li><\/ul><table width=\"760\"><tbody><tr><td><p>\u00a0<\/p><\/td><td><p>Bedrock Agents<\/p><\/td><td><p>Bedrock AgentCore<\/p><\/td><\/tr><tr><td><p><strong>Orchestration<\/strong><\/p><\/td><td><p>AWS-managed (fixed)<\/p><\/td><td><p>Your code, any framework<\/p><\/td><\/tr><tr><td><p><strong>Tool protocol<\/strong><\/p><\/td><td><p>Action Groups (OpenAPI)<\/p><\/td><td><p>MCP (open standard)<\/p><\/td><\/tr><tr><td><p><strong>Deployment<\/strong><\/p><\/td><td><p>Config-only (no container)<\/p><\/td><td><p>Your Docker image<\/p><\/td><\/tr><tr><td><p><strong>Customization<\/strong><\/p><\/td><td><p>Console knobs<\/p><\/td><td><p>Unlimited \u2014 it&#8217;s your code<\/p><\/td><\/tr><\/tbody><\/table>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-a755311 elementor-widget elementor-widget-heading\" data-id=\"a755311\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<h2 class=\"elementor-heading-title elementor-size-default\">AgentCore Components Explained<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-533b2681 elementor-widget elementor-widget-text-editor\" data-id=\"533b2681\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p>AgentCore might look overwhelming with all it&#8217;s different parts, so here I&#8217;m just focusing on the bare minimum of resources you need to get an agent up and running. AgentCore has three core components. Understanding what each one does (and doesn&#8217;t do) is the key to getting your first agent running.<\/p><ol><li><strong> The Runtime<\/strong><\/li><\/ol><p>The <strong>Runtime<\/strong> is your Docker container running on AWS-managed compute. It&#8217;s where your agent logic lives \u2014 the Python code, the LLM calls, the orchestration loop.<\/p><p>Think of it as a Fargate task that AWS manages for you: it pulls your image from ECR, starts the container, and routes incoming invocations to it. You don&#8217;t manage instances, scaling, or networking (unless you choose VPC mode for private resources).<\/p><p><strong>What it does:<\/strong><\/p><ul><li>Runs your agent code (any language, any framework)<\/li><li>Receives invocations via the invoke-agent-runtime API (IAM-authenticated)<\/li><li>Has an IAM role that lets it call Bedrock (for the LLM) and the Gateway (for tools)<\/li><\/ul><p><strong>What it doesn&#8217;t do:<\/strong><\/p><ul><li>It doesn&#8217;t decide which tools to call \u2014 that&#8217;s your LLM<\/li><li>It doesn&#8217;t route tool calls \u2014 that&#8217;s the Gateway<\/li><li>It doesn&#8217;t execute tools \u2014 that&#8217;s the tool backend (Lambda, an API, or another service)<\/li><\/ul><p><strong>Configuration that matters:<\/strong><\/p><ul><li>server_protocol = &#8220;HTTP&#8221; \u2014 your container is an HTTP server, not an MCP server<\/li><li>network_mode = &#8220;PUBLIC&#8221; \u2014 AWS handles outbound internet; use &#8220;VPC&#8221; only if you need private network access<\/li><li>Container must be ARM64 architecture<\/li><\/ul><ol start=\"2\"><li><strong> The Gateway<\/strong><\/li><\/ol><p>The <strong>Gateway<\/strong> is an MCP (Model Context Protocol) endpoint that routes tool calls from your Runtime to the backends that execute them. It&#8217;s the router between &#8220;the LLM decided to call a tool&#8221; and &#8220;the tool actually runs.&#8221;<\/p><p>When your agent&#8217;s MCP client sends a tool call, the Gateway:<\/p><ol><li>Verifies the request (AWS_IAM SigV4 authentication)<\/li><li>Looks up which target matches the tool name<\/li><li>Invokes that target&#8217;s backend with the tool&#8217;s parameters<\/li><li>Returns the response back to your agent<\/li><\/ol><p><strong>What it does:<\/strong><\/p><ul><li>MCP protocol endpoint (tool discovery + tool execution)<\/li><li>Routes each tool name to the correct backend (Lambda, an external MCP server, or other AWS services)<\/li><li>Authenticates every request using IAM<\/li><\/ul><p><strong>What it doesn&#8217;t do:<\/strong><\/p><ul><li>It doesn&#8217;t decide which tool to call \u2014 that&#8217;s the LLM<\/li><li>It doesn&#8217;t run tool logic \u2014 that&#8217;s whatever backend the target points to<\/li><li>It has no knowledge of your agent&#8217;s conversation or state<\/li><\/ul><ol start=\"3\"><li><strong> Gateway Targets<\/strong><\/li><\/ol><p>A <strong>Gateway Target<\/strong> is the binding between a tool name and a backend that executes it. For each tool you want your agent to use, you create one target that says: &#8220;when the MCP client calls tool X, route it to backend Y.&#8221;<\/p><p>The backend doesn&#8217;t have to be a Lambda function, Gateway Targets support multiple target types. Lambda is the most common for custom logic, but you can also point targets at other MCP-compatible endpoints or AWS services. In this tutorial we use Lambda because it&#8217;s the simplest way to run custom code without managing servers, but keep in mind that the Gateway is a generic MCP router, not a Lambda-specific feature.<\/p><p>Each target includes a <strong>tool schema<\/strong> \u2014 the name, description, and input parameters that get advertised to the LLM. When your agent calls list_tools_sync(), it receives these schemas, and that&#8217;s how the LLM knows what tools are available and what they do.<\/p><p><strong>Pattern for Lambda targets:<\/strong> one Lambda, many aliases, many targets. You don&#8217;t need a separate Lambda per tool \u2014 use aliases (alias name = tool name) and dispatch inside the handler.<\/p><p><strong>What We&#8217;re Building in this Tutorial<\/strong><\/p><p>A minimal agent that:<\/p><ul><li>Runs Claude Sonnet in a container managed by AgentCore<\/li><li>Has one MCP tool (GetCakeRecipe) backed by a Lambda function<\/li><li>Deploys entirely via terraform apply<\/li><\/ul><p>Ask it <em>&#8220;Can you give me a cake recipe?&#8221;<\/em> and it calls the tool, gets a Schwarzw\u00e4lder Kirschtorte recipe, and formats the answer. Ask it anything else and it answers from its own knowledge without calling the tool.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-3eed2aab elementor-widget elementor-widget-heading\" data-id=\"3eed2aab\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<h2 class=\"elementor-heading-title elementor-size-default\">Architecture<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-087b441 elementor-widget elementor-widget-code-highlight\" data-id=\"087b441\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"code-highlight.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t<div class=\"prismjs-default copy-to-clipboard \">\n\t\t\t<pre data-line=\"\" class=\"highlight-height language-javascript line-numbers\">\n\t\t\t\t<code readonly=\"true\" class=\"language-javascript\">\n\t\t\t\t\t<xmp>invoke-agent-runtime (AWS API, SigV4-signed)\r\n        \u2502\r\n        \u25bc\r\nAgentCore Runtime (your Docker container)\r\n  \u2514\u2500 Strands Agent + Claude Sonnet via Bedrock\r\n        \u2502\r\n        \u2502  MCP over HTTPS (SigV4-signed automatically)\r\n        \u25bc\r\nAgentCore Gateway (AWS_IAM auth, MCP protocol)\r\n        \u2502\r\n        \u2502  Invokes tool backend\r\n        \u25bc\r\nTool Backend (Lambda function in this example)\r\n  \u2514\u2500 Returns static recipe JSON\r\n<\/xmp>\n\t\t\t\t<\/code>\n\t\t\t<\/pre>\n\t\t<\/div>\n\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-bc3d832 elementor-widget elementor-widget-heading\" data-id=\"bc3d832\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<h2 class=\"elementor-heading-title elementor-size-default\">The Python Code in Detail<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-6f79db3b elementor-widget elementor-widget-text-editor\" data-id=\"6f79db3b\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<h4><strong>The Tool Function<\/strong><\/h4><p>The simplest possible tool receives whatever parameters the LLM passes and returns a JSON response:<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-fdb53ad elementor-widget elementor-widget-code-highlight\" data-id=\"fdb53ad\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"code-highlight.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t<div class=\"prismjs-default copy-to-clipboard \">\n\t\t\t<pre data-line=\"\" class=\"highlight-height language-javascript line-numbers\">\n\t\t\t\t<code readonly=\"true\" class=\"language-javascript\">\n\t\t\t\t\t<xmp># src\/lambda\/tool\/handler.py\r\ndef handler(event, context):\r\n    return {\r\n        \"status\": \"ok\",\r\n        \"recipe\": {\r\n            \"name\": \"Oma's Schwarzw\u00e4lder Kirschtorte\",\r\n            \"servings\": 12,\r\n            \"ingredients\": [\r\n                \"200g dark chocolate\", \"200g butter\", \"200g sugar\",\r\n                \"5 eggs\", \"150g flour\", \"2 tsp baking powder\",\r\n                \"500ml heavy cream\", \"3 tbsp kirsch (cherry brandy)\",\r\n                \"1 jar sour cherries (drained)\", \"chocolate shavings\",\r\n            ],\r\n            \"steps\": [\r\n                \"Melt chocolate and butter, let cool.\",\r\n                \"Beat eggs and sugar until fluffy, fold in chocolate.\",\r\n                \"Fold in flour + baking powder. Bake 175\u00b0C, 25 min.\",\r\n                \"Slice into 3 layers. Whip cream with kirsch.\",\r\n                \"Layer: cake, cream, cherries. Repeat. Decorate.\",\r\n            ],\r\n        },\r\n    }\r\n<\/xmp>\n\t\t\t\t<\/code>\n\t\t\t<\/pre>\n\t\t<\/div>\n\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-48f51442 elementor-widget elementor-widget-text-editor\" data-id=\"48f51442\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p>In a real agent, this function would query a database, call an API, or do anything you need. We use a Lambda function here because it&#8217;s the simplest serverless option, but Gateway Targets can also point to other backends. The contract is the same regardless: receive parameters, return JSON.<\/p><h4><strong>The Agent Container (app.py)<\/strong><\/h4><p>This is the brain. Let&#8217;s walk through each piece:<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-66e7e0d elementor-widget elementor-widget-code-highlight\" data-id=\"66e7e0d\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"code-highlight.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t<div class=\"prismjs-default copy-to-clipboard \">\n\t\t\t<pre data-line=\"\" class=\"highlight-height language-javascript line-numbers\">\n\t\t\t\t<code readonly=\"true\" class=\"language-javascript\">\n\t\t\t\t\t<xmp>app = BedrockAgentCoreApp()\r\n_agent = None\r\n<\/xmp>\n\t\t\t\t<\/code>\n\t\t\t<\/pre>\n\t\t<\/div>\n\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-5070ae9c elementor-widget elementor-widget-text-editor\" data-id=\"5070ae9c\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p>The app is the HTTP server AgentCore talks to. The _agent is cached globally so we don&#8217;t rebuild it (and re-discover tools) on every invocation \u2014 the agent&#8217;s MCP connection persists across requests within the same container lifetime.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-8c8da16 elementor-widget elementor-widget-code-highlight\" data-id=\"8c8da16\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"code-highlight.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t<div class=\"prismjs-default copy-to-clipboard \">\n\t\t\t<pre data-line=\"\" class=\"highlight-height language-javascript line-numbers\">\n\t\t\t\t<code readonly=\"true\" class=\"language-javascript\">\n\t\t\t\t\t<xmp>def _get_agent():\r\n    global _agent\r\n    if _agent is not None:\r\n        return _agent\r\n\r\n    mcp_tools = []\r\n    if GATEWAY_URL:\r\n        mcp = MCPClient(lambda: aws_iam_streamablehttp_client(\r\n            endpoint=GATEWAY_URL,\r\n            aws_region=AWS_REGION,\r\n            aws_service=\"bedrock-agentcore\",\r\n        ))\r\n        mcp.start()\r\n        mcp_tools = list(mcp.list_tools_sync())\r\n\r\n    _agent = Agent(\r\n        model=model,\r\n        tools=mcp_tools,\r\n        system_prompt=\"You are a helpful assistant. Use the GetCakeRecipe tool when the user asks for a recipe.\",\r\n    )\r\n    return _agent\r\n<\/xmp>\n\t\t\t\t<\/code>\n\t\t\t<\/pre>\n\t\t<\/div>\n\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-19c72d3f elementor-widget elementor-widget-text-editor\" data-id=\"19c72d3f\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p>MCPClient takes a factory function (the lambda:) that creates the transport connection. This is lazy \u2014 the actual HTTPS connection to the Gateway is only established when mcp.start() is called.<\/p><p>mcp.start() opens the connection and keeps it alive for subsequent calls.<\/p><p>mcp.list_tools_sync() asks the Gateway &#8220;what tools do you have?&#8221; and gets back the tool schemas you defined in Terraform. This is how the agent discovers tools dynamically \u2014 add a new tool in Terraform, and the agent picks it up on next startup without a code change.<\/p><p>Agent(&#8230;) wires everything together: the LLM (model), the available actions (tools), and the behavioral instructions (system_prompt). The system prompt tells the LLM <em>when<\/em> to use the tool \u2014 without it, the LLM might answer recipe questions from its own training data instead of calling GetCakeRecipe.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-7a86504 elementor-widget elementor-widget-code-highlight\" data-id=\"7a86504\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"code-highlight.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t<div class=\"prismjs-default copy-to-clipboard \">\n\t\t\t<pre data-line=\"\" class=\"highlight-height language-javascript line-numbers\">\n\t\t\t\t<code readonly=\"true\" class=\"language-javascript\">\n\t\t\t\t\t<xmp>@app.entrypoint\r\ndef invoke_agent(payload: dict):\r\n    prompt = payload.get(\"prompt\", \"\")\r\n    agent = _get_agent()\r\n    result = agent(prompt)\r\n    return {\"result\": str(result)}\r\n<\/xmp>\n\t\t\t\t<\/code>\n\t\t\t<\/pre>\n\t\t<\/div>\n\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-de21afc elementor-widget elementor-widget-text-editor\" data-id=\"de21afc\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p>@app.entrypoint registers this function as the handler for incoming invocations. When someone calls invoke-agent-runtime, AgentCore delivers the payload here.<\/p><p>agent(prompt) \u2014 this single line triggers the full agent loop:<\/p><ol><li>The prompt is sent to Claude along with the tool schemas<\/li><li>Claude decides whether to call GetCakeRecipe or answer directly<\/li><li>If it calls the tool, the MCP client sends the request to the Gateway, the Gateway invokes the tool backend, and the result comes back<\/li><li>Claude sees the tool result and formulates its final answer<\/li><li>The result object contains the final text<\/li><\/ol><h4><strong>Dockerfile<\/strong><\/h4>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-53534d5 elementor-widget elementor-widget-code-highlight\" data-id=\"53534d5\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"code-highlight.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t<div class=\"prismjs-default copy-to-clipboard \">\n\t\t\t<pre data-line=\"\" class=\"highlight-height language-javascript line-numbers\">\n\t\t\t\t<code readonly=\"true\" class=\"language-javascript\">\n\t\t\t\t\t<xmp>FROM python:3.12-slim\r\nWORKDIR \/app\r\nRUN apt-get update && apt-get install -y --no-install-recommends gcc g++ \\\r\n    && rm -rf \/var\/lib\/apt\/lists\/*\r\nCOPY requirements.txt .\r\nRUN pip install --no-cache-dir -r requirements.txt\r\nCOPY app.py .\r\nEXPOSE 8080\r\nCMD [\"python\", \"app.py\"]\r\n<\/xmp>\n\t\t\t\t<\/code>\n\t\t\t<\/pre>\n\t\t<\/div>\n\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-de4ce29 elementor-widget elementor-widget-text-editor\" data-id=\"de4ce29\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p>gcc\/g++ are needed because some Python packages (cryptography, certain boto3 dependencies) compile C extensions. The image must be built for ARM64 \u2014 AgentCore doesn&#8217;t support x86.<\/p><h3><strong>The Terraform<\/strong><\/h3><h4><strong>IAM: Three Roles, Three Boundaries<\/strong><\/h4>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-fa69df4 elementor-widget elementor-widget-code-highlight\" data-id=\"fa69df4\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"code-highlight.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t<div class=\"prismjs-default copy-to-clipboard \">\n\t\t\t<pre data-line=\"\" class=\"highlight-height language-javascript line-numbers\">\n\t\t\t\t<code readonly=\"true\" class=\"language-javascript\">\n\t\t\t\t\t<xmp># Runtime role \u2014 AgentCore runs your container with this identity\r\nresource \"aws_iam_role\" \"runtime\" {\r\n  assume_role_policy = jsonencode({\r\n    Statement = [{ Principal = { Service = \"bedrock-agentcore.amazonaws.com\" }, ... }]\r\n  })\r\n}\r\n\r\n# Gateway role \u2014 AgentCore uses this to invoke your tool backends\r\nresource \"aws_iam_role\" \"gateway\" {\r\n  assume_role_policy = jsonencode({\r\n    Statement = [{ Principal = { Service = \"bedrock-agentcore.amazonaws.com\" }, ... }]\r\n  })\r\n}\r\n\r\n# Lambda role \u2014 execution role for our tool function (this example uses Lambda)\r\nresource \"aws_iam_role\" \"lambda\" {\r\n  assume_role_policy = jsonencode({\r\n    Statement = [{ Principal = { Service = \"lambda.amazonaws.com\" }, ... }]\r\n  })\r\n}\r\n<\/xmp>\n\t\t\t\t<\/code>\n\t\t\t<\/pre>\n\t\t<\/div>\n\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-7a26cb7 elementor-widget elementor-widget-text-editor\" data-id=\"7a26cb7\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p>The Runtime role needs these permissions:<\/p><ul><li>bedrock:InvokeModel \u2014 call Claude<\/li><li>bedrock-agentcore:InvokeGateway \u2014 call the Gateway (THE ONE EVERYONE FORGETS)<\/li><li>ecr:BatchGetImage \u2014 pull the container image<\/li><li>logs:PutLogEvents \u2014 write CloudWatch logs<\/li><\/ul><p>The Gateway role needs:<\/p><ul><li>Permissions to invoke whatever backend your targets use (in this example: lambda:InvokeFunction)<\/li><\/ul><h4><strong>The Gateway and Tool Binding<\/strong><\/h4>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-433713a elementor-widget elementor-widget-code-highlight\" data-id=\"433713a\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"code-highlight.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t<div class=\"prismjs-default copy-to-clipboard \">\n\t\t\t<pre data-line=\"\" class=\"highlight-height language-javascript line-numbers\">\n\t\t\t\t<code readonly=\"true\" class=\"language-javascript\">\n\t\t\t\t\t<xmp>resource \"aws_bedrockagentcore_gateway\" \"main\" {\r\n  name            = \"myagent-gateway\"\r\n  role_arn        = aws_iam_role.gateway.arn\r\n  authorizer_type = \"AWS_IAM\"\r\n  protocol_type   = \"MCP\"\r\n}\r\n\r\nresource \"aws_lambda_alias\" \"get_cake_recipe\" {\r\n  name             = \"GetCakeRecipe\"\r\n  function_name    = aws_lambda_function.tool.function_name\r\n  function_version = \"$LATEST\"\r\n}\r\n\r\nresource \"aws_bedrockagentcore_gateway_target\" \"get_cake_recipe\" {\r\n  gateway_identifier = aws_bedrockagentcore_gateway.main.gateway_id\r\n  target_configuration {\r\n    mcp {\r\n      lambda {\r\n        lambda_arn = aws_lambda_alias.get_cake_recipe.arn\r\n        tool_schema {\r\n          inline_payload {\r\n            name        = \"GetCakeRecipe\"\r\n            description = \"Returns a cake recipe. Call this when the user asks about baking.\"\r\n            input_schema { type = \"object\" }\r\n          }\r\n        }\r\n      }\r\n    }\r\n  }\r\n}\r\n<\/xmp>\n\t\t\t\t<\/code>\n\t\t\t<\/pre>\n\t\t<\/div>\n\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-d69002a elementor-widget elementor-widget-text-editor\" data-id=\"d69002a\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p>The description in tool_schema is what the LLM sees. Write it clearly \u2014 this is how Claude decides whether to call your tool. A vague description means the tool never gets used; an overly broad one means it gets called when it shouldn&#8217;t be.<\/p><h4><strong>The Runtime<\/strong><\/h4>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-ccc625d elementor-widget elementor-widget-code-highlight\" data-id=\"ccc625d\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"code-highlight.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t<div class=\"prismjs-default copy-to-clipboard \">\n\t\t\t<pre data-line=\"\" class=\"highlight-height language-javascript line-numbers\">\n\t\t\t\t<code readonly=\"true\" class=\"language-javascript\">\n\t\t\t\t\t<xmp>resource \"aws_bedrockagentcore_agent_runtime\" \"main\" {\r\n  agent_runtime_name = \"myagent_runtime\"\r\n  agent_runtime_artifact {\r\n    container_configuration {\r\n      container_uri = \"${aws_ecr_repository.agent.repository_url}:latest\"\r\n    }\r\n  }\r\n  role_arn = aws_iam_role.runtime.arn\r\n  protocol_configuration { server_protocol = \"HTTP\" }\r\n  network_configuration  { network_mode = \"PUBLIC\" }\r\n  environment_variables = {\r\n    MODEL_ID    = \"eu.anthropic.claude-sonnet-4-5-20250929-v1:0\"\r\n    AWS_REGION  = \"eu-central-1\"\r\n    GATEWAY_URL = aws_bedrockagentcore_gateway.main.gateway_url\r\n  }\r\n}<\/xmp>\n\t\t\t\t<\/code>\n\t\t\t<\/pre>\n\t\t<\/div>\n\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-11155a1 elementor-widget elementor-widget-text-editor\" data-id=\"11155a1\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p>GATEWAY_URL is computed from the Gateway resource \u2014 Terraform wires them together automatically.<\/p><h3><strong>Deploy and Test<\/strong><\/h3>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-de73c6f elementor-widget elementor-widget-code-highlight\" data-id=\"de73c6f\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"code-highlight.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t<div class=\"prismjs-default copy-to-clipboard \">\n\t\t\t<pre data-line=\"\" class=\"highlight-height language-javascript line-numbers\">\n\t\t\t\t<code readonly=\"true\" class=\"language-javascript\">\n\t\t\t\t\t<xmp>terraform init && terraform apply<\/xmp>\n\t\t\t\t<\/code>\n\t\t\t<\/pre>\n\t\t<\/div>\n\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-50a8af7 elementor-widget elementor-widget-text-editor\" data-id=\"50a8af7\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p>The Terraform includes a null_resource that builds the ARM64 Docker image and pushes it to ECR before creating the Runtime. One command, no manual steps.<\/p><p>After apply (2\u20133 minutes for the container to start):<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-dd198c5 elementor-widget elementor-widget-code-highlight\" data-id=\"dd198c5\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"code-highlight.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t<div class=\"prismjs-default copy-to-clipboard \">\n\t\t\t<pre data-line=\"\" class=\"highlight-height language-javascript line-numbers\">\n\t\t\t\t<code readonly=\"true\" class=\"language-javascript\">\n\t\t\t\t\t<xmp>aws bedrock-agentcore invoke-agent-runtime \\\r\n  --agent-runtime-arn \"$(terraform output -raw runtime_arn)\" \\\r\n  --payload '{\"prompt\": \"Give me a cake recipe\"}' \\\r\n  --content-type application\/json \\\r\n  \/dev\/stdout<\/xmp>\n\t\t\t\t<\/code>\n\t\t\t<\/pre>\n\t\t<\/div>\n\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-523af4f5 elementor-widget elementor-widget-heading\" data-id=\"523af4f5\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<h2 class=\"elementor-heading-title elementor-size-default\">Things That Will Bite You<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-edec8fa elementor-widget elementor-widget-text-editor\" data-id=\"edec8fa\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<ol><li><strong>Missing <\/strong>InvokeGateway permission. Agent works but never calls tools. No error in logs \u2014 the MCP client gets a silent 403. Add bedrock-agentcore:InvokeGateway to the Runtime role.<\/li><li>server_protocol = &#8220;MCP&#8221; on the Runtime. Your container speaks HTTP (BedrockAgentCoreApp is an HTTP server). MCP is only the protocol between Runtime \u2192 Gateway. Set &#8220;HTTP&#8221;.<\/li><li><strong>x86 container image.<\/strong> AgentCore only runs ARM64. You need &#8211;platform linux\/arm64 and QEMU\/buildx for cross-compilation on Intel\/AMD machines.<\/li><li><strong>Image not refreshing after push.<\/strong> AgentCore caches the pulled image. Change any environment variable to force a restart with the fresh image.<\/li><li><strong>VPC mode with no outbound route.<\/strong> network_mode = &#8220;VPC&#8221; requires the subnets to have internet access (NAT or VPC endpoints). Use &#8220;PUBLIC&#8221; unless you specifically need private network access.<\/li><\/ol>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-e24df0b elementor-widget elementor-widget-heading\" data-id=\"e24df0b\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<h2 class=\"elementor-heading-title elementor-size-default\">What You Can Build with This<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-f06f0b5 elementor-widget elementor-widget-text-editor\" data-id=\"f06f0b5\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p>The cake recipe is one tool returning static data. But the same architecture supports agents that reason across multiple tool calls to answer complex questions.<\/p><p>Consider a billing data assistant where a user asks: <em>&#8220;Which of my customers have unpaid invoices from last quarter, and what&#8217;s their total outstanding amount?&#8221;<\/em><\/p><p>No single database query answers that. The agent breaks it down:<\/p><ol><li>First it calls a tool that lists unpaid invoices filtered by date range<\/li><li>Then it calls a tool that aggregates amounts grouped by customer<\/li><li>It combines both results and presents a ranked list with totals<\/li><\/ol><p>The LLM decides this sequence on its own \u2014 you don&#8217;t hardcode the chain. You just provide the tools (each one a parameterized query) and a system prompt that describes when to use which. The agent figures out that answering the user&#8217;s question requires two steps, picks the right tool for each, passes the right parameters (date range, paid status filter), and synthesizes the results into one coherent answer.<\/p><p>Scale that to 10 or 15 tools covering different aspects of a database \u2014 customers, invoices, payments, revenue aggregations, full-text search \u2014 and the agent can answer almost any analytical question a business user throws at it.<\/p><p>That&#8217;s the jump from &#8220;one tool, one response&#8221; to a real production agent: the LLM becomes an autonomous query planner that chains multiple data retrievals together, each one scoped and parameterized, to answer questions that no single query could handle alone.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t","protected":false},"excerpt":{"rendered":"<p>Learn how to develop, deploy, and test a scalable AI agent using AWS AgentCore and Terraform.<\/p>\n","protected":false},"author":23,"featured_media":94772,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[100],"tags":[182,168],"class_list":["post-94763","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-blog","tag-ai-ml-2","tag-aws"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.skaylink.com\/en\/wp-json\/wp\/v2\/posts\/94763","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.skaylink.com\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.skaylink.com\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.skaylink.com\/en\/wp-json\/wp\/v2\/users\/23"}],"replies":[{"embeddable":true,"href":"https:\/\/www.skaylink.com\/en\/wp-json\/wp\/v2\/comments?post=94763"}],"version-history":[{"count":12,"href":"https:\/\/www.skaylink.com\/en\/wp-json\/wp\/v2\/posts\/94763\/revisions"}],"predecessor-version":[{"id":94782,"href":"https:\/\/www.skaylink.com\/en\/wp-json\/wp\/v2\/posts\/94763\/revisions\/94782"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.skaylink.com\/en\/wp-json\/wp\/v2\/media\/94772"}],"wp:attachment":[{"href":"https:\/\/www.skaylink.com\/en\/wp-json\/wp\/v2\/media?parent=94763"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.skaylink.com\/en\/wp-json\/wp\/v2\/categories?post=94763"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.skaylink.com\/en\/wp-json\/wp\/v2\/tags?post=94763"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}