relay

Self-host quickstart

Quickstart

Clone the repo, run two commands, watch a streaming agent with tool calls light up the dashboard.

Prerequisites

You need Node 20+, pnpm 9+, Go 1.22+, and Docker. Plus at least one provider API key — Anthropic or OpenAI.

# macOS, via Homebrew
brew install node pnpm go
# Docker Desktop: https://www.docker.com/products/docker-desktop

1. Clone & install

The whole stack lives in one repo. pnpm installinstalls every workspace package in one shot.

git clone https://github.com/KevinCorrea5103/relay
cd relay
pnpm install

2. Add a provider key

Drop your Anthropic or OpenAI key into .env. You don't need both; one is enough.

echo "OPENAI_API_KEY=sk-..."        >> .env
# and/or
echo "ANTHROPIC_API_KEY=sk-ant-..."  >> .env

3. Bootstrap

One idempotent command generates the master key, brings up Postgres, applies migrations, creates a tenant, and mints your firstRELAY_API_KEY. Re-running it is safe.

pnpm bootstrap

You'll see something like:

 .env file created from .env.example
 RELAY_MASTER_KEY generated and written to .env
 RELAY_INTERNAL_SECRET generated
 Build @relayhq/sdk
 Build @relayhq/db
 docker compose up postgres
 apply migrations
 bootstrap tenant + RELAY_API_KEY minted relay_live_AbCd…

 Setup complete. Run `pnpm dev` to start the whole stack.

4. Start everything

pnpm dev runs the runtime (Go), control plane (Node), dashboard, and marketing site in parallel. One Ctrl-C kills all four.

pnpm dev

You'll see four colored log streams:

[runtime]  listening on http://localhost:4100
[api]      [control-plane] listening on http://localhost:4000
[dash]     ▲ Next.js 15  ✓ Ready in 1071ms · localhost:3000
[web]      ▲ Next.js 15  ✓ Ready in 1065ms · localhost:3001

Open the dashboard:

  • http://localhost:3000 — internal observability (runs, traces)
  • http://localhost:3001 — marketing site / docs

5. Fire your first agent

In a new terminal, run the bundled example. It uses a built-in calculator and a custom get_user tool that runs in your local Node process.

pnpm example                               # uses claude-sonnet-4-6 by default

RELAY_MODEL=gpt-4o-mini       pnpm example  # switch model via env
RELAY_MODEL=claude-haiku-4-5  pnpm example "Compute (17+8)*3"

You'll see tokens stream in, tool calls intercalated with their results, and a final answer:

[model=gpt-4o-mini] > Look up u_001 and u_003. What's the combined balance, and how much would 7% tax on it be?

→ get_user({"id":"u_001"}) = {"name":"Ada Lovelace","tier":"pro","balanceUsd":1480.5}
→ get_user({"id":"u_003"}) = {"name":"Alan Turing","tier":"enterprise","balanceUsd":9320.75}
→ calculator({"a":1480.5,"b":9320.75,"op":"+"}) = 10801.25
→ calculator({"a":10801.25,"b":0.07,"op":"*"}) = 756.0875
The combined balance of u_001 and u_003 is $10,801.25. The 7% tax on this amount would be approximately $756.09.

[done] usage={"input_tokens":1960,"output_tokens":198}

Refresh localhost:3000 — the run is there with a complete event-by-event trace.

Where to go next

Use the SDK in your own code

Two official SDKs (TypeScript and Python). Both are on package registries. Point them at your local control plane and pass the API key the bootstrap printed. For Go / Rust / anything else, see Languages & SDKs.

my-agent.ts
// npm install @relayhq/sdk
import { createAgent, builtin } from "@relayhq/sdk";

const agent = createAgent({
  baseUrl: "http://localhost:4000",     // your local control plane
  apiKey: process.env.RELAY_API_KEY!,   // the relay_live_… you got
  model: "claude-sonnet-4-6",
  tools: [builtin.calculator],
});

for await (const event of agent.run("What is 23 * 47?")) {
  if (event.type === "token") process.stdout.write(event.text);
}
my_agent.py
# pip install relayhq
import asyncio, os
from relayhq import create_agent, builtin

agent = create_agent(
    base_url="http://localhost:4000",
    api_key=os.environ["RELAY_API_KEY"],
    model="claude-sonnet-4-6",
    tools=[builtin.calculator],
)

async def main():
    async for event in agent.run("What is 23 * 47?"):
        if event["type"] == "token":
            print(event["text"], end="", flush=True)

asyncio.run(main())

Add semantic memory

One option flips on per-tenant memory backed by pgvector. See Memory for the full pipeline.

const agent = createAgent({
  model: "gpt-4o-mini",
  memory: { namespace: `user:${userId}` },
});

Add custom tools

Your handler runs in your process. The runtime pauses, the SDK fulfills, you continue. See Tools.

Troubleshooting