Getting a demo agent to run uname -a takes half an hour. Running Bedrock Managed Agents in production, with real users, flaky networks and an auditor asking what the agent did last Tuesday, takes discipline. This is the hardening checklist we would apply before letting the OpenAI-powered harness anywhere near a workload that matters, built on the patterns in the sessions documentation.
Move execution to AgentCore Runtime
A self-hosted exec server on a laptop is perfect for learning and terrible for uptime. For production, use Amazon Bedrock AgentCore Runtime as the execution environment. The reference CDK app provisions the Runtime, its storage and its networking, including a NAT gateway for egress, and the session role setup covers AgentCore Runtime activation.
What you gain: isolated execution per session, no hosts to patch, and the same outbound-only model as self-hosted. What you inherit: real infrastructure with real standing cost. More on that below.
Skills and MCP tools: give the agent hands, carefully
Skills are reusable instructions the agent picks up from the skills folder in its workspace. Use them for the stuff you are tired of pasting into prompts: your deployment conventions, how to read your log format, which directories are off-limits. Version them in Git like code, review changes like code, and ship them with the workspace. A skill is a prompt with a change history, which is exactly what you want when someone asks why the agent started behaving differently on Thursday. Keep each skill small and single-purpose; one giant skill file turns into the same unmaintainable mega-prompt you were trying to escape.
MCP servers expose tools through the Model Context Protocol, so the agent can query a ticket system or a database without you writing bespoke glue for each one. Production rules:
- Every MCP server is an attack surface. Run only servers you trust, pinned to specific versions.
- Give each server its own narrowly scoped credentials. A read-only reporting tool does not need write access because it was convenient during the hackathon.
- Prefer tools that are naturally idempotent or have a dry-run mode. You will be glad later.
One state file per concurrent session
The example scripts track session state in a file. Run two sessions from the same shell with the default and they will cheerfully overwrite each other’s state. Set BMA_STATE_FILE per session so concurrent workers never collide:
set -euo pipefailexport BMA_REGION=us-west-2export BMA_ENDPOINT="https://bedrock-mantle.${BMA_REGION}.api.aws"for job in nightly-report build-triage; do ( export BMA_STATE_FILE="$PWD/state/${job}.json" export BMA_WORKSPACE_DIRECTORY="$PWD/workspaces/${job}" mkdir -p "$(dirname "$BMA_STATE_FILE")" "$BMA_WORKSPACE_DIRECTORY/skills" ./scripts/bma/0.create-session.sh ) &donewait

Treat events as hints and items as truth
The SSE event stream is great for live progress, but connections drop, load balancers time out idle streams, and your consumer will restart mid-turn. Design for it:
- Parse on SSE boundaries (a blank line ends an event), never on raw chunks.
- On disconnect, reconnect with backoff.
- After any reconnect, reconcile against durable items from the last item ID you processed. Items are the ledger; events are the radio commentary.
import json, time, requests, boto3from botocore.auth import SigV4Authfrom botocore.awsrequest import AWSRequestREGION = "us-west-2"BASE = f"https://bedrock-mantle.{REGION}.api.aws"creds = boto3.Session().get_credentials().get_frozen_credentials()def signed(method, path): req = AWSRequest(method=method, url=BASE + path) SigV4Auth(creds, "bedrock-mantle", REGION).add_auth(req) return dict(req.headers)def reconcile(session_id, after=None): """Walk durable items forward from a cursor; return new items and the cursor.""" seen = [] while True: q = f"?limit=100&order=asc" + (f"&after={after}" if after else "") path = f"/openai/v1/agents/sessions/{session_id}/items{q}" page = requests.get(BASE + path, headers=signed("GET", path), timeout=30).json() seen += page["data"] if page["data"]: after = page["last_id"] if not page["has_more"]: return seen, afterdef follow(session_id, cursor=None): path = f"/openai/v1/agents/sessions/{session_id}/events" backoff = 1 while True: try: with requests.get(BASE + path, headers=signed("GET", path), stream=True, timeout=(10, 300)) as r: backoff = 1 for line in r.iter_lines(decode_unicode=True): if line and line.startswith("data:"): print("event:", line[5:].strip()[:120]) except requests.RequestException: time.sleep(backoff); backoff = min(backoff * 2, 30) items, cursor = reconcile(session_id, cursor) for item in items: print("item:", json.dumps(item)[:120])
Credentials expire, so in a long-running worker, refresh them before re-signing rather than caching a frozen set forever.
Idempotent submits, or how not to run a migration twice
A turn submit is a POST. If the network dies after the service accepted it but before you saw the response, a blind retry submits the same work twice. For an agent with shell access, that can mean running the same destructive command twice.
The fix is boring and effective: embed a client-generated request tag in the message text, and before any resubmit, inspect the session and reconcile items. If an item containing your tag already exists, the original submit landed, so do not send it again. Only resubmit when the session is idle and your tag is nowhere in the items.
Do not trust idle to mean success
A session returning to idle means the turn ended, not that it worked. Agents are optimistic narrators; they will happily summarize a failed command as progress if the output looked plausible. Inspect command-execution items and check their exit codes before you mark a job green. A pipeline that treats “the agent stopped talking” as success will eventually report a failed deploy as done.
Same energy for cancellation: posting agent.session.input.cancel stops further work, but it does not undo side effects already executed. If a tool wrote rows or deleted files, compensating actions are your job.
Least privilege and cost hygiene for Bedrock Managed Agents in production
- Session role: allow inference only on the specific models you approved. Keep it separate from the identities that create sessions and attach executors, as the prerequisites describe.
- Observability: persist items to your own store per session, log every submit with its request tag, and alert on sessions stuck
in_progressor landing infailed. - Cost: inference bills at Bedrock rates, but stacks bill while idle. NAT gateways are the usual culprit. Audit regularly:
aws ec2 describe-nat-gateways --region us-west-2 \ --filter Name=state,Values=available \ --query "NatGateways[].{id:NatGatewayId,vpc:VpcId,created:CreateTime}" \ --output table
Tear down non-production stacks on a schedule, and remember that deleting a session does not delete workspace files; they follow their own lifecycle. Finally, it is a preview: pin your client code behind a thin wrapper and watch the AWS announcements for API changes.
At Enkompass (enkompass.net), this is our home turf. We build production agent platforms on Bedrock and AgentCore with least-privilege tool access, durable audit trails and automated teardown of idle infrastructure. If you’d rather have seasoned cloud people handle it instead of learning the hard way at 2 AM, let’s talk. Reach out through our contact page or call us directly at +1-828-436-5667 — we’ll help you turn this from a someday-project into a done-deal.
