A complete, working implementation of this recipe — downloadable as a zip or browsable file by file. Generated by our build pipeline; tested with full coverage before publishing.
You’ll build an Express middleware proxy that sits between your application and the xAI Grok API. Every incoming message is classified for PII indicators, scanned by guardrails, and validated through a safety chain before reaching Grok — and every outgoing completion is checked again before reaching your end users. When risky content is detected, a circuit breaker returns a canned safe response instead. By the end you’ll have a working server that blocks PII in both directions, tracks your Grok spending, and fails safely when downstream services are unavailable.
Prerequisites
Node.js >= 22 (required by the engines field in package.json)
pnpm 10.x (the project uses "packageManager": "pnpm@10.0.0")
An xAI API key — get one from x.ai. You’ll store it in XAI_API_KEY.
Familiarity with TypeScript, Express, and async/await. No prior knowledge of the guardrails or agent-handoff packages is assumed.
Step 1: Scaffold the project
Start with a fresh directory. Create package.json, tsconfig.json, vitest.config.ts, and eslint.config.mjs so TypeScript, tests, and linting are wired up from the start.
Run pnpm install to pull down every package listed in package.json. This includes the Grok AI SDK, Express 5, Zod, the guardrails library, and the REAA packages for circuit breaking, classification, handoff validation, and budget tracking.
terminal
pnpm install
Expected output: pnpm downloads and links all packages into node_modules/. You’ll see a progress bar and a final “Done” message with the install time. No errors.
Step 3: Set up environment variables
The server reads seven environment variables at startup, all validated by Zod. Create a .env file from the example below — the config module will provide sensible defaults for everything except XAI_API_KEY, which is required.
export const SAFE_RESPONSES: Record<string, string> = { blocked: "I'm sorry, I cannot process this request at the moment due to content safety policies.", unavailable: "The service is temporarily unavailable. Please try again later.", error: "An unexpected error occurred. Your request could not be completed.",};
The Deps interface is the central contract — every module that the middleware needs (the Grok client, the safety chain, the circuit breaker, the budget interceptor, and the config) is typed here. The middleware function receives a single deps object and destructures what it needs.
Step 5: Create the config module
Use Zod to parse and validate process.env at startup. Every env var gets a default so the server always starts with a complete config — only XAI_API_KEY is strictly required.
Create src/config.ts:
ts
import { z } from "zod";const envSchema = z.object({ XAI_API_KEY: z.string().min(1, "XAI_API_KEY is required"), GUARDRAILS_THRESHOLD: z.coerce.number().min(0).max(1).default(0.7), CIRCUIT_BREAKER_FAILURE_THRESHOLD: z.coerce.number().int().positive().default(5), CIRCUIT_BREAKER_RECOVERY_TIMEOUT_MS: z.coerce.number().int().positive().default(30000), BUDGET_DAILY_LIMIT_USD: z.coerce.number().min(0).default(10.0), LOG_LEVEL: z.string().default("info"), PORT: z.coerce.number().int().positive().default(3000),});export type AppConfig = z.infer<typeof envSchema>;export function createConfig( env: Record<string, string | undefined>,): AppConfig { return envSchema.parse(env);}
What’s happening:z.coerce.number() converts string env vars (everything from process.env is a string) to numbers. If parsing fails — for example, a missing XAI_API_KEY — Zod throws immediately and the server won’t start with bad config.
Step 6: Create the Grok client and circuit breaker
The Grok client wraps the AI SDK’s generateText function with the xAI provider. The circuit breaker wraps every Grok call so that repeated failures automatically open the circuit and return a canned response instead of hammering a broken downstream service.
What’s happening: The circuit breaker is configured with a "single" recovery strategy — after recoveryTimeoutMs milliseconds, it allows one trial call through the half-open circuit. If that call succeeds, the circuit closes; if it fails, the circuit stays open and the timer resets. The CircuitOpenError is re-exported so the middleware can catch it specifically.
Step 7: Create the classifier and guardrails
The classifier uses @reaatech/agent-mesh-classifier to detect language and intent from incoming text. The guardrails module wraps @presidio-dev/hai-guardrails with a PII guard and a secret guard — both configured to check all detection types.
Create src/lib/classifier.ts:
ts
import { classifierService } from "@reaatech/agent-mesh-classifier";import type { ClassifierOutput } from "../types.js";// Build a minimal valid AgentRegistry (array of AgentConfig) as a placeholderconst stubRegistry = [ { agent_id: "grok-pii-guard", display_name: "PII Safety Guard", description: "Detects PII and offensive content in customer messages", endpoint: "memory://classifier", type: "mcp" as const, is_default: true, confidence_threshold: 0.7, clarification_required: false, examples: [ "my email is john@example.com", "SSN 123-45-6789", "credit card 4111-1111-1111-1111", ], },];export type { ClassifierOutput };export async function detectLanguage(text: string): Promise<string> { const result = await classifierService.classify(text, stubRegistry); return result.detected_language;}export async function getClassification( text: string,): Promise<ClassifierOutput> { const result = await classifierService.classify(text, stubRegistry); return { agent_id: result.agent_id, confidence: result.confidence, ambiguous: result.ambiguous, detected_language: result.detected_language, intent_summary: result.intent_summary, entities: result.entities, };}
Create src/lib/guardrails.ts:
ts
import { piiGuard, secretGuard, GuardrailsEngine, SelectionType,} from "@presidio-dev/hai-guardrails";import type { GuardrailResult } from "../types.js";export { GuardrailsEngine };export function createGuardrailsEngine(threshold?: number): GuardrailsEngine { return new GuardrailsEngine({ guards: [ piiGuard({ selection: SelectionType.All }), secretGuard({ selection: SelectionType.All }), ], ...(threshold !== undefined ? { threshold } : {}), });}function allGuardsPassed( result: Awaited<ReturnType<GuardrailsEngine["run"]>>,): boolean { for (const guardResult of result.messagesWithGuardResult) { for (const msg of guardResult.messages) { if (!msg.passed) { return false; } } } return true;}export async function scanMessages( messages: Array<{ role: string; content: string }>, engine: GuardrailsEngine,): Promise<GuardrailResult> { try { const results = await engine.run(messages); if (allGuardsPassed(results)) { return { passed: true, detections: [] }; } const detections: GuardrailResult["detections"] = []; for (const guardResult of results.messagesWithGuardResult) { for (const msg of guardResult.messages) { if (!msg.passed) { detections.push({ guard: guardResult.guardName, rule: msg.reason ?? "blocked", }); } } } return { passed: false, detections }; } catch { return { passed: false, detections: [{ guard: "engine", rule: "guardrails-engine-error" }], }; }}export async function scanText( text: string, engine: GuardrailsEngine,): Promise<GuardrailResult> { return scanMessages([{ role: "user", content: text }], engine);}
What’s happening:scanMessages runs both the PII guard and the secret guard against every message. If the engine itself throws (network error, misconfiguration), the function fails closed — returning passed: false with an "engine" detection so the middleware treats it as a blocked request rather than silently passing bad content.
Step 8: Create the budget tracker and handoff validator
The budget tracker enforces a daily spending cap using the REAA budget middleware. The handoff validator checks that Grok completions conform to the Agent Handoff Protocol before they reach the customer.
What’s happening: The budget uses a soft cap at 80% of the daily limit and a hard cap at 100%. The softCap is informative — the interceptor records spending at both caps — while the hardCap would be enforced by the middleware layer when you integrate budget checks before calling Grok. The handoff validator constructs a full HandoffPayload from the Grok response text and validates it against the declared agent capabilities.
Step 9: Create the safety chain
The safety chain orchestrates pre-call and post-call checks. Before Grok sees a prompt, it classifies the text and scans for PII. After Grok responds, it scans the completion for PII and validates the handoff payload.
What’s happening: The dynamic import() calls for guardrails.js and handoff-validator.js ensure those modules are only loaded when the safety chain actually runs — not at module evaluation time. The pre-call check classifies and scans the prompt; the post-call check scans the completion and validates the handoff payload. Either failing returns a SafetyDecision with passed: false and a descriptive reason string.
Step 10: Create the guard middleware
This is the core of the recipe. The middleware receives a request, extracts the prompt, runs the pre-call safety check, executes the Grok call through the circuit breaker, runs the post-call safety check, and records spending. Every code path — blocked prompt, circuit open, Grok error, blocked completion, unexpected error — returns an HTTP 200 with a JSON body so the caller always gets a safe response.
Create src/middleware/guard.ts:
ts
import type { Request, Response } from "express";import type { Deps } from "../types.js";import { SAFE_RESPONSES } from "../constants.js";import { CircuitOpenError } from "@reaatech/circuit-breaker-core";export function createGuardMiddleware(deps: Deps) { return async (req: Request, res: Response): Promise<void> => { res.setHeader("Content-Type", "application/json"); const requestId = `req-${String(
What’s happening: The middleware always returns HTTP 200 — even for blocked or errored requests — so the caller can inspect the blocked and reason fields rather than handling HTTP-level errors. Every response path calls budgetInterceptor.afterStep() so spending is always tracked, even when Grok is never called. The estimateCost helper uses the grok-2-1212 pricing of $0.15 per million input tokens and $0.60 per million output tokens.
Step 11: Wire up the Express server
The entry point wires everything together: it creates the config, instantiates each dependency, mounts the guard middleware on POST /api/generate, adds a health check on GET /health, and registers signal handlers for graceful shutdown.
Create src/index.ts:
ts
import express from "express";import type { Express } from "express";import { createConfig } from "./config.js";import { GrokClient } from "./lib/grok-client.js";import { createCircuitBreaker } from "./lib/circuit-breaker.js";import { createBudgetTracker } from "./lib/budget-tracker.js";import { checkPrompt, checkCompletion,} from "./lib/safety-chain.js";import { createGuardMiddleware } from "./middleware/guard.js";const config = createConfig(process.env);const grokClient = new GrokClient(config.XAI_API_KEY);const circuitBreaker = createCircuitBreaker({ failureThreshold: config.CIRCUIT_BREAKER_FAILURE_THRESHOLD, recoveryTimeoutMs: config.CIRCUIT_BREAKER_RECOVERY_TIMEOUT_MS,});const budgetInterceptor = createBudgetTracker(config.BUDGET_DAILY_LIMIT_USD);registerSignalHandlers();const safetyChain = { checkPrompt, checkCompletion };const deps = { grokClient, safetyChain, circuitBreaker, budgetInterceptor, config,};const app: Express = express();app.use(express.json());app.post("/api/generate", createGuardMiddleware(deps));app.get("/health", (_req, res) => { res.json({ status: "ok", uptime: process.uptime() });});const server = app.listen(config.PORT, () => { console.log(`Server listening on port ${String(config.PORT)}`);});export async function gracefulShutdown(signal: string): Promise<void> { console.log(`Received ${signal}, shutting down gracefully...`); return new Promise<void>((resolve) => { server.close(() => { console.log("Server closed"); resolve(); }); });}export function handleSigterm(): void { void gracefulShutdown("SIGTERM");}export function handleUncaught(err: Error): void { console.error("Uncaught exception:", err); void gracefulShutdown("uncaughtException");}export function handleRejection(reason: unknown): void { console.error("Unhandled rejection:", reason); void gracefulShutdown("unhandledRejection");}export function registerSignalHandlers(): void { process.on("SIGTERM", () => { handleSigterm(); }); process.on("uncaughtException", (err) => { handleUncaught(err); }); process.on("unhandledRejection", (reason) => { handleRejection(reason); });}export { app };
What’s happening: The deps object bundles every dependency into a single argument for the middleware — this is the Deps interface from Step 4 in action. The app is exported so tests can use supertest without starting a real HTTP server. Signal handlers catch SIGTERM, uncaught exceptions, and unhandled rejections, closing the server cleanly.
Step 12: Run the tests
The test suite covers the guard middleware, guardrails engine, safety chain, Grok client, circuit breaker, classifier, budget tracker, config, and the full Express app via supertest. All dependencies are mocked so no real API calls are made.
terminal
pnpm test
Expected output: vitest runs all test files and prints a summary. You should see something like:
All tests pass. Coverage thresholds are set to 90% across lines, branches, functions, and statements — if any metric falls below that, the test run fails.
To start the server:
terminal
pnpm tsx src/index.ts
Expected output:
code
Server listening on port 3000
You can now send a test request:
terminal
curl -X POST http://localhost:3000/api/generate \ -H "Content-Type: application/json" \ -d '{"prompt": "Hello, how can I help you?"}'
With a valid API key and a clean prompt, you’ll get a response like:
json
{ "text": "Hello! How can I assist you today?", "usage": { "inputTokens": 10, "outputTokens": 5 }}
Send a prompt containing PII and the middleware blocks it before Grok ever sees it:
terminal
curl -X POST http://localhost:3000/api/generate \ -H "Content-Type: application/json" \ -d '{"prompt": "my email is john@example.com and my SSN is 123-45-6789"}'
The response will be:
json
{ "text": "I'm sorry, I cannot process this request at the moment due to content safety policies.", "blocked": true, "reason": "pii-in-prompt"}
Next steps
Add a persistent budget store. The current SpendStore lives in memory — replace it with a database-backed implementation from @reaatech/agent-budget-spend-tracker to survive server restarts.
Integrate with your own application. The /api/generate endpoint is a drop-in proxy. Route your app’s customer-facing messages through this server and point the Grok client to your production xAI API key.
Add custom guard rules. The piiGuard and secretGuard support custom detection rules — extend them with patterns specific to your industry (medical record numbers, financial account IDs, etc.).