Field notes · Agentic AI

Four Agents Walk Into a Service Bay

or: why the most important agent is a state machine

Prefer to watch? Every animation in this post, narrated in about three minutes, with captions built in. The narration is a local AI clone of my voice.

A booking, a link, and four AI agents that turn "it makes a noise sometimes" into a summary a technician can act on. Built for scale, speed, reliability, visibility and failover, and told in animations.

Before we start: this is not how it is at Nissan

At Nissan I designed and shipped Tell Me More, a multi-agent intake chatbot. This post is not that system. It's my own recreation of the idea, expanded, in my own words. Every design, name, number and line of pseudocode here is mine and illustrative, not Nissan's architecture, data, prompts or code.

the intake
agent Hi! I'll help your technician get ready. What's going on with the car?
you brakes squeal when I stop, and the AC smells weird
→ two concerns queued: brakes (safety, first), AC smell (comfort, next)
agent Got it, let's start with the brakes. Is it every stop, or only some?

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.
01

"It makes a noise sometimes"

Most service visits start with a vague sentence. A technician needs facts.

Before and after. Before: a note that says "makes a noise sometimes". After intake: a structured summary with what (high-pitched squeal), where (front left, braking), when (started two weeks ago, getting worse), how to reproduce (light braking under 20 mph, mornings), warning lights (none), and safety (drivable, flagged for priority). BEFORE "makes a noise sometimes" call the customer back → AFTER INTAKE Concern 1 · brakes whathigh-pitched squeal wherefront left, braking when2 weeks, worse reprolight braking, <20 mph lightsnone safetypriority: brakes confirmed by the customer ✓ the technician walks in knowing what to look for
The whole product in one picture: a vague note in, a confirmed, structured summary out.
02

The whole system, in one animation

Three acts. Every later section zooms into one box.

Animation · a booking, a conversation, a hand-off
0 / 14Press play to follow one appointment from booking to the technician's screen.
The whole system. A booking asks the intake service for a signed link, which the dealership texts to the customer. The customer's browser connects over an API Gateway WebSocket to a chat service on ECS, which keeps session state in DynamoDB and passes every message through guardrails to the Conversation Agent on AgentCore. The agent calls three tools in parallel: the Rubric, Vehicle History and Dealership Internal agents. When the customer confirms a summary, it is saved and Step Functions delivers it to the dealership, with a dead-letter queue for failures. CloudWatch collects traces and metrics from every step. BOOKING · once per appointment booking system signed link text / email CONVERSATION · every turn customer's browser API Gateway · WebSocket chat service · FastAPI on ECSstate machine per concern DynamoDBsessions guardrails · PII redaction · content checks Conversation Agent · AgentCorethe only one the customer talks to · memory Rubricextract fields Vehicle Historypast repairs Dealershipwarranty · parts HAND-OFF · once per concern summarycustomer confirms saved onceidempotent key Step Functions · deliverretries with backoff dead letters dealership systemtechnician sees it before the visit next concernfrom the queue CloudWatch · traces · metrics · alarmsevery step above reports here
    03reliability

    Four agents, one conductor

    One agent talks. Three are its tools. Code decides what happens next.

    The Conversation Agent sits in the middle and is the only agent the customer sees. It calls three tools: the Rubric Agent, which extracts fields and decides what is still missing; the Vehicle History Agent, which pulls past repairs for the VIN; and the Dealership Internal Agent, which checks warranty, parts and advisories. Outside the agents, a state machine owns each concern's lifecycle: queued, intake, summary, confirmed, delivered. customer Conversation Agent talks · plans the next question tooltooltool Rubricfields and gaps Vehicle Historypast repairs by VIN Dealershipwarranty · parts STATE MACHINE (no LLM) queued intake summary confirmed sent
    Agents handle language; the state machine handles control. A model never decides whether a summary was confirmed.
    04scale

    Safety first: the concern queue

    Three problems in one breath? A priority queue, safety first:

    Interactive · what the customer mentions, and what we ask about first

      Mention a few problems, in any order.

      05speed

      Knowing when you're done

      Fewer questions, same facts: every answer should fill as many fields as it can.

      Interactive · one answer, many fields

      Each tile is a fact the technician needs. Pick an answer.

      Sorry, sorry, quick interruption

      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.

      06speed

      Memory is a cache with manners

      Memory is layered caching, nearest first:

      Memory tiers. The model's context window holds only the current concern and recent turns. Session memory holds every extracted fact for this conversation, so nothing is asked twice. Long-term memory, per VIN, holds facts from earlier visits. Systems of record, like the vehicle history and dealership systems, are reached through tools. Each tier is larger and slower than the one above it, and PII is redacted before anything is written. context windowthis concern + last turns session memoryevery fact so far · never ask twice long-term memory, per VIN"last spring: rear brake pads replaced" systems of record, via toolsvehicle history · warranty · parts smaller and faster at the top · PII redacted before any write
      Built on AgentCore's short- and long-term memory; the tiering is the same instinct as an ad server's local cache.
      07speed

      One turn, against the clock

      Same work, three schedules:

      Animation · time to the first word, three ways
      Timeline of one conversational turn under the chosen schedule, with the moment the customer sees the first word marked

      Pick a schedule.

      illustrative timings, in seconds
      turn · pseudocode
      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 reply
      fan outtools at once
      deadline2 s, then skip
      streamfirst words early
      turn IDretries can't duplicate
      08scale

      Monday, 9 am

      Links open in waves; the model has a quota. Drag the wave:

      Interactive · a morning of model calls against the quota
      Model calls per minute over a 90-minute morning, against the quota line, with failed or queued calls shaded
      calls wanted per minutequotathrottled (errors)

      admit · pseudocode
      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.

      09reliabilityfailover

      When the model says no

      Design as if the model will be slow, wrong or gone. Break something:

      Animation · break something
      1. Pick a failure above.

      Every failure lands on one ladder:

      Animation · the failover ladder
      1. retry with jittermost throttles clear in a second
      2. another regioncross-region inference, same model
      3. a smaller modelfaster, cheaper, still good at intake
      4. scripted questionsthe rubric's questions, in order: no model
      5. "we'll text you"progress saved, link sent again

      Pick an outage and watch it fall down the ladder.

      Sessions live in a DynamoDB global table; hand-offs retry in Step Functions.

      10visibility

      Quality is a metric too

      Fast and error-free isn't enough. Ship a bad prompt:

      Interactive · the on-call dashboard
      simulated: one tick per minute; alarm when completion rate stays under target for 3 minutes

      Every turn is a trace in AgentCore observability and CloudWatch.

      11visibilityreliability

      Ship prompts like code

      Animation · shipping a prompt
      1. Pick a prompt to ship.
      12reliability

      Guardrails both ways

      Guardrails on the way in and out. Incoming messages are redacted for personal information, checked for harmful or off-topic content and for prompt injection before any agent sees them. Outgoing replies are checked for the no-diagnosis rule and for leaked personal information before the customer sees them. IN message redact PII content · injection agents OUT reply no diagnosis, ever no PII customer
      Content and PII checks with Bedrock Guardrails; no-diagnosis is its own check.
      Animation · one message in, one reply out
      inHi, I'm Sam Rivera, 555-0142. My brakes squeal.
      out—

      Press send.

      Sorry to interrupt, again

      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.

      13

      Ten decisions, one page

      My calls for this recreation, with what each one costs.

      1. How do the agents work together?
        group chatone big agentone talker, three tools

        Every turn stays explainable. Cost: the talker must stay fast.

      2. Who decides what happens next?
        the modela state machine

        “Done” is a fact, not an opinion. Cost: new flows need code.

      3. How do messages travel?
        pollingserver-sent eventsWebSocket

        Two-way, streaming, managed. Cost: reconnects to handle.

      4. Where does session state live?
        in memorya relational databaseDynamoDB, saved every turn

        Resumable, and global for failover. Cost: a write per turn.

      5. How do tools run?
        one after anotherin parallel, with a deadline

        The turn waits for the slowest, capped. Cost: sometimes no history.

      6. What does the model remember?
        the full transcriptfacts + recent turns, tiered

        Small prompts are fast and cheap. Cost: extraction must be right.

      7. 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.

      8. What if the model is down?
        show an errorwait it outthe failover ladder

        The last rung needs no model. Cost: plainer questions.

      9. 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.

      10. 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.

      14

      The well-earned drink

      Every Brew Miles trip ends with one:

      1. Let models talk and let code decide; the most important agent is a state machine.
      2. Fan out with deadlines, stream the reply, and save every turn by its ID.
      3. Treat quota like capacity: a token bucket, a queue, and a graceful "later".
      4. Build a failover ladder whose last rung needs no model.
      5. Put quality on the dashboard, and ship prompts like code.