Skip to content
Redline
Esc
navigateopen⌘Jpreview
On this page

Vercel AI SDK

Connect a Vercel AI SDK agent to Redline — experiments, production monitoring and runtime protection. The complete document, written to be followed by a coding agent inside the repository.

You are connecting a repository whose agent is built on the Vercel AI SDKgenerateText, streamText, or a ToolLoopAgent — to the Redline platform: experiments AND production monitoring. Follow these steps exactly and do not modify the repository’s existing agent code. The whole integration is new files under redline/, plus one flag on AI SDK v6.

0. Confirm the shape

Grep the repository for from "ai" / from 'ai' together with generateText, streamText or ToolLoopAgent. If present, this is the page. If the agent is LangChain.js or a hand-rolled loop over the OpenAI client, it is not — see Integrations for the right page.

The Vercel AI SDK is loader-hooked by redline dev: attached tools reach your agent as it is constructed, and its own telemetry fills the waterfall. Nothing to merge by hand.

1. Install the SDK and scaffold

At the repository root:

npm i -D @redlineai/sdk
npx redline init

This creates redline.config.ts (leave it as generated) and redline/my-agent.ts, the doorway file. Every file under redline/ is imported by the CLI: it must only export agents and must not start a server on import.

2. Find the repository’s agent entry point

Locate the function that RUNS the agent — the one that receives a prompt or a messages array and returns (or streams) the model’s answer. Search for generateText(, streamText(, new ToolLoopAgent(, .generate(, .stream(. Read its signature. Do NOT modify it.

3. Wire it into redline/my-agent.ts

Replace the TODO. The contract:

  • Export defineAgent({ id, name, description, run }) from @redlineai/sdk.
  • id is a kebab-case slug unique to this agent.
  • run(task, ctx) is async. task.prompt is the task’s instructions. It must return the final answer as a string.
  • If the entry point takes a messages array, pass [{ role: "user", content: task.prompt }].
  • If it STREAMS, collect the streamed text and return the whole of it at the end.
  • If it needs a working directory, create a fresh one: fs.mkdtempSync(path.join(os.tmpdir(), "run-")).
import { defineAgent } from "@redlineai/sdk";
import { customerSupportAgent } from "../src/support-agent";   // unchanged

export const supportAgent = defineAgent({
  id: "support-agent",
  name: "Support Agent",
  description: "Order lookup, policy lookup and ticket creation for an e-commerce store.",
  async run(task, ctx) {
    const result = await customerSupportAgent.generate({ prompt: task.prompt });

    // Optional — the run's transcript, from the SDK's own step record.
    for (const step of result.steps ?? []) {
      for (const call of step.toolCalls ?? []) ctx?.tool?.(call.toolName, call.input);
      for (const out of step.toolResults ?? []) ctx?.toolResult?.(out.toolName, out.output);
    }
    if (result.usage?.totalTokens) ctx?.usage?.(result.usage.totalTokens);

    return result.text;
  },
});

For a plain generateText entry point:

run: async (task) => (await generateText({ model, prompt: task.prompt, tools })).text,

For streamText:

run: async (task) => {
  const { textStream } = streamText({ model, prompt: task.prompt, tools });
  let out = "";
  for await (const chunk of textStream) out += chunk;
  return out;
},

4. Attached tools — do NOTHING

An experiment can attach MCP servers and, on every run, the project’s Linux machine as a machine_run shell tool. redline dev hooks the AI SDK’s module loader before it imports your files, so those tools are merged into the ToolLoopAgent / generateText call as it is built.

Do not call redlineTools — that would connect everything a second time. Verify by counting: three tools declared, four in the agent, the injection landed. Attached SKILLS need nothing at all — they arrive inside task.prompt.

5. Three failures that are nearly guaranteed if you skip them

5a. The agent must know its tools exist. Registering a tool is not the same as the agent reaching for it. An agent whose instructions describe a narrow job answers “I have no way to do that” while machine_run sits unused. Because the AI SDK is patched, the tool is in the list — but the instructions may still need one line saying attached tools may be used when the task calls for them.

5b. A tool that throws must not kill the run. The AI SDK surfaces a tool error as a tool-error part and continues by default; do not wrap the call in something that re-throws it to the top.

5c. Do not drop the reasoning. Map the result’s steps onto ctx as shown in §3 — ctx.thinking(text), ctx.tool(name, input), ctx.toolResult(name, out), ctx.usage(tokens). A run whose transcript shows only an answer is a run nobody can judge. Not every model emits reasoning; if the transcript has none, check the model before changing code.

6. Connect and verify

The agent’s own environment (model API keys etc.) must load the way the repo normally loads it — .env, exported vars. Then:

export REDLINE_URL=https://tryredlineai.co
export REDLINE_API_KEY=rl_…          # Agents page → Runner key
npx redline dev

redline dev must be running on this machine at all times for anything to work. It is the long-lived worker that connects this repository’s agent to Redline: experiments execute inside it, the agent shows ONLINE on the Agents page only while it runs, asset snapshots for Protect upload through it, policies and honeypots arrive and re-arm through it every 20 seconds, and dev sessions stream to Monitor through it. If it stops, the agent goes offline and none of that happens.

What the two variables are:

  • REDLINE_URL is the web address of the Redline platform. It is NOT an address of anything in this repository; nothing in the repository hosts it.
  • REDLINE_API_KEY is the runner key minted on the Agents page (Runner key button). It is the only credential needed. Do not commit it.

In a terminal you are watching: npx redline dev. On a server or VM, after you log out:

nohup npx redline dev > ~/redline-dev.log 2>&1 &
disown

It opens NO inbound port — outbound only, NAT-friendly. Success is the line:

connected — support-agent registered and online

Leave it running; runs execute in it when an experiment is launched. It runs several runs in parallel by spawning worker processes (REDLINE_CONCURRENCY, default 4) — an experiment’s “at once” setting fills by itself.

7. When something is wrong

  • “no agents found” → the redline/ file exports no defineAgent object.
  • Registration rejected → the id collides with another agent; pick another slug.
  • The agent says it has no tools → §5a, or the repository imports ai before redline dev could hook it (a bundled/pre-compiled entry). Run against the source, not a build.
  • Session shows the transcript but no model calls, tokens or tool calls → AI SDK ≤6 without experimental_telemetry (§8a).
  • Waterfall labels every step “turn N” → see known issues.
  • The run dies on the first tool error → §5b.
  • ask_user (if the agent has one) ENDS the run. Redline runs are unattended — return the questions as the answer rather than inventing replies.

Everything else: known issues.

8. Monitoring — the same agent, watched in production

Nothing extra to write for dev: while redline dev is up, monitoring is on by default (REDLINE_MONITOR=1). Every conversation the agent works — user message, model calls with token counts, both halves of every tool call, reasoning, the final answer — ALSO streams to Monitor → Sessions.

8a. REQUIRED on AI SDK v6 and below — enable telemetry. AI SDK v7 reports its calls with no opt-in. AI SDK ≤6 emits spans only when the call sets it, and without it every session shows the transcript with no waterfall, tokens or tool calls — which reads as monitoring being broken when it is this flag missing:

export const customerSupportAgent = new ToolLoopAgent({
  model,
  experimental_telemetry: { isEnabled: true },   // v6: required. v7: harmless.
  instructions: `…`,
  tools: { checkOrderStatus, lookupPolicy, createTicket },
});
// or on each generateText / streamText call

VERIFY after the first run: the session must show model calls and a token count, not a single line per turn.

For the DEPLOYED agent — real users, no redline dev — wrap the message handler with observe(). ONE wrapper, no other monitoring code anywhere:

import { observe } from "@redlineai/sdk";
import { customerSupportAgent } from "../src/support-agent";

export const handleMessage = observe(
  async (conversationId: string, userId: string, text: string) => {
    const result = await customerSupportAgent.generate({ prompt: text });
    return result.text;
  },
  { agent: "support-agent", sessionArg: 0, userArg: 1, inputArg: 2 },
);

Each call becomes a session: the wrapper records the input and the returned answer itself, and every model and tool call the AI SDK emits during the call lands in the SAME session on the async context. Calls sharing sessionArg land in one transcript. Config is environment only — REDLINE_API_KEY, REDLINE_URL, REDLINE_AGENT — so a deploy needs no code; without the key the wrapper is inert and the handler runs untouched. Errors are reported and re-thrown; nothing in it can take the handler down.

8b. Your application has to CALL handleMessage. A wrapper only records calls to the function it wraps. If the repository already has a pipeline for inbound messages — a route handler, a queue worker, a websocket loop — that pipeline must call handleMessage for each message. That is one call-site change in existing code and the only one this integration asks for. Without it, experiments work and production sessions never appear.

(createMonitor remains as the explicit low-level client underneath, for a handler shape the wrapper cannot wrap — see monitoring.)

What the platform does with a session, with no further code:

  • It appears in Monitor → Sessions as a readable conversation with the waterfall beside it.
  • Rule detectors match every span as it is written; within minutes the monitor re-examines the session against every rule the project defines (Monitor → Violations). Rules are written in the console, in plain language, not in the repository.
  • When the session closes, the intent miner reads it for recurring user behaviour (Monitor → Intents).

9. Runtime protection — what to expect once connected (nothing to write)

While redline dev is up, the SDK also ENFORCES the project’s security policies inside the agent’s process. Nothing to add, nothing in the repository changes; everything is configured in the console (Protect → the agent → Policies) and reaches the running process within ~20 seconds of being saved. The AI SDK’s model calls go through its loader-hooked provider, so the gate sees them.

  • Governance rules decide what the agent may DO — a condition over the action about to happen (tool name and arguments) and a verdict. A denied call never executes: the model receives [redline] The call to <tool> was denied by policy: <reason> as the tool result and answers around it. require_approval holds the call and names who could approve. warn lets the call run and appends [redline] Policy note on <tool>: <reason> to the tool result the model reads; log lets it run and records it silently.
  • The guard classifier decides about TEXT — a separate switch, not a rule. It scores every user message and tool result for a prompt injection; above the block threshold the content is withheld and the model never sees it.
  • Honeypots — approved bait tools are injected alongside the agent’s own; a call to one is recorded as a hijack signal and answered with a bland unavailability line.

Streaming responses pass through unjudged — text already left as it was produced. Everything fails OPEN: if the platform or classifier is unreachable, the agent runs ungoverned rather than breaking. Every enforcement is recorded with its evidence in the console.

What a healthy session looks like

Seven user turns, one model call each, the tool nested under the call that made it: 23 spans — 7 inputs, 7 model calls, 7 outputs, 2 tools. If the turn count matches the user messages and tools indent under their model call, the tree is right.

Was this page helpful?