Back to Selected Work

ALEX — AWS Multi-Agent Architecture Study

A month spent building out, running and reading someone else's distributed agent system on AWS: what it actually does, what I deployed, what I sent back upstream, and what I am not claiming.

Whose project this is

ALEX is the capstone of Ed Donner's “AI in Production” Udemy course. The architecture, the agent prompts and the Terraform are his work, published under the MIT licence; my copy is a fork. I did not design this system and this page does not present it as mine.

What this page is: a record of a system I spent about a month building out, deploying and reading line by line — the topology as it actually runs, the AWS services I stood up and then destroyed, the four corrections I sent back upstream, and an explicit list of the things this experience does not qualify me to claim.

What I set out to learn

  • How a queue decouples an HTTP request from an agent run that takes minutes rather than milliseconds, and what the dead-letter path is for.
  • What an agent orchestrator looks like once the framework glue is stripped away — which steps are genuinely model decisions, and which are ordinary deterministic code that just happens to sit next to a model.
  • How Terraform splits a system this size into separately applied stages, and what breaks when the ordering assumptions between them are not met.
  • How a model hosted by Bedrock is driven from inside a Lambda, and where AWS region boundaries stop being an abstraction.
  • What managed AWS services cost while idle — which is the lesson that ended the deployment.

The architecture, as it actually runs

Redrawn from the Terraform and the Lambda handlers rather than from the course diagrams, because on one point the two do not agree: the three specialist agents are exposed to the orchestrator as function tools and invoked with InvocationType="RequestResponse", so the planner blocks on each one. They run one after another, in whatever order the model asks for them — not simultaneously.

The ALEX analysis path, from the Next.js frontend to the specialist agent LambdasA request from the Next.js frontend reaches API Gateway and the API Lambda, which writes a job row to Aurora and enqueues a message on the analysis_jobs SQS queue, backed by a dead-letter queue. An event source mapping delivers the message to the planner Lambda, which runs a deterministic pre-pass — tagging missing instruments, updating prices and loading the portfolio summary — before constructing a Bedrock-backed orchestrator agent. That agent invokes the reporter, charter and retirement Lambdas one at a time as synchronous RequestResponse calls. A separate retrieval path — an ingestion Lambda using SageMaker embeddings, an S3 Vectors index and a researcher MCP server on App Runner — is not invoked by the analysis path in the repository as read.Next.js frontendClerk authAPI GatewayAPI Lambdawrites the job row, enqueues, returnsSQS analysis_jobsredrive policyDLQrepeated failuresPlanner LambdaSQS event source mappingDeterministic pre-passhandle_missing_instruments → Tagger Lambdaupdate_instrument_pricesload_portfolio_summaryno model decides whether these runPlanner agentBedrock Claude via LiteLLMRequestResponse — blocking, one at a timeReporter Lambdafunction toolCharter Lambdafunction toolRetirement Lambdafunction toolRetrieval pathIngestion LambdaSageMaker embeddingsS3 Vectors indexresearcher MCP serverApp Runner, Dockerisednothing in the analysis path invokes itAurora Serverless v2RDS Data APIevery stage reads and writes the job row here
Read from terraform/6_agents/main.tf for the queue, its redrive policy and the event source mapping; backend/planner/agent.py for the deterministic pre-pass and the three tool invocations; terraform/4_researcher and terraform/5_database for the App Runner service and the Aurora cluster.

Request path

  1. 01

    The job is created and the request ends

    The Next.js frontend calls API Gateway, which invokes the API Lambda. It writes a job row to Aurora and puts a message on the analysis_jobs queue. The HTTP response returns immediately; nothing holds a connection open for the analysis.

    backend/api · terraform/6_agents/main.tf

  2. 02

    The queue hands off to the planner

    An aws_lambda_event_source_mapping wires analysis_jobs to the planner Lambda. A redrive policy diverts messages that keep failing to a dead-letter queue rather than retrying them forever.

    terraform/6_agents/main.tf — analysis_jobs, analysis_jobs_dlq, planner_sqs

  3. 03

    A deterministic pre-pass runs before any model does

    The planner fills in unclassified instruments by invoking the tagger Lambda, refreshes instrument prices, and loads the portfolio summary. This is ordinary Python control flow — no model chooses whether these steps happen.

    backend/planner/agent.py — handle_missing_instruments, update_instrument_prices, load_portfolio_summary

  4. 04

    Only then is the orchestrator agent built

    create_agent constructs the planner agent against a Bedrock-hosted Claude model through LiteLLM, with the reporter, charter and retirement agents attached as three function tools.

    backend/planner/agent.py — create_agent

  5. 05

    Each specialist is a blocking Lambda call

    Every tool call is a boto3 Lambda invoke with InvocationType="RequestResponse". The planner waits for that agent to return before the model can choose the next tool, so the three specialists execute in sequence.

    backend/planner/agent.py — invoke_lambda_agent

  6. 06

    Results are exchanged through the database

    Each agent writes its own output back to Aurora Serverless v2 through the RDS Data API instead of returning results up a call chain, and the frontend polls the job row to find out when the run is finished.

    backend/*/agent.py · terraform/5_database/main.tf

What I deployed, and what I tore down

  • I applied the Terraform against my own AWS account and ran the system end to end: the queue and its dead-letter queue, the five agent Lambdas, the Aurora Serverless v2 cluster, the SageMaker embedding endpoint, the S3 Vectors index and the Dockerised researcher service on App Runner.
  • The repository holds seven Terraform root configurations, numbered 2_sagemaker through 8_enterprise. The first guide provisions IAM through the console and has no Terraform of its own, which is why the guide count and the stage count do not match — a detail I had previously got wrong on this site.
  • The SageMaker step fails outside us-east-1, because the Deep Learning Container image is pulled from a registry account that differs per region. Working that out is what produced one of the notes I sent upstream.
  • Then I destroyed all of it. A SageMaker endpoint and an Aurora Serverless v2 cluster bill for being available rather than for being used, and this was a study project with exactly one user. Nothing described on this page is running today.

What I contributed upstream

Four changes, each on a public branch of my fork. All of them came from running the guides against the real code and finding a place where the two disagreed. The linked comparisons are the diffs.

  • A database-integrity verification step in guide 5

    The verification script itself is Ed's — it predates my fork. What I added is the guide step that has a reader run it at the moment the schema is created, so a bad apply surfaces there instead of three stages later.

    Compare add-verify-database-step6
  • A fix to the planner's local test harness

    The harness created an analysis job for a user id that did not exist in the database, and handed a Pydantic model to code that expected a dict. It failed for anyone who reached that step.

    Compare fix-guide8-undefined-accounts
  • Corrections to guide 8's logging and validation sections

    The sample code referred to fields — asset_class_allocation, region_allocation — that are not on the InstrumentClassification schema the agents actually use. I rewrote the Charter validation and Tagger explainability sections against the agent code as written.

    Compare fix-guide8-undefined-accounts
  • A cross-region note for SageMaker deployments

    Deploying the embedding endpoint outside us-east-1 requires resolving the Deep Learning Container image from the registry account AWS publishes for that region. The note records where that value has to be set.

    Compare docs/sagemaker-region-note

What this is not

  • Not my architecture. Every design decision described here is Ed Donner's. I can explain why the system is shaped this way; I cannot take credit for choosing it.
  • Not a running deployment. It ran in my AWS account during the study and was destroyed afterwards, deliberately.
  • Not evidence that I can operate this at scale. I ran it with my own test data, alone, with no traffic, no incidents and no on-call.
  • Not a source of cost or latency figures. I measured neither, so I quote neither.
  • Not a security review. The IAM policies, secrets handling and network boundaries are as the course ships them. I neither audited nor hardened them.