Step 1 — What you need
You need three things before starting. You may already have them; skim and decide.Node.js 22+
The gateway is TypeScript; we run it with
tsx, no separate build step.OpenRouter API key
Paid proxy to dozens of models. The gateway uses it for the planner LLM.
Supabase project
Hosted Postgres (free tier). Stores conversation history between turns.
Checking Node
Checking Node
Getting an OpenRouter key
Getting an OpenRouter key
Sign up at openrouter.ai, add a few dollars of credit, and copy the key from the API section. It looks like
sk-or-v1-<long random string>.Getting Supabase credentials
Getting Supabase credentials
Create a project at supabase.com, then grab two values from Project Settings → API:
- Project URL (looks like
https://abcdef.supabase.co) - Service role key (starts with
eyJ..., this is sensitive — don’t paste it in chat apps).
Step 2 — Get the code and install
Step 3 — Apply the database schema
The gateway expects two tables in your Supabase project. From the Supabase web UI, go to SQL Editor, then run the two files in order:
These create
gateway_sessions, gateway_messages, and gateway_tasks tables with row-level security policies appropriate for a service-role caller. You won’t edit these tables directly — the gateway reads and writes them.
Step 4 — Configure the gateway
Creategateway/.env.local from the template:
gateway/.env.local
examples/.env (used by the sample Python agents — the file already exists, you just add the key):
examples/.env
What’s a “bearer token”?Think of
GATEWAY_API_KEY like the password on a movie ticket booth. Whoever holds this string can ask the gateway to do work on their behalf. The gateway checks it on every request by hashing both sides and comparing the hashes in constant time (so neither a timing nor a length attack can recover the token). Don’t paste it into chat apps or commit it to a public repo. Rotate it when you suspect it leaked.Step 5 — Start one agent
Open a terminal. Start the joke agent — it’s one Python file that listens on port 3773 and answers with jokes:Step 6 — Start the gateway
In a second terminal:The “no DID identity configured” line is fine for now. The DID signing chapter turns on cryptographic signing. Leave this terminal running too.
Step 7 — Ask a question
In a third terminal, load your gateway token into the shell so you don’t have to copy-paste it every time:Reading the output line by line
That format is called Server-Sent Events (SSE). It’s plain HTTP, but the server keeps the connection open and writes events one at a time instead of sending one big response at the end. Two parts per event: a label (event: session) and a JSON payload (data: {...}).
What each event means, in the order they arrived:
| # | Event | What it means |
|---|---|---|
| 1 | session | The gateway opened a conversation. session_id is the unique handle; you can pass it back later to resume. |
| 2 | plan | The planner started its first turn. |
| 3 | task.started | The planner decided to call the joke agent. input: {input: "..."} is what it’s sending. |
| 4 | task.artifact | The agent replied. The text inside <remote_content> is the real answer. That envelope is there so the planner (and you) remember this is untrusted data. |
| 5 | task.finished | That call is complete. |
| 6 | text.delta (many) | The planner is now writing its own final answer, streamed a word or two at a time. Concatenate them in order (they all share a part_id). |
| 7 | final | Done. stop_reason: "stop" means “natural end”. usage reports token counts for billing. |
| 8 | done | Last event. Close the connection. |