Architecture
The layers, the data flow, and how a chat turn actually works.
SupaNet is a React front end over Supabase, with the AI logic living in Supabase Edge Functions so that secrets never reach the browser.
The layers
- Front end - a React + Vite app. Auth is wrapped in an
AuthContext; aProtectedRoutegates the authenticated app. Pages cover chat, artifacts, files, skills, webhooks, tools, agents, activity, usage, and settings. - Supabase - Postgres (with
pgvectorfor embeddings), Auth, Storage, Realtime, and Edge Functions. - Edge Functions - the server-side brain. The
chatfunction runs the assistant; other functions handle webhooks, the public MCP server, standalone artifact pages, inbound email, and scheduled runs. - OpenRouter - the model provider. SupaNet talks to it through a shared client so every loop (chat, webhook, scheduler, guardrails) behaves consistently.
The key security stance
The browser holds only the public anon key. The OpenRouter API key lives as an edge-function secret and is used only server-side. Row-level security (RLS) decides what any given user can read or write. This means even though the anon key ships in the bundle, the data behind it is protected by Postgres, not by hiding the key.
How a chat turn works
- The client sends the message history plus the user's access token to the
chatedge function.- In a group thread (shared with other members), only messages containing
@aitrigger the assistant. Private threads always trigger.
- In a group thread (shared with other members), only messages containing
- The function assembles a system prompt: it loads all always-on prompts
(
skills.auto_apply = true) with the service role, optionally layered with an invoked skill or an agent's instructions.- In a group thread, the system prompt includes a note that this is a shared conversation and lists who the participants are.
- It loads the active tools the model is allowed to use.
- It runs an agentic loop: the model may call tools; the function executes them, feeds the results back, and loops until the model is done or it hits a turn limit.
- The reply streams back to the browser as server-sent events.
Group chat
A conversation can be shared with other workspace members via the People
picker. Members see the full thread, can post as themselves, and talk to each
other. The assistant only replies when someone writes @ai, keeping the thread
lightweight and cost-effective for team collaboration. The message history sent
to the model prefixes each human message with the sender's name so the assistant
can follow who said what.
Model selection
Never hardcode a model id. Features bind to a profile key, not a specific model:
orchestrator- the main brain for chat, agents, webhooks, and scheduled runs.utility- cheap and fast, used for guardrail checks and other lightweight calls.
Admins re-point a key to a different model in Settings → Models. The database row is the source of truth; an environment variable is only a fallback.
Realtime
Chat messages and conversations are published over Supabase Realtime, so a message sent on one device appears on another. The client de-duplicates between its optimistic insert and the realtime echo so nothing double-renders.
Configuration as data
The recurring theme: capabilities are rows, not code. Tools, skills, prompts, agents, guardrails, and webhooks all live in tables and are read at runtime. This is what lets an admin reshape the assistant's behaviour from the UI without a redeploy, and it is the backbone every other building page builds on.