The short version, for whichever hat you're wearing
- One agent talks; three work as its tools, and a plain state machine decides what happens next.
- Every turn has a deadline, tools run in parallel, and the reply streams.
- When the model fails, the conversation doesn't: retry, another region, a smaller model, then scripted questions.
- Agents as tools, not a group chat: one caller, typed outputs, schema-checked JSON.
- Save state every turn, keyed by turn ID, so a retry or a reconnect never duplicates or loses anything.
- Fan out tool calls with a deadline; a slow tool is skipped and flagged, never waited on.
- Separate the conversation from the workflow: agents for language, a state machine for control, Step Functions for hand-off.
- Admission control in front of the model: a token bucket and a queue that favours live conversations.
- A failover ladder, ending in a deterministic script that needs no model at all.
- The LLM is the least reliable component; design as if it will be slow, wrong or gone.
- Quality is a metric: rubric scores per dimension sit on the same dashboard as p99.
- Prompts are code: versioned, tested against golden conversations, shipped by canary, rolled back by alias.
- Plan for Monday 9 am: quotas are a capacity plan, like hosts.
- Page on symptoms: p99 turn time, errors, cost per session, completion rate.
- Every prompt change gets a golden-set run and a canary, with a one-command rollback.
- The product is a better first visit: the technician knows what, where, when and how to reproduce before the car arrives.
- Safety concerns jump the queue, and the agent never guesses a diagnosis.
- Success is completed, confirmed summaries, in few turns, that a technician can act on without calling the customer.
"It makes a noise sometimes"
Most service visits start with a vague sentence. A technician needs facts.
The whole system, in one animation
Three acts. Every later section zooms into one box.
Four agents, one conductor
One agent talks. Three are its tools. Code decides what happens next.
Safety first: the concern queue
Three problems in one breath? A priority queue, safety first:
Knowing when you're done
Fewer questions, same facts: every answer should fill as many fields as it can.
Every mechanic I've talked to says the most useful thing a customer can say is "I can make it happen again." That's the reproduction field. If you remember one thing from this post, make it that. Okay, back to it.
Memory is a cache with manners
Memory is layered caching, nearest first:
One turn, against the clock
Same work, three schedules:
async def handle_turn(session, message):
message = guardrails.check(redact_pii(message)) # nothing raw reaches a model
state = await sessions.load(session.id) # DynamoDB: the source of truth
if state.already_answered(message.turn_id): # a retried or duplicate turn
return state.last_reply
concern = state.queue.current() # highest-priority open concern
fields, history, dealer = await gather_with_deadline(
rubric.extract(concern, message), # what did that answer tell us?
vehicle_history.lookup(state.vin), # past repairs, cached per VIN
dealership.check(state.vin, concern), # warranty, parts, advisories
deadline=2.0) # a slow tool is skipped, not waited on
concern.merge(fields)
reply = await conversation.next_turn(concern, history, dealer) # streams to the browser
await sessions.save(state, turn_id=message.turn_id) # saved every turn: resumable
return replyMonday, 9 am
Links open in waves; the model has a quota. Drag the wave:
def admit(call, bucket, queue):
if bucket.take(call.tokens): # under quota: go now
return run(call)
if call.session.is_active: # mid-conversation beats new arrivals
return queue.push_front(call)
if queue.wait_seconds() < 20: # a short wait is better than an error
return queue.push(call)
return offer_callback(call.session) # shed gracefully: "we'll text you when it's quiet"A token bucket, a queue that favours live chats, and graceful load shedding.
When the model says no
Design as if the model will be slow, wrong or gone. Break something:
Every failure lands on one ladder:
Sessions live in a DynamoDB global table; hand-offs retry in Step Functions.
Quality is a metric too
Fast and error-free isn't enough. Ship a bad prompt:
Every turn is a trace in AgentCore observability and CloudWatch.
Ship prompts like code
Guardrails both ways
The hardest thing to teach a diagnostic agent is to not diagnose. It's like asking me not to recommend a beer on a brewery tour. Possible, but you have to check every single time.
Ten decisions, one page
My calls for this recreation, with what each one costs.
- How do the agents work together?group chatone big agentone talker, three tools
Every turn stays explainable. Cost: the talker must stay fast.
- Who decides what happens next?the modela state machine
“Done” is a fact, not an opinion. Cost: new flows need code.
- How do messages travel?pollingserver-sent eventsWebSocket
Two-way, streaming, managed. Cost: reconnects to handle.
- Where does session state live?in memorya relational databaseDynamoDB, saved every turn
Resumable, and global for failover. Cost: a write per turn.
- How do tools run?one after anotherin parallel, with a deadline
The turn waits for the slowest, capped. Cost: sometimes no history.
- What does the model remember?the full transcriptfacts + recent turns, tiered
Small prompts are fast and cheap. Cost: extraction must be right.
- What happens at the quota?let it throttlebuy more quotatoken bucket, queue, shed
Live chats never fail for new ones. Cost: some wait, or get a text.
- What if the model is down?show an errorwait it outthe failover ladder
The last rung needs no model. Cost: plainer questions.
- How does a summary reach the dealership?call it inlineStep Functions + retries + DLQ
A slow dealer system never blocks a customer. Cost: delivery is async.
- How do prompts ship?edit in placemanual spot checksgolden set, judge, canary, alias
Regressions stop before customers see them. Cost: a golden set to curate.
The well-earned drink
Every Brew Miles trip ends with one:
- Let models talk and let code decide; the most important agent is a state machine.
- Fan out with deadlines, stream the reply, and save every turn by its ID.
- Treat quota like capacity: a token bucket, a queue, and a graceful "later".
- Build a failover ladder whose last rung needs no model.
- Put quality on the dashboard, and ship prompts like code.
Show the public sources this post links
- Amazon Bedrock AgentCore and its memory and observability docs (AWS).
- Multi-agent collaboration on Amazon Bedrock (AWS docs).
- Cross-region inference and Bedrock quotas (AWS docs).
- Amazon Bedrock Guardrails (AWS).
- API Gateway WebSocket APIs and Step Functions error handling (AWS docs).
- DynamoDB global tables (AWS).
- Using load shedding to avoid overload, timeouts, retries and backoff with jitter, and idempotent APIs (AWS Builders' Library).
- Generative AI observability in CloudWatch (AWS docs).
- Token bucket (Wikipedia).