Orbit: an AI Sales Agent in Production

Case Study

A 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.

8 weeks to production605 customers, 6,465 SKUsFirst agent-led sale in week 51,100+ tests

The product, live

Orbit admin: customers table with reorder forecasts, confidence and consent
Orbit admin: unified inbox across SMS, WhatsApp, Instagram, Messenger and web chat
Orbit admin: leads pipeline with qualification statuses per channel
Orbit admin: runtime settings with catalog feeds, nudge cadence and integrations
1 / 4
The admin, live: every customer with a forecast date, auto-send decision, calibrated confidence and consent state. Names replaced for the screenshot; the data is production.

The numbers

605
Customers synced
6,465 SKUs, 4 years of order history
11% → 60%
Forecast coverage
of customer×SKU pairs, ×5.5
9
Compliance checks
ordered, fail-closed, any one = DROP
6
Agent guard rails
outside the model, it cannot see them
1,100+
Tests
test code roughly 1:1 with production code
1 vCPU
Infrastructure
one VPS, one Postgres, no Redis

Under the hood

Orbit system diagram: three inbound contours, agent spine, compliance gate and control plane
ComplianceGate: nine ordered checks between any author and the customer
Forecast backoff ladder: coverage growth from 11 to 60 percent of customer-SKU pairs
1 / 3
Three inbound contours on one spine: forecast-driven reorder reminders, leads from ads and the site web chat over WhatsApp, Instagram and Messenger, and abandoned Shopify carts. All of it converges on the agents and one compliance gate. On the right, the control plane: the owner talks to the data over MCP and drives operations by voice through a codex agent.

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 touch

Built 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.

Читати цей кейс українською