HTTP with Fastify

One concrete BYO HTTP recipe: agent SSE, interruptible graph, and durable Postgres adapters. Fastify is the sample server — the same patterns drop into Hono, Nest, or plain Node.

1. Install

Install from npm (@monorch/ai@0.2.0). Native binaries for @monorch/runtime ship as optional platform packages — see platforms.

terminal
pnpm add @monorch/ai
# optional: pnpm add pg   # only for @monorch/ai/postgres

2. Agent + SSE

support.ts
import Fastify from "fastify";
import { agent, tool } from "@monorch/ai";
import { openai, mock } from "@monorch/ai/openai";
import { z } from "zod";

const add = tool({
  name: "add",
  input: z.object({ a: z.number(), b: z.number() }),
  permission: { type: "roles", roles: ["agent"] },
  execute: ({ a, b }) => ({ sum: a + b }),
});

const bot = agent({
  name: "math",
  model: process.env.OPENAI_API_KEY
    ? openai("gpt-4.1-mini")
    : mock([
        { toolCalls: [{ id: "1", name: "add", arguments: { a: 2, b: 3 } }] },
        { text: "2 + 3 = 5" },
      ]),
  tools: [add],
  instructions: "Use tools for math.",
});

const app = Fastify();

app.post("/support/stream", async (req, reply) => {
  const message = (req.body as { message?: string })?.message ?? "2+3";
  reply.hijack();
  reply.raw.writeHead(200, {
    "content-type": "text/event-stream",
    "cache-control": "no-cache",
    connection: "keep-alive",
  });
  for await (const ev of bot.stream(message)) {
    reply.raw.write(`data: ${JSON.stringify(ev)}\n\n`);
  }
  reply.raw.end();
});

3. Interrupt + resume

refund.ts
import { graph, memorySaver } from "@monorch/ai";

const checkpointer = memorySaver();

const refund = graph("refund")
  .node("lookup", async ({ input }) => ({
    output: `order:${input.orderId}`,
    state: { orderId: input.orderId },
  }))
  .interrupt("approve", { prompt: "Approve refund?" })
  .node("pay", async ({ outputs }) => `refunded:${outputs.lookup}`)
  .compile({ checkpointer });

app.post("/refund", async (req) => {
  const body = req.body as { orderId?: string; threadId?: string };
  const threadId = body.threadId ?? `refund-${Date.now()}`;
  const run = await refund.start(
    { orderId: body.orderId ?? "ord_1" },
    { threadId },
  );
  return { id: run.id, threadId, status: run.status };
});

app.post("/refund/:threadId/resume", async (req) => {
  const run = await refund.restore((req.params as { threadId: string }).threadId);
  const resumed = await run.resume("approved");
  return { id: resumed.id, status: resumed.status, outputs: resumed.outputs };
});

4. Postgres (optional)

Swap in-memory helpers for durable adapters. pg is an optional peer.

postgres.ts
import pg from "pg";
import {
  ensureMonorchSchema,
  postgresCheckpointer,
  postgresThreads,
} from "@monorch/ai/postgres";

const pool = new pg.Pool({ connectionString: process.env.DATABASE_URL });
await ensureMonorchSchema(pool);

const checkpointer = postgresCheckpointer(pool);
const threads = postgresThreads(pool);

// graph compile({ checkpointer })
// agent.run(msg, { threadId: "t1", memory: threads })

5. Smoke

terminal
pnpm smoke
# optional live provider:
# LIVE_SMOKE=1 OPENAI_API_KEY=... pnpm smoke:live

The repo smoke at examples/fastify covers handoffs, MCP, OTel, branching, abort, hot-reload, and Postgres stand-ins — Fastify is only the sample host.

FAQ