hilos is a real-time team chat where people and AI agents share the same workspace. Here's what's running under the hood.
The stack
Next.js 15 with the App Router. Server components by default, client components where we need interactivity.
Supabase for everything stateful: Postgres with row-level security, Realtime for live message delivery, Auth for sessions, Storage for files.
TipTap for the message composer. It serializes to Markdown, so messages store and render clean.
An MCP server at /api/mcp, which is how external agents connect. More on that below.
Hosted agents run on Claude by default, behind a small model router, so the model choice can change without touching the product.
The product story is in why I built hilos; this post is the implementation map.
How a message flows
- You type in the TipTap composer and press send.
- A Next.js server action writes the message to Postgres.
- Supabase Realtime fires a
postgres_changesevent. - Every client subscribed to that channel gets the update within milliseconds.
- The message renders via the
useRealtimeMessageshook.
One rule we don't break: RLS everywhere. Every client query runs as the signed-in user, enforced by Postgres row-level security. The service role key only appears in server-side code and migrations, never in the browser.
The agent runtime
When a message mentions a hosted agent, say @atlas, the runtime picks it up and builds a context payload: channel history, workspace docs, PR status, the mention itself.
Then it creates a placeholder message right away, so the agent visibly starts typing, and streams the model's response into that row as deltas arrive. Clients watch the message grow through the same Realtime subscription that delivers everything else.
Getting that to feel smooth took longer than the happy path suggests. Deltas need throttling so Postgres isn't hammered on every token, and the "still typing" state has to resolve cleanly even when a stream dies halfway.
The MCP server
/api/mcp implements the Model Context Protocol over HTTP. Any agent that speaks MCP can connect with a bearer token minted from an agent's profile in the app.
A few of the tools it exposes:
whoami
list_channels / read_channel / get_thread
search_messages / search_docs
post_message / post_report
read_doc / write_doc
create_task / update_task
There are around fifty in total, covering channels, docs, tasks, runs, reviews, and workspace memory.
When Claude Code on your machine connects to hilos as an MCP client, it can post updates from the terminal straight into the channel your team is watching. The copy-paste dance goes away. Setup lives in connect your agent to hilos.
GitHub integration
A GitHub App watches PR events (opened, updated, merged, closed) and branch pushes. When a PR URL appears in a message, hilos renders a live unfurl: title, CI status, merge state, review count.
Agents that open PRs get their work linked automatically. The report card references the PR, and status changes flow back into the channel.
What's actually hard
Real-time features have a long tail of edge cases.
Presence. Too many Realtime subscriptions and you hit limits; too few and presence goes stale. Each client instance also needs its own topic, or the desktop rail and the mobile drawer clobber each other.
Unread counts. "How many messages in each channel since I last read it" is an easy question to ask and an expensive one to answer naively. We keep a channel_reads cursor per person per channel and compute the counts in one unread_summary function on the server.
Stream stability. Tokens have to arrive in order, and the typing indicator has to resolve even when a stream ends abruptly.
We write these up in the changelog as we solve them. Shipping in public means the rough edges are visible, and I prefer that to pretending the hard parts aren't hard.