Tailrace
Guides

Ship an agent

Scaffold a Next.js agent with Tailrace, verify block and tokenize locally, then deploy to Vercel for semi-prod use.

You will leave this tutorial with a Vercel-hosted Next.js agent that governs model and tool traffic with Tailrace: secrets blocked before the provider, PII tokenized, and values restored at egress.

Stack: Next.js App Router + AI SDK + Vercel. Scaffold with tailrace create next, then follow the steps below. For Cloudflare Workers or OpenAI Agents SDK, see Other stacks.

Prerequisites

  • Node 20+
  • A package manager (npm, pnpm, or yarn)
  • Optional for live / deploy: an OpenAI API key and the Vercel CLI

Snippet-only setup (no scaffold): Quickstart.

Step 1: Scaffold and run

npx @tailrace/cli create next my-agent
cd my-agent
npm install   # or pnpm / yarn
npm run dev

If you already have @tailrace/cli installed: tailrace create next my-agent.

Open http://localhost:3000. The route uses an in-process mock when OPENAI_API_KEY is unset - no provider key required for local verify.

From source / contributing

To run the published monorepo example instead of a scaffolded app:

git clone https://github.com/tailrace/tailrace.git
cd tailrace
pnpm install
pnpm --filter @tailrace/core build
pnpm --filter @tailrace/ai-sdk build
pnpm --filter example-nextjs-ai-sdk dev

That path is for contributors and anyone comparing against examples/nextjs-ai-sdk. The rest of this tutorial assumes a scaffolded my-agent directory; adjust paths if you cloned the monorepo.

Step 2: Verify block and tokenize locally

Run A: block a secret

Click Run A - block secret, or:

curl -s -X POST http://localhost:3000/api/chat \
  -H 'content-type: application/json' \
  -d '{"prompt":"Use sk_test_51FakeKeyForTailraceTests000FAKE"}' | jq .

Expect 422 and "entity": "api_key". The fake key never reaches the model. Server logs show entity and hashes only - never the raw value.

Run B: tokenize + restore

Click Run B - tokenize + restore, or:

curl -s -X POST http://localhost:3000/api/chat \
  -H 'content-type: application/json' \
  -H 'x-workflow-id: test-session' \
  -d '{"prompt":"Email customer@example.com"}' | jq .

Expect 200, modelSaw containing <EMAIL_…>, and text containing customer@example.com.

Step 3: Understand the Tailrace wiring

Read app/api/chat/route.ts in your project (same shape as the monorepo example). The flow is:

  1. createTailrace + withAiSdk - default policy (secrets → block, common PII → tokenize).
  2. tailrace.model(...) / tailrace.tools(...) - same workflowId and agent on both.
  3. generateText - Tailrace scans prompts and tool traffic before work proceeds.
  4. tailrace.restore at { kind: "egress", sink: "ui" } - detokenize only at egress.
  5. PolicyViolationError → HTTP 422 JSON.
OptionPurpose
agentSelects identities overrides in your policy. Defaults to "default".
workflowIdVault scope for tokens. Same ID + same value → same token every time.

Pass the same workflowId to model, tools, and restore within one conversation. The example reads it from x-workflow-id (or generates a per-request UUID).

Fluent vs standalone wrappers, streaming streamBlockBehavior, and full options: AI SDK reference.

Step 4: Switch to a live provider

cp .env.example .env.local
VariablePurpose
OPENAI_API_KEYWhen set, the route uses openai("gpt-4o-mini") instead of mock
TAILRACE_VAULT_KEYStable vault secret (required for live / deploy)

Restart the dev server, then ask the model to use the governed tool:

Look up customer cust_42 and draft a short reply to their email.

lookupCustomer returns synthetic customer@example.com. Tailrace tokenizes that result on the tool in boundary; egress restore puts the real address back in the UI response.

Step 5: Deploy to Vercel

  1. Deploy from your scaffolded app directory (Import Git in the dashboard, or CLI from that directory). Monorepo contributors: set the Vercel project root to examples/nextjs-ai-sdk.
  2. Add env vars in the Vercel project:
    • OPENAI_API_KEY - provider key
    • TAILRACE_VAULT_KEY - stable secret (for example openssl rand -base64 32). Keep the same value across deploys so tokens remain decryptable.
  3. Deploy:
cd my-agent
vercel

Step 6: Semi-prod verify

Production curls

Replace YOUR_DEPLOYMENT with your Vercel hostname:

# Expect 422 + entity api_key
curl -s -X POST "https://YOUR_DEPLOYMENT.vercel.app/api/chat" \
  -H 'content-type: application/json' \
  -d '{"prompt":"Use sk_test_51FakeKeyForTailraceTests000FAKE"}' | jq .

# Expect 200, modelSaw with <EMAIL_…>, text with customer@example.com
curl -s -X POST "https://YOUR_DEPLOYMENT.vercel.app/api/chat" \
  -H 'content-type: application/json' \
  -H 'x-workflow-id: ship-verify' \
  -d '{"prompt":"Email customer@example.com"}' | jq .

Use a stable x-workflow-id across related requests in a conversation so tokens stay consistent.

memoryVault vs kvVault

Within a single request, default memoryVault is enough for tokenize + restore (the vault lives for the request lifetime).

For multi-invocation token stability on serverless (same token across separate function invocations), use kvVault with a Redis/Upstash shim - a short adapter over { get, put, delete } per docs/vault.md §2. See the example README for a concrete Upstash-shaped shim. Tailrace does not bundle a Redis package.

Audit hygiene

Pass onDecision (as the example does) if you want structured logs. Decisions carry entity, rule, and hashes - never raw sensitive values. Do not log prompts or vault plaintext in production.

Troubleshooting

Tokens change between requests

You are not passing the same workflowId to model and restore. Thread the session ID through both.

restore throws INVARIANT

You called restore at a model or tool boundary. Use { kind: "egress", sink: "…" } only.

Stream aborts on benign output

A secret-class pattern matched in model output. Default policy blocks echoed secrets. Adjust policy only with care - secrets cannot be overridden to allow without dangerouslyAllowSecrets: true.

Other stacks

tailrace create also scaffolds Cloudflare Workers and OpenAI Agents SDK apps:

npx @tailrace/cli create cloudflare my-agent
npx @tailrace/cli create openai my-agent

Full flags and template details: CLI reference.

On this page