Work / AI platform
An omnichannel AI platform, from first commit to production
How we built a multi-tenant SaaS where AI agents answer customers on WhatsApp, Instagram, Telegram and web chat, and hand the conversation to a human when they should.
The problem
Small and mid-size businesses in Brazil run customer service on WhatsApp. Teams are overloaded, answers depend on who is on shift, and managers have little visibility into quality.
We built the answer as a joint venture with a Brazilian cloud provider: AI agents that know each business, answer right away on every channel, and hand the conversation to a person when they should. I lead engineering and product, from architecture to production.
Architecture
Three main services and a few small ones. It is deliberately not a microservice system: services are split only where the runtime has to differ.
- Platform API (NestJS). A modular monolith that owns tenants, authentication, the real-time inbox and every integration. PostgreSQL through Prisma, BullMQ on Redis for background work, and Socket.IO for real time.
- AI engine (Python, FastAPI and LangGraph). Stateless. Each request arrives with the context it needs and leaves with a decision. Each AI workflow is its own LangGraph graph.
- WhatsApp gateway (Go). A focused service that manages WhatsApp connections and sends signed webhooks to the API.
- Clients. A Nuxt web app for the teams and an embeddable chat widget for websites.
How a conversation is handled
- Retrieve. The agent searches the company's own knowledge (agentic RAG on Qdrant), isolated per tenant.
- Act. The model can call tools in a bounded loop, including the customer's connected SaaS tools and a hand-off to a human.
- Check. The answer is validated before it goes out. If the agent can't answer safely, a person takes over.
Decisions worth explaining
A modular monolith, not microservices
For a small team, network boundaries cost more than they protect. Module boundaries are enforced by lint rules on imports instead, and services are split only where the runtime differs: Python for AI, Go for WhatsApp.
Trade-offMost of the platform is one deploy unit, so deploys had to become boring and safe. That became its own project: zero-downtime deploys.
A provider-agnostic LLM layer
OpenAI, Anthropic, Gemini and xAI sit behind one interface, with fallback when a provider fails. No feature depends on a single vendor.
Trade-offWorkflows use the features every provider shares, unless one explicitly opts into something provider-specific.
MCP for integrations, controlled by the platform
Customers' SaaS tools connect through MCP. The platform decides what each conversation is allowed to use, and the AI engine only executes the calls. When a tool is unavailable, the conversation goes to a human instead of failing quietly.
Owning the WhatsApp layer
We started on a third-party WhatsApp gateway. It was heavy and did far more than we needed. I replaced it with a focused service in Go: only the operations we use, with supervised reconnection.
Trade-offIt runs as a single instance by design. A WhatsApp connection can only be held by one process at a time, so this service restarts gracefully instead of going blue-green.
Knowing what every conversation costs
Every AI request is metered per customer and reconciled with what the model providers actually bill, so the cost of a conversation is measured, not estimated.
Outcome
The platform runs in production for business customers and ships several times a day with zero-downtime deploys.
Client names, volumes, financial figures and product internals are withheld. The code is private.