RC2026

AI Agent Case Study

·
DEMO

Supply Genie

Personal demo of an AI worker for supply-chain exceptions: playbooks that run themselves, a live phone agent, and a person on every customer-facing step.

Artifacts

Stack

AI Agent Case StudyVoice AITool CallingHuman-in-the-LoopFailure Handling

Synopsis

Supply Genie is a personal demo of an AI worker for two supply-chain exceptions, a shipment delay and a worker absence. A webhook opens an incident, a fixed playbook turns the incident into ordered actions (for a delay: a Slack alert, a one-way Twilio call that reads the new ETA to the supplier, a purchase-order amendment and a customer email), and an operator workspace shows every step. The customer email waits for an operator's approval. Operators can ask a Gemini 2.5 Flash chat about the incident or tell the chat to run a step, and a separate phone agent streams live calls into Gemini Live. The project was built while applying to HappyRobot and is a conceptual demo inspired by how HappyRobot approaches the problem, not a production system: no users, no deployment and no outcome metrics, and the repo's prompts and UI labels still carry the HappyRobot name. The demo is about the engineering around the model: retry-safe intake, approvals stored as data, versioned purchase-order writes, and failure handling that refuses to auto-retry a Slack post or a phone call when the outcome of the first attempt is unknown.

An AI worker for supply-chain exceptions that does the routine steps itself and waits for a person on anything customer-facing.

How a Supply Genie exception flows, recreated with sample data. A webhook with an Idempotency-Key opens a shipment-delay incident for PO-48213: a new key returns 201, the same key returns the existing incident with 200, and a missing key is rejected with 422. The incident's playbook runs four steps in order: a Slack alert, a Twilio call to the supplier and a version-checked PO amendment are completed, and the customer email is locked until an operator approves. The operator's Approve and Reject buttons decide it once; a second click gets 409. Below, an operator chat on Gemini 2.5 Flash and a phone agent on Gemini Live both request steps through the same command list and approval rule, and neither can approve.
A webhook opens one incident, plain Python runs the playbook, and the customer email waits for a person; both AI agents go through the same rules.
Product features
#1

Approve

Routine steps run on their own; the customer email waits in the workspace until an operator approves or rejects it.

Supply Genie operator workspace, recreated with sample data. The inbox lists four incidents grouped into needs your action and recently resolved. The selected shipment delay for PO-48213 shows its playbook with Notify ops, Call supplier and Amend PO done and Email customer locked and awaiting the operator. An amber approval card previews the customer email (recipient, subject and body announcing the new Thursday 14 November delivery) with Approve and send and Reject buttons. The context panel shows the shipment's original and current ETA and an action history in which the PO amendment moved the order from version 3 to 4.
#2

Recover

A failed step keeps its error and attempts, retries with jittered backoff, and goes to a person after the third failure instead of looping.

Supply Genie failed step, recreated with sample data. For PO-60420 the Amend PO step has failed twice because the purchase order is not in the ERP. A red card shows the error, a fallback note, and each attempt with its time and result: two failures with jittered retry delays of 0.13 and 0.31 seconds and a third attempt scheduled as the last try before the dead-letter list. The earlier steps stay done, the customer email still waits for approval, and the context panel shows the incident's Idempotency-Key, correlation ID and a Retry failed button.
#3

Ask

The operator chat answers from the incident's own action rows, and asking it to email the customer produces an approval card, not an email.

Supply Genie operator chat, recreated with sample data. On the PO-60420 incident the operator asks why the last step failed; the Gemini 2.5 Flash chat calls get_incident_status and explains that the PO is missing from the ERP and what happens next. The operator then asks it to email the customer the new ETA; the chat calls execute_command, which returns needs_approval, and shows an approval card instead of sending the email.
#4

Call

The phone agent works through four objectives on a live call, hangs up when all are confirmed, and leaves a structured summary in Slack.

Supply Genie phone agent, recreated with sample data. On the left, a three-minute outbound call with Northwind Components about PO-48213 runs through Twilio Media Streams, Pipecat and Gemini Live; the timestamped transcript shows the agent confirming four objectives (cause, new ETA, mitigation and risk) before hanging up, and the transcript is saved with the call. On the right, the structured call summary posted by Supply Genie to the ops-exceptions Slack channel lists the resin shortage, the new Thursday 14 November ETA, a partial shipment of 600 units on Tuesday and the risk of a second slip.

Architecture

System Layers

Operator workspace (Next.js)

Next.js 16, React 19, Tailwind 4 and shadcn/ui. The incident inbox polls every five seconds and groups incidents into needs-action, escalated and resolved; a playbook strip marks the gated step with a lock; approval cards sit in the center column; an action history shows each step's retries, errors and approval decision; and a chat panel relays questions and commands to Gemini through the API. Every status on screen comes from a database row.

Iglobe

API (FastAPI)

Python and FastAPI. The delay and absence webhooks require an Idempotency-Key header; other routes handle approvals, retries, the dead-letter list and requeue, chat, and the Twilio voice webhooks plus the media-stream WebSocket. Middleware stamps a correlation ID on every request and response and stops non-voice requests after 90 seconds (default). The readiness probe reports database health, circuit-breaker states and active calls.

IIserver

Agents (Gemini)

The operator chat calls Gemini 2.5 Flash with three function tools (incident status, shipment lookup, run a command) and loops for up to five tool rounds per message. The phone agent runs Gemini Live with native audio inside a Pipecat pipeline and has five tools, adding call-progress tracking and hang-up. Neither model talks to Slack, Twilio or Resend directly: both agents request incident actions through the same command list, executor and approval rule. After each call, Gemini 2.5 Flash turns the transcript into a structured summary that is saved with the call and posted to Slack.

IIIcpu

State (PostgreSQL and Redis)

PostgreSQL, through SQLAlchemy and Alembic, holds suppliers, shipments, purchase orders, incidents, action runs, approvals, voice sessions and transcripts. Each step's status, attempt count, request and response payloads and last error live on a single action_runs row, and in queued mode the same table is the work queue. Redis holds only short-lived call state: a checkpoint per active call, with a one-hour expiry by default, and a command list per call.

IVdatabase

Connectors and failure handling

Slack (slack-sdk), Twilio (REST calls and Media Streams) and Resend are real integrations; the purchase-order system is a PostgreSQL table with a version column, and the labor (HRIS) system is a mock. Slack, Twilio and Resend calls each run behind a per-connector timeout and circuit breaker. Failed actions retry with exponential backoff and full jitter, then move to a dead-letter list that can be requeued through the API.

Vlayers

Execution Flow

6 steps
  1. Step 01

    Webhook opens an incident

    An upstream system posts a shipment delay with an Idempotency-Key. A repeated key returns the existing incident with 200. A new key creates the incident and four action runs in one transaction, with the customer email stored as needs_approval next to a pending approval.

  2. Step 02

    Internal steps run in order

    The executor, inline by default or in the queue worker, posts the Slack alert, places the one-way supplier call and amends the purchase order with the version check. Each step is saved in a separate transaction and recorded as an OpenTelemetry span.

  3. Step 03

    Failures stay contained

    A failed step stores the error and the next retry time. Retries use jittered backoff and come from the queue worker or the Retry failed button in the workspace. A timeout on Slack or Twilio dead-letters the step at once; a third failure dead-letters the step and moves the incident to Escalated in the inbox. Dead-lettered steps are requeued through the API.

  4. Step 04

    The gate waits for a person

    The workspace marks the email step as awaiting approval and shows an approval card in the center column; the operator can ask the chat for a status summary first. Approve sends the email through Resend with the action's idempotency key; Reject marks the step skipped. A second decision on the same approval gets 409.

  5. Step 05

    A live call, when needed

    Separately from the playbook, a call into the Twilio number, or an outbound call started through the voice API, reaches the Gemini Live agent. The agent reads incident status, can request a step through the same approval rule, and leaves a structured summary in Slack after hang-up.

  6. Step 06

    The incident resolves

    Once every action is completed or skipped, the incident is marked resolved and moves to Recently resolved in the inbox. Each step's payloads, errors and approval decision stay on the action and approval rows.

  7. Done

Core Engineering

Field Notes
01

Retry-safe intake and versioned purchase orders

The delay and absence webhooks reject a request without an Idempotency-Key header (422).

A known key returns the existing incident with 200 instead of 201, so a sender that retries never opens a second incident or runs the playbook twice, for example calling the contractor agency twice for a single absence. When two requests race with the same new key, a unique constraint lets a single insert through; the losing request rolls back and returns the winning incident. Purchase-order amendments use a version compare-and-swap instead of a lock: the UPDATE bumps the version and only matches while the version is unchanged since the read, so a concurrent edit produces a conflict error rather than an overwrite.

03

Approval gates stored as rows

A customer-facing action, here the customer email, is created as needs_approval next to a pending row in the approvals table, under a setting that is on by default.

The gate follows the action type, not a model's sense of risk, and neither agent has a tool that approves. A decision is a single UPDATE that only matches while the approval is still pending, so a double click or a racing request gets 409 instead of a second email. Approving moves the action to pending and dispatches the action; rejecting marks the action skipped. Asking either agent to email the customer produces a gated approval instead of an email.

05

A PostgreSQL queue, and no durable engine yet

By default, actions run inline, inside the API request that created or approved the actions.

With queued mode switched on in configuration, the actions are marked queued instead, and a background worker claims the oldest queued action with SELECT … FOR UPDATE SKIP LOCKED, runs that incident's queued actions, and requeues failed actions once the backoff window has passed. A queue-status endpoint reports queue depth, retriable and dead-lettered counts, and worker health. A written evaluation in the repo compares the queue with a durable workflow engine such as Temporal and recommends keeping the queue until defined adoption gates are met; selecting the durable mode of the workflow-engine interface raises an error on purpose.

07

Operator chat with Gemini tool calling

The chat panel sends the operator's message, the conversation so far and a snapshot of the incident to Gemini 2.5 Flash with three function tools: get_incident_status, list_active_shipments and execute_command.

The backend runs each tool call and returns the result to the model, for up to five rounds per message. The execute_command tool accepts only a fixed list of commands: internal steps run immediately through the same executor as the playbook and report success or the connector's error, while a request to email the customer becomes an approval card instead of an email. Status answers are grounded in the action rows, because get_incident_status returns each step's status and last error.

02

Playbooks run by plain Python

A playbook is a Python list, so a delay always becomes four action runs (Slack alert, supplier call, PO amendment, customer email) and an absence three (labor-system update, contractor call, site-manager alert in Slack).

A deterministic executor walks the runs in sequence order, sends each pending action to the matching connector, saves the request and response payloads on the row, and writes every action in a separate database transaction, so a later failure cannot roll back an earlier PO change. An incident resolves once every run is completed or skipped, and escalates when any run is dead-lettered. No model decides what a playbook does next.

04

Failure handling per connector

Slack, Twilio and Resend calls each run behind a dedicated timeout (defaults: 10 s, 15 s and 10 s) and a circuit breaker that opens after five straight failures, lets calls through again after 60 seconds and closes after two successes (defaults).

A failed action retries with exponential backoff and full jitter (200 ms base, 10 s cap) and is dead-lettered after the third failed attempt, with a fallback note stored on the row. Timeouts get stricter treatment: a timed-out Slack post, Twilio call or manager alert may still have gone through, and no idempotency key reaches Slack or Twilio, so the action goes straight to the dead-letter list for a person to check. Email stays retriable because every attempt sends Resend the same idempotency key. Dead-lettered actions can be listed and requeued through the API.

06

Live phone agent: Twilio, Pipecat, Gemini Live

Inbound calls, and outbound calls started through the voice API, get TwiML that opens a Twilio Media Stream to a WebSocket on the backend.

A Pipecat pipeline converts the 8 kHz telephone audio and streams the audio to and from Gemini Live, with Gemini's voice-activity detection tuned (high start and end sensitivity, 260 ms of silence to end a turn; defaults). When the caller talks over the agent, a custom Pipecat processor sends Twilio a clear event so buffered agent audio stops playing. The agent marks four call objectives (cause, new ETA, mitigation, risk) through a tool and closes the call after a brief closing statement once all four are confirmed; the transcript is saved and summarized into Slack. The playbook's supplier and contractor calls are different: one-way Twilio calls that read a fixed message.

08

Call control from any server

Groundwork for running several backend servers, built but never deployed to more than a single server.

Each active call records the owning server's ID and a heartbeat, and checkpoints state to Redis every five seconds and on each progress or end-call tool call: objective flags, close state and the transcript tail, with an expiry. A control command (only end_call is supported) runs locally when the call lives on the receiving server; otherwise the command goes onto a per-call Redis list that the owning server polls every second. Routing fails closed: no Redis bus returns 503, no checkpoint returns 404, and a checkpoint older than 20 seconds returns 409 (timings are defaults). Kubernetes and NGINX sticky-routing templates and a multi-pod smoke-test runbook are in the repo; the smoke test has not been run.

Reflections
Outcomes
  • “Keep the model out of the failure path. Gemini picks from a fixed tool list, and plain Python owns retries, breakers, timeouts and state changes. A bad model turn can request the wrong step, but cannot approve a customer email or change how failures are retried.
  • “Idempotency belongs at the edge, before any side effect. A required header plus a unique constraint stop a retried webhook from opening a second incident and calling the same contractor agency twice.
  • “An approval is a row, not a prompt. A needs_approval status plus a compare-and-swap on the decision means a double click returns 409 and the customer gets a single email; a prompt telling the model to ask first would guarantee neither outcome.
  • “A timeout means unknown, not failed. A Twilio call that timed out may still ring, so a non-idempotent connector should stop and hand the action to a person; email can retry because every attempt sends Resend the same idempotency key.
  • “Adopting a durable workflow engine is a decision with entry criteria. The repo's evaluation keeps the PostgreSQL queue until at least one adoption gate holds: workflows that regularly run past 30 minutes with timers, compensation across more than three external systems, worker restarts that keep needing manual fixes, or audits that need replayable history.
Next Steps
  • Send OpenTelemetry traces to a real backend (spans only print to the console today, and only when enabled) and rebuild the KPI view the UI dropped, on top of the existing SQL KPI endpoint.
  • Deploy behind a public wss:// host, apply the sticky-routing templates and run the multi-pod smoke test, then record voice latency with the probe scripts already in the repo.
  • Add evals for the chat and voice agents, for example whether a request maps to the right command and whether customer-facing steps always land in the approval queue.

One Last Thing

The core challenge: Supply-chain exceptions such as a late shipment or a missing worker are coordinated by hand across phone, email, Slack and order systems. Handing that work to an AI agent is only safe if a retried webhook cannot open a second incident, a timed-out call is never placed twice by an automatic retry, a purchase order is never silently overwritten, and a customer email never goes out without a person's approval.

More from the Archive

View All
04In progress

Project

Aegis Compliance Agent

Sovereign multi-agent compliance & eligibility engine for global banking.

Coming Soon
05Case study

Frontend Engineering

Vestra Assets

Next.js review workspace that turns financial statements into checked, exportable transaction data.

Read more
06In progress

Project

IDP Studio

Low-code Agentic Workflow Designer.

Coming Soon