Building an AI Brain Trust: From Basic Chat to Context-Aware Multi-Agent Conversations
We just wrapped a marathon dev session transforming a simple discussion feature into a sophisticated AI chat UX with multi-agent consensus, real-time context, and dynamic stream visualization.
It was late, the kind of late where the only company you have is your monitor's glow and a half-empty coffee mug. But the energy was high. We'd just pushed through a significant chunk of work, taking our application's discussions feature from a foundational concept to a truly modern, AI-powered conversational experience. The goal? To build not just a chat, but an interactive AI brain trust – complete with markdown rendering, auto-continue, project context injection, persona switching, and a mesmerizing stream flow visualization.
And I'm thrilled to report: we're there. All core features are implemented, type-checking clean, and the dev server is humming along. We even have a test discussion (9c398d16-cbfd-4eba-96ee-2bed16c34a97) where Kimi and OpenAI, playing as the "NyxCore" persona, are having a lively 15-message debate, entirely in German, without a single provider name leaking into the output. That's a win in my book.
The Vision: A Modern AI Dialogue
Our ambition was simple yet challenging: create an AI chat UX that feels intuitive, powerful, and deeply integrated. This meant moving beyond basic text responses to a rich, interactive environment where the AI agents could truly collaborate and understand their operational context.
Markdown Magic & Responsive UI
First up was the chat itself. We needed a robust way to display AI responses, especially code snippets and formatted text.
src/components/discussion/chat-message.tsx: This became our reusable chat bubble. User messages are plain, while assistant messages get full markdown treatment. We also baked in hover-to-copy functionality for individual messages and a subtle streaming indicator.src/components/markdown-renderer.tsx: The heavy lifting for markdown came here. We integratedrehype-highlightandhighlight.jsfor beautiful, syntax-highlighted code blocks. A small but mighty detail: a copy button for code blocks, which required a neat little React trick (more on that in "Lessons Learned"). Acompactprop ensures it renders nicely within the chat context.
// Simplified snippet from src/components/markdown-renderer.tsx
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
import { materialDark } from 'react-syntax-highlighter/dist/esm/styles/prism';
import { useRef, useState } from 'react';
const CodeBlock = ({ className, children }) => {
const codeRef = useRef<HTMLElement>(null);
const [copied, setCopied] = useState(false);
// ... (copy logic using codeRef.current?.innerText)
return (
<pre ref={codeRef} className={className}>
<button onClick={handleCopy} className="copy-button">
{copied ? 'Copied!' : 'Copy'}
</button>
<SyntaxHighlighter style={materialDark} language={language}>
{children}
</SyntaxHighlighter>
</pre>
);
};
// ... (used within markdown-renderer for `code` elements)
The Brain Trust: Discussion Engine Overhaul
This was the core of the AI intelligence. We completely rewrote src/server/services/discussion-service.ts to handle complex multi-agent interactions and context management.
- Context is King: AI without context is just a parrot. We introduced
projectIdandlanguageto ourDiscussionmodel. Now, every AI interaction starts with abuildSystemPrompt()that includes:- The chosen persona's instructions.
- The detected language (from the first user message).
- Crucially, project context: descriptions, blog posts, and consolidation patterns from the linked project are injected, giving the AI a deep understanding of its operational domain.
- Multi-Agent Consensus: For our "consensus" mode, we wanted the AIs to feel like a unified team, not a collection of disparate providers.
- Neutral Identity: Gone are the
[ANTHROPIC]:or--- OPENAI ---prefixes. AIs now participate as "Alpha," "Beta," "Gamma," or the chosenNyxCorepersona, fostering a sense of integrated intelligence. - Seamless Language: Language detection (de/en/fr/es) ensures the AI responds appropriately from the start.
- Neutral Identity: Gone are the
- Auto-Pilot Mode (
autoRound()): This is where the magic of continuous AI dialogue happens. TheautoRound()function allows the system to generate a response from the next provider in rotation without requiring user input. This powers our "auto-continue" feature, letting AIs debate amongst themselves. - Cancellation: Proper
AbortSignalsupport means we can cleanly stop streaming responses if the user decides to intervene or close the discussion.
Real-time Streaming with SSE
To make the auto-continue and live AI responses feel truly dynamic, we leaned into Server-Sent Events (SSE).
src/app/api/v1/events/discussions/[id]/route.ts: This endpoint now supports a?auto=1query parameter, enabling the auto-continue loop. We also integratedAbortControllerwithReadableStream.cancel()for robust cancellation.
The Conductor's Baton: tRPC Router
Our tRPC router (src/server/trpc/routers/discussions.ts) got a significant upgrade to support the new features:
projectIdis now part of thecreateinput, linking discussions to projects from inception.projectrelation is included ingetqueries, making project context easily accessible.- A new
updatePersonamutation allows users to switch the active AI persona mid-chat, dynamically altering the AI's approach and tone.
The Stage: Discussion Detail Page
The frontend experience on src/app/(dashboard)/dashboard/discussions/[id]/page.tsx was completely reimagined.
- Redesigned Header: We moved from flat badges to a more functional two-row layout:
- Row 1: Title, mode, live status, markdown download, and the crucial Auto play/stop controls.
- Row 2: The star of the show – our
StreamFlowvisualization and project context indicators.
StreamFlowComponent: This visualizer is a game-changer. ImagineKIMI ··· > NyxCore < ··· OPENAI– with animated dots pulsing during streaming, the active provider highlighted, and the central persona (e.g., NyxCore) glowing. It provides instant visual feedback on who's "thinking" and who's contributing. Clicking the persona in the flow opens a dropdown, allowing mid-chat persona switching.- Auto-Continue Loop: A prominent "Play" button starts the AI debate, "Stop" halts it, and sending a manual message also pauses the auto-loop, giving the user full control.
saveAsMarkdown(): A simple but powerful feature to download the entire discussion as a.mdfile for offline review or sharing.- Auto-Scroll: During streaming, the chat now intelligently auto-scrolls when near the bottom, ensuring the user always sees the latest AI output without manual intervention.
Lessons Learned (from the "Pain Log")
No significant development session is complete without hitting a few snags. These are the moments where real learning happens.
-
DOM Manipulation vs. React Refs for Reliability:
- The Problem: I initially tried to implement the code block copy functionality using
document.querySelector("pre:hover")to grab the content. This failed spectacularly.querySelectoroften returns the wrong element when multiple code blocks are visible, andinnerTextisn't directly available on a genericElementtype. It was brittle and unreliable. - The Fix: The correct React way is to use
useRef. By attaching auseRef<HTMLPreElement>directly to the<pre>element, we get a direct, reliable reference to the DOM node. ReadingpreRef.current?.innerTextgives us exactly the text we need, every time. - Takeaway: For interacting with specific DOM elements in React,
useRefis almost always the cleaner, more robust solution compared to globaldocument.querySelectorcalls. It ensures you're targeting the correct instance of a rendered component.
- The Problem: I initially tried to implement the code block copy functionality using
-
Schema Sanity Checks and ORM Field Names:
- The Problem: When loading project context, I was trying to access
blogPost.summaryandblogPost.publishedAt. My schema, however, defined these fields asexcerptandupdatedAt. A classic case of mental model vs. reality. - The Fix: A quick glance at the Prisma schema file (
schema.prisma) and a correction toblogPost.excerptandblogPost.updatedAtresolved it. - Takeaway: Even seasoned developers make these mistakes. Always double-check your schema or ORM definitions when fetching or mapping data. A few seconds verifying field names can save minutes of debugging "undefined" errors.
- The Problem: When loading project context, I was trying to access
-
The Importance of ORM Client Refresh:
- The Problem: After adding
projectIdto theDiscussionmodel in Prisma, my dev server kept complaining thatprojectIdwasn't a valid field when trying to create a new discussion. The schema was updated, but the application wasn't picking it up. - The Fix: The Prisma client was stale. Running
npm run db:push(to apply schema changes to the database),npm run db:generate(to regenerate the Prisma client based on the new schema), and then restarting the dev server (npm run dev) immediately resolved the issue. - Takeaway: When making schema changes with an ORM like Prisma, always remember the three-step dance:
db:push(ordb:migrate),db:generate, and a full application restart. The generated client code is what your application interacts with, and it needs to be up-to-date.
- The Problem: After adding
What's Next?
While the core functionality is solid, there are always immediate next steps:
- Commit all the uncommitted changes – a big one!
- Thoroughly test the auto-continue flow end-to-end.
- Verify persona switching mid-chat works seamlessly.
- Ensure the
.mddownload preserves all formatting. - Confirm project context injection is working as expected.
- Start thinking about token/cost tracking and displaying it.
- Perhaps a "rounds" counter during auto-play to give a sense of progress.
This session was a huge leap forward. We've laid the groundwork for a truly intelligent and interactive AI assistant, transforming a simple chat into a dynamic platform for collaborative AI intelligence. The journey continues, but for tonight, we celebrate a significant milestone.
{
"thingsDone": [
"Implemented modern Markdown Chat UX with streaming indicators and copy buttons.",
"Overhauled Discussion Engine for multi-agent consensus, language detection, and project context injection.",
"Integrated auto-continue functionality via SSE endpoint.",
"Redesigned Discussion Detail Page with StreamFlow visualization and interactive controls (auto-play, persona switching, markdown download).",
"Updated Prisma schema and tRPC router to support new discussion features (projectId, language, persona updates).",
"Created a test discussion demonstrating multi-agent, persona-driven, context-aware dialogue in German."
],
"pains": [
"Reliably extracting text from code blocks for copy functionality (resolved with useRef).",
"Mismatched schema field names when loading project context (resolved by checking schema).",
"Stale Prisma client after schema changes requiring regeneration and server restart."
],
"successes": [
"Achieved type-checking clean implementation across all new features.",
"Successfully implemented neutral participant names for consensus mode.",
"Developed a visually engaging and informative StreamFlow component.",
"Created a robust auto-continue mechanism for AI-to-AI dialogue.",
"Ensured comprehensive project context injection into AI system prompts."
],
"techStack": [
"TypeScript",
"React",
"Next.js",
"Prisma",
"PostgreSQL",
"Redis",
"tRPC",
"Server-Sent Events (SSE)",
"rehype-highlight",
"highlight.js"
]
}