{"slug": "strands-agents-agentcore-runtime-a-perfect-match", "title": "Strands Agents + AgentCore Runtime - a perfect match", "summary": "Architecture of the AWS Briefing Agent, which was refactored to use the Strands Agents SDK and Amazon Bedrock Knowledge Base, resulting in a 75% reduction in code. The Strands Agents SDK provides core components like the Agent class and conversation manager, while the AgentCore Runtime Python SDK wraps the agent function as an HTTP service with endpoints for invocations and health checks. The deployment process uses the `@aws/agentcore` CLI, which packages the Python source code into a container image for the AgentCore Runtime.", "body_md": "This is the third in a series of posts documenting the architecture, implementation, and lessons learned from building the AWS Briefing Agent - a personalised AWS assistant deployed on `Amazon Bedrock AgentCore Runtime`\n\n.\n\n- Part 1:\n[Building a Full-Stack AI Agent on Bedrock AgentCore](https://dev.to/aws-heroes/building-a-full-stack-ai-agent-on-amazon-bedrock-agentcore-2p) - Part 2:\n[Data Ingestion: RSS Feeds, Knowledge Base, S3 Vectors, and Metadata Filtering](https://dev.to/aws-heroes/data-ingestion-rss-feeds-knowledge-base-s3-vectors-and-metadata-filtering-4n8m) - Part 3: Strands Agents + AgentCore Runtime - a perfect match\n- Part 4: Adding Memory to the Agent\n- Part 5: Experimenting with API Gateway\n- Part 6: Observability and Evaluations\n- Part 7: Third Party Integrations - Identity, Gateway and Slack Notifications\n\nThe initial implementation of the AWS Briefing Agent called the `AWS News Feed`\n\nRSS feed on every invocation. After setting up an `Amazon Bedrock Knowledge Base`\n\n, the next step was to refactor the code to take advantage of an agentic framework. The decision was made to adopt `Strands Agents`\n\nSDK as an open source SDK that helps you build and run AI agents in just a few lines of code. In our case, switching to the Knowledge Base and adopting `Strands Agents`\n\nSDK helped us to reduce the number of lines of code in our implementation logic by 75%.\n\n## Using Strands Agents SDK\n\nThe core of the `Strands Agents`\n\ncode is straightforward and shown in the code snippet below:\n\n``` python\nfrom strands import Agent\nfrom strands.models import BedrockModel\nfrom strands.agent.conversation_manager import SlidingWindowConversationManager\nfrom strands_tools import retrieve\nfrom agent.tools.slack_formatter.tool import format_slack_message\n\nmodel = BedrockModel(\n    guardrail_id=GUARDRAIL_ID,\n    guardrail_version=GUARDRAIL_VERSION,\n    guardrail_trace=\"enabled\",\n)\n\nagent = Agent(\n    system_prompt=_load_system_prompt(),\n    model=model,\n    tools=[retrieve, format_slack_message] + gateway_tools,\n    session_manager=session_manager,\n    conversation_manager=SlidingWindowConversationManager(\n        window_size=20,\n        should_truncate_results=True,\n        per_turn=True,\n    ),\n    callback_handler=None,\n)\n\nresult = agent(message)\n```\n\nWe start by importing a number of classes and functions from two packages (`strands-agents`\n\nand `strands-agents-tools`\n\n) and one local module. `Agent`\n\nis the core class for the agent itself, `BedrockModel`\n\nis the model provider, `SlidingWindowConversationManager`\n\ncontrols how conversation history is trimmed, and `retrieve`\n\nis a pre-built tool that is used to query a Bedrock Knowledge Base. The `format_slack_message`\n\nis a local custom tool within this project - a Python function decorated with the `@tool`\n\nannotation.\n\nWe instantiate the `BedrockModel()`\n\nwithout specifying a model_id. At this point, Strands uses its default model, which is current Claude Sonnet on Bedrock. We include details of a Bedrock Guardrail when we instantiate the model, purely to demonstrate the use of guardrails which we cover this later in the blog post.\n\nFinally, we create the agent by wiring together its core components.\n\n## Deploy to Amazon Bedrock AgentCore Runtime\n\nThe `AgentCore Runtime`\n\nPython SDK provides a lightweight wrapper that helps to deploy your agent function as HTTP services\n\n``` python\n# Import the runtime\nfrom bedrock_agentcore.runtime import BedrockAgentCoreApp\n\n# Initialise the app\napp = BedrockAgentCoreApp()\n\n# Decorate the function\n@app.entrypoint\ndef invoke(payload: Dict[str, Any], context: Any = None) -> Dict[str, Any]:\n    \"\"\"Entry point for AgentCore Runtime.\"\"\"\n    message = payload.get(\"prompt\", payload.get(\"message\", \"\"))\n    ...\n    return response\n```\n\n`BedrockAgentCoreApp`\n\nwraps your function in an HTTP server that listens om port 8080 with two endpoints:\n\n-\n`/invocations`\n\n- a POST endpoint for agent interactions. This gets invoked when customers call the`InvokeAgentRuntime`\n\naction with the payload in JSON format -\n`/ping`\n\n- a GET endpoint for health checks to verify your agent is operational and ready to handle requests\n\nThe `@app.entrypoint`\n\ndecorator registers your invoke function as the handler for incoming requests. When AgentCore Runtime receives a request, it deserialises the JSON body into payload, provides a context object (with session_id, request_headers, etc.), calls your function, and serialises the returned dict back as the HTTP response.\n\n## Using the Container Build\n\nWhen using the `@aws/agentcore`\n\nCLI and running `agentcore deploy`\n\n, the CLI needs to turn the Python source code into a runnable container image on `AgentCore Runtime`\n\n. This is controlled by the `build`\n\nfield in the `agentcore.json`\n\nfile. The default setting is `CodeZip`\n\n, in which the CLI zips up the Python source code, uploads it, and AgentCore resolves dependencies using `uv --no-build`\n\n. This is fast but has a hard constraint, as every dependency must have a pre-built wheel. In our code, we have a package that only ships source distributions, which required us to switch to the `Container`\n\nbuild setting. This also makes our build more production-ready.\n\nWhen you run `agentcore deploy`\n\nwith the `Container`\n\nbuild type, the CLI synthesis a CloudFormation stack that includes a CodeBuild project, an ECR repository, the `AgentCore Runtime`\n\nresource, and IAM roles. The CLI packages the `codeLocation`\n\ndirectory (agent/) and uploads it to S3 as the CodeBuild source artefact. CodeBuild pulls the provided Dockerfile and builds the container image. You can see all the steps in the CodeBuild project below:\n\nAfter the image builds successfully, CodeBuild tags it and pushes it to the ECR repository as shown below:\n\nThe stack updates the Runtime resource to point at the new ECR image URI. AgentCore pulls the image from ECR the next time it starts a container for an invocation.\n\n## Built-In Conversation Managers\n\nIn the `Strands Agents`\n\nSDK, the user messages and agent responses are all added to the context. As the conversation grows within a session, this starting having a material impact on response times. We modified the default `SlidingWindowConversationManager`\n\nmanager:\n\n- reducing the\n`windowSize`\n\nfrom the default of 40 to 20. This sets the maximum number of messages to keep - setting the\n`per_turn`\n\nparameter to false. This runs the sliding window before every model call within the same invocation, rather than waiting until after the agent loop completes.\n\nThis reduced the average response time from around 80 seconds down to 15 seconds.\n\n## Adding Bedrock Guardrails\n\n`Amazon Bedrock Guardrails`\n\nare designed to help you safely build and deploy responsible generative AI applications with confidence. We decided to include a guardrail in the architecture, to understand where it fits in and what it can provide.\n\nThe guardrail itself was defined in CDK with content filters (sexual, violence, hate, insults, misconduct and prompt attack), a topic policy (deny off-topic sports questions), and a managed profanity word list:\n\n```\n# ----------------------------------------------------------------\n# Bedrock Guardrail — content safety for the agent\n# ----------------------------------------------------------------\nguardrail = bedrock.CfnGuardrail(\n    self,\n    \"BriefingAgentGuardrail\",\n    name=\"briefing-agent-guardrail\",\n    description=\"Content safety guardrail for the AWS Briefing Agent\",\n    blocked_input_messaging=\"I'm sorry, I can't process that request. Please rephrase your question about AWS announcements.\",\n    blocked_outputs_messaging=\"I'm sorry, I can't provide that response. Let me try a different approach.\",\n    content_policy_config=bedrock.CfnGuardrail.ContentPolicyConfigProperty(\n        filters_config=[\n            bedrock.CfnGuardrail.ContentFilterConfigProperty(\n                type=\"SEXUAL\",\n                input_strength=\"HIGH\",\n                output_strength=\"HIGH\",\n            ),\n            bedrock.CfnGuardrail.ContentFilterConfigProperty(\n                type=\"VIOLENCE\",\n                input_strength=\"HIGH\",\n                output_strength=\"HIGH\",\n            ),\n            # HATE, INSULTS, MISCONDUCT, PROMPT_ATTACK\n        ],\n    ),\n    topic_policy_config=bedrock.CfnGuardrail.TopicPolicyConfigProperty(\n        topics_config=[\n            bedrock.CfnGuardrail.TopicConfigProperty(\n                name=\"Sports\",\n                definition=\"Questions about sports scores, match results, player transfers, league standings, fixtures, or any sporting events.\",\n                type=\"DENY\",\n            ),\n        ],\n    ),\n    word_policy_config=bedrock.CfnGuardrail.WordPolicyConfigProperty(\n        managed_word_lists_config=[\n            bedrock.CfnGuardrail.ManagedWordsConfigProperty(\n                type=\"PROFANITY\",\n            ),\n        ],\n    ),\n)\n```\n\nWhen the agent is invoked, the request first reaches the AgentCore Runtime and runs the handler code first. The guardrail itself is only applied when the handler makes the Bedrock inference call. Bedrock evaluates the input before running the model inference, and then inspects the output before returning it. We did encounter some interesting behaviour when implementing the guardrail.\n\n### IAM Permission Gap\n\nThe first invocation after adding the guardrail failed with:\n\n```\nAccessDeniedException: User is not authorized to perform: bedrock:ApplyGuardrail\non resource: arn:aws:bedrock:eu-west-1.xxx\n```\n\nThe AgentCore execution role (auto-created by the @aws/agentcore-cdk construct) includes `bedrock:InvokeModel`\n\nand `bedrock:InvokeModelWithResponseStream`\n\n, but not `bedrock:ApplyGuardrail`\n\n. The construct doesn’t know about guardrails — they’re a Bedrock feature, not an AgentCore feature. We ended up having to use the `aws iam put-role-policy`\n\nCLI command to add the missing permission\n\n### Topic policies can false-positive on legitimate queries\n\nThe initial topic policy denied \"questions not related to AWS services, cloud computing, or technology\". The intention was that it would be easy to demonstrate, and would ensure that the user input was relevant. However, when the user asked questions such as \"what are the top announcements today\", the classifier ended up deciding this was a blocked topic. In the end, to demonstrate how topic policies work, we changed it to explicitly deny sporting questions.\n\n### Guardrail versions can be deleted by CDK updates\n\nWhen we updated the topic policy, we changed the version description for the guardrail. The CDK stack updated the guardrail version resource, so that CloudFormation deleted version 1 and created version 2. Unfortunately, the version number is also defined in the `agentcore.json`\n\nfile. This meant that the AgentCore Runtime container still had version 1 baked into its environment, which meant calls now failed with the following exception:\n\n```\nValidationException: The guardrail identifier or version provided in the request does not exist.\n```\n\nIn the end it was a case of having to update the version number in `agentcore.json`\n\n, redeploy the agent, and start a new session.", "url": "https://wpnews.pro/news/strands-agents-agentcore-runtime-a-perfect-match", "canonical_source": "https://dev.to/aws-heroes/strands-agents-agentcore-runtime-a-perfect-match-3a51", "published_at": "2026-05-20 21:17:54+00:00", "updated_at": "2026-05-20 21:32:24.754112+00:00", "lang": "en", "topics": ["artificial-intelligence", "machine-learning", "large-language-models", "open-source", "cloud-computing"], "entities": ["AWS Briefing Agent", "Amazon Bedrock AgentCore Runtime", "Amazon Bedrock Knowledge Base", "Strands Agents SDK", "AWS News Feed", "BedrockModel", "SlidingWindowConversationManager"], "alternates": {"html": "https://wpnews.pro/news/strands-agents-agentcore-runtime-a-perfect-match", "markdown": "https://wpnews.pro/news/strands-agents-agentcore-runtime-a-perfect-match.md", "text": "https://wpnews.pro/news/strands-agents-agentcore-runtime-a-perfect-match.txt", "jsonld": "https://wpnews.pro/news/strands-agents-agentcore-runtime-a-perfect-match.jsonld"}}