Mission control for a fleet of AI agents
By the time we'd built the trading Mini App, the acquisition bot, and the AI persona agents, the client's operations team had a new problem: they were managing everything through terminal windows and direct database queries. Starting an agent meant SSH-ing into the server. Checking a conversation meant querying PostgreSQL. Changing a persona's behavior meant editing a config file and restarting a process. The system worked, but it was opaque — only we could operate it. The client needed a control panel that would put their entire operation into a single browser window.
agent management
We built a full-featured admin panel with JWT-protected authentication. The core of it is agent management: operators can create and edit persona definitions — personality traits, knowledge boundaries, conversation style, restricted topics — and see them reflected in running agents without redeploying anything. Each Telegram account used by the agents has its own status card showing session health, last activity, and profile metadata. From the same interface, operators can start, stop, or pause any agent process. Under the hood, these process commands travel through Redis — the Next.js frontend hits a FastAPI endpoint, which publishes a command to the Redis channel that the Python agent runner subscribes to. The agent responds, and the UI updates via WebSocket within a second or two.
live chat viewer
The live chat viewer became the feature the operations team uses most. It displays all ongoing agent conversations in real time — every message, both sides, as it happens. Operators can filter by agent, by user, by conversation status. When something looks off — an agent drifting off-topic or a high-value user asking complex questions — the operator can take over the conversation directly from the panel. The technical challenge here was fan-out: with dozens of agents running simultaneous conversations, the WebSocket layer needs to push updates to every connected admin client efficiently. We used a Redis-backed pub/sub model where each new message is published once and every connected WebSocket client subscribed to the relevant channels receives it. It's simple but effective — admin page load times stay under two seconds even with hundreds of active conversations.
campaigns and analytics
Tracking links were a feature the client requested for campaign attribution. The panel generates short URLs in the format `/go/{code}` — a public redirect endpoint that logs the click (referrer, timestamp, UTM parameters) before sending the user to the destination. The analytics dashboard shows click counts, unique visitors, and conversion rates per link. Since the redirect endpoint is public-facing and attached to marketing campaigns, it occasionally handles traffic spikes that dwarf normal admin panel usage. We kept the redirect handler as thin as possible — a single database insert and an HTTP 302 — with connection pooling tuned to avoid becoming a bottleneck. The analytics aggregation runs on a separate read path so that reporting queries never slow down redirects.
The channel statistics dashboard rounds out the operational picture — subscriber growth, message activity, and engagement metrics pulled from Telegram's API and rendered with Recharts. The UI itself is built on Next.js with Radix UI components and Tailwind — deliberately minimal and functional. The operations team doesn't need animations or fancy transitions; they need information density and fast navigation. Every table is sortable, every list is searchable, and the entire panel works on a tablet because the team sometimes monitors agents from their phones.
funnel tools and tradeoffs
User management for the funnel bot lives here too — operators can see where each user is in the acquisition funnel, what messages they've received, and whether they've converted. It's a read-heavy view that occasionally needs manual intervention: moving a user back in the funnel, resending a message, or flagging an account. The honest architectural tension in this project is that the admin panel bridges two worlds — a Next.js frontend and a Python backend ecosystem. Every agent operation crosses that boundary through Redis, which works cleanly but means debugging issues requires context-switching between two codebases and two mental models. If we were starting over, we'd consider a unified runtime — but the tradeoff was acceptable given that each piece was built incrementally as the platform grew.
Stack
Frontend: Next.js 16, React 19, Tailwind CSS 4, Radix UI, Recharts, Lucide icons
Backend: FastAPI, SQLAlchemy 2, PostgreSQL, Redis, WebSocket
Auth: jose (JWT)
