Agentic AI · AWS Strands · Bedrock AgentCore

Building a Multi-Agent M&A Research Desk on AWS

One coordinator, four specialists, a sandbox, a memory of prior deals, and a human gate on every consequential action — built end to end on Strands Agents and Bedrock AgentCore, with nothing leaving your AWS account.

June 2026 Strands Agents AgentCore Runtime Memory · Gateway · Identity Code Interpreter
Contents
  1. The problem: deal research
  2. Why own the whole stack
  3. Architecture
  4. Multi-agent fan-out
  5. Sandboxed compute
  6. Memory that changes the verdict
  7. Tools via Gateway + Identity
  8. Structured output + evals
  9. Human-in-the-loop gating
  10. Streaming, observability, deploy
  11. What you get
01

The problem: deal research at a roll-up

Atlas Deal Desk evaluates acquisition targets for a mid-market industrial roll-up. Given a short list — Acme Robotics, Bridgewell Dynamics, Norwood Automation — it has to do the unglamorous core of corporate development: build a DCF for each, pull public-market comparables, read the sector tailwinds, check the open diligence red flags, weigh it all against what prior deals taught the committee, and hand back a clear PURSUE / HOLD / PASS for each with an IRR and a rationale.

None of those steps is hard alone. The difficulty is that they interlock and pull in different kinds of work: a DCF is arithmetic that must be exact; macro research is open-web; diligence is a credentialed internal API; and the final call depends on institutional memory — "we walked from Brightline because integration risk was the silent killer." Pour all of that into one prompt with every tool attached and you get the failure mode every agent engineer knows: context bloat, unreliable tool selection, and methodology no one can review.

The fix is structure — a team, not a monolith — and a production substrate underneath it. The interesting question is who owns that substrate.

02

Why own the whole stack

A managed agent runtime gets you to a capable agent in a few API calls — the loop, the sandbox, memory, and skills all run as a service. That velocity is real. But for a buyer in finance, pharma, or the public sector, one question decides the platform before any feature comparison: does our data leave our account?

Atlas answers "no" by construction. It runs entirely on AWS services you own:

THE LOOP
Strands Agents
An open-source SDK that is the reason→act→observe loop. Tools are typed Python functions; sub-agents compose as tools. Runs in your process.
THE SCAFFOLDING
Bedrock AgentCore
Composable, serverless services — Runtime, Memory, Gateway, Identity, Code Interpreter, Observability — each adopted à la carte, all inside your account.

Inference is Bedrock. Code runs in an AgentCore microVM. Memory is an AgentCore store. Secrets live in AgentCore Identity. Every call is captured by CloudTrail and traced by AgentCore Observability. The trust boundary is your own IAM and VPC — not a second vendor's access model. That is the entire argument, and for regulated deal work it is usually the whole conversation.

03

Architecture

Your AWS account · your region · your IAM & CloudTrail USER REQUEST "Evaluate Acme, Bridgewell, and Norwood" COORDINATOR · Strands Agent (Claude on Bedrock) recall_priors · load_skill · record_decision (gated) fans out to the team, then synthesizes the thesis macro-trends sector tailwinds · web_search financial-analyst DCF · runs Python competitive-positioning moat · diligence tracker valuation-comps peer multiples · median — BEDROCK AGENTCORE SERVICES — Code Interpreter isolated microVM · pandas DCF + comps math runs here Memory prior-deal lessons semantic · namespaced Gateway + Identity diligence tracker via MCP credential never in code AgentCore Runtime — /invocations (SSE) · /ping microVM per session · immutable versions · named endpoints Observability OTEL spans → CloudWatch GenAI
Atlas end to end. The Strands coordinator and four sub-agents run on AgentCore Runtime; heavy compute, memory, and credentialed tools are AgentCore services — all inside one AWS account.
04

Multi-agent fan-out

The team is four specialists, each its own Strands Agent with its own context window and its own tools, exposed to the coordinator as a tool — the agents-as-tools pattern. Heavy, short-lived context (a full comps pull, a DCF's intermediate tables) stays inside the specialist and never crowds the coordinator.

@tool
def financial_analyst(target: str) -> str:
    """Build a DCF for one target and return NPV, IRR, and the three
    most sensitive assumptions."""
    sub = build_subagent("financial-analyst")   # own context + run_python tool
    return str(sub(f"Build a DCF for {target}…"))   # returns a compact result

The coordinator calls all four and gets four compact summaries back, never the raw work. When you want deterministic parallelism instead of letting the model choose when to fan out, the same four agents drop into a Strands GraphBuilder graph — coordinator node, four specialist nodes, edges fanning out — and run concurrently.

The rule that generalizes

If a step carries its own large, short-lived context that's irrelevant to the outer task, give it its own agent. A 5-year DCF qualifies; the intermediate model is irrelevant once the IRR pops out. Writing a one-line Slack note does not — the isolation overhead would exceed the benefit.

One config flip — ATLAS_MULTIAGENT — toggles the coordinator between a solo agent and the full roster, so the workshop can measure the before/after rather than assert it.

05

Sandboxed compute

The financial-analyst doesn't eyeball a DCF; it writes pandas and runs it. Model-generated code on your own host is a bad evening waiting to happen, so it executes in AgentCore Code Interpreter — a managed, serverless microVM, per session, server-side. One run_python tool hides two backends so the agent logic never changes:

def run_python(code: str, data_files: list[str]) -> str:
    if config.use_code_interpreter:          # ATLAS_CODE_INTERPRETER=1
        return _run_agentcore(code, data_files)   # managed microVM, pulls CSVs from S3
    return _run_local(code, data_files)        # local subprocess fallback for dev

The target financials live in a versioned S3 bucket; the sandbox pulls the requested CSVs into its working directory before the code runs. The seam is the point: for a customer who needs everything in their own VPC, you swap the backend for a container job that reads their S3 — and the agent doesn't change. Only the execution boundary moves.

Loud, not silent

When the managed sandbox is enabled but the SDK or permission is missing, run_python returns a loud AGENTCORE_ERROR: rather than quietly falling back to local execution. You should always know which boundary your code actually ran in.

06

Memory that changes the verdict

The committee learns from every deal. Those lessons — Brightline (integration risk killed it), Keswick (customer concentration repriced at renewal), Tessaro (a clean profile plus a motivated seller is the best return) — live in AgentCore Memory with a semantic strategy, under a namespace, and are recalled before any recommendation.

AgentCore Memory — short-term & long-term SHORT-TERM (this session) turn-by-turn conversation the current evaluation in progress ephemeral · scoped to the run extract (semantic strategy) LONG-TERM (across deals) namespace: deal-priors/committee Brightline · Keswick · Tessaro lessons vector recall by relevance coordinator: recall_priors("Bridgewell concentration") → "Keswick rule: >30% from one customer = discount the forward multiple, diligence the renewal pre-LOI"
Recall happens before the recommendation. The Keswick prior turns a clean-looking Bridgewell run-rate into a HOLD — exactly the kind of judgment a prompt-only agent forgets.

In code, two surfaces: a recall_priors tool the coordinator calls explicitly, and a MemoryHookProvider that loads recent priors on AgentInitializedEvent and persists each turn on MessageAddedEvent — so today's evaluation is tomorrow's prior. Until you provision the store, a local fallback reads the seed markdown, so the workshop runs from minute one.

07

Tools via Gateway + Identity

Before competitive-positioning forms a view, it must check the committee's diligence tracker for open red flags — "Bridgewell: top-customer renewal unconfirmed." In production that tracker is an internal API or a SaaS issue tracker, exposed to the agent as MCP tools through AgentCore Gateway, with the credential held by AgentCore Identity (outbound auth) and never written into agent code.

ConcernManaged-runtime approachAtlas on AWS
Expose a toolMCP server attached to the agentAgentCore Gateway turns an API/Lambda into MCP tools
Hold the credentialManaged vaultAgentCore Identity (outbound, user-delegated or autonomous)
Audit the accessProvider consoleCloudTrail — every token fetch, in your account

Gateway also gives you semantic search across a large tool inventory, so an agent facing hundreds of tools keeps a focused, low-latency set. To keep the workshop runnable with no third-party signup, Atlas ships a local mock of the tracker with the same issues; set ATLAS_GATEWAY_URL and the real Gateway/MCP path takes over without touching the agent.

08

Structured output + the eval harness

The committee needs a comparable artifact, not free text. The coordinator returns a validated InvestmentThesis via Strands structured output — the agent fills a Pydantic model, the SDK validates it, and the same shape is what the grader checks. It replaces a managed rubric with an artifact you own in code.

And decomposition is a claim — "the team is better than the solo agent." A claim needs a measurement. Atlas ships an eval suite that runs the same tasks against both and grades the result:

Eval harness — solo vs team, same tasks tasks.yaml R1 schema_valid R2 verdict_present R3 priors_applied R4 priors_applied R5 action_taken F1 numeric_tol ± run against both SOLO coordinator only no roster TEAM 4 sub-agents + memory + sandbox GRADERS schema_valid verdict_present priors_applied action_taken numeric_tolerance ± side-effect + judgment graders SCORECARD pass / fail per task wall time solo vs team delta team wins on priors & thesis tasks
The grader for R5 checks a side effect — a decision actually written to the sink — not text. numeric_tolerance grades IRR within a band, because a reorder-style judgment has legitimate spread.
# solo coordinator vs full team, side by side
uv run python evals/run.py --compare
09

Human-in-the-loop gating

Recording a committee decision is a side effect you don't want an agent taking unsupervised. Atlas gates it with a Strands hook on BeforeToolCallEvent: when record_decision is about to run, an approver is asked; deny, and the call is cancelled and the model is told.

def _before_tool(self, event):
    name = _tool_name(event)
    if name not in self.gated:          # only gate side-effecting tools
        return
    if not self.approver(name, _tool_input(event)):
        _cancel(event, "DENIED by a human reviewer.")   # the call never runs

The approver is pluggable: a terminal prompt in the CLI, Allow / Deny buttons in the Streamlit UI, or an auto-approve in the eval harness. Same hook, three surfaces — the gate is policy, not UI.

10

Streaming, observability & deployment

Because the coordinator is "just" a Strands program, deployment is ordinary AWS. Wrapping it in a BedrockAgentCoreApp gives it the production contract — POST /invocations with SSE streaming and GET /ping — running in an isolated microVM per session:

@app.entrypoint
async def invoke(payload: dict):
    agent = build_coordinator()
    async for event in agent.stream_async(payload["prompt"]):
        yield event            # streamed to the UI as it happens

The CLI (v0.21+) packages the source as a CodeZip and deploys through CDK / CloudFormation in a single command — no Dockerfile, no ECR push. The CDK stack creates the Runtime, the IAM execution role, and a DEFAULT endpoint entirely inside your account. Each update is a new immutable version; point a named endpoint (dev / test / prod) at a version and roll back instantly by repointing it. The same code runs on your laptop or the Runtime — only the credential source changes.

The audit story falls out of native AWS

Every model call, tool call, and sub-agent hop is an OpenTelemetry span in the AgentCore / CloudWatch GenAI dashboard. CloudTrail records every Bedrock, Memory, Identity, and Code Interpreter call. S3 versioning records every skill change. "Show me every action this agent took, which model, which data, on what date" has a native answer — no extra vendor in the audit scope.

11

What you get

Atlas Deal Desk ships as a hands-on workshop: a starter/ tree with eight stubbed functions — one per capability — and a complete reference to diff against. Build it in order and the deal team comes alive a piece at a time.

EXERCISES 1–4
Connect → team → sandbox → memory
Wire the AWS session, assemble the specialists, run a DCF in Code Interpreter, recall prior-deal lessons from Memory.
EXERCISES 5–8
Gateway → coordinator → gate → thesis
Reach the tracker through Gateway + Identity, assemble the coordinator, gate the write, and return a validated thesis.

The throughline is the same one that makes decomposition worth it: every piece — a sub-agent, a skill, a memory, a gated action — is a part you can add, version, test, and reason about on its own. A monolith grows by accretion into something no one understands; this grows by composition into something you can still explain to a teammate, to an auditor, or to yourself six months later.

Code

The full repository — both agents, the four sub-agents, skills, the sandbox abstraction, the eval harness, the Streamlit UI, and the provisioning scripts — is on GitHub: github.com/laghao/atlas-deal-desk. Runs entirely on Amazon Bedrock + AgentCore. MIT licensed.