novo agents
get started

Set up with your coding agent

You don't have to wire Novo in by hand. Copy the setup skill below into Claude Code, Cursor, or whatever you build with. Your coding agent reads it, asks you a few questions, and integrates Novo end to end.

The skill walks your agent through the whole path: install the SDK, configure the API key (without ever pasting it into chat), create your agent, add a streaming route, and verify a live run with its cost. It works pasted directly as a prompt, or saved into your repo, for example as .claude/skills/novo-setup/SKILL.md.

raw file: novoagents.ai/setup.md
Before you paste: create an API key in the console under api keys and put it in your env file yourself. The skill tells your coding agent to never ask for the key in chat.
setup-skill.md
---
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.