Plymouth AI Lab · Logistics OS · System design

Logistics OS architecture

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.

RuntimeCloudflare Workers + Agents SDK
ModelsClaude Haiku 5.5 · Sonnet 5.5
ObservabilityLangfuse Cloud
Hosting cost$0 + model tokens
Voice target< 1 s to first audio
System map

Three surfaces, one Worker, shared tools

Pick a flow to trace one request through the system. Click any box for details.

Connection Traced flow Optional
Voice latency budget

From the caller going quiet to the agent speaking

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.

0200400600 80010001200 ms Speech to text · end of turn Network to Anthropic Claude Haiku 5.5 · first token Text to speech · first audio 300 ms40 ms350 ms200 ms First audio ≈ 890 ms Target 1 s

Filler before slow tools

The agent says “Let me pull that up” while track_shipment runs, so the line never goes silent.

Speak sentence by sentence

Reply text goes to text-to-speech at each sentence boundary instead of after the full answer.

Prompt caching

The system prompt and tool schemas are cached, which cuts time to first token on every turn after the first.

Barge-in

When the caller starts talking, agent audio stops and a new turn begins, like a real phone call.

Agents and tools

The model chooses; the code acts

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.

Customer support agent

chatvoiceHaiku 5.5
ToolKindReturns
track_shipmentreadStatus and events, after the caller gives the destination ZIP
search_faqreadMatching answer with its source entry
get_quotecomputeLine-item quote from the pricing engine
escalate_to_humanwriteTicket number for a callback

Operations copilot

chat + componentsSonnet 5.5
ToolKindRenders as
search_shipmentsreadSortable table
get_shipmentreadShipment card with timeline
create_shipmentwrite · approvePre-filled form with Confirm
update_shipmentwrite · approveBefore/after diff with Approve
Rich components · example data

What a tool result looks like on screen

The model returns structured data. The page maps each tool name to a component. These three use the demo's seed records.

get_shipmentDelayed
LX-48213
Miami, FL → Monterrey, NL · Standard · 4 pallets
Estimated deliveryThu, Oct 22OriginallyTue, Oct 20
  1. Oct 15Picked up · Miami, FL
  2. Oct 16Departed Miami gateway
  3. Oct 17Arrived Houston hub
  4. Oct 18Held at Houston hub · severe weather
get_quoteMIA → MEX · Express
Actual weight120 kg Dimensional weight
120 × 80 × 100 cm ÷ 5000
192 kg Billable weight192 kg Zone 3 express
192 kg × $3.10
$595.20 Fuel surcharge
12%
$71.42 Total$666.62 USD
Calculated by the pricing engine. The model only read the inputs back to the customer.
update_shipment · needs approvalLX-48213
FieldBeforeAfter ServiceStandardExpress DeliveryThu, Oct 22Wed, Oct 21 Charge$1,284.00$1,432.00
Nothing is written until someone approves.
Secrets and config

How keys reach the Worker

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.

1 · LOCAL

.env

Gitignored. Source of truth on your machine. .env.example is committed with empty values.

2 · GITHUB

Actions secrets

Set with gh secret set on Plymouth-AI-Lab/logistics-os.

3 · DEPLOY

GitHub Action

On push to main: wrangler deploy, then wrangler secret put for each key.

4 · RUNTIME

Worker env

Agents read env.ANTHROPIC_API_KEY and the Langfuse keys at runtime. Local dev uses .dev.vars.

KeyUsed byStatus
ANTHROPIC_API_KEYAll agents, for Claude callsneeds a key
LANGFUSE_SECRET_KEYTrace exportin .env + GitHub
LANGFUSE_PUBLIC_KEYTrace exportin .env + GitHub
LANGFUSE_BASE_URLTrace export (cloud.langfuse.com)in .env + GitHub
CLOUDFLARE_API_TOKENDeploy workflowneeds a token
Where it runs

Hosting and cost

We host only code and data. Every model runs as a managed API.

PieceRuns onCost
Web page, API, agentsCloudflare Workers + Durable Objects (SQLite)Free · 100k requests/day
Shipment dataCloudflare D1Free · 5 GB
Speech, v1Workers AI (Deepgram Nova-3, Workers AI voices)Free daily allocation
Traces and evalsLangfuse CloudFree Hobby plan
Language modelsAnthropic API
Speech, v2Deepgram + ElevenLabs
Upgrade path

Start free, upgrade the parts buyers notice

V1 · DEMO

Everything on Cloudflare

Claude for reasoning, Workers AI for speech, Langfuse for traces. Ships first.

V2 · SALES VOICE

Natural call-center voice

Swap speech providers to Deepgram and ElevenLabs. Two lines of config in the voice agent.

V3 · PHONE LINE

Call a real number

Move the voice agent to LiveKit Agents with a SIP trunk. Tools and prompts stay the same.

V4 · CLIENT DEPLOY

Client's own cloud

Same Claude models through AWS Bedrock or Google Vertex AI for data residency.

Repository

Repo layout

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