Orbit: an AI Sales Agent in Production
Case StudyA North American B2B distributor had a classic problem: hundreds of restaurant and cafe clients who reorder in cycles, and managers who physically cannot keep up with repeat sales across the whole base. Orbit is the system I built for that: it forecasts when each client will need to reorder, opens a conversation at the right moment, and an AI agent leads it to an order, a reschedule, or a handoff to a human. It also qualifies inbound leads from ads and rescues abandoned carts. It runs in production, writes to real customers, and made its first sale without a human in the loop within a month of the first commit.
The product, live
The numbers
Under the hood
Letting an LLM talk to real customers
The scary part of agentic sales is not the conversation quality, it is the blast radius. Orbit treats the model as untrusted: the agent proposes, plain TypeScript disposes. Its reply tool only accumulates text; the actual send happens after the turn, through the gate. Six rails sit outside the model:
1 opt-out detection is fail-closed: classifier error = treated as STOP 2 one turn at a time per conversation, enforced by the queue 3 reply limit per conversation, counted in Postgres, not by the model 4 iteration cap per turn, separate from the reply limit 5 deny-first permission hooks on every tool call 6 conversation language frozen before the model runs
The gate proved itself in production twice in the first weeks. A kill-switch test showed a human manager cannot bypass it either. And the first live SMS to a US number was dropped with NO_CONSENT, which was the correct call: express consent was missing. The safest bug report is the one your own system files.
Incidents from production
The agent went live early behind feature flags, so the interesting failures happened on real traffic, with full audit trails to reconstruct them. A few that shaped the architecture:
The echo that silenced the bot
A messenger webhook delivered the echo of our own outgoing message 0.9 s before the send API call returned and recorded it. The system saw an unknown outbound, assumed a human operator had taken over, and politely went silent. Fix: record-before-send. The outbound row is committed before the API call, updated on success, deleted on failure.
The retry that double-texted a customer
A send failed after the message had actually been delivered, the queue retried the turn, and the customer got the same message twice. Fix: a conflict-tolerant journal that keeps the first verdict of the chain, plus success-first classification of provider responses.
Five messages in one thought
During the first live sale the model emitted five reply calls in a single turn, and all five went out: the reply limit was per conversation, and no cap existed per turn. Fix: a per-turn cap and a charter rule, one message per thought.
The turn that produced nothing
Once in a while the model would end a turn with no tool calls at all, and the lead heard silence. Fix: a retry with a system note, then a deterministic fallback reply that goes through the full compliance gate. Silence is now impossible by construction.
What I built
ComplianceGate
Nine ordered checks in front of every outbound message: kill-switch, suppression, consent, win-back rules, quiet hours computed per recipient time zone, frequency caps, content validation, idempotency. Author and channel are independent: human messages pass the same gate.
Forecast engine
Reorder probability as a due-by-horizon gamma CDF over 4 years of history, with an empirical-Bayes backoff ladder for sparse pairs. Probability and confidence are separate values: the reminder fires on one, a human reads the other. The math was independently re-derived during review.
Conversation state machine
Engagements live in Postgres with a partial unique index guaranteeing at most one live conversation per contact. An unsubscribed state that never reopens. The LLM holds no state of record.
Deterministic test harness
Fake clock plus a fake scheduler that fires delayed jobs when test time advances, verified for parity with the real queue. LLM record-replay cassettes make CI deterministic and free. 1,100+ tests, roughly one line of test per line of code.
Owner controls without redeploys
Message texts, agent charter and knobs are editable at runtime with hard legal floors: settings tune behavior inside the legal envelope, never outside it. STOP handling and quiet hours cannot be turned off. Plus a kill-switch in the admin UI.
Analytics over MCP
The owner asks questions about their own business from their AI assistant. The MCP layer runs on a read-only Postgres role with a five-table whitelist: even a prompt injection tops out at reading data the owner already owns. No sends, no settings, no secrets.
Stack
TypeScript · Node.js 22 · Fastify · PostgreSQL · pg-boss · Prisma · Claude Agent SDK · Twilio SMS · WhatsApp / Instagram messaging · Telegram bot · Next.js admin · MCP · Docker on a single VPS
Want an AI agent that actually ships?
The pattern generalizes: forecast or trigger, a constrained agent, a compliance gate shaped by your jurisdiction, and an audit log that lets you trust it. If your product needs an agent that talks to real customers with real consequences, I have done it once already.
Get in touchBuilt solo in 8 weeks, live in production since July 2026. The client is under NDA: names, brands and identifying details are changed or omitted, the engineering and the numbers are real.







