# Deploying a CrewAI Multi-Agent Crew to Amazon Bedrock AgentCore

*Full source for this project:* [https://github.com/ratishjain12/crewai-bedrock-agentcore](https://github.com/ratishjain12/crewai-bedrock-agentcore)  
  
Amazon Bedrock AgentCore is AWS's answer to a problem every team building agents eventually hits: your agent works great on your laptop, and then you need to actually run it somewhere with scaling, session isolation, observability, and a secure invocation boundary without hand-rolling that infrastructure yourself.

This post walks through taking a real multi-agent [CrewAI](https://www.crewai.com/) crew - a two-agent "Research Crew" that researches a topic and turns the findings into a report from `crewai run` on a laptop to a secured, containerized deployment on AgentCore Runtime. Every step here was actually run against a live AWS account, including the mistakes.

## What is Bedrock AgentCore, actually?

Strip away the marketing and AgentCore is a managed runtime with one real contract: give it a container (or a zipped Python package) that speaks HTTP on port 8080 — `POST /invocations` for the actual work, `GET /ping` for health and AgentCore handles the rest: provisioning, scaling, session isolation, IAM, logging, and (optionally) JWT-based auth in front of it. It's explicitly framework-agnostic. Nothing about the contract cares whether the code behind it is Strands, LangChain, LangGraph, or in this case, CrewAI.

That framework-agnosticism matters here: AgentCore's own CLI ships built-in project templates for Strands, LangChain/LangGraph, GoogleADK, OpenAI Agents, and Autogen but not CrewAI. The escape hatch is a **BYO (bring your own code) agent**: you register a code location and an entrypoint file, and AgentCore doesn't care what's inside as long as it implements the HTTP contract.

## The architecture

Here's what we ended up with:

![](https://cdn.hashnode.com/uploads/covers/639ca8d6052fc36d4a8f42ec/3dbd07b6-47e5-48fe-9e97-d243cd536923.png align="center")

Walking it left to right:

*   **Client** - anything calling the agent: a script, another service, `agentcore invoke`.
    
*   **Identity Provider (Cognito) / IAM-JWT Auth** - the front door. AgentCore Runtime supports two inbound-auth modes: `AWS_IAM` (SigV4-signed requests, the default) or `CUSTOM_JWT` (bearer tokens from any OIDC-compliant issuer - Cognito here). This layer validates the request before it ever reaches your code.
    
*   **AgentCore Runtime → MicroVM → Docker Container → Agent Application Code** - each invocation gets an isolated microVM running your container. Your code (`main.py` in this project) is the innermost layer — it never sees infrastructure concerns, just a payload in and a result out.
    
*   **Execution Role** - the IAM role AgentCore assumes on your container's behalf, scoped to exactly what it needs.
    
*   **ECR** - where the container image lives; the Execution Role pulls it to start the runtime.
    
*   **CloudWatch** - logs and traces from every invocation land here automatically.
    
*   **Bedrock (LLM)** - where the actual model calls go. The Execution Role, not the client, is what's authorized to invoke Bedrock - the client never touches AWS credentials at all.
    

## The demo: a CrewAI Research Crew

The crew is intentionally simple - two agents, sequential process:

*   **Researcher** — gathers information on a topic using `SerperDevTool` (web search), backed by `bedrock/apac.amazon.nova-lite-v1:0`.
    
*   **Analyst** — turns the researcher's output into a structured markdown report, backed by the same model.
    

It's defined declaratively, CrewAI's JSON-first format - `crew.jsonc` for the crew/task graph, `agents/researcher.jsonc` and `agents/analyst.jsonc` for each agent's role/goal/backstory/model/tools:

```jsonc
// agents/researcher.jsonc
{
  "role": "Senior Research Specialist for {topic}",
  "goal": "Find comprehensive and accurate information about {topic}...",
  "llm": "bedrock/apac.amazon.nova-lite-v1:0",
  "tools": ["SerperDevTool"]
}
```

Locally, `crewai run` reads this straight out of `pyproject.toml`'s `[tool.crewai]` block. Neither `crew.jsonc` nor the loader cares whether you're running on a laptop or inside AgentCore - which is exactly what makes the port to a BYO agent almost mechanical.

## Wiring it into AgentCore

A BYO agent needs exactly one thing beyond the existing project: an entrypoint that speaks the AgentCore HTTP contract. CrewAI conveniently exposes a `load_crew()` helper that does the JSON-first loading programmatically, so the entrypoint is a thin wrapper:

```python
# app/ResearchAgent/main.py
from pathlib import Path
from bedrock_agentcore.runtime import BedrockAgentCoreApp
from crewai.project.crew_loader import load_crew

app = BedrockAgentCoreApp()
CREW_PATH = Path(__file__).parent / "crew.jsonc"

@app.entrypoint
def invoke(payload, context):
    crew, default_inputs = load_crew(CREW_PATH)
    topic = payload.get("prompt", default_inputs.get("topic", ""))
    result = crew.kickoff(inputs={**default_inputs, "topic": topic})
    return {"result": result.raw}

if __name__ == "__main__":
    app.run()
```

`BedrockAgentCoreApp` handles the HTTP contract (`/invocations`, `/ping`) — the crew itself doesn't know it's running inside AgentCore at all.

The agent is registered in `agentcore/agentcore.json` as a flat resource entry — `codeLocation`, `entrypoint`, `build` type, environment variables, and (as it turned out) auth config, all declared, not templated:

```jsonc
{
  "runtimes": [{
    "name": "ResearchAgent",
    "build": "Container",
    "entrypoint": "main.py",
    "codeLocation": "app/ResearchAgent/",
    "envVars": [
      { "name": "SERPER_API_KEY", "value": "..." },
      { "name": "AWS_DEFAULT_REGION", "value": "ap-south-1" }
    ]
  }]
}
```

## Two bugs that only show up in deployment

Everything above sounds clean in hindsight. It wasn't, the first time through — and both failures are worth calling out because neither is obvious from the CrewAI or AgentCore docs alone.

**1\. The package was too big for a** `CodeZip` **build.** AgentCore's default build type zips your code and its dependencies and caps the artifact at 250 MB. `crewai[bedrock,tools]` blew past that at 285 MB. The reason: the `tools` extra doesn't scope dependencies per-tool — it pulls in `crewai-tools`' *entire* dependency tree, because the package ships all ~40 tool wrappers as one unit. The researcher agent uses exactly one tool (`SerperDevTool`, a thin `requests` wrapper), but that one line in `pyproject.toml` also drags in chromadb, lancedb, pyarrow, onnxruntime, pymupdf, and kubernetes — dependencies for RAG tools and PDF tools and a Kubernetes tool we never touch. The fix was switching the runtime's `build` type from `CodeZip` to `Container` — a container image isn't subject to the 250 MB cap, so the same heavy dependency tree just... works. (The leaner fix, for anyone hitting this without wanting to move to containers, is dropping the `tools` extra and hand-writing a ten-line Serper tool against `requests` instead.)

**2\. Cross-region model IDs silently picked the wrong region.** The crew uses `bedrock/apac.amazon.nova-lite-v1:0` — a cross-region inference profile that only routes within APAC regions. Locally, everything worked, because the AWS CLI's default profile region was `ap-south-1`. Deployed, every invocation failed with *"The provided model identifier is invalid."* The cause: CrewAI's Bedrock LLM provider builds its own boto3 session and only checks `AWS_DEFAULT_REGION` / `AWS_REGION_NAME` env vars for region — it never falls back to `~/.aws/config` the way a bare `boto3.Session()` does. With neither env var set, it silently defaulted to `us-east-1`, where an APAC-only inference profile is, correctly, rejected. The fix was one env var — `AWS_DEFAULT_REGION=ap-south-1` — set explicitly in both `agentcore/.env.local` (for local `agentcore dev`) and the runtime's `envVars` in `agentcore.json` (for the deployed container). Two separate places, because — importantly — `.env.local` **never reaches the deployed runtime.** It lives outside the container's `codeLocation` and is read only by the local dev server; production env vars are a completely separate, explicit declaration.

## Locking down the front door: Cognito JWT auth

By default, AgentCore Runtime protects invocations with IAM/SigV4 — fine for service-to-service calls already inside your AWS account, but not something you'd hand to, say, a frontend client. Swapping in JWT auth is a matter of setting two fields on the runtime:

```jsonc
"authorizerType": "CUSTOM_JWT",
"authorizerConfiguration": {
  "customJwtAuthorizer": {
    "discoveryUrl": "https://cognito-idp.<region>.amazonaws.com/<pool-id>/.well-known/openid-configuration",
    "allowedClients": ["<app-client-id>"]
  }
}
```

The catch: AgentCore doesn't provision the identity provider for you — there's no `agentcore add cognito`. Cognito (user pool, resource server, domain, app client) is a plain AWS resource you create yourself; AgentCore's CLI only consumes the resulting discovery URL. This project's `scripts/cognito/` directory has three small scripts for exactly that lifecycle: `create-cognito.sh` provisions everything and prints the config block above; `get-bearer-token.sh` does the client-credentials token exchange for testing; `destroy-cognito.sh` tears it back down. Once `CUSTOM_JWT` is set, plain boto3 `invoke_agent_runtime` calls stop working — SigV4 signing and JWT bearer tokens are mutually exclusive per runtime, so invocation becomes a raw HTTPS `POST` with an `Authorization: Bearer <token>` header instead.

## Closing thoughts

None of the individual pieces here are exotic — CrewAI's JSON-first crews, AgentCore's BYO-agent model, Cognito client-credentials grants. What's worth taking away is that the two real failures — a silent dependency bloat past a packaging limit, and a silent region fallback inside a third-party LLM provider — both looked like AgentCore problems at first and were actually one level down, in how CrewAI resolves its own dependencies and its own AWS region. Deploying an existing framework onto a new runtime surfaces exactly these kinds of assumptions the framework was making that never had to be explicit before.

* * *

*Full source for this project:* https://github.com/ratishjain12/crewai-bedrock-agentcore
