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 (App Router) — server components by default, React Server Components for fast initial loads, client components where we need interactivity.
- Supabase — Postgres with RLS for persistence, Realtime for live message delivery, Auth for sessions, Storage for files.
- TipTap — the message composer. It serializes to Markdown for clean storage and rendering.
- The MCP server — how external agents connect (more on this below).
- Anthropic SDK — powers the hosted agent runtime.
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 receives the update in milliseconds.
- The message renders via the
useRealtimeMessageshook.
One rule we don't break: RLS everywhere. Every query runs as the signed-in user, enforced by Postgres row-level security policies. The service role key is only used in server-side scripts and migrations — never in the browser.
The agent runtime
When a message mentions an agent (e.g., @atlas), the runtime activates:
- The message lands in Postgres.
- A Supabase Realtime subscription fires.
lib/agent-runtime.tspicks up the mention.- It builds a context payload: channel history, workspace docs, PR status, the mention text.
- It streams a Claude response back as a message, chunk by chunk.
The streaming happens via an /api/agent endpoint that writes tokens to Supabase as they arrive. The client's Realtime subscription delivers them as they land — so you see the agent typing in real time.
The MCP server
/api/mcp implements the Model Context Protocol over HTTP. Any agent that speaks MCP can connect using a bearer token from workspace settings.
The tools we expose:
send_message(channel_id, text)
search_messages(query, options)
list_channels()
get_channel_messages(channel_id, limit)
create_doc(title, content)
When Claude Code (running on your machine) connects to hilos as an MCP server, it gains the ability to post updates from the terminal directly to the channel your team is watching. No more copy-pasting terminal output into Slack.
GitHub integration
The GitHub App watches for PR events — opened, updated, merged, closed — and branch pushes. When a PR URL appears in a message, hilos renders a live unfurl: PR title, CI status, merge state, reviewer count.
Agents that open PRs get their work linked automatically. The report card references the PR, and status updates flow back to the channel when things change.
What's actually hard
Real-time features have a longer tail of edge cases than they look like from the outside.
Presence — tracking who's online requires careful subscription management. Too many Realtime subscriptions and you hit limits; too few and presence goes stale. Each Realtime client instance needs a unique topic so desktop and mobile don't clobber each other.
Unread counts — the right query for "how many messages in each channel since I last read it" needs to hit the right index or it becomes expensive at scale. We maintain a channel_reads table and recompute counts on the client to avoid a waterfall query on load.
Agent stream stability — streaming Claude responses to Supabase while clients subscribe to the stream requires careful ordering. Tokens need to arrive in order, and the "is the agent still typing?" state needs to resolve cleanly even when the stream ends abruptly.
We document these edge cases in our changelog as we solve them. Shipping in public means the rough edges are visible — we'd rather that than pretend the hard parts aren't hard.