About this demo
Handoff is a front-end demo of a support-operations copilot, built to show the user-experience problems of AI products: answers that stream, tools that take time and fail, actions a human must approve, model output that is not always right, and connections that drop.
What is real
- The streaming protocol: Server-Sent Events over a POST, parsed by a hand-written client, with per-leg sequence numbers and commit points.
- The UI: streaming Markdown with citation chips, tool cards with progress and retries, approval cards with editable arguments, reconnect handling, the event inspector.
- Retrieval: BM25 search over ten policy documents; citations resolve to the passages the model was given.
- Live mode with OpenAI: the Responses API with streamed function calls. It was run against the real API while building the demo, and the recorded stream is a test fixture.
What is simulated
- The back office: orders, refunds, shipments and emails. No money moves and no email is sent. Tool cards say so.
- Scripted mode: an authored scenario that streams like a model. It is the default, so the public demo never spends an API key.
- Failures: the "Inject a failure" switches make the server drop the connection, fail a tool or send bad arguments on purpose.
- Live mode with Anthropic Claude is implemented but not live-verified: no Anthropic key was available while building it. It is tested against a hand-written stream fixture only.
How a run works
The browser owns the conversation. Each request (a "leg") carries the committed transcript and runs one model turn plus the tools it asked for, streaming events back. When an action needs approval the leg ends; the operator's decision travels in the next request. Because the server keeps no state between requests, any serverless instance can serve any leg, and a dropped connection resumes from the last committed step.
What this design does not guarantee
- A tool that was running when the connection dropped runs again on resume (at-least-once). A production backend would pass the call id as an idempotency key.
- A live model turn cut off by a drop is regenerated, not resumed, and may differ; the UI labels it.
- Nothing runs while the tab is closed. Long-running work that must outlive the browser needs a durable event log and a worker.
- Rate limiting is per server instance, so it bounds abuse rather than enforcing an exact quota.
Try the component states, or open the inspector during a run to see every event.