Previously in the Series

In the previous articles, we explored how AI Agents can transform business operations from a bakery CEO’s perspective, showcased the data workflow, and built the whole thing with CloudFormation and SAM.

Our CEO’s AI Intern graduated, showed up on day one, received an Intern’s Laptop (Action Groups), followed the Intern’s Guide (Instructions), and started answering questions like “How did we do with the hospitality business in the first half of the year?” It worked.

But here’s the thing about Interns — they eventually need a promotion.

The Intern’s Problem

Remember how we built the Intern? The Intern’s Laptop (tooling) was an “OpenAPI schema” (it is a custom JSON schema; for the illustration, I’ll call it OpenAPI) wired to a separate Lambda function. The Intern’s Guide (instructions) lived in a CloudFormation YAML. The University the Intern graduated from (Foundation Model) was hardcoded to a Bedrock ARN. Testing meant deploying to AWS and hoping for the best.

Want to change the University? Redeploy the stack. Want to give the Intern a new tool? Write a new Lambda, define a new Action Group, update the OpenAPI schema, and create a new agent version. Want to test locally before onboarding? Not an option — this Intern only exists in the AWS console.

Our Intern was effective but inflexible. Tied to one office, one desk, one rigid set of procedures.

It’s time for a promotion. The Intern is becoming an Employee — portable, testable, and ready for the real world. We’re migrating the CEO Financial Advisor from native Bedrock Agents to the Strands Agents SDK with AWS AgentCore Runtime.

And along the way, we’ll answer the question every serverless enthusiast is asking: Is Lambda dead for AI Agents? Is the Docker container (ECR) suddenly hot again?

Note 1. If you don’t feel comfortable with the code, I still recommend you compare the original bedrock-agent-stack.yaml with the new Strands-based implementation. Both live in the same repository so you can see the evolution side by side.

Note 2. The full repository: https://github.com/javatask/ai-agent-ceo-fin-advisor — the original native Bedrock Agent is in the root, and the Strands migration is in the strands/ folder.

Why Promote the Intern? Five Reasons to Migrate

1. The Employee Can Switch Universities

Native Bedrock Agents are bound to models within the Bedrock ecosystem. If the CEO wants a different “University” — a different model provider — the whole agent needs restructuring.

Strands is model-agnostic. The Employee can use Claude Sonnet 4 via Bedrock in production, prototype with a local Llama model on a developer’s laptop via Ollama, and evaluate OpenAI through LiteLLM next quarter — all with the same code. No vendor lock-in. The Employee picks the best University for each task.

# Production: Bedrock
agent = Agent(model="us.anthropic.claude-sonnet-4-20250514-v1:0", ...)

# Local development: Ollama
from strands.models.ollama import OllamaModel
agent = Agent(model=OllamaModel(host="http://localhost:11434"), ...)

# Evaluation: OpenAI via LiteLLM
from strands.models.litellm import LiteLLMModel
agent = Agent(model=LiteLLMModel(model_id="gpt-4o"), ...)

Same Employee, different Universities, zero code changes to tools or instructions.

2. The Employee’s Laptop is Just Python

This is where the migration pays for itself in the first week.

Remember the Intern’s Laptop? It required an OpenAPI schema for every tool, a separate Lambda function as the executor, an Action Group configuration, and a CloudFormation resource wiring it all together. Adding a single tool meant touching four or five files across two repositories.

The Employee’s Laptop is a Python function with a decorator.

Before — The Intern’s Laptop (Native Bedrock Agents):

# 1. Lambda function (separate file, separate SAM stack)
# 2. OpenAPI-style schema in CloudFormation:
ActionGroups:
  - ActionGroupName: financial-analysis-actions
    ActionGroupExecutor:
      Lambda: !Ref LambdaFunctionArn
    FunctionSchema:
      Functions:
        - Name: analyze_industry_performance
          Description: |
            Analyzes financial performance metrics across a network of bakeries...
          Parameters:
            date_from:
              Type: string
              Description: date from in format YYYY-MM-DD
              Required: true
            date_to:
              Type: string
              Description: date to in format YYYY-MM-DD
              Required: true
            industry:
              Type: string
              Description: industry for each need to do analysis
              Required: true

After — The Employee’s Laptop (Strands Agents):

from strands import Agent, tool

@tool
def analyze_financial_performance(industry: str, date_from: str, date_to: str) -> str:
    """
    Analyze financial metrics for a specific industry and date range.
    
    Args:
        industry: Industry segment (schools, cafes, shops, factories, restaurants, hotels)
        date_from: Start date in YYYY-MM-DD format
        date_to: End date in YYYY-MM-DD format
    
    Returns:
        Financial analysis summary with bookings, billings, and billing rates
    """
    data = generate_industry_data(industry, date_from, date_to)
    return format_analysis(data)

No OpenAPI schema. No separate Lambda. No Action Group. The @tool decorator turns any Python function into an agent tool. The docstring is the tool description that the LLM reads — the same detailed description we gave the Intern, but now it lives right next to the code it describes.

Strands has been called the “Flask of AI Agents” — and after migrating, I understand why. The same simplicity that Flask brought to web development, Strands brings to agent development.

3. The Employee Can Lead a Team

Native Bedrock Agents are optimised for single-Intern tasks. Our CEO had one Intern analyzing finances. But what if the CEO needs an entire team?

Strands provides built-in patterns for multi-agent architectures:

  • Swarm Patterns — Dynamic, autonomous handoffs between Employees. The financial analyst realises a compliance check is needed and hands it off to the compliance specialist automatically.

  • Agent Graphs — Structured flowcharts where Employees determine execution paths based on intermediate results.

  • Agent-as-Tool — Hierarchical delegation where a “Head of Finance” Employee calls specialised sub-Employees as if they were ordinary tools on a Laptop.

For the bakery CEO, this means a future where one request like “Prepare the quarterly board report” triggers a team: an Industry Analyst gathers data, a Compliance Checker validates numbers, and a Report Writer assembles everything into a polished deliverable.

4. The Employee Explains Their Thinking

Debugging the Intern was painful. The reasoning steps were hidden behind a service boundary. You could see the final output and the tool invocation logs, but the intermediate thinking — why the Intern chose one tool over another, how it interpreted the CEO’s ambiguous question about “hospitality” mapping to industry=hotels — was largely opaque.

The Employee, powered by Strands with AgentCore, emits native OpenTelemetry traces for every model invocation, tool call, and reasoning step. You get 100% traceability of decisions. In practice, teams report up to 50% faster problem resolution simply because they can see what the Employee is thinking.

When the CEO asks, “Why did the report show different numbers than last time?” — you can trace every step the Employee took to produce that answer.

5. The Employee Gets a Proper Office (AgentCore)

Migrating to Strands is often a stepping stone to AWS AgentCore, which cleanly separates the “Employee’s brain” (Strands) from the “Employee’s office” (AgentCore):

  • Persistent Memory — Sophisticated episodic (short-term) and semantic (long-term) memory. The CEO can say, “Compare this to what you told me last quarter,” and the Employee remembers. Far more robust than the Intern’s basic session persistence.

  • Policy Guardrails — A Cedar-based policy engine that enforces deterministic security boundaries outside the Employee’s reasoning loop. Think: “Block all financial projections that exceed 3-year horizons” or “Never share salary data outside the C-suite session.”

  • Scaling and Isolation — MicroVM-level session isolation and automatic scaling. One Employee’s failure never cascades to others. Each CEO session runs in complete isolation.

When the Intern Still Shines

Before we go further, I don’t want to give the impression that native Bedrock Agents should be abandoned. They shouldn’t. In fact, Bedrock Agents remain the fastest way to go from zero to a working AI Agent.

If you’re a CEO who just had the conversation with your daughter from the first article and want to see an Intern in action this afternoon, native Bedrock Agents are unbeatable:

  • No code required for the first experiment — The AWS Console lets you define an agent, attach a Knowledge Base, and test it in the playground without writing a single line of code.

  • Built-in Action Groups with Lambda — For teams already comfortable with Lambda, the OpenAPI schema approach gives you a structured, well-documented way to wire tools.

  • Managed versioning and aliases — The Bedrock console handles agent versions, aliases, and rollbacks out of the box. No Docker, no ECR, no CDK.

  • Low barrier to value — You can prove the concept to stakeholders in hours, not days. That first “wow, it understood my question and mapped hospitality to hotels” moment matters.

The Intern is the right choice when you’re exploring whether AI Agents fit your business at all. The Employee is the right choice when you already know they do and you need to scale, customise, and operate them in production.

Think of it this way: you wouldn’t hire a full-time Employee before you’ve validated the role with an Intern. Native Bedrock Agents are that validation step. Strands with AgentCore is what comes after.

What Changed: The Migration Map

The Architecture, Before and After

Before — The Intern’s Office:

The Intern’s architecture with native Bedrock Agents

After — The Employee’s Office:

The Employee’s architecture with Strands and AgentCore

The key difference: the Employee’s brain and Laptop now live in the same container. No more Lambda cold starts for tool calls. No more OpenAPI schemas. No more version management through the Bedrock console. Everything is code, everything is testable, everything ships with ./deploy_image.sh.

The Big Question: Is Lambda Dead for Agents?

Short answer: No, but it lost the default position.

Lambda was the hero of serverless for a decade. I used it for the Intern’s tooling in the previous article. But for AI Agents specifically, Lambda’s billing model creates an uncomfortable economic reality.

The I/O Wait Problem

An AI Agent is not a traditional Lambda workload. When the CEO asks “How did cafes perform in Q1 2024?”, the Employee does this:

  1. Receive prompt → 1ms

  2. Call LLM for reasoningwait 3–8 seconds

  3. Execute tool (analyse data) → 50ms

  4. Call LLM for reflection and formattingwait 3–8 seconds

  5. Return response → 1ms

In a 15-second interaction, the function spends roughly 70% of its time waiting for the LLM to think. With Lambda, you pay for every millisecond of that wait. With AgentCore Runtime, I/O wait time when the CPU is idle is not billed.

Think of it this way: Lambda charges rent while the Employee sits and thinks. AgentCore only charges when the Employee acts.

The Economics, Side by Side

A Concrete Example

Consider our bakery CEO’s Employee processing 100 questions per day, each averaging 12 seconds of execution with roughly 8 seconds of LLM wait time:

Lambda (1024 MB, ARM64):

  • Duration billed: 100 × 12s × 1 GB = 1,200 GB-seconds/day

  • Monthly: ~36,000 GB-seconds

  • But 24,000 of those GB-seconds are pure waiting

AgentCore:

  • Only the ~4 seconds of active computation per request is billed

  • Monthly active compute is roughly 60–70% lower

At 100 requests/day, the difference is a few dollars. At 10,000 requests/day with complex multi-step reasoning chains, the gap becomes material.

When Lambda Still Makes Sense

Lambda isn’t dead — it’s specialised. Choose Lambda when:

  • Single-shot, low-latency tasks — Quick classification or extraction that completes in under 3 seconds with minimal LLM wait time.

  • High volume, tiny tasks — Millions of simple operations where the 1 million free requests/month tier dominates the economics.

  • Existing infrastructure — Your organisation has a mature Lambda CI/CD pipeline, and the migration cost exceeds the runtime savings.

Choose AgentCore when:

  • Complex multi-step reasoning — The Employee loops through multiple LLM calls and tool invocations. You refuse to pay rent while they think.

  • Long-running workflows — Anything beyond Lambda’s 15-minute ceiling: research reports, multi-industry comparisons, deep analysis.

  • Memory-heavy agents — Built-in episodic memory beats manually wiring DynamoDB.

  • Compliance requirements — Cedar-based policy guardrails and session isolation are essential for regulated environments.

The Verdict: ECR Is Hot Again

The container renaissance for AI workloads is real. ECR + AgentCore gives you:

  • ARM64 Graviton for cost-efficient compute

  • Local development parity — the same Docker image runs on your laptop and in production

  • No cold starts that compound with LLM latency

  • A billing model designed for agentic workloads, not request-response APIs

For serious agentic AI, the Lambda-first reflex needs a rethink. Containers are the natural home for Employees that think, wait, reason, and think again.

First Day on the Job: Deploying the Employee

Just like we walked through the Intern’s deployment, here’s how the Employee starts work. And this time, you can test locally before the Employee ever touches AWS.

Prerequisites

# Python package manager
curl -LsSf https://astral.sh/uv/install.sh | sh

# AWS CDK CLI
npm install -g aws-cdk

# Docker buildx (for ARM64 builds)
docker buildx create --use

Step 1: Test Locally (The Intern Could Never)

# Clone and install
git clone https://github.com/javatask/ai-agent-ceo-fin-advisor.git
cd ai-agent-ceo-fin-advisor/strands
uv sync

# Run the Employee locally
uv run python run_agent.py

# In another terminal — ask the same question as the Intern
curl -X POST http://localhost:8080/invocations \
  -H "Content-Type: application/json" \
  -d '{"prompt": "How did we do with the hospitality business in the first half of the year?"}'

The Employee answers on your laptop. No AWS deployment. No waiting for CloudFormation. No prepare-agent step.

Step 2: Deploy Infrastructure

cd cdk
cdk bootstrap    # First time only
cdk deploy       # Creates ECR repo + IAM role

Step 3: Ship the Employee to AWS

cd ..
./deploy_image.sh <ecr-repository-uri-from-cdk-output>

Step 4: Create the Employee’s Office

This is the step where we actually create the AgentCore Runtime — the managed environment that will run our container. Think of Steps 2–3 as building the office and moving the furniture in (ECR repo + Docker image). Step 4 is handing the keys to AgentCore and saying “run this.”

Why is this a separate script and not part of the CDK stack? At the time of writing, AgentCore Runtime doesn’t have native CloudFormation support yet. You can automate it via a CDK Custom Resource backed by a Lambda (the stack.py in the repo has a commented-out example), but for a first deployment, a simple boto3 script is clearer and easier to debug.

python3 << 'EOF'
import boto3

client = boto3.client('bedrock-agentcore-control', region_name='us-east-1')
cfn = boto3.client('cloudformation', region_name='us-east-1')

stack = cfn.describe_stacks(StackName='StrandsAgentStack')['Stacks'][0]
outputs = {o['OutputKey']: o['OutputValue'] for o in stack['Outputs']}

response = client.create_agent_runtime(
    agentRuntimeName='ceo_fin_advisor_v2',
    agentRuntimeArtifact={
        'containerConfiguration': {
            'containerUri': f"{outputs['ECRRepositoryUri']}:latest"
        }
    },
    networkConfiguration={'networkMode': 'PUBLIC'},
    roleArn=outputs['AgentRoleArn']
)

print(f"Runtime ARN: {response['agentRuntimeArn']}")
EOF

Step 5: Ask the Same Question

import boto3, json

client = boto3.client('bedrock-agentcore', region_name='us-east-1')

response = client.invoke_agent_runtime(
    agentRuntimeArn="<runtime-arn-from-step-4>",
    runtimeSessionId="session_12345678901234567890123456789012",
    payload=json.dumps({
        "prompt": "How did we do with the hospitality business in the first half of the year?"
    })
)

result = json.loads(response['response'].read())
print(result['result']['content'][0]['text'])

Same question. Same CEO. Smarter Employee.

Giving the Employee a New Tool: Before and After

Remember the pain of adding a tool to the Intern? Let’s say the CEO now wants to compare two industries side by side.

Before — Giving the Intern a New Tool:

  1. Write Lambda function code for the new tool

  2. Add SAM resource for the new Lambda (or update the existing one)

  3. Add function definition to the OpenAPI-style schema in CloudFormation

  4. Update or create a new Action Group in the Bedrock Agent config

  5. Deploy CloudFormation: aws cloudformation deploy ...

  6. Prepare agent: aws bedrock-agent prepare-agent --agent-id $AGENT_ID

After — Giving the Employee a New Tool:

  1. Write the function:
@tool
def compare_industries(industry_a: str, industry_b: str, year: int) -> str:
    """Compare financial performance between two industries for a given year.
    
    Args:
        industry_a: First industry to compare
        industry_b: Second industry to compare
        year: Year to analyze (e.g. 2024)
    """
    data_a = generate_industry_data(industry_a, f"{year}-01-01", f"{year}-12-31")
    data_b = generate_industry_data(industry_b, f"{year}-01-01", f"{year}-12-31")
    return format_comparison(data_a, data_b)
  1. Add it to the Employee’s Laptop:
agent = Agent(
    model="us.anthropic.claude-sonnet-4-20250514-v1:0",
    tools=[analyze_financial_performance, send_email_report, compare_industries]
)
  1. Test locally: uv run python run_agent.py

  2. Deploy: ./deploy_image.sh <ecr-uri>

Four steps instead of six. Zero infrastructure configuration. No prepare-agent ritual. The productivity gain is not incremental — it is transformational.

The CDK Stack: The Employee’s Office Lease

For those who want to see the infrastructure, here’s the complete CDK stack. Compare this to the original bedrock-agent-stack.yaml — It’s significantly simpler because the agent logic lives in the container, not in CloudFormation.

# ECR Repository — where the Employee's container image lives
ecr_repo = ecr.Repository(
    self, "AgentRepository",
    repository_name="ceo-fin-advisor-strands",
    removal_policy=RemovalPolicy.DESTROY,
)

# IAM Role — the Employee's permission badge
agent_role = iam.Role(
    self, "AgentRuntimeRole",
    assumed_by=iam.ServicePrincipal("bedrock-agentcore.amazonaws.com"),
)

# Bedrock model access — which Universities the Employee can consult
agent_role.add_to_policy(iam.PolicyStatement(
    actions=["bedrock:InvokeModel", "bedrock:InvokeModelWithResponseStream"],
    resources=[
        f"arn:aws:bedrock:{self.region}::foundation-model/*",
        "arn:aws:bedrock:*:*:inference-profile/*"
    ],
))

# ECR pull — so AgentCore can fetch the Employee's container
ecr_repo.grant_pull(agent_role)

Three resources. One cdk deploy. The Employee is ready. And still under GitOps control — the same best practice we established with the Intern.

What’s Next for the Employee

The migration to Strands is not just a framework swap — it’s a foundation for the Employee’s career growth:

  • Multi-Agent Team — A “Head of Finance” Employee that delegates to specialised Industry Analysts using the Agent-as-Tool pattern. One CEO question, multiple Employees collaborating.

  • Persistent Memory — AgentCore’s episodic memory so the CEO can say, “Compare this to what you told me last quarter,” and the Employee actually remembers.

  • Policy Guardrails — Cedar-based rules that enforce financial reporting compliance outside the Employee’s reasoning loop. No more hoping the LLM follows safety instructions.

  • CI/CD Pipeline — Automated testing and deployment so Employee updates go from git commit to production in minutes. True GitOps for AI Agents.

Conclusion

The Intern served us well — and still serves others well. For teams exploring AI Agents for the first time, native Bedrock Agents remain the fastest path from idea to working prototype. That console-driven, no-code-required first experiment is genuinely valuable. Don’t skip it.

But once you’ve validated the concept and the CEO is asking for more tools, more industries, faster iteration, and production reliability, the Intern needs a promotion. The Employee, built on Strands Agents and deployed via AgentCore, is flexible. It runs locally, switches models, adds tools in minutes, explains its reasoning, and lives in a container that runs the same everywhere.

And perhaps most importantly: the Employee’s office (AgentCore) doesn’t charge rent while the Employee is thinking. For agentic AI workloads where 70% of execution time is LLM wait, that changes the economics fundamentally.

The Intern has graduated. The Employee has arrived. And they brought their own container.


The complete repository is at github.com/javatask/ai-agent-ceo-fin-advisor. The original native Bedrock Agent lives in the root; the Strands migration is in the strands/ folder. Compare them side by side — the difference speaks for itself.