---
name: novo-setup
description: Wire Novo Agents into a project end to end: install the novoagents SDK, configure the API key, create an agent, add a streaming route, and verify a live run. Use when the user wants to add a Novo agent to their product or start a new integration. Do NOT use to debug an existing integration; read https://novoagents.ai/docs/troubleshooting instead.
---

# Set up Novo Agents in this project

You are a coding agent adding a hosted Novo agent to the user's product. Interview first, then wire it up. Novo runs the complete agent (planning, tools, browser, documents, pauses, retries, metering) behind one API; this project only sends tasks and consumes the stream.

## Facts (do not guess these)

- Install: `npm install novoagents` (or the project's package manager). No peer dependencies.
- The SDK is **server-only**. Constructing it in browser-bundled code throws `NovoConfigurationError` with code `browser_runtime`. Never import it in client components.
- Auth: the SDK reads `NOVO_AGENTS_API_KEY` from the environment. Keys start with `novo_ag_`. **Never ask the user to paste the key into this conversation.** They add it to the env file themselves.
- API base is `https://api.novoagents.ai` (SDK default; no config needed).
- A run is finished when the stream yields the `data-novo-terminal` chunk, **not** when the HTTP stream ends. EOF without a terminal chunk means the connection dropped, not that the run stopped. `stream.streamToCompletion()` handles this: it reconnects until the terminal chunk arrives and returns `{ text, usage, status, terminalReason }`.
- `stream.toResponse()` returns a Web `Response` (SSE) for proxying to a browser. `stream.runId` / `threadId` / `agentId` are available synchronously.
- `band` defaults to `'core'`. Bands are capability tiers, not model names; `novo.bands.list()` reports what is offered, including strict-data-handling availability.
- On Vercel, any route that proxies the stream needs `export const maxDuration = 300` (or your plan's max) or the platform cuts the stream mid-run.
- Runs cost real money from the workspace's prepaid credits. New workspaces get starter credits. Cap any run with `budgetCents`.

## Step 1: Interview the user

Ask in one batch; skip anything the repo already answers:

1. What's your product, and what should the agent do for your users? (This becomes the agent's `context`.)
2. Where should it run: inside this app (detect the framework yourself), or a standalone script first?
3. Which abilities does it need: web search/fetch · a real browser · files and shell · documents (Office/PDF) · media generation · GitHub · your internal APIs?
4. Should it pause for human approval or questions before consequential actions?
5. Is strict data handling (verified zero-data-retention inference routes) required? Default is standard.
6. Rough budget per run? Default cap 500 cents.

Success: you can state the `context` sentence, the band, the capabilities object, and where the route goes. Restate the plan in two sentences and get a yes before writing code.

## Step 2: Map answers to config

| Answer | Config |
|---|---|
| web search/fetch | `capabilities: { web: true }` |
| real browser | `capabilities: { browser: true }` |
| files, shell, documents | `workspace: { type: 'managed' }` (documents are on by default; add `shell: true` for commands) |
| human approval / questions | `capabilities: { userInteraction: true }` |
| media generation | `capabilities: { media: true }` (or per-modality; see docs) |
| GitHub | register a connection first: https://novoagents.ai/docs/api/github-connections |
| internal APIs | register MCP/HTTP tools first: https://novoagents.ai/docs/concepts/adapters |
| strict data handling | check `novo.bands.list()` availability, then `dataHandling: 'strict'` on the run |

Leave `subagents` at its default (on): delegation is Novo's job, not yours.

Success: every interview answer maps to a config field above, or you've told the user what needs registering first.

## Step 3: Install and key

1. Install `novoagents` with the project's package manager.
2. Add `NOVO_AGENTS_API_KEY=` to the env file (`.env.local` or equivalent) and tell the user: create a key at `https://app.novoagents.ai` → **api keys** (it is shown once) and paste it into that file themselves.
3. Confirm the env file is gitignored.

Success: the env var is wired and never appears in code, chat, or commits.

## Step 4: Create the agent once

Agents are configuration, not conversations: create one and reuse its id across threads. Write `scripts/novo-setup.ts`:

```ts
import { NovoAgents } from 'novoagents';

const novo = new NovoAgents();
const agent = await novo.agents.create({
  context: '<the context sentence from the interview>',
  band: 'core',
  // capabilities / workspace from Step 2
});
console.log(agent.id); // ag_...
```

Run it once (`npx tsx scripts/novo-setup.ts`), store the printed id as `NOVO_AGENT_ID` in the env file.

**Checkpoint:** creating an agent is free; runs bill credits. Confirm with the user before Step 5's live run.

## Step 5: Add the route

Next.js App Router (adjust path to the project):

```ts
// app/api/agent/route.ts
import { NovoAgents } from 'novoagents';

export const maxDuration = 300;
const novo = new NovoAgents();

export async function POST(request: Request): Promise<Response> {
  const { input } = (await request.json()) as { input: string };
  const thread = await novo.threads.create({
    agentId: process.env.NOVO_AGENT_ID!,
  });
  const stream = await novo.threads.stream(thread.id, {
    input,
    budgetCents: 500,
  });
  return stream.toResponse(); // SSE to the browser
}
```

Express: `stream.toResponse()` is a Web `Response`. Pipe it with `Readable.fromWeb(response.body)` and copy its headers. Hono/Bun/Deno accept the `Response` unchanged. Standalone script: skip the route and use Step 6's shape directly.

Success: the project typechecks.

## Step 6: Verify with a live run

```ts
const thread = await novo.threads.create({ agentId });
const stream = await novo.threads.stream(thread.id, {
  input: 'Say hello in five words.',
  budgetCents: 100,
});
const result = await stream.streamToCompletion();
console.log(result.text, `cost: ${result.usage.costCents.toFixed(2)}¢`);
```

Success: text prints and a nonzero cost is reported. Failures: 401 → key missing/wrong (check the env var name and the `novo_ag_` prefix); stream ends with no terminal → you consumed raw EOF instead of `streamToCompletion()`.

## Step 7: Consume safely in the UI (if there is one)

- Done means the `data-novo-terminal` chunk, never EOF.
- The wire is AI SDK `UIMessageChunk`s plus `data-novo-*` extensions. Branch on `data-novo-*` first if you validate chunk types.
- For runs longer than one connection, resume with the `Last-Event-Id` header: https://novoagents.ai/docs/guides/reconnect-a-dropped-stream

Success: the UI code branches on `data-novo-*` chunk types and treats only the terminal chunk as completion.

## Step 8: Report back

Tell the user: the agent id, files you created or touched, the smoke run's cost, and the three docs to read next (https://novoagents.ai/docs/concepts/capabilities, https://novoagents.ai/docs/concepts/streaming, https://novoagents.ai/docs/concepts/human-in-the-loop).

Success: the user can point at the agent id, the route file, and the smoke run's printed cost.

## When something fails

- `browser_runtime` → the SDK got into client code; move it behind a server route.
- 401 → wrong or missing key; verify `NOVO_AGENTS_API_KEY` and the `novo_ag_` prefix.
- Stream stops with no terminal chunk → reconnect or use `streamToCompletion()`; on Vercel also check `maxDuration`.
- `409 thread_busy` → the thread has a running turn; the message queues as a follow-up.
- Full error catalog: https://novoagents.ai/docs/errors. The machine-readable docs index is https://novoagents.ai/llms.txt.

Do not invent capability flags: the exact agent schema is https://novoagents.ai/docs/api/agents.
