A production-shaped multi-agent GenAI system: a thin LLM orchestrator coordinates three containerized domain specialists over the A2A (agent-to-agent) protocol, with zero-trust per-request identity, policy-as-code authorization, per-user long-term memory, and online LLM-as-judge evaluation.
Built on Amazon Bedrock AgentCore with the Strands agent framework and Claude Sonnet 4.5.
Scope. This is an engineering showcase of agent architecture — orchestration, identity, authorization, memory, and evaluation. The three backend integrations (Klaviyo send, Shopify products, analytics) are mock Lambda tools, and account-specific identifiers (account ID, Cognito pool, ARNs) are placeholders. Deploying requires your own AWS account + Cognito; the unit tests and CI run with no AWS.
Mailo turns a request like "Research Bloom Petal and plan a Valentine's campaign with subject lines and a send time" into a complete campaign plan. Rather than one monolithic prompt, it splits the work across a coordinator and three specialists, each owning a domain and its own tools:
orchestrator— validates the user, reads per-user memory, plans, and delegates over A2A. A pure planner with no direct tool/Gateway access.brand_intel— brand profile, competitor benchmarks, campaign metrics.content_gen— subject lines, tone adjustment, product-led copy.campaign_planning— audience segments, send-time recommendations, and the send itself (Cedar-gated).
flowchart TD
U["User (Cognito JWT)"] -->|Bearer + Session-Id| O["Orchestrator<br/>HTTP · CUSTOM_JWT · per-request agent"]
O <-->|per-user read/write| M["AgentCore Memory<br/>/users/{sub}/preferences · /facts"]
O -->|A2A + M2M Bearer + workload token| BI["brand_intel"]
O --> CG["content_gen"]
O --> CP["campaign_planning"]
BI -->|MCP| GW
CG -->|MCP| GW
CP -->|MCP| GW["MCP Gateway<br/>Cedar policy engine (ENFORCE)<br/>recipients < 5000"]
GW --> L1["Analytics (Lambda · mock)"]
GW --> L2["Shopify (Lambda · mock)"]
GW --> L3["Klaviyo send (Lambda · mock)"]
Full walkthrough — request lifecycle, the two-client identity model, A2A vs MCP, Cedar, memory isolation, observability, and evaluation — in docs/ARCHITECTURE.md.
| Area | What's implemented |
|---|---|
| Agentic orchestration | Thin LLM planner fanning out to three specialists over the A2A protocol; a fresh per-request agent (no singletons) so each request has its own identity and memory scope |
| Zero-trust identity | Two-client Cognito model; per-request M2M OAuth token minting; specialists exchange the inbound workload token for their own Gateway bearer; tenant identity comes from the validated JWT sub, never the request body |
| Prompt-injection hardening | A2A tool surface whitelisted to list + send — discover_agent is dropped so the model physically cannot fabricate a specialist URL |
| Policy-as-code authz | Cedar policy enforced at the Gateway (recipients < 5000) — holds even if the model is jailbroken, because it lives outside the prompt |
| Per-user memory | AgentCore Memory (USER_PREFERENCE + SEMANTIC strategies) namespaced per user, with defense-in-depth isolation |
| Evaluation | Custom LLM-as-judge + built-in evaluators, sampling 25% of live traffic continuously |
| Tooling via MCP | Specialists reach Lambda backends through an MCP Gateway — discovery, auth, and Cedar enforcement all at the boundary |
| Ops | Zero-instrumentation OpenTelemetry → CloudWatch; multi-stage non-root Docker; AWS CDK IaC |
Python 3.12 · Strands Agents · Amazon Bedrock (Claude Sonnet 4.5) · Bedrock AgentCore (Runtime · Gateway · Memory · Identity/Token Vault · Cedar policies · Evaluations) · A2A · MCP · Cognito · AWS CDK · OpenTelemetry · pytest · GitHub Actions.
mailo/
app/
orchestrator/ # HTTP planner: A2A client, memory, JWT identity (identity.py)
brand_intel/ # A2A specialist + Analytics tool
content_gen/ # A2A specialist + Shopify tool
campaign_planning/ # A2A specialist + Klaviyo send tool (Cedar-gated)
agentcore/ # AgentCore project spec (agentcore.json) + CDK
policies/ # Cedar policy (permit_send.cedar)
tools/ # MCP tool schemas (klaviyo / shopify / analytics)
tests/ # unit tests (run with no AWS)
scripts/ # deploy + auth helper scripts
docs/ARCHITECTURE.md # full system walkthrough + diagrams
The security-critical logic (JWT identity extraction, A2A tool whitelist) is unit-tested and runs without AWS:
pip install pyjwt pytest
pytestCI (GitHub Actions) runs ruff + pytest on every push.
Requires AWS credentials, Bedrock model access (Claude Sonnet 4.5), and a Cognito user pool with a
human + M2M app client. Replace the placeholder identifiers in mailo/agentcore/agentcore.json and
mailo/agentcore/aws-targets.json, copy mailo/agentcore/.env.local.example to .env.local with
your M2M client credentials, then:
cd mailo
agentcore deploy
bash ../scripts/wire_specialists.sh # re-merge orchestrator → specialist URLs after deployThe full deploy ritual and the IAM/Cedar ordering notes are in docs/ARCHITECTURE.md.
The AgentCore CLI scaffolded the project skeleton and CDK. The engineering I authored: the
orchestrator and all three specialists, the A2A delegation + tool whitelist, the per-request
M2M / workload-token identity flow, the JWT-sub tenant identity, the MCP client
lifecycle, the Cedar authorization, the per-user memory integration, the evaluation
rubric, the unit tests + CI, and the architecture documentation.