Multi-agent support: from prototype to a production widget
A multi-agent system with prompt-injection defenses validated the need. The final architecture differed, and I built what it relies on.
Results
3+
specialist agents, each with its own RAG corpus
self-reportedprototype README
Produção
support widget in the portal with HMAC-verified identity
self-reportedportal production environment
Summary
Context
Revvo's customer support had to triage technical and business questions, answer from the documentation and escalate to humans when needed, without exposing personal data and without falling for prompt injection.
Action
I built a multi-agent system on LangGraph. An entry layer detects prompt injection and sanitizes PII, an LLM intent router picks among specialist agents with their own RAG corpora, and escalation opens tickets in PostgreSQL, tracked in a React backoffice. When the company adopted a different agent architecture, I moved on to integrating the support widget into the portal: CSP, conversation persistence inside the iframe, and HMAC-verified identity.
Result
The prototype worked and validated the need, but the solution adopted was another engineer's agent, built from scratch. The support widget I integrated is in production in the portal, with a history that follows the user across devices. The tools I built afterwards (Jira triage and the MCP gateway) feed exactly that agent.
Lesson
Losing the architecture decision is not losing the problem: it pays to build the pieces the adopted solution will need.
Architecture
Long version
The multi-agent prototype
- Entry guard. Before any agent runs, the message goes through prompt-injection detection and personal-data sanitization.
- Intent router. An LLM classifies the message and picks the specialist.
- Specialists with their own RAG. Each specialist, technical or business, queries its own corpus on Vertex AI. That keeps one context from contaminating another.
- Escalation. When the agent cannot resolve a request, it opens a ticket in PostgreSQL with the history. The React backoffice shows the queue to the human team.
The decision that was not mine
The company went with the support agent of a senior AI Engineer, who built a separate architecture. Instead of pushing mine, I started building what that agent needed: the Jira triage server and the MCP gateway through which it reaches its tools (see the gateway case).
The widget in production
The missing piece was bringing support inside the customer portal. Three problems showed up:
- CSP. The widget iframe was blocked; I declared the right
frame-src. - The conversation vanished on every load. Reading the widget's code, I found that inside an
iframe it only fetches history when the parent page answers
config-setto theloadedevent. I confirmed it in staging by sending the event by hand, then implemented the handshake with an explicittargetOrigin, never*. - Identity. The backend derives an HMAC-SHA256 of the email with the inbox key, and only the hash reaches the browser. Without it, history is stuck to one browser; with it, it follows the user on any device.
The secrets went into Secret Sync via Terraform, in the development and staging environments.
Lessons learned
- Defense at the entry. Injection and PII are handled before the router, not inside each agent.
- Read the third party's code. The widget only loaded history after an undocumented handshake, and I found it by reading the source.
- HMAC is not just a lock. Without the hash, history does not follow the user across devices: the security mechanism is also the continuity mechanism.