Amazon Bedrock AgentCore provides managed container runtimes for AI agents that need long-running compute, session affinity, and background processing. It spins up a microVM per session, keeps it warm across messages from the same user, and shuts it down after an idle timeout. This skill covers using AgentCore to build a Telegram bot that invokes Claude to make code changes, with an async entrypoint pattern that returns fast while the agent works in the background.
- AgentCore Runtime: A container-based compute environment managed by Bedrock. You provide a Docker image with your agent code, and AgentCore handles provisioning, scaling, and lifecycle.
- Runtime Session: Each unique
runtimeSessionIdgets its own dedicated microVM. The session persists (workspace, memory, processes) until idle timeout or explicit termination. - Session Affinity: Route subsequent requests from the same user to the same container by reusing the same
runtimeSessionId. Derive it deterministically from the user identifier. - Async Entrypoint Pattern: The
@app.entrypointhandler returns immediately after spawning background work. Useadd_async_taskto keep the container alive andcomplete_async_taskwhen done. - Idle Timeout: Configurable via
idleRuntimeSessionTimeout(default 900s / 15 min, range 60-28800s). The microVM terminates after this period of inactivity. from_asset()in CDK: Builds the Docker image from a local directory, pushes to ECR, and wires it into the Runtime resource automatically. No separate build pipeline needed.
- Python 3.11+
- AWS CDK CLI (
npm install -g aws-cdk) - Docker or Finch
- AWS credentials with Bedrock AgentCore permissions
- A Telegram bot token from @BotFather
app/
my_agent/
Dockerfile
main.py # AgentCore entrypoint
requirements.txt
lib/
agent.py # Agent logic
auth.py # Authorization
functions/
telegram_webhook.py # Lambda webhook relay
infra/
stack.py # CDK stack
bedrock-agentcore>=1.0.0
claude-agent-sdk>=0.1.0
boto3>=1.35.0
httpx>=0.27.0
When to use: Any agent that needs more than a few seconds to complete its work. The entrypoint returns immediately so the caller (Lambda, API) gets a fast response.
from bedrock_agentcore.runtime import BedrockAgentCoreApp
import threading
app = BedrockAgentCoreApp()
@app.entrypoint
def handle_request(payload, context=None):
# Parse the incoming request
user_id = payload["user_id"]
message = payload["message"]
# Register an async task so the container stays alive
task_id = app.add_async_task(
f"task-{user_id}",
{"user_id": user_id},
)
# Spawn background work
threading.Thread(
target=do_work,
args=(user_id, message, task_id),
daemon=True,
).start()
# Return immediately
return {"status": "accepted", "task_id": task_id}
def do_work(user_id, message, task_id):
try:
# Your long-running agent logic here
result = run_agent(user_id, message)
send_response(user_id, result)
finally:
# Always complete the task so the container can go idle
app.complete_async_task(task_id)add_async_task tells AgentCore the container is busy (reports HealthyBusy on /ping). complete_async_task lets it go idle and eventually terminate. Always call complete_async_task in a finally block.
When to use: When you need workspace, conversation history, or installed dependencies to persist across multiple requests from the same user.
# In the Lambda webhook relay
user_id = str(payload.get("message", {}).get("from", {}).get("id", "unknown"))
session_id = f"telegram-user-session-id-{user_id}"
# AgentCore requires session IDs to be at least 33 characters
client.invoke_agent_runtime(
agentRuntimeArn=AGENT_RUNTIME_ARN,
runtimeSessionId=session_id,
contentType="application/json",
accept="application/json",
payload=json.dumps(payload).encode("utf-8"),
)The same runtimeSessionId always routes to the same microVM. The workspace (git repos, node_modules, temp files) survives between requests. Conversation history stored in-memory persists as long as the session is alive.
When to use: Deploying an AgentCore Runtime with CDK. Builds the Docker image from source and handles ECR automatically.
import aws_cdk.aws_bedrock_agentcore_alpha as agentcore
agent_runtime = agentcore.Runtime(
self, "MyRuntime",
runtime_name="my_agent",
agent_runtime_artifact=agentcore.AgentRuntimeArtifact.from_asset("app/my_agent/"),
protocol_configuration=agentcore.ProtocolType.HTTP,
lifecycle_configuration=agentcore.LifecycleConfiguration(
idle_runtime_session_timeout=cdk.Duration.minutes(15),
),
environment_variables={
"MY_VAR": "value",
},
)CDK builds the Dockerfile in the specified directory, pushes the image to a managed ECR repo, and creates the Runtime resource. No manual image tagging or ECR commands.
When to use: Running Claude as a headless coding agent inside the AgentCore container.
from claude_agent_sdk import query, ClaudeAgentOptions
async for message in query(
prompt=user_message,
options=ClaudeAgentOptions(
system_prompt=system_prompt,
allowed_tools=["Read", "Write", "Edit", "Bash", "Glob", "Grep"],
cwd=workspace_path,
max_turns=30,
),
):
if hasattr(message, "result"):
result_text = str(message.result)The system prompt is where you define guardrails (which files the agent can modify, validation requirements, deployment workflow). allowed_tools controls what Claude can do. cwd sets the working directory. max_turns caps the number of tool-use cycles.
@app.entrypoint— Decorator that registers the request handler. Receives(payload: dict, context=None). Return value is sent back to the caller.app.add_async_task(task_name: str, metadata: dict) -> task_id— Registers a background task. Container reports HealthyBusy until completed.app.complete_async_task(task_id)— Marks a background task as done. Container can go idle.
invoke_agent_runtime(agentRuntimeArn, runtimeSessionId, contentType, accept, payload)— Sends a request to an AgentCore Runtime. Returns the response body.
query(prompt, options) -> AsyncIterator— Invokes Claude with tools. Yields messages including tool calls and results.ClaudeAgentOptions(system_prompt, allowed_tools, cwd, max_turns)— Configuration for the Claude invocation.
- Conversation history is in-memory. It persists across messages within the same session because the container stays alive. But if the container recycles (idle timeout, crash, explicit stop), the history is gone. For durable history, persist to DynamoDB or S3.
- Session IDs must be at least 33 characters. AgentCore rejects shorter session IDs. Pad with a prefix if needed.
- One request at a time per user. If the agent uses a per-user git workspace, concurrent requests from the same user will conflict. Track active users and reject concurrent requests.
from bedrock_agentcore.runtime import BedrockAgentCoreApp
import threading
app = BedrockAgentCoreApp()
@app.entrypoint
def handle_request(payload, context=None):
task_id = app.add_async_task("work", {})
def background():
try:
# Do work here
pass
finally:
app.complete_async_task(task_id)
threading.Thread(target=background, daemon=True).start()
return {"status": "accepted", "task_id": task_id}import json, os, boto3
AGENT_RUNTIME_ARN = os.environ["AGENT_RUNTIME_ARN"]
def handler(event, context):
body = event.get("body", "{}")
payload = json.loads(body) if isinstance(body, str) else body
user_id = str(payload.get("message", {}).get("from", {}).get("id", "unknown"))
client = boto3.client("bedrock-agentcore")
client.invoke_agent_runtime(
agentRuntimeArn=AGENT_RUNTIME_ARN,
runtimeSessionId=f"telegram-user-session-id-{user_id}",
contentType="application/json",
accept="application/json",
payload=json.dumps(payload).encode("utf-8"),
)
return {"statusCode": 200, "body": json.dumps({"ok": True})}- Kiro
aws-agentcorepower: Official Kiro power for Amazon Bedrock AgentCore. Covers the full AgentCore API, runtime management, and deployment workflows. Install from the Kiro Powers panel. This skill focuses specifically on the Telegram bot + async entrypoint + Claude SDK pattern that the power doesn't cover in detail.