A live demo of two AI agents for a freight carrier. A customer support agent answers by chat and by voice call. An operations copilot finds, creates and edits shipments. The data is fake; the engineering is real.
Pick a flow to trace one request through the system. Click any box for details.
Each stage streams into the next, so the caller hears the first sentence while the rest of the reply is still being written. Bars are drawn to scale.
The agent says “Let me pull that up” while track_shipment runs, so the line never goes silent.
Reply text goes to text-to-speech at each sentence boundary instead of after the full answer.
The system prompt and tool schemas are cached, which cuts time to first token on every turn after the first.
When the caller starts talking, agent audio stops and a new turn begins, like a real phone call.
Tools are typed functions in one shared module. Prices come from a pricing engine written in code, and every write in the copilot waits for a person to approve it.
| Tool | Kind | Returns |
|---|---|---|
| track_shipment | read | Status and events, after the caller gives the destination ZIP |
| search_faq | read | Matching answer with its source entry |
| get_quote | compute | Line-item quote from the pricing engine |
| escalate_to_human | write | Ticket number for a callback |
| Tool | Kind | Renders as |
|---|---|---|
| search_shipments | read | Sortable table |
| get_shipment | read | Shipment card with timeline |
| create_shipment | write · approve | Pre-filled form with Confirm |
| update_shipment | write · approve | Before/after diff with Approve |
The model returns structured data. The page maps each tool name to a component. These three use the demo's seed records.
Keys never live in the repo or the browser. They go from your machine to GitHub, and the deploy workflow copies them into the Worker.
.envGitignored. Source of truth on your machine. .env.example is committed with empty values.
Set with gh secret set on Plymouth-AI-Lab/logistics-os.
On push to main: wrangler deploy, then wrangler secret put for each key.
envAgents read env.ANTHROPIC_API_KEY and the Langfuse keys at runtime. Local dev uses .dev.vars.
| Key | Used by | Status |
|---|---|---|
| ANTHROPIC_API_KEY | All agents, for Claude calls | needs a key |
| LANGFUSE_SECRET_KEY | Trace export | in .env + GitHub |
| LANGFUSE_PUBLIC_KEY | Trace export | in .env + GitHub |
| LANGFUSE_BASE_URL | Trace export (cloud.langfuse.com) | in .env + GitHub |
| CLOUDFLARE_API_TOKEN | Deploy workflow | needs a token |
We host only code and data. Every model runs as a managed API.
| Piece | Runs on | Cost |
|---|---|---|
| Web page, API, agents | Cloudflare Workers + Durable Objects (SQLite) | Free · 100k requests/day |
| Shipment data | Cloudflare D1 | Free · 5 GB |
| Speech, v1 | Workers AI (Deepgram Nova-3, Workers AI voices) | Free daily allocation |
| Traces and evals | Langfuse Cloud | Free Hobby plan |
| Language models | Anthropic API | Your Claude credits |
| Speech, v2 | Deepgram + ElevenLabs | Signup credit, then paid |
Claude for reasoning, Workers AI for speech, Langfuse for traces. Ships first.
Swap speech providers to Deepgram and ElevenLabs. Two lines of config in the voice agent.
Move the voice agent to LiveKit Agents with a SIP trunk. Tools and prompts stay the same.
Same Claude models through AWS Bedrock or Google Vertex AI for data residency.
logistics-os/ ├── apps/web/ # Vite + React + Tailwind single page (Support · Copilot tabs) │ └── src/components/tools/ # one component per tool result ├── worker/ │ ├── index.ts # Hono routes + routeAgentRequest │ ├── agents/ # SupportChatAgent, SupportVoiceAgent, CopilotAgent │ ├── tools/ # shared typed tools (zod schemas) │ ├── pricing/ # zones, dimensional weight, surcharges │ ├── telemetry/ # Langfuse tracing │ └── db/ # schema.sql, seed.ts, nightly reseed ├── docs/architecture.html # this page ├── .env # local secrets, gitignored ├── .env.example ├── .github/workflows/deploy.yml └── wrangler.jsonc # Worker, D1, Durable Objects, cron, assets