One phone call, four agents, two streams.
The whole stack was built in 24 hours. It connects a regular phone line to a Retell-powered conversational layer, hands transcript chunks to a small OpenAI Swarm of cooperating agents, persists banking actions against an in-memory ledger, and pins generated application PDFs to IPFS through Pinata. A second WebSocket pipes everything to a Next.js operator console for live observability.
What hits what, in what order
A single inbound call fans out to two streams: the LLM bidirectional WebSocket back into Retell, and a separate observability WebSocket into the operator console.
The Retell ↔ FastAPI bidirectional socket
Retell forwards real-time transcripts and waits for streamed LLM responses on a per-call WebSocket. The bridge starts a fresh LlmClient and immediately greets the caller by name.
OpenAI Swarm with handoff functions
Triage owns intent classification and delegates by calling a transfer_to_X function. Each specialist has its own tool surface and an explicit way home.
Real bank operations against an in-memory ledger
transfer_funds is the canonical example: validate, check funds, mutate balances, record a payment, return a Result that the LLM can read back to the caller.
LaTeX → PDF → Pinata IPFS
When the caller applies for a loan or credit card, the Applications Agent renders a LaTeX template, compiles to PDF with pdflatex, and pins the file to IPFS through Pinata. The CID surfaces in the operator console.
Tradeoffs we made under a 24-hour clock
Retell collapses STT, TTS, barge-in handling, and the LLM bidirectional WebSocket into one provider. We had hours, not days.
Swarm's handoff-as-a-tool model maps 1:1 to a phone-call transfer. Simpler mental model, smaller diff against vanilla OpenAI tool calling.
The challenge sponsor was Pinata. Bonus: a content-addressed CID is naturally tamper-evident, which is a nice property for a regulated artifact.
Hackathon scope. The dict's shape ports cleanly to a real DB later — no ORM lock-in, no migrations to maintain over the weekend.
One bidirectional socket per operator, identical contract to the LLM channel. Easier to extend with operator-initiated actions later.
Matches Vercel deployment defaults and lets the rebuilt project page ship serverless route handlers (Live AI) without a second backend.
Want the real telephony loop?
The full Python backend is in server/. Set the env vars below and run uvicorn main:app — the operator console here will pick the WebSocket back up with a one-line change in src/lib/store.ts.