# naïve — Full Content Index
> Full text of every blog post, formatted for LLM ingestion. How agent-run companies work, what the primitives underneath them do, and what we learned building Vetta.
Source: https://usenaive.ai/llms-full.txt
Last generated: 2026-09-13T11:10:18.777Z
---
# Vetta: the most efficient managed agent for long horizon tasks
URL: https://usenaive.ai/blogs/introducing-vetta
Author: Dennis Zax
Published: 2026-08-17
Updated: 2026-09-07
Tag: Launch
> Same model, same tasks, only the stack changed: Vetta finishes a task 2.7x cheaper than the next-best harness while completing more of them. We built every layer of the stack to make that number possible.
## TL;DR
- Vetta is a managed agent for long horizon work: tasks measured in hours, where the agent keeps its own state and finishes without a person in the loop.
- On Long Horizon Terminal Bench, with the model held fixed, Vetta costs $0.2232 per task against $0.5995 for Claude Code at the same latency. 2.7x cheaper, at a higher resolve rate.
- We built and optimize every layer: the harness, the serverless runtime, completion windows, sandboxes, budgets, and identity. The efficiency is structural, not a discount.
- Vetta is the engine under Naïve: every agent a company template provisions with naive up is a Vetta agent, and every primitive it reaches for is a Vetta primitive.
- Vetta is in early access today at vetta.sh.
Intelligence stopped being the bottleneck for autonomous work. Economics did not. A model that can finish a multi-hour task is useless in production if the run costs more than the work is worth, or if the machine hosting it bills for every hour the agent spends waiting.
Vetta is our answer: a managed agent built for long horizon tasks, where every layer of the stack is optimised for one number, the most intelligence per dollar spent.
## The engine under Naïve
Naïve is a company you run; Vetta is what runs it. Every [company template](/templates) on this site is a repository with a `naive.config.ts` that declares a team of agents, and `naive up` provisions that team into your organization as Vetta agents. The media channel that posts every day, the agency that answers the inbox, the newsletter that ships on schedule: each is a set of Vetta sessions with a budget, a completion window, an isolated computer, and an identity of its own.
That is also why the [primitives](/primitives) on this site map so cleanly to what follows. Runtime, Identity, Cloud, and Trust are Vetta's layers with Naïve's names on them. Everything in this post about cost is therefore a statement about what it costs to run a Naïve company, and if you want the engine without the company, it is available directly at [vetta.sh](https://vetta.sh).
## Where the money goes
A long horizon task pays three bills from start to finish. It pays for every model call. It pays for the machine hosting the loop, including every hour the agent spends waiting on a schedule, a CI run, or a reply. And it pays a quieter third bill: the person who has to hand over credentials, approve access, and watch the run.
Most stacks only control one of those bills, because they only own one layer. We built all of them: the harness, the serverless runtime underneath it, the orchestration and self-improvement layers on top, and the surfaces you drive it from.
The model is a commodity: 20+ providers plug into the same routing layer, and every arm in the benchmarks below ran the same model. What decides the cost of a finished piece of work is everything wrapped around the model, and we own all of it. Vetta runs multiple harnesses out of the box today, and our own harness, tuned against this exact runtime, is next.
Each layer below exists to cut one of the three bills: the completion window prices the model call, the runtime and its sandboxes eliminate the idle machine, and identity removes the person from the loop. First, the proof that it adds up.
## The proof
We publish results on the [benchmarks page](https://vetta.sh/benchmarks), computed from the same database our billing runs on. Two suites, two different axes.
[**Long Horizon Terminal Bench**](https://vetta.sh/benchmarks/long-horizon-terminal-bench) holds the model fixed (`glm-5.2` in every arm) and swaps the harness across five contenders and three completion windows. This is the live efficiency leaderboard from that page, dollars per attempted task first:
The result repeats off the terminal too: on [SWE-Bench Pro](https://vetta.sh/benchmarks/swe-bench-pro), Vetta is cheapest per solved task on all three models it was run against.
One benchmark could be a lucky configuration. The same result on two suites, across two different axes, is a property of the stack. So where does the 2.7x come from? Take the three bills in order.
## Bill one: the model call
Latency is a price you choose per request. The completion window is one field with three settings: `immediate` answers now, `priority` soon, `loose` eventually. Same model, same weights, different tariff. It defaults on the agent and overrides per session:
```typescript title="One field decides the tariff" {4}
const session = await vetta.sessions.create({
agent_id: agent.id,
message: "Reconcile yesterday's refunds against the ledger",
window: "priority", // immediate | priority | loose
});
```
Isolating the window in the benchmark data shows what it is worth. Dollars per completed task, Vetta against the cheapest competing harness in the same window:
An agent a person is watching should be on `immediate`. An agent that starts at midnight and delivers at nine has no reason to be, and pays materially less for the same model.
That settles what a model call costs. But a long horizon agent spends most of its wall-clock time not calling the model at all, which is the second bill.
## Bill two: the idle machine
The Vetta Runtime is where most of the cost behaviour on long horizon work comes from, and it is serverless from the ground up: the agent loop runs on durable objects, not on a machine you rent by the hour.
**The loop is an alarm, not a call stack.** Each cycle: wake on a trigger, take one bounded turn, commit the transcript and cursor to durable storage, sleep. Between turns there is no process to pay for. A session waiting on a nightly schedule or a human reply costs only what its stored state occupies, and a crash loses at most one turn, never the run.
That shape is what changes the hosting bill at fleet scale. From [Naïve Lab](https://usenaive.ai/lab), the modelled cost of hosting one million agents for a month, over published rate cards:
**Every call is priced before it is made.** Budgets are structural, not a report you read the next morning. An agent cannot be created without one; a call that would breach the cap is refused before it runs.
```typescript title="A budget is a constructor argument, not a report" {4}
const agent = await vetta.agents.create({
name: "nightly-triage",
model: "glm-5.2",
budget: { cap_micro_usd: 50_000_000, max_task_micro_usd: 5_000_000, period: "month" },
});
```
**Cost-efficient sandboxes.** Each session gets an isolated computer: a paused micro-VM meters its stored disk and no vCPU at all, so an agent that works for twenty minutes across an eight hour window pays for twenty minutes of compute.
```typescript title="A paused sandbox bills storage only" {4}
const computer = await vetta.computers.create({ vcpu: 2, memory_mb: 2048, browser: true });
await vetta.computers.exec(computer.id, "pnpm test");
await vetta.computers.pause(computer.id); // no vCPU metered until resume
```
**V8-isolate sandboxes (coming soon).** The next runtime tier drops the machine entirely: an in-isolate shell plus a WASM registry runs roughly 95% of typical ops commands inside a V8 isolate, no real computer needed. On our isolated-vm backend a sandbox goes from create to first execution in about 2.79 ms and holds resident state in about 1.2 MB of RAM per agent, which is what makes fleets of mostly-idle agents economically boring. This is [ongoing research at Naïve Lab](https://usenaive.ai/lab).
With the model call priced and the idle machine gone, one cost is left, and it is the one nobody meters: the human who has to stay in the loop.
## Bill three: the person in the loop
An agent that needs a person to lend it their logins, share a bot token, or approve every credential is not unattended, it is supervised. Vetta removes that dependency by giving the agent someone to be. An identity is a named persona with real endpoints: verified domains, inboxes, phone numbers, and two pieces built for unattended operation.
**Passport.** Third-party apps the identity has authorized over OAuth. The agent connects to real services as its persona, not as a shared bot token.
**Vault.** A write-only credential store. Values are injected at the network boundary; the agent uses secrets it can never read, and no route ever returns a credential value.
```typescript title="One persona, a real OAuth grant, a sealed credential" {14}
const ava = await vetta.identities.create({
name: "Ava Sales",
description: "Outbound SDR persona for the growth team",
});
await vetta.identities.attach(agent.id, ava.id);
// Passport: the persona authorizes a real service over hosted OAuth
await vetta.connections.connect({ auth_config_id: crmAuth.id, identity: ava.id });
const vault = await vetta.vaults.create({ display_name: "ava-vault", identity_id: ava.id });
await vetta.vaults.credentials.create(vault.id, {
kind: "env_var",
key: "STRIPE_KEY",
value: stripeKey, // travels once, sealed server-side; no route ever returns it
});
```
Every identity action is gated by policy, so what a persona may do is declared, not hoped. Access is set up once, then the agent runs for months without anyone lending it a login.
That is the whole argument. Model calls priced per request, idle time metered at storage, and no standing human cost: three bills, each cut structurally. The 2.7x on the leaderboard is what they add up to.
## What it means for a Naïve company
A Naïve company is a fleet of these agents that has to stay economically boring for months, not a single run that has to impress once. The three bills above are the reason the templates can run unattended: a channel that wakes on a schedule and sleeps between posts pays for storage while it waits, an agency inbox that nobody is watching can answer on `priority` instead of `immediate`, and every agent in the team carries its own budget, so a bad day for one cannot become a bad month for the company.
The same is true of the identity layer. When a template gives an agent a [domain](/primitives/domains), an [inbox](/primitives/email), a [vault](/primitives/vault), and a set of connections, those are the Vetta identity primitives described above, provisioned for you by `naive up`. You never lend a Naïve agent your own login, and nothing in your organization can read a credential it was given.
## Start building
Vetta is in early access. Create an agent with a budget, pick a window, give it work.
If you want the company rather than the engine, pick a [template](/templates) and run `naive up`; the agents it provisions are the ones on the leaderboard above. Everything else about Vetta, from the API reference to the studio, lives at [vetta.sh](https://vetta.sh).
The models will keep getting better. The bill for running them is ours to fix.
---
# Introducing the computer: a micro-VM for every agent session
URL: https://usenaive.ai/blogs/introducing-sandbox
Author: Dennis Zax
Published: 2026-08-02
Updated: 2026-09-07
Tag: Launch
> Every Vetta session gets an isolated Linux micro-VM with a shell, a filesystem and an optional real browser. It pauses between turns, so an idle agent costs storage only. Here is how it works and what it costs.
## TL;DR
- Every session runs on a computer: a disposable Linux micro-VM with a real filesystem, a bash shell and an optional managed browser, provisioned by Vetta.
- The computer is bound to the session's wake/sleep cycle. It runs while the agent takes a turn and pauses between turns, so an agent waiting on a schedule or a reply is metered for nothing but stored disk.
- There is no creation fee. Running time is metered per second on provisioned vCPU and memory; a full hour of a session's 1 vCPU / 1 GiB computer is $0.0666.
- Snapshots checkpoint a running VM without stopping it, forks boot new computers from a checkpoint, and persistent volumes carry disk and paused memory across park and sleep.
- Outbound networking is open by default with cloud metadata and internal ranges blocked; allow_outbound: false is a complete, immutable kill-switch. Nothing reaches in.
- A computer is auto-destroyed when the organization's balance hits zero or when it exceeds its max runtime (12 hours by default), so a forgotten box cannot drain a budget.
## Somewhere to run the code
Agents write code constantly, and the moment they do they need somewhere to run it. Not a deployment or a cluster. A shell, a disk, and a way to look at the result. Every Vetta session gets exactly that: a **computer**, a disposable Linux micro-VM that Vetta provisions and operates, wired into the same durable runtime that runs the harness.
This post is about that machine: what is inside it, how it maps onto the session's turn cycle, and what it costs. The [Computer](/primitives/computer) and [Sandbox](/primitives/sandbox) primitive pages summarize the surface; the [computer reference](/docs/computer/index) has every field.
## One computer per session, paused between turns
The runtime runs one bounded harness turn, commits the transcript, and sleeps. The computer's lifecycle is bound to that same cycle:
- While the agent takes a turn, the computer is **running** and metered per second on the vCPU and memory it was provisioned with.
- Between turns, waiting on a schedule, a human reply, or a long external job, the computer is **sleeping**. No vCPU and no memory are metered.
- The next shell command or file operation **wakes it automatically**, filesystem intact, in roughly 300 ms.
That is the first of the runtime's efficiency levers: most wall-clock time on a long task is spent waiting, and waiting is nearly free. An agent that works for twenty minutes across an eight-hour window pays for twenty minutes of compute.
A computer moves through four states. Only one of them costs compute.
| State | What it means | Billed |
| --- | --- | --- |
| `running` | Live, during a turn or a direct exec | Per-second vCPU and memory |
| `sleeping` | Idle, disk preserved, wakes on the next op | No compute |
| `parked` | Held out of rotation; ops are rejected until an explicit resume | No compute |
| `destroyed` | Terminal; disk and ephemeral state torn down | Nothing further |
You rarely park manually; the runtime sleeps and wakes the computer around each turn. Only cold boots pay the full provisioning cost, about two seconds from a clean image and about three from a snapshot.
## A real Linux box, not a restricted runtime
Every computer is a full Linux userland with an explicit slice of resources set at create time. There are no fixed instance classes: raise vCPU, memory, and disk per workload up to the ceilings. A session's own computer is provisioned at 1 vCPU / 1 GiB; the defaults below apply when you create one yourself.
| Resource | Default | Range |
| --- | --- | --- |
| vCPU | 2 | 1 to 16 |
| Memory | 4096 MiB | 128 to 65536 MiB |
| Disk | service default | whole GiB, up to 100 |
Inside a session the agent reaches the machine through built-in tools. Each one is a governed action, quoted against the agent's [budget](/primitives/budgets) before it runs and subject to the agent's [policy](/primitives/approvals) of `allow`, `ask`, or `deny`.
### Shell
The `bash` tool runs a command as `bash -c` in a non-login shell and returns buffered `stdout`, `stderr`, and an `exit_code` once the process exits or `timeout_ms` elapses. You can set a `cwd` for the call, and `env` for the call only; nothing persists to the next command. Output is capped at 262,144 characters each for stdout and stderr. It is a single request and response, not a stream, so a long-running command should write progress to a file that the agent reads back. Details: [Shell](/docs/computer/shell).
### Filesystem
Five operations cover the disk: `read`, `write`, `list`, `mkdir`, and `remove`. They take absolute paths, move binary data with `encoding: "base64"`, and are capped at 4 MiB per file per read or write. `list` returns immediate children including dotfiles; `remove` is recursive and refuses `/`. Scratch work lives here. Anything the agent should hand back is promoted with `publish_file` to [Files](/primitives/files), which is organization-scoped and outlives the sandbox. Details: [Filesystem](/docs/computer/filesystem).
### Browser
A computer can have an optional **managed browser** attached. It is a real browser, driven through `goto`, `click`, `type`, `extract`, and `screenshot`, and every action re-validates the current page URL against a required `allowed_domains` list, so a redirect off the allow-list is blocked. Private, loopback, and cloud metadata hosts are always blocked regardless of the list. A guard refuses credential-shaped input through `type`, write-looking actions are blocked unless `allow_writes` is set, and screenshots land in Files as a `fil_` id rather than as bytes in the transcript. Sessions default to a 15-minute timeout and can run up to six hours. The browser carries no charge of its own; what a browsing agent pays for is the tokens it spends driving it. Details: [Browser](/docs/computer/browser).
## Networking: out, not in
A computer has general outbound access by default, so the agent can install packages, call APIs, and fetch data without configuration. The filter on the egress path is not a general private-address block, so here it is exactly: cloud metadata (`169.254.0.0/16`), `10.0.0.0/8`, and `172.16.0.0/12` are unreachable from `bash` and tools; `192.168.0.0/16` is not reliably blocked; the sandbox's own loopback is reachable, because it is the agent's own machine. Do not treat "RFC1918 is blocked" as a security boundary for the shell.
The one control that is a boundary is `allow_outbound: false` at create time: a complete, verified kill-switch for all outbound traffic, immutable for the life of the computer. The browser is different. Its private, loopback, and metadata block is enforced by us on every action and does hold.
Inbound is the other direction, and today there is none: no port-preview surface, so a sandbox reaches out and nothing reaches in. Coming soon, all sandbox HTTPS egress will be pinned through Vetta's egress proxy so the [vault](/primitives/vault) can substitute secrets at the network boundary; the sandbox, the model, and your logs only ever see opaque placeholders. Details: [Networking](/docs/computer/networking).
## Snapshots, forks and volumes
State on a computer is ephemeral by default: a destroy tears it down. Three mechanisms make it outlive a single machine.
A **snapshot** is a durable checkpoint taken without stopping the VM. The computer keeps running while its disk is captured, so the agent can mark a known-good baseline mid-task. A **fork** creates a new computer from a snapshot, with its own lifecycle and billed like any fresh create. Prepare one baseline, then fork several computers to try different approaches in parallel. A **persistent volume**, attached at create time, keeps the computer's disk and its paused memory alive across park and sleep. Volumes protect state across the idle cycle, not across a destroy; anything you must keep permanently belongs in Files.
| Concept | Stops the VM? | Cost while idle | Survives destroy? |
| --- | --- | --- | --- |
| Snapshot | No | Snapshot storage | Yes, it is a separate artifact |
| Fork | No (new computer) | Per its own lifecycle | It is its own computer |
| Persistent volume | No | Storage only | No |
| Pause / resume | Yes | Storage only | No |
Details: [Snapshots and volumes](/docs/computer/snapshots-and-volumes).
## Driving a computer directly
Sessions provision their computer for you. You can also drive one from the [CLI](/docs/cli/computer) or the [TypeScript SDK](/docs/sdk/computers).
```bash title="The lifecycle from the CLI"
# Create with explicit resources and a browser (--browser requires --allowed-domains)
vetta computer create --name box --vcpu 2 --memory-mb 4096 --browser --allowed-domains "*.example.com"
# Run commands, read back the buffered result
vetta computer exec box -- "ls -la /workspace"
# Files: operation first, then the computer
vetta computer fs write box --path /workspace/notes.txt --content "hello"
vetta computer fs read box --path /workspace/notes.txt
# Checkpoint without stopping, then boot a fork from the returned snapshot_id
vetta computer snapshot box
vetta computer create --name box-2 --snapshot
# Hold it for free, wake it later, or tear it down
vetta computer pause box
vetta computer resume box
vetta computer delete box
```
The SDK is one method per route.
```typescript title="The same lifecycle, typed"
import { randomUUID } from "node:crypto";
import { createClient } from "@usenaive-sdk/vetta";
const client = createClient({
baseUrl: "https://api.vetta.sh",
apiKey: process.env.VETTA_API_KEY!,
fetch: globalThis.fetch,
idempotencyKey: () => randomUUID(),
});
const box = await client.computers.create({ name: "scratch", vcpu: 2, memory_mb: 4096 });
await client.computers.fs(box.id, "write", { path: "/workspace/main.py", content: "print(40 + 2)\n" });
const result = await client.computers.exec(box.id, "python3 /workspace/main.py", 30_000);
// { stdout: "42\n", stderr: "", exit_code: 0, duration_ms: 412 }
const snapshot = await client.computers.snapshot(box.id);
const fork = await client.computers.create({ name: "variant-b", snapshot_id: snapshot.snapshot_id });
await client.computers.pause(box.id); // no vCPU or memory metered until resume
await client.computers.delete(fork.id);
```
## What it costs, exactly
There is no creation fee. A running computer is metered per second on its provisioned vCPU and memory, booked against the agent's budget at the end of each turn and drawn from the organization's prepaid USD balance.
| Resource | Per second | Per hour |
| --- | --- | --- |
| vCPU | $0.000014 | $0.0504 |
| Memory (per GiB) | $0.0000045 | $0.0162 |
A session's computer is 1 vCPU / 1 GiB, so a full hour of running time is $0.0504 + $0.0162 = $0.0666; a computer you create at the 2 vCPU / 4 GiB default is $0.1656. A sleeping or parked computer meters no vCPU and no memory. Compute spend appears as the `computer` component of the agent's spend breakdown and as a line item on the organization ledger, reported on the wire in integer micro-USD.
Two failsafes bound the downside. A computer is **auto-destroyed** when the organization's balance is exhausted, or when it exceeds its max runtime, `max_duration_hours`, 12 hours by default. That ceiling is wall-clock from create to destroy, not billed seconds, so a computer that sleeps most of the window still hits it on schedule. An optional `idle_timeout_minutes` pauses a computer to storage-only after it sits idle. Every non-destroyed computer counts toward the organization's concurrency cap, 25 computers and 10 browser sessions by default, raised on request. Details: [Limits and billing](/docs/computer/limits-and-billing).
## What is next
Everything above describes the micro-VM that ships today. A fully virtualized **V8 isolate** mode (`mode: "isolate"`) is coming soon for the lightweight majority of agent work, with near-instant boot and near-zero idle. Vault secret substitution on sandbox egress is also coming; today vault injection is live only on the MCP connector path, where credentials never enter the sandbox.
## Get started
Create an agent with a budget and give it work; its session provisions the computer. Or provision one yourself with `vetta computer create`. The full surface is in the [computer reference](/docs/computer/index), and the [Vetta launch post](/blogs/introducing-vetta) covers how the computer fits alongside completion windows and budgets.
## FAQ
### What is a Vetta computer?
A computer is the sandbox a session works in: a disposable Linux micro-VM with a filesystem, a bash shell and an optional managed browser. Vetta provisions and operates it; the agent reaches it through built-in tools, and you can drive it directly from the CLI, SDK or API.
### What does an idle computer cost?
Only running time is metered. Between turns the runtime sleeps the computer, and a sleeping or parked computer meters no vCPU and no memory. Storage is the only thing you can pay for while a computer waits, and the boot disk sits inside the allowance the computer ships with.
### How is a computer billed while it runs?
Per second on its provisioned vCPU and memory: $0.000014 per vCPU-second and $0.0000045 per GiB-second, with no creation fee. Compute spend shows up as the computer component of the agent's spend breakdown and as a line item on the organization ledger.
### What is the difference between a snapshot, a fork and a volume?
A snapshot checkpoints a running computer's disk without stopping it. A fork is a new computer created from a snapshot, billed like any fresh create. A persistent volume keeps disk and paused memory alive across park and sleep, but not across a destroy.
### Can the agent expose a port from the computer?
Not today. A computer has outbound access but there is no port-preview surface, so nothing reaches in. Anything the agent should hand back is promoted with publish_file to Files, which outlives the sandbox.
---
# What is an AI-native cloud? The hosting slice an agent operates
URL: https://usenaive.ai/blogs/what-is-an-ai-native-cloud
Author: Dennis Zax
Published: 2026-07-30
Updated: 2026-09-07
Tag: Guide
> An AI-native cloud is the hosting and data slice an agent can provision by API: apps, Postgres, storage, functions, auth, compute, queues and sandboxes, gated by allow / ask / deny policies and a USD budget.
## TL;DR
- An AI-native cloud is the part of a cloud whose primary operator is an agent, not a person in a console: hosting and data primitives it can create, ship to and query through one API.
- On Naïve that slice is the Cloud category on /primitives: Apps, Database, Storage, Functions, Auth, Compute, Queue and Sandbox, next to the computer every session already runs in.
- Governance lives at the tool boundary, not in the prompt: every cloud action resolves to allow, ask or deny, an ask freezes the session until someone approves, and a USD budget prices calls before they run.
- Secrets stay write-only. App secrets and vault credentials are set once and never returned by any route, so the agent uses them without ever reading them.
- It is a slice of AI agent infrastructure, not a hyperscaler replacement. Provisioning is opinionated and agent-shaped: a real URL, a managed database, a queue, a disposable box.
Search for **AI cloud** and the results are a mess: GPU marketplaces, "AI platforms", and every hyperscaler's landing page with a chatbot bolted on. If you build agents, the useful definition is narrower, and it is a slice of [AI agent infrastructure](/blogs/what-is-ai-agent-infrastructure), not the whole stack.
An **AI-native cloud** is cloud infrastructure whose primary operator can be an agent. The agent creates the app, ships the build, provisions the database, queues the work and tears it all down again, through the same API surface and under the same policy and budget as every other action it takes. A person is still in the loop, but at the approval boundary, not in a console clicking through wizards.
This post absorbs our older launch posts for apps, backend, compute and queue. They described the same idea in a vocabulary we have since retired; this is the current version.
## AI cloud vs. AI-native cloud
| Phrase people say | What they usually mean | What an agent actually needs |
| --- | --- | --- |
| "AI cloud" | GPUs, model hosting, or a console with a copilot | Durable apps and data the agent ships and runs |
| "Cloud for AI" | Training and inference capacity | Provisioning it can call itself, metered against its own budget |
| **AI-native cloud** | Rarely said; this post | Provision by API, one budget, one policy, credentials it never sees |
If the operator is still a human in a console, you have cloud *near* AI. If the operator can be an agent calling one API without long-lived provider keys in its context, you have an agent-shaped cloud surface.
## What has to be true
Four properties separate an AI-native cloud from a normal one with an SDK.
1. **Provisioning is a tool call.** Creating an app, running a migration or pushing a deployment is something the agent does mid-session, not something a person does before the session starts.
2. **Policy is enforced at the tool boundary.** Every capability resolves to `allow`, `ask` or `deny` before it runs. The model can attempt anything; the runtime decides what actually executes.
3. **Money is structural.** A budget in USD is a constructor argument on the agent, and calls are priced before they are made. Overspend is refused, not discovered on an invoice.
4. **Secrets are write-only.** The agent uses a database connection string, an API key or an OAuth token without any route ever returning the value to it.
On Naïve these are not features of the cloud primitives specifically. They are how [policies](/docs/concepts/policies), [budgets](/docs/concepts/budgets) and the [vault](/primitives/vault) work for every primitive an agent holds. The cloud slice just inherits them.
## The eight cloud primitives
The Cloud category on [/primitives](/primitives) is the hosting and data slice. Eight primitives, plus the computer every session already runs in.
**[Apps](/primitives/apps).** A hosted web application on a real URL. An app is an organization-level resource: it does not belong to an agent, it outlives any session, and by default every agent in the organization can operate it (narrow that per agent with the `apps` tool's `allowed_apps`). Two types: `frontend_only`, which is `active` at birth, and `fullstack`, which is `provisioning` until its managed database is ready and then has the connection string pushed into its environment automatically. Creation is idempotent on the name. A deployment is one request carrying a map of file paths to contents; the build advances `queued` to `building` to `ready` on read. Full detail in the [Apps API](/docs/api/apps).
**[Database](/primitives/database).** The managed Postgres behind a `fullstack` app. The agent runs SQL against it through the app's own `db/query` route, so schema, CRUD and migrations all happen against the database the app already uses, with no second connection to manage.
**[Storage](/primitives/storage).** File buckets on the app the agent deployed. This is distinct from [Files](/docs/capabilities/files), the organization-scoped store where a session's own artifacts land when the agent calls `publish_file`. Scratch work stays in the sandbox; deliverables go to Files; app assets go to Storage.
**[Functions](/primitives/functions).** Backend logic at the edge, for the parts of an app that need to run server-side without standing up a second platform.
**[Auth](/primitives/auth).** Sign-in for the apps the agent builds, with email, Google and GitHub. The agent ships a product with users, not a static page.
**[Compute](/primitives/compute).** Containers and long-running workers that scale on demand, including GPU workloads, for the jobs that do not fit inside a request.
**[Queue](/primitives/queue).** Durable message queues for agent pipelines: work handed off durably rather than held in a process that has to stay running.
**[Sandbox](/primitives/sandbox).** A disposable code sandbox the agent can run, checkpoint, fork and throw away. It is the same machine as the computer below, created on demand for a task: a snapshot checkpoints it without stopping it, and a [fork](/docs/computer/snapshots-and-volumes) boots a new computer from that checkpoint, billed like any fresh create. We wrote about it in [introducing the computer](/blogs/introducing-sandbox).
### The computer underneath
Every session on Vetta already runs inside a [computer](/primitives/computer): a real Linux micro-VM with a filesystem, a shell and an optional browser scoped to an allowlist of domains. It defaults to 2 vCPU and 4 GiB of memory and can be sized up to 16 vCPU, 64 GiB and a 100 GiB disk. It pauses when the agent sleeps and resumes on the next turn, so idle time meters storage, not compute. That is what makes the cloud slice affordable to operate from an agent: the operator costs nothing while it waits for a build or an approval. Details in [Computer](/docs/capabilities/computer) and the [sandbox lifecycle reference](/docs/computer/sandbox).
## Apps get their tools back
The part of this that is genuinely new, and that the old launch posts could not describe, is that a shipped app can hand tools back to the agents that operate it. A `fullstack` app that serves an MCP endpoint declares the path once:
```typescript title="One app, its own tools"
const app = await vetta.apps.create({ name: "storefront", type: "fullstack", mcp: "/mcp" });
```
Setting `mcp` mints an opaque bearer token and pushes it into the app as the write-only secret `VETTA_MCP_TOKEN`. From then on, every turn of every agent that can access the app is offered the endpoint's tools as `.`, for example `storefront.list_orders`, under the agent's normal `allow` / `ask` / `deny` policy. The platform injects the bearer server-side; the token never reaches the sandbox or the transcript. An unreachable endpoint contributes no tools that turn and never fails the run.
That closes the loop an AI-native cloud is supposed to close. The agent ships the app; the app becomes part of the agent's toolset; the same policy governs both. See [Tools from your apps](/docs/capabilities/tools#tools-from-your-apps).
## Governance: allow, ask, deny, budget
Handing an agent a raw cloud IAM role fails in familiar ways: keys leak into logs and prompts, always-on resources dominate the bill, and revoke means chasing roles by hand. The fix is not a better prompt. It is enforcement the model cannot reach.
**Policies.** Every tool an agent holds, including the cloud primitives and the `.` tools above, resolves to one of three permissions. `allow` runs without confirmation. `ask` emits a `tool.confirm` event and pauses the tool; the session goes idle with `stop_reason: "awaiting_approval"` until a person confirms or rejects it, and the decision records who answered. `deny` means the tool is not offered to the model at all. Policies resolve from an organization default with a per-agent, per-tool override, and a held tool consumes no budget while it waits.
```typescript title="Destructive tools ask; the rest run"
const agent = await vetta.agents.create({
name: "ops",
model: "zai-org/GLM-5.2-FP8",
harness: "pi",
budget: { capMicroUsd: "50000000", maxTaskMicroUsd: "5000000", period: "month" }, // $50 / $5
tools: {
default_config: { permission: "allow" },
configs: {
bash: { enabled: true, permission: "allow" },
publish_file: { enabled: true, permission: "allow" },
"storefront.refund_order": { enabled: true, permission: "ask" },
},
},
});
```
Approving from the CLI is one command:
```bash
vetta session confirm --session $SID --tool-call $CALL_ID
```
An `ask` does not hold forever. `ask_timeout_seconds` bounds the wait and `on_timeout` decides what happens when it elapses; the default is `deny`. For the full pattern read [how to add human approval to an AI agent](/blogs/how-to-add-human-approval-to-an-ai-agent).
**Budgets.** An agent cannot be created without one. Before every model call or priced tool call, a pre-flight gate quotes an upper bound and checks it against the organization balance, the agent's period cap, the per-task ceiling and any session budget, in that order. A call that would breach any of them is refused and a `budget.exceeded` event is emitted so the agent can wrap up cleanly. One honest caveat: compute is metered from what actually ran and booked afterwards, so a period can finish slightly past its cap. It is on the ledger either way. See [budgets](/primitives/budgets).
**Secrets.** App secrets are write-only environment variables: the value goes to the app's runtime and never comes back on any read, and the list returns names and timestamps only. Vault credentials behave the same way. Neither an app secret nor a vault credential is returned by any route once written, which is the property that makes it safe to let an agent set them.
## Declaring the cloud instead of clicking it
Cloud resources on Naïve are declared in a `naive.config.ts` and reconciled with `naive up`. An `apps` entry names the app, its type, the directory to ship and the environment it needs, with secret values read from your shell at apply time so they never sit in the file:
```typescript title="naive.config.ts (apps fragment)"
apps: [
{
name: "dashboard",
type: "fullstack",
deploy_dir: "dist",
mcp: "/mcp",
env: { PUBLIC_URL: "https://dashboard.example", API_KEY: { from_env: "NAIVE_API_KEY" } },
},
],
```
`naive up` upserts every declared resource by name and reports each as `created`, `updated`, `unchanged`, `deleted` or `refused`. Re-running is always safe: the deploy directory is hashed and an unchanged tree is not uploaded, and nothing is deleted by omission. Every app records the project that created it, and an apply that touches an app owned by another project stops rather than overwrites. The full reference is the [naive CLI](/docs/cli/naive).
## Bottom line
An AI-native cloud is not a synonym for "we bought GPUs", and it is not the whole of Naïve. It is the hosting and data slice whose primary user can be an agent: provisioned by tool call, metered against a budget the agent cannot raise, governed by allow / ask / deny at the boundary, and fed secrets it uses but never reads. Start with one app, one small budget and `ask` on anything destructive. Widen from there.
## FAQ
### What is an AI-native cloud?
A cloud layer built so an agent can be the operator. Apps, databases, storage, functions, auth, compute, queues and sandboxes are created through one API, under the agent's own allow / ask / deny policy and USD budget, with credentials the agent uses but never reads.
### Is an AI-native cloud the same as AI agent infrastructure?
No. Agent infrastructure covers the agent itself (model, prompt, tools, skills, budget, harness), sessions, identity, money, policies and audit. The AI-native cloud is only the hosting and data slice of it.
### Does Naïve replace AWS, GCP or Azure?
No. Naïve exposes an opinionated, managed set of cloud primitives an agent can call. Upstream providers can still run underneath. The product boundary is what an agent needs: provision, ship, query, meter, approve, revoke.
### Can an agent provision infrastructure safely?
Only if enforcement sits outside the prompt. On Naïve every tool an agent holds resolves to allow, ask or deny before it runs, an ask pauses the session until a human confirms, and a pre-flight budget gate refuses any call that would breach the agent's cap.
### Where should I start?
Give one agent one fullstack app with a small budget, set the destructive tools to ask, and let it ship a first deployment. Widen the blast radius only after the audit trail shows it behaving.
---
# What is AI agent infrastructure? The runtime and gateway layers
URL: https://usenaive.ai/blogs/what-is-ai-agent-infrastructure
Author: Dennis Zax
Published: 2026-07-30
Updated: 2026-09-07
Tag: Guide
> AI agent infrastructure is the runtime an agent loop runs on plus the gateway primitives it acts through, with policy enforced at every tool call. A map of the nine categories.
## TL;DR
- AI agent infrastructure is everything an agent needs besides the model: a runtime that keeps a long run alive cheaply, and gateway primitives that let it act in the world.
- The runtime layer is the computer, sessions, the completion window, budgets, skills, sub-agents, deployments and webhooks. Using Vetta is the runtime; it is never a knob you pick.
- The gateway layer is the identity, money, automation, content, intelligence, market data, cloud and trust primitives an agent reaches through one tool surface.
- Governance lives at the tool boundary, not in the prompt: every call resolves allow, ask or deny before it runs, and every call is priced against a USD budget before it runs.
- Fifty-three primitives across nine categories are listed at /primitives; this post is the map.
## The problem the category exists to solve
Most teams find out they need agent infrastructure the same way. The demo loop works. Then the first real task needs a browser login, an inbox that receives replies, a card with a cap, a run that survives a restart, and a way to stop everything at 2 a.m. without redeploying.
A model API returns tokens. A general cloud serves request-response apps. Neither was built for a process that acts for hours, spends real money, holds credentials, and is stopped by policy rather than by a person watching a terminal.
**AI agent infrastructure** is the layer that fills that gap. It has two halves.
- The **runtime**: what the agent loop runs on, and how that run is metered.
- The **gateway**: the primitives the agent acts through, each one a tool call that policy can see.
The [primitives catalogue](/primitives) is the same map, one card per primitive.
## Model, harness, runtime: which layer you actually pick
| Layer | What it is | Who chooses it |
| --- | --- | --- |
| Model | The weights that answer a turn | You, per agent |
| Harness | The agent loop: assemble a turn, call the model, parse tool calls, decide what carries forward | You, per agent, via the `harness` field |
| Runtime | The infrastructure the loop runs on: durable state, sandboxed computers, model routing, budgets, the ledger | Nobody. Using Vetta is the runtime |
| Tools | What the harness reaches for, and what each call costs | You, per agent, gated by policy |
Vetta ships several harnesses and `pi` is the default. Switching one agent's harness changes nothing about the durable loop, the budget gate or the policy layer under it. The three layers are described in [How Vetta works](/docs/how-vetta-is-built).
## The runtime layer
The runtime is the part you never configure and always pay for, so it is where efficiency is decided. The runtime category holds twelve primitives; the eight below are the ones you meet first.
**[Sessions](/primitives/sessions).** A session is one run of an agent, and the loop that drives it is an alarm, not a call stack: wake, take one bounded turn, commit the transcript to durable storage, sleep. A run that spans hours pays for storage between turns, not for a machine sitting hot. A crash loses one turn, never the run.
**[Computer](/primitives/computer).** A session's computer is a disposable Linux micro-VM with a filesystem, a shell and an optional browser. It pauses when the session sleeps, so idle time meters disk rather than vCPU.
**[Completion window](/primitives/completion-window).** One field with three settings: `immediate`, `priority`, `loose`. Same model, three tariffs. It defaults on the agent and overrides per session. If a model does not serve a window, the call is refused with `window_unavailable` rather than quietly moved to another lane.
**[Budgets](/primitives/budgets).** An agent cannot be created without one. Every model call and every priced tool call is quoted before it runs and checked, in order, against the organization balance, the agent's period cap, the per-task ceiling and any session budget. A call that would breach the cap is refused and a `budget.exceeded` event is emitted, so the agent can wrap up rather than crash.
```bash title="A budget is a constructor argument"
vetta agent create --name Refunder \
--model zai-org/GLM-5.2-FP8 --harness pi \
--skill refund-policy \
--budget-usd 50 --max-task-usd 5 --budget-period month \
--window immediate --system "You process refunds."
```
**[Skills](/primitives/skills).** Versioned playbooks the model reads on demand rather than carrying in every prompt. Pin a revision with `slug@N`.
**[Sub-agents](/primitives/subagents).** A coordinator with a version-pinned roster. The lead hands each member one brief; the member runs in its own session and only the result comes back. Delegation is capped at one level, and a shared board carries state that outlives any single session.
**[Deployments](/primitives/deployments).** Cron for agents. Each fire starts a fresh session under a per-run budget, runs to idle and stops. Scheduled work is the canonical `loose` window use case.
**[Webhooks](/primitives/webhooks).** Signed events when a run goes idle or a job finishes. Each delivery carries an HMAC-SHA256 signature in a versioned header, and during a secret rotation the header carries both the old and the new signature so a verifier accepts either.
The rest of the category: [structured outputs](/primitives/structured-outputs), a JSON schema the final answer must satisfy; [files](/primitives/files), organization-scoped storage that outlives the sandbox; [model routing](/primitives/model-router) under all of it; and the [audit log](/primitives/audit-log), every control-plane action attributed to a principal.
## The gateway layer
Everything below is a tool call, so the same policy check and the same budget quote apply to a card as to `bash`.
### Identity and legal
An agent that needs a person to lend it a login is supervised, not unattended. An [identity](/docs/identity/overview) is a named persona the agent acts as, with real endpoints: a [domain](/primitives/domains), an [inbox](/primitives/email) on it, a [phone number](/primitives/phone) with carrier registration. The relationship is many-to-many: one agent can hold a support persona and a sales persona; one `billing@` persona can be shared by two agents. The [Profile](/primitives/profile) primitive is the persona object itself. When the work needs a company behind the name, [KYC](/primitives/verification) and [LLC formation](/primitives/formation) sit in the same category.
### Money
Spend under caps the agent cannot raise. [Cards](/primitives/cards) are virtual cards with a hard limit. The [onchain wallet](/primitives/payments) pays per request in stablecoins. [Trade](/primitives/trading) connects a brokerage. [Credits](/primitives/billing) is the one prepaid balance every gateway call draws on.
### Automation
Hands on a computer that is not the sandbox. The [browser](/primitives/browser) signs up and logs in on the agent's behalf, and credentials land in the vault, not the prompt. [Mobile](/primitives/mobile) drives real apps on cloud devices. [Connect](/primitives/connections) is OAuth into the apps you already pay for, with tokens vaulted.
### Content
[Image](/primitives/images), [video](/primitives/video), [clips](/primitives/clips), [audio](/primitives/audio), one [media library](/primitives/media) and [social](/primitives/social) publishing across platforms from one compose.
### Intelligence
Live [search](/primitives/search) with URL extraction and cited multi-step research, and [Brain](/primitives/brain), company knowledge plus memory whose answers cite the source document.
### Market data
Research instead of guessing: [SEO](/primitives/seo), [AEO](/primitives/aeo) for how a brand appears in AI answers, [app store data](/primitives/app-data), [places](/primitives/business), [commerce](/primitives/ecommerce), [company data](/primitives/company-data), [people](/primitives/people) (off by default) and public [social data](/primitives/social-data).
### Cloud
The slice of hosting an agent can provision itself: [apps](/primitives/apps) to a real URL, a managed [Postgres database](/primitives/database), [storage](/primitives/storage), [functions](/primitives/functions), [auth](/primitives/auth), [compute](/primitives/compute), [queues](/primitives/queue) and disposable [sandboxes](/primitives/sandbox). Cloud is a category here, not the whole product; [What is an AI-native cloud?](/blogs/what-is-an-ai-native-cloud) covers it on its own.
### Trust and ops
Why the rest is safe to run. [Approvals](/primitives/approvals) freeze a risky call until a person decides. The [vault](/primitives/vault) holds credentials the agent uses and never reads. [MCP sessions](/primitives/sessions-mcp) scope tool access per end user and are revocable. [Jobs](/primitives/jobs) lists every async job in one place.
## Policy at the tool boundary
The old way to govern an agent was to write rules into the system prompt and hope. The infrastructure way is to enforce them where every capability converges: the tool call.
On Vetta a [policy](/docs/concepts/policies) is resolved from two layers, most specific wins: the organization sets a default every agent inherits, and each agent overrides it per tool. The resolved policy is evaluated on every tool call, connect attempt and primitive use, and each tool lands on one of three permissions.
| Permission | What happens |
| --- | --- |
| `allow` | The tool runs without confirmation |
| `ask` | The runtime emits a `tool.confirm` event and pauses the tool until a person approves or rejects it |
| `deny` | The tool is not offered to the model at all |
```typescript title="Per-tool policy on the agent"
const agent = await vetta.agents.create({
name: "ops",
model: "zai-org/GLM-5.2-FP8",
harness: "pi",
budget: { capMicroUsd: "50000000", maxTaskMicroUsd: "5000000", period: "month" }, // $50 / $5
tools: {
default_config: { permission: "allow" },
configs: {
bash: { enabled: true, permission: "allow" },
publish_file: { enabled: true, permission: "allow" },
"tracker.get_issue": { enabled: true, permission: "ask" }, // a connection/MCP tool, keyed `.`
},
},
});
```
A held `ask` call consumes no budget while it waits. The session goes idle with `stop_reason: "awaiting_approval"` and can sit there for hours at storage cost:
```bash title="Answering an ask"
vetta session confirm --session $SID --tool-call $CALL_ID # or
vetta session confirm --session $SID --tool-call $CALL_ID --reason "not this account"
```
The decision records who answered, on the event and on the audit trail. An `ask` does not have to wait forever: `ask_timeout_seconds` bounds it, and `on_timeout` says what happens next, `deny` by default.
Beyond individual tools, a policy scopes which external systems an agent may reach. Connections are gated by an allowlist, the mode to use for anything that runs unattended, and a call outside it is refused with a typed error before any external request is made. Some actions require approval out of the box: connecting a new third-party app, provisioning a phone number, purchasing a domain. An approval rule can also carry a spend threshold, so a primitive action whose quoted cost exceeds it forces a human decision even when the primitive is otherwise allowed.
This is why the identity layer above is safe to hand to an unattended agent. The persona, its inbox, its connections and its vault are all reached through tool calls, so what a persona may do is declared on the agent, not hoped for in the prompt. Combined with the budget gate, an agent left running overnight can neither exceed its spend nor touch a system it was not granted.
## Configuration as code
A whole crew of agents, with their identities, vaults, skills and apps, is one file. A blueprint repository carries a `naive.config.ts`, and `naive up` reconciles every declared resource against the platform by name, reporting each as `created`, `updated`, `unchanged`, `deleted` or `refused`. `naive up --dry-run` plans without writing. Nothing is deleted by omission; only a name under `removed` deletes. The [templates](/templates) are published blueprints you clone and claim.
## Where to start
Create an agent with a model, a prompt and a budget. Run it as a session and stream the events. Then add primitives as the work needs them: a browser for the first login, an inbox for the first reply, a card with a cap for the first purchase, `ask` on the first irreversible tool. The [quickstart](/docs/quickstart) walks the same path, and [Introducing Vetta](/blogs/introducing-vetta) explains why we built the runtime this way. If you are embedding agents inside your own product, [Building AI agents into your SaaS](/blogs/building-ai-agents-into-your-saas) covers the multi-tenant shape.
Agent infrastructure stops being optional the moment an agent leaves the chat window. Treat the runtime and the gateway as one surface, with policy at the tool boundary, rather than a pile of scripts around a model.
## FAQ
### What is AI agent infrastructure?
The layer between a model and the world. It has two halves: a runtime that runs the agent loop durably and meters it (computer, sessions, completion window, budgets, skills, sub-agents, deployments, webhooks) and a set of gateway primitives the agent acts through (identity, money, automation, content, intelligence, market data, cloud, trust), all governed by policy at the tool call.
### How is this different from an agent framework or a model API?
A model API returns tokens. A framework is the loop that decides what to do with them. Infrastructure is what the loop runs on and what it reaches for: a durable session, a sandboxed computer, a budget that refuses a call before it runs, an inbox, a card, a vault. On Vetta the loop is the harness field on the agent; the layers under it do not change when it does.
### Where is policy enforced?
At the tool-call boundary. Every tool resolves to allow, ask or deny, from an organization default overridden per agent and per tool. An ask tool emits a tool.confirm event and holds the session at storage cost until a person answers; the approver is recorded on the audit trail. Connections are scoped with an allowlist, and a call outside it is refused before any external request is made.
### Do I have to give up my own agent loop?
No. The harness is the one layer you choose per agent, and several loops are published. The runtime, the budget gate, the tool surface and the policy layer are the same under all of them.
### How do I get started?
Create an agent with a model, a system prompt and a budget, run it as a session, and add primitives as the work needs them. A blueprint repository with a naive.config.ts declares skills, identities, vaults, apps and agents by name, and naive up reconciles them against the platform.
---
# Naïve inside your agent framework: skill.md, MCP tools, policies
URL: https://usenaive.ai/blogs/naive-inside-your-agent-framework
Author: Dennis Zax
Published: 2026-07-15
Updated: 2026-09-07
Tag: Guide
> Keep the coding agent or orchestrator you already use. Point it at usenaive.ai/skill.md, reach every Naïve primitive as an MCP tool, and gate each one with allow, ask, or deny.
## TL;DR
- Your framework or coding agent keeps running the loop. Naïve is the primitives layer underneath it: computers, identities, vaults, connections, deployments, all reached as tools.
- Onboarding is one prompt. Any coding agent (Claude Code, Cursor, Codex) reads https://usenaive.ai/skill.md, logs in, declares the company in naive.config.ts, and runs naive up.
- Every primitive is an MCP tool. POST /v1/mcp publishes the API as a tool catalog generated from the route table, so a tool call is an ordinary, scoped API call.
- Governance is per tool: allow, ask, or deny. MCP tools default to ask, an ask pauses the session until a person confirms, and the approver is recorded.
- Credentials live in the vault and are injected server-side when the agent connects to an MCP server. No route ever returns a secret value.
## Keep the loop, change what it reaches for
Naïve inside your agent framework means keeping the loop you already have and putting real primitives underneath it. A coding agent on your machine, an orchestrator you wrote, or [Vetta](https://vetta.sh), our managed agent, decides what to do next. Naïve is what it reaches for when the next step is to provision a computer, connect to a third-party app, use a credential, deploy a schedule, or send something into the world.
The integration surface is deliberately small. There is one onboarding manifest, `https://usenaive.ai/skill.md`, that any coding agent can read. There is one protocol, MCP, that any framework's tool layer already speaks. And there is one place governance happens: the tool call, where each tool resolves to `allow`, `ask`, or `deny` before it runs.
The [quickstart](/docs/quickstart) and the [MCP server reference](/docs/api/mcp) hold the exact commands.
## What each layer owns
| Layer | Owns | Does not own |
|---|---|---|
| Your framework or coding agent | The model, the prompt, the planning loop, state, streaming | Identity, credentials, spend enforcement, approvals |
| Naïve | Organization, agents, sessions, computers, identities and personas, vaults, connections, deployments, budgets, policies, audit | Your graph topology, your model provider, your harness |
The split matters because the usual failure is bolting credentials and approvals onto the framework, which puts policy in a prompt where the model can argue with it. Naïve keeps orchestration in your stack and moves every real-world action behind a server that checks identity, budget, and policy first, whatever the model decides to attempt.
## Step one: your coding agent reads skill.md
The onboarding path is a prompt, not an SDK install. The home page hands you this string to paste into Claude Code, Cursor, Codex, or whatever agent you use:
```text title="The onboarding prompt"
Read https://usenaive.ai/skill.md and follow it to set up naïve in my project: register or log in, declare the company in naive.config.ts, then naive up. Check you are in the project directory first — if not, ask me where it is.
```
`skill.md` is a skill in the same sense as any other: YAML frontmatter plus a Markdown body of instructions the agent follows. It tells the agent how to authenticate, that a plan and prepaid credits come before anything else, that every agent needs a budget it must ask the operator for rather than pick, and that tools which send, move money, or delete should get an `ask` or `deny` policy. It also tells the agent not to rebuild server-side guarantees: no client-side budget math, no approval queue of its own.
If you prefer to drive it by hand, the CLI is one install away:
```bash title="Install the CLI"
npm install -g @usenaive-sdk/vetta-cli
```
The package installs both the `vetta` and `naive` binaries; every command works under either name. `naive claim --key sk_live_...` binds the clone to your organization, and `naive up` reconciles `naive.config.ts` against the platform. See the [`naive` CLI reference](/docs/cli/naive).
## Step two: primitives arrive as MCP tools
Once the company exists, your framework calls it through [`POST /v1/mcp`](/docs/api/mcp): the Vetta API published as a Model Context Protocol tool catalog. It is a JSON-RPC endpoint that speaks `initialize`, `tools/list`, and `tools/call`, and it authenticates with the same organization-scoped API key as every other endpoint.
Three properties make it safe to hand to a model:
- **The catalog is generated from the route table.** One tool per served route, named `resource_verb`: `agents_create`, `sessions_list_events`, `computers_exec_command`. A tool cannot exist without a route, and the argument schema is the same schema the server validates with.
- **A tool call is an ordinary API call.** It is re-dispatched through the full middleware chain with the caller's own bearer, so authentication, scopes, the plan gate, and rate limits apply once and identically. A key scoped `agents:read` calling `agents_create` gets a `403` back as a tool error. A tool can never reach further than its key.
- **Every tool carries `annotations.readOnlyHint`.** `true` for every read, `false` for every write, so a client can allow reads and ask on writes without maintaining a list.
Nine routes are deliberately withheld from the catalog: the endpoint itself, the never-ending SSE stream, the multipart upload, the four API key routes that mint or return live credentials, the one vault route that accepts a secret value, and the model proxy. They answer normally over HTTP; they are simply never handed to a model.
An agent that runs on Vetta declares the server like any other MCP server:
```jsonc title="Naïve as an MCP server on an agent"
{
"mcp_servers": [
{ "type": "url", "name": "vetta", "url": "https://api.vetta.sh/v1/mcp" }
]
}
```
A framework with its own MCP client points at the same URL with the same header. There is deliberately no `vetta mcp` command: every operation behind the endpoint already has one, and this endpoint exists for MCP clients.
## Step three: allow, ask, or deny, per tool
Governance is the reason to route tool calls through Naïve rather than call vendors from the loop. Every tool resolves to one of three [permissions](/docs/concepts/policies):
| Permission | Behavior |
|---|---|
| `allow` | The tool runs without confirmation. |
| `ask` | The runtime emits a `tool.confirm` event and pauses the tool until you approve or reject it. |
| `deny` | The tool is not offered to the model at all. |
Tools are configured as one object: a `default_config` that sets the baseline, and a `configs` map of per-tool overrides keyed by tool name. MCP tools are keyed `.`, so the catalog above lands in the same map as `bash`:
```jsonc title="Allow reads, ask on writes"
{
"tools": {
"default_config": { "permission": "ask" },
"configs": {
"vetta.agents_list": { "enabled": true, "permission": "allow" },
"vetta.sessions_list_events": { "enabled": true, "permission": "allow" },
"vetta.agents_create": { "enabled": true, "permission": "ask" },
"tracker.delete_project": { "enabled": false }
}
}
}
```
The default permission for MCP tools is `ask`, so a newly exposed server tool never auto-runs. To trust one, give it an explicit `allow` by name; there is no server-wide trust switch. When an `ask` fires, the session goes idle with `stop_reason: "awaiting_approval"`, and you resolve it from the CLI:
```bash title="Answer an ask"
vetta session confirm --session $SID --tool-call $CALL_ID --allow
vetta session confirm --session $SID --tool-call $CALL_ID --deny --reason "not this account"
```
The decision records who answered, on the event and in the audit trail. A held tool consumes no budget while it waits, and because the loop is durable, an agent can sit on a confirmation for hours at storage cost only. `ask_timeout_seconds` and `on_timeout` bound the wait; the default on timeout is `deny`. See [Approvals](/primitives/approvals) for the primitive and [how to add human approval to an AI agent](/blogs/how-to-add-human-approval-to-an-ai-agent) for the driving code.
## Step four: credentials live in the vault, not in the agent
Configuration is split so secrets never live on the agent definition. The agent declares MCP servers by name and URL. The [vault](/primitives/vault) holds the credential, as a `static_bearer` or `mcp_oauth` credential keyed by the server URL, and the platform injects it server-side when the agent connects:
```bash title="A bearer for an MCP server, sealed into a vault"
vetta vault create --name ava-secrets --identity idn_...
printf '%s' "$TOKEN" | vetta vault set --vault vlt_... --mcp-bearer https://mcp.example.com/mcp
vetta vault credentials --vault vlt_... # metadata only; no value is ever returned
```
The value travels on stdin, never in argv, and the sandbox never receives the token. The model, the transcript, and your logs see that the connection worked and nothing else. Secret fields are write-only on the API, and the one route that accepts a secret value is withheld from the MCP catalog so a model never types one into a `tool.started` event. An auth failure does not stop the session; it surfaces as a `session.error` event naming the server and is retried on the next wake.
Third-party apps follow the same rule through [connections](/docs/identity/connections): an identity authorizes an app over OAuth or an API key through a hosted link, and the agent reaches the app's tools as `.` under the same allow, ask, deny filter.
## Declare it once in naive.config.ts, then naive up
Everything above is config, and a coding agent following `skill.md` writes it for you. A `naive.config.ts` declares skills, identities, vaults, apps, and agents by name. `naive up` reconciles them against the platform in that order, so agents can reference the rest, and reports every resource as `created`, `updated`, `unchanged`, `deleted`, or `refused` with the reason. A vault credential's value is `from_env` only, read from your shell at apply time, so a secret never sits in the file. Re-running is always safe: nothing is deleted by omission, and `naive up --dry-run` says what an apply would do without writing anything.
The config is the source of truth: a field edited by hand in the dashboard is drift, and the next `naive up` converges it back. Your orchestrator code changes as often as you like; the company does not.
## Where the loop runs
Framework choice and where the loop runs are separate decisions. [Hosted vs bring-your-own runtime](/blogs/hosted-vs-bring-your-own-runtime-for-ai-agents) compares both. If your loop runs on your own machine or in your own service, it reaches Naïve over MCP as above. If you want the loop hosted too, a Vetta agent bundles a model, a prompt, tools, [skills](/primitives/skills), a mandatory USD [budget](/primitives/budgets), and the harness you pick, and runs in metered [sessions](/primitives/sessions) on our runtime, which pauses between turns. Either way the tool boundary is the same.
## What stays enforced
No matter which framework or transport you pick:
- **Identity.** Every call carries an organization-scoped API key, and identity-scoped primitives run as a named identity or persona.
- **Budget.** Every agent has a USD budget from birth, and a call that would breach it is refused before it runs.
- **Policy.** Every tool resolves to `allow`, `ask`, or `deny` before it runs, with `ask` as the default for anything reached over MCP.
- **Audit.** Approvals record the approver, and control-plane actions are principal-attributed in the audit log.
- **Revoke.** Deny the tool, scope the key, or delete the key. See [how to revoke AI agent access instantly](/blogs/how-to-revoke-ai-agent-access-instantly).
## Where to start
1. Paste the onboarding prompt into your coding agent. It will ask for your API key and your budget; it must not invent either.
2. Point your framework's MCP client at `https://api.vetta.sh/v1/mcp` with the same bearer, or declare it in `mcp_servers` on a Vetta agent. See [MCP sessions](/primitives/sessions-mcp).
3. Set the policy before the first write: allow reads, `ask` on writes, `deny` anything the agent should never have.
4. Put the first MCP credential in a vault and confirm the token never appears in a transcript.
5. Commit `naive.config.ts` and make `naive up` the only way the company changes.
## FAQ
### Does Naïve replace my agent framework or coding agent?
No. The loop stays where it is: a coding agent on your laptop, your own orchestrator, or Vetta, our managed agent. Naïve supplies the primitives that loop reaches for, exposed as MCP tools, and enforces identity, budget, and per-tool policy on the server side of every call.
### What does a coding agent do when it reads skill.md?
It follows the onboarding manifest: register or log in, confirm the organization, declare skills, identities, vaults, apps, and agents in naive.config.ts, then run naive up to reconcile them against the platform. It asks the operator for anything it must not invent, such as an API key or a budget.
### How do primitives show up as tools?
POST /v1/mcp is a JSON-RPC endpoint that speaks initialize, tools/list, and tools/call. The catalog is generated from the API's route table, one tool per served route, named resource_verb. Any MCP client can point at it with the same bearer key it uses for the REST API.
### How do I stop an agent from doing something irreversible?
Set that tool to ask or deny in the agent's tools config. An ask pauses the session with a tool.confirm event until someone approves or rejects it; a deny means the tool is never offered to the model. MCP tools default to ask, so a newly exposed tool never auto-runs.
### Where do the credentials for an MCP server live?
In a vault, as a static_bearer or mcp_oauth credential keyed by the server URL. The token is injected server-side when the agent connects. The sandbox, the transcript, and the model only ever see that the connection worked.
---
# How to revoke an AI agent's access instantly
URL: https://usenaive.ai/blogs/how-to-revoke-ai-agent-access-instantly
Author: Dennis Zax
Published: 2026-07-11
Updated: 2026-09-07
Tag: Guide
> Six ways to cut an agent off on Naïve: interrupt or cancel the session, delete the vault credential, revoke the connection, revoke the API key, deny the tool. Why network-boundary injection makes each one instant.
## TL;DR
- Revocation is a ladder, not one switch: stop the session, remove the credential, revoke the connection, revoke the key, or deny the tool. Pick the narrowest rung that ends the risk.
- Interrupt stops a running turn at its next commit boundary and keeps the session resumable; cancel is terminal and releases the sandbox. Both work mid-action.
- Credentials live in the vault and are injected at the network boundary, so deleting one removes the secret from the only place it ever existed. The agent held a placeholder the whole time.
- A connection is revoked with one call; the provider drops the stored token and every later call is refused. An API key revoked with vetta keys revoke fails in-flight requests with 401.
- A deny policy removes the tool from the model's catalog entirely; a call a policy blocks is written to the audit log as policy.denied.
Revoking an agent's access means every further action is denied, now, without redeploying anything or rotating a key you share with the rest of production. Earlier versions of this guide described one suspend call on a bundled object; that model is gone. An agent on Naïve is a model, a prompt, tools, skills, a budget and a harness; the things it can reach are held separately, in a session, a vault, a connection, an API key and a policy. Each has its own off switch, and the right revocation is usually the narrowest one that ends the risk.
This guide walks the ladder from narrowest to widest, then explains why every rung takes effect immediately. If you need a pause rather than a stop, read [how to add human approval to an AI agent](/blogs/how-to-add-human-approval-to-an-ai-agent) instead.
## The revocation ladder
| Target | What it stops | Reach | Reversible |
| --- | --- | --- | --- |
| Session (interrupt) | The current turn | One run | Yes, resume or steer |
| Session (cancel) | The whole run, sandbox released | One run | No |
| Vault credential | Every use of one secret | Every session that referenced it | Re-seal a new value |
| Connection | Every call through one third-party account | One identity's account | Reconnect via Connect Link |
| API key | Every request that key authenticates | One organization's key | Mint another |
| Policy (deny) | One tool, from the model's catalog | New sessions of the agent | Edit the policy |
Most incidents need one rung. A runaway loop is a session problem. A leaked token is a vault or key problem. A departing customer is a connection problem. Reaching for the widest cut first turns one misbehaving run into an outage for every agent in the organization.
## Stop the run: interrupt, steer, cancel
A session is one metered, resumable run of an agent. Three operations control it, documented under [session operations](/docs/concepts/session-operations).
**Interrupt** stops the current turn at the next commit boundary. You lose at most one turn, never the run. The session lands at `idle` with `stop_reason: "interrupted"`, its history and sandbox preserved, and the interruption is written to the audit log as `session.interrupted`.
```bash title="Interrupt a running session"
vetta session interrupt --session $SID
```
**Steer** is an interrupt and a new message in one call. The agent wraps up the current turn cleanly and then follows the redirect: the right move when the agent is doing the wrong thing rather than an unauthorized thing.
```bash title="Interrupt and redirect atomically"
vetta session send --session $SID --interrupt --message "Stop. Issue a store credit instead."
```
**Cancel** is the terminal operation. The in-flight turn stops, the sandbox is released, and the session moves to `cancelled`. The record and its events are permanent; files the session produced go with the sandbox, while anything promoted with `publish_file` or uploaded to the Files API survives. There is no archive and no delete route; cancel is the only terminal operation.
```typescript title="Cancel from the SDK"
import { randomUUID } from "node:crypto";
import { createClient } from "@usenaive-sdk/vetta";
const client = createClient({
baseUrl: "https://api.vetta.sh",
apiKey: process.env.VETTA_API_KEY!,
fetch: globalThis.fetch,
idempotencyKey: () => randomUUID(),
});
await client.sessions.interrupt(sessionId); // stop the turn, keep the run
await client.sessions.cancel(sessionId); // terminal
```
A session stop does not touch the credentials the agent was using. If a secret may be compromised, keep climbing.
## Cut the credential: the vault
The [vault](/docs/identity/vault) is where an agent's secrets live, and its defining property is that the agent never sees them. A credential is sealed once and referenced by ID. Reads return metadata only; there is no reveal route and the schema has no value field. The design is in [introducing Vault](/blogs/introducing-vault).
Revoking a secret is therefore a delete. List the vault's credentials to find the ID, then remove it:
```bash title="Delete one credential"
vetta vault credentials --vault vlt_...
vetta vault rm --vault vlt_... --credential vcr_...
```
There is no update command on purpose. Rotation is `set` a new credential, then `rm` the old one, so `last_injected_at` stays attributable to exactly one secret. During an incident, "when was this value last used?" is the first question, and an in-place update would make it unanswerable.
```bash title="Rotate a credential"
printf '%s' "$NEW_KEY" | vetta vault set --vault vlt_... --env PAYMENTS_API_KEY --host api.payments.example.com
vetta vault rm --vault vlt_... --credential vcr_old...
```
If the whole vault has to go, `vetta vault delete ` soft-deletes the container and destroys its secrets for good. In the SDK: `client.vaults.credentials.delete(vaultId, credentialId)` and `client.vaults.delete(id)`; see the [vaults reference](/docs/sdk/vaults).
## Cut one MCP integration
MCP servers are declared on the agent by name and URL, and their auth is never on the agent definition. A `static_bearer` or `mcp_oauth` credential in the vault is matched to the server by URL and injected server-side when the agent connects. The sandbox never receives the token, which is why there is no separately revocable "MCP session" any more: there is no token in the agent's hands to revoke.
To cut one integration without touching the rest, delete that server's vault credential. An auth failure surfaces as a `session.error` event naming the server; the agent's other tools keep working. Or remove the tools themselves in the agent's tool configs, where every MCP tool is keyed `.` and can be set to `enabled: false` or `permission: "deny"`. The default permission for MCP tools is `ask`, so a newly exposed server tool never auto-runs. See the [MCP connector](/docs/capabilities/tools#mcp-connector) docs.
Naïve also publishes its own API as an MCP tool catalog at `POST /v1/mcp`. Every tool call is re-dispatched as an ordinary API call carrying the caller's own bearer, so a tool can never reach further than the key that invoked it, and revoking that key revokes that client. Routes that mint or accept secrets are withheld from the catalog entirely. See the [MCP server](/docs/api/mcp) reference.
## Revoke a connection
A [connection](/docs/identity/connections) is an authorized account in a third-party app, bound to one identity. The agent never handles the raw credential; we store it, inject it at call time, and refresh managed OAuth tokens server-side.
Revoking it is one call, `client.connections.disconnect(id)` in the SDK. The provider drops the stored credential, the account moves to `disconnected`, and every later call through it is refused.
```bash title="Disconnect one account"
vetta identity disconnect --connection ca_...
```
Two related cuts: deleting the org-level auth config means connections already minted against it stop refreshing. And `vetta identity detach` revokes an agent's grant to a persona: the identity is untouched and running sessions are not interrupted, but the next session cannot select it. The full route list is in the [connections API](/docs/api/connections).
## Revoke or rotate an API key
Keys are organization-scoped and carry explicit scopes, so a CI key or a sandbox credential need not be an admin key. Revocation is immediate: in-flight requests holding the old secret start failing with `401`. Rotation mints a replacement secret under the same ID and invalidates the old one.
```bash title="Revoke or rotate a key"
vetta keys revoke key_...
vetta keys rotate key_...
```
In the SDK these are `client.keys.revoke(id)` and `client.keys.rotate(id)`; only `rotate` answers a new secret, once. Both land in the audit log as `api_key.revoked` and `api_key.rotated`. `vetta keys list` shows `last_used_at`, which makes stale keys easy to find before they become the incident. Reference: [vetta keys](/docs/cli/keys) and [authentication](/docs/api/authentication).
## Deny at the policy layer
Every capability an agent has flows through one place, the tool call, and [policies](/docs/concepts/policies) are checked there before a tool runs. Each tool resolves to `allow`, `ask`, or `deny`. A `deny` means the tool is not offered to the model at all. Connections run under the same filter: `connections.mode: "allowlist"` keeps a new connector type unreachable until you add it, and an attempt outside the list is refused with a typed `forbidden` error before any external request is made.
```bash title="Ship a tighter policy as a new agent version"
vetta agent update ops --file ops.json
```
The thing to know about policy as a revocation tool is versioning. An update mints a new immutable agent version, and every session pins the version it started on. A deny protects every session created from then on, but it does not reach into a run already going. For that, interrupt or cancel first, then let the next session pick up the new version.
## Why the boundary makes revocation instant
Every rung above takes effect immediately for one structural reason: the agent never held the thing you are revoking.
A vault value is substituted outside the sandbox, at the network boundary, and only when the request is bound for the credential's destination. Inside the sandbox, in the model's context, in the transcript and in your logs, there is only a placeholder. Delete the credential and the placeholder is inert; there is no cached copy to expire because there was never a copy. The same holds for a connection token, injected at call time, and for MCP auth, injected server-side at connect. Revocation never chases a secret through the agent's memory; it changes one row in a store the agent cannot read.
Policy works the same way. Enforcement is at the tool-call boundary rather than inside a prompt, so a policy holds regardless of what the model decides to attempt. The `env_var` kind extends the boundary rule to general sandbox egress, substituting only for the bound host, so a prompt-injected agent that sends the placeholder elsewhere sends the placeholder, not the secret. That kind is coming soon and refused today.
## Verify the cut
- After an interrupt, `vetta session get $SID` shows `idle` and `stop_reason: "interrupted"`. After a cancel, `cancelled`.
- After a credential delete, `vetta vault credentials --vault vlt_...` no longer lists it.
- After a disconnect, `vetta identity connections show ca_...` reads `disconnected`; that read reconciles against the provider.
- After a key revoke, any request with the old secret is `401`, and `GET /v1/audit_logs` carries `api_key.revoked` with the acting principal.
- After a policy change, a new session of the agent does not see the denied tool, and any blocked call is recorded as `policy.denied`.
## Wire it in before you need it
Revocation under pressure is only reliable if the one-liner already exists. Put the right rung in the right place: a session cancel behind the stop button in your admin UI, a connection disconnect in your offboarding flow, a vault rotation on a schedule, a key revoke in your incident runbook. Each is one command or one SDK call, scoped to exactly what needs to stop.
## FAQ
### How do I stop a running AI agent immediately?
Interrupt or cancel its session. vetta session interrupt stops the current turn at the next commit boundary and leaves the session idle with stop_reason interrupted, so you can resume or steer it. vetta session cancel is terminal: the in-flight turn stops, the sandbox is released, and the session moves to cancelled. The record and event history are kept either way.
### Do I have to rotate my API key to revoke one agent?
No. Keys are one rung on the ladder, not the only one. Cancel the session, delete the vault credential, or revoke the connection the agent was acting through. Rotate or revoke a key when the key itself may have leaked; revoking one key never affects another organization, and in-flight requests holding the old secret start failing with 401.
### Can an agent copy a secret before I revoke it?
Not from the vault. Credential values are write-only and never returned by any route; the agent, the transcript, and your logs only ever see an opaque placeholder. The real value is substituted outside the sandbox at the network boundary, so once you delete the credential there is nothing left for the agent to have kept.
### Does a deny policy stop a session that is already running?
A policy change mints a new agent version, and every session pins the version it was created with. Sessions created from then on never see the tool. For a session already running, interrupt or cancel it first, then let the next session pick up the new version.
### What is the narrowest way to revoke one MCP integration?
Delete the vault credential matched to that server URL, or disable the server's tools in the agent's tool configs. The agent's other tools keep working. If the MCP client is something operating your Naïve account through POST /v1/mcp, revoke the API key it authenticates with; every tool call carries that key and can never reach further than it.
---
# How to add human approval to an AI agent
URL: https://usenaive.ai/blogs/how-to-add-human-approval-to-an-ai-agent
Author: Dennis Zax
Published: 2026-07-11
Updated: 2026-09-07
Tag: Guide
> Gate an AI agent's risky tool calls with an ask policy: the session pauses, a person approves it over a webhook, the dashboard or the CLI, and a USD budget caps spend regardless.
## TL;DR
- Human approval means a risky tool call freezes until a person decides. It is enforced at the tool-call boundary, not requested in the prompt.
- Every tool on a Vetta agent resolves to allow, ask or deny. An ask tool emits a tool.confirm event and parks the session idle with stop_reason awaiting_approval.
- The pending call sits on the session's pending_actions. Surface it over a session.idle webhook, the vetta session inbox command, or the dashboard, then allow or deny it.
- Every decision records who made it, and an ask_timeout_seconds with an on_timeout disposition keeps a forgotten approval from hanging forever.
- A mandatory USD budget prices every call before it runs, so even an approved agent cannot overspend.
## Hold the call, do not ask the model
Human approval for an AI agent means a sensitive action stops and waits for a person. Issue the refund, delete the project, send the mail: none of it happens until someone says so. The unsafe way to get this is to ask the model in its system prompt to "check with a human first". The safe way is for the platform to hold the call itself, so the policy holds regardless of what the model decides to attempt.
On Vetta that hold lives at the tool-call boundary. This guide covers how a tool call resolves to allow, ask or deny, what an `ask` does to a running session, how a person answers it, and why a USD budget sits underneath all of it. It replaces our older posts on spend caps, budget caps, agent permissions and governance, which now redirect here.
## Where approval lives: the tool-call boundary
Every capability an agent has flows through a tool call, so that is where Vetta checks policy, before the tool runs rather than in an audit afterwards. Each tool resolves to one of three permissions:
| Permission | Behavior |
| --- | --- |
| `allow` | The tool runs without confirmation. |
| `ask` | The runtime emits a `tool.confirm` event and holds the tool until a person approves or rejects it. |
| `deny` | The tool is not offered to the model at all. The agent cannot call it. |
The resolved permission comes from two layers, most specific wins: an organization default that every agent inherits, and a per-agent, per-tool override. Tools are configured as a single toolset: a `default_config` that applies to every tool, and per-tool `configs` that enable a tool and override its permission.
`allow` is convenient, but it is not automatically the right default. For anything irreversible or externally visible (payouts, deletes, outbound mail) set `ask`. For anything the agent should never do, set `deny` rather than leaning on the permissive baseline. The [Approvals primitive](/primitives/approvals) is this mechanism; the [policies docs](/docs/concepts/policies) are the reference.
## Step 1: mark the risky tools `ask`
An agent is a declarative configuration, so the cleanest place to set permissions is the agent's checked-in `.agent.yaml`:
```yaml title="refunder.agent.yaml"
name: Refunder
model:
id: zai-org/GLM-5.2-FP8
harness: pi
system: |
You process refunds. Always confirm the order first, then apply the
refund-policy skill. Escalate anything ambiguous rather than guessing.
skills:
- refund-policy
tools:
default_config:
permission: allow
configs:
bash: { enabled: true, permission: ask }
browser: { enabled: true, permission: ask }
budget:
cap_usd: 50
max_task_usd: 5
period: month
```
```bash
vetta agent apply -f refunder.agent.yaml
```
To flip one tool on an agent that already exists, without touching the rest of its config:
```bash
vetta agent tools Refunder --tool bash --permission ask
```
Connection and MCP tools take the same treatment, keyed `.`, for example `tracker.get_issue`. Some identity actions are approval-gated out of the box: connecting a new third-party app, provisioning a phone number and purchasing a domain all hold for a person before anything external happens.
An agent update mints a new immutable version rather than editing the current one, and a session's tool set is fixed at creation from the agent's configuration. To change permissions on a running session, update the agent and start a new one.
## Step 2: what the agent hits
When the agent calls a tool set to `ask`, the runtime does not run it. It streams a `tool.confirm` event and parks the session: `status` goes `idle` with `stop_reason: "awaiting_approval"`, and the held call, with its `tool_call_id`, `name` and `args`, is exposed on `pending_actions[]`.
```json title="vetta session get $SID"
{
"id": "ses_4a...",
"status": "idle",
"stop_reason": "awaiting_approval",
"pending_actions": [
{
"kind": "tool",
"tool_call_id": "call_8Q...",
"name": "refund",
"args": { "order_id": "4821", "amount_micro_usd": 42000000 }
}
]
}
```
A held tool consumes no budget while it waits. The session loop is durable (wake, take a turn, commit, sleep), so an agent can sit on a confirmation for hours at storage cost only. That is what makes it acceptable to gate aggressively.
## Step 3: get the pending call in front of a person
An idle session that is waiting on you looks exactly like one that finished its work; `status` cannot tell them apart. `stop_reason` can, so every surface filters on it.
**Webhook.** Subscribe an endpoint to `session.idle`. Each delivery is a signed JSON envelope whose `data` carries the `session_id` and `stop_reason`, so your handler branches on `awaiting_approval` and fetches the session for the pending call. Verify `Vetta-Signature` against the raw body first, and reject deliveries whose `Vetta-Timestamp` is more than 300 seconds old.
```bash
vetta webhook add \
--url https://example.com/hooks/vetta \
--events session.idle,budget.exceeded
```
```typescript title="hooks/vetta.ts"
// `verify` is the HMAC-SHA256 check from the webhooks docs; the SDK does not ship one.
app.post("/hooks/vetta", express.raw({ type: "application/json" }), async (req, res) => {
if (!verify(req.body, req.headers, process.env.VETTA_WEBHOOK_SECRET!)) return res.sendStatus(400);
const event = JSON.parse(req.body.toString("utf8"));
if (event.type === "session.idle" && event.data.stop_reason === "awaiting_approval") {
const session = await client.sessions.get(event.data.session_id);
await notifyApprovers(session.id, session.pending_actions);
}
res.sendStatus(200);
});
```
**CLI.** `vetta session inbox` lists every session waiting on a person and what each one wants, and `vetta session list` takes a `--stop-reason` filter for scripts:
```bash
vetta session inbox --human
vetta session list --stop-reason awaiting_approval --json | jq -r '.data[].id'
```
**SDK.** The same filter is a query on `sessions.list`:
```typescript
const waiting = await client.sessions.list({ stop_reason: "awaiting_approval" });
```
**Dashboard.** The session page shows one card per held call, with the tool name, its arguments and approve or deny buttons. A product that embeds agents for its own customers forwards the webhook into its own approval UI instead.
## Step 4: approve or deny
A confirmation is a verb and no payload: allow or deny, optionally with a reason on a deny. From the CLI:
```bash
vetta session confirm --session $SID --tool-call call_8Q... --allow
vetta session confirm --session $SID --tool-call call_8Q... --deny --reason "not this account"
```
From the SDK, walk `pending_actions` and answer each held tool:
```typescript
const s = await client.sessions.get(sessionId);
for (const a of s.pending_actions) {
if (a.kind !== "tool") continue; // a question is answered, not approved
await client.sessions.confirmTool(sessionId, {
tool_call_id: a.tool_call_id,
decision: a.name === "refund" ? "allow" : "deny",
reason: "out of policy",
});
}
```
Branch on `kind`, not on `stop_reason` alone. `pending_actions[]` also carries `kind: "question"` rows when the agent parked itself on a question for you; those take an answer through `sessions.answer`, and sending them a confirmation is a validation error.
Once the last pending action is answered the session resumes automatically. On allow the tool runs and the agent continues. On deny the tool never runs and the deny message is fed back to the agent in-band, so it can plan around the refusal instead of crashing. Every resolved confirmation records the approving principal, the user or API key id that answered, on the event and in the [audit log](/primitives/audit-log) alongside the decision and any reason. That is what lets you prove afterwards that a payout was signed off by a person.
## Do not let an approval hang forever
An `ask` tool does not hold the turn indefinitely. Two fields on the policy bound the wait: `ask_timeout_seconds` sets how long a call may sit unanswered, and `on_timeout` decides what happens when it elapses.
| `on_timeout` | Behavior when the window elapses |
| --- | --- |
| `deny` | The call is rejected as if you denied it and the agent is told. The safe default. |
| `allow` | The call is auto-approved and runs. |
| `escalate` | The session stays parked on `awaiting_approval` and a notification is re-sent. The decision is deferred, not made. |
Omit the timeout and the session waits indefinitely, at storage cost. For an unattended overnight run, `deny` is almost always right.
## Budgets cap spend regardless
Approval decides which actions may happen. A budget decides how much they may cost, and it applies whether or not a person is watching. An agent cannot be created without one: a period cap (`cap_micro_usd`), a per-task ceiling (`max_task_micro_usd`) and a reset `period` of `day`, `week` or `month`. Money is integer micro-USD on the wire; the CLI's `--budget-usd` and `--max-task-usd` (and `cap_usd` / `max_task_usd` in `.agent.yaml`) take decimal dollars and convert client-side.
```bash
vetta agent create --name nightly-triage --model zai-org/GLM-5.2-FP8 \
--budget-usd 50 --max-task-usd 5 --budget-period month
```
Before each model call or priced tool call, the runtime computes a quote, an upper bound at the session's completion window, and checks it in order against the organization balance, the agent's period cap, the task ceiling and any session budget. If the quote would breach any of them the call is refused, `budget.exceeded` is emitted, and the agent is told in-band so it can wrap up cleanly. A session can carry its own cap too, and work that pauses at it resumes when you raise it:
```bash
vetta session create --agent Refunder --budget-usd 2.00
vetta session budget --session $SID --usd 5.00 # raise; must exceed what is already consumed
```
The two mechanisms compose. An identity policy can attach a spend threshold to an approval rule, so an action whose quoted cost exceeds the amount forces a human decision even when the primitive is otherwise allowed. The budget caps the total; the policy governs which actions may spend at all. A `budget_paused` session shows up in `vetta session inbox` next to the approvals, because both are waiting on a person. See the [Budgets primitive](/primitives/budgets) and the [budgets docs](/docs/concepts/budgets).
## How you know it worked
- The agent calls an `ask` tool and you see a `tool.confirm` event, then `session.idle` with `stop_reason: "awaiting_approval"`. Nothing external happened.
- Allow: the tool runs, `tool.completed` follows, and the session continues on its own.
- Deny: no side effect, and the agent receives the deny message in-band.
- The confirmation event names the principal who answered.
- A call that would breach the cap is refused with `budget.exceeded` before it runs, approved or not.
## Where to go next
Wire one tool first: set it to `ask`, drive a session into it, answer from the CLI, and watch the stream. Then add the webhook so approvals reach the people who own the decision.
Approval is one half of trust; the other half is taking access away. [How to revoke AI agent access instantly](/blogs/how-to-revoke-ai-agent-access-instantly) covers that side. If you are embedding agents in a product your own customers use, [Building AI agents into your SaaS](/blogs/building-ai-agents-into-your-saas) is the companion read. The reference material lives at [Policies](/docs/concepts/policies), [Session operations](/docs/concepts/session-operations), [Events and streaming](/docs/concepts/events-and-streaming) and [Webhooks](/docs/capabilities/webhooks).
## FAQ
### How do I add human approval to an AI agent?
Set the tool's permission to ask in the agent's toolset. When the agent calls it, the runtime emits a tool.confirm event, holds the call and parks the session idle with stop_reason awaiting_approval. A person resolves it with vetta session confirm, the SDK's sessions.confirmTool, or the dashboard, and the session resumes on its own.
### What happens to a paused agent while it waits for approval?
Nothing runs and nothing is billed for compute. The held tool consumes no budget, and because the session loop is durable the agent can wait for hours at storage cost only. Once the last pending action is answered the session picks up where it stopped.
### Which AI agent actions should require approval?
Anything irreversible or externally visible: payouts, refunds, deletes, sending mail. Set those tools to ask, and set deny for capabilities the agent should never have. Connecting a new third-party app, provisioning a phone number and purchasing a domain require approval out of the box.
### Can an approval be tied to how much an action costs?
Yes. An identity policy can carry a spend threshold that forces approval when an action's quoted cost exceeds it, on top of the agent's budget. The budget caps total spend; the policy decides which actions may spend at all.
### What if nobody answers an approval request?
The policy's ask_timeout_seconds bounds the wait and on_timeout decides the outcome: deny rejects the call and tells the agent, allow auto-approves it, and escalate keeps the session parked and re-notifies. Deny is the default.
---
# Hosted vs bring-your-own harness for AI agents
URL: https://usenaive.ai/blogs/hosted-vs-bring-your-own-runtime-for-ai-agents
Author: Dennis Zax
Published: 2026-07-11
Updated: 2026-09-07
Tag: Guide
> In Vetta the harness (the agent loop) is the layer you pick and the runtime is always managed. When to keep the default harness, when to bring your own, and what each choice does to cost per completed task.
## TL;DR
- Where an agent's loop comes from and what runs underneath it are separate decisions, and only the first one is yours to make.
- The harness is the agent loop: assemble a turn, call the model, parse tool calls, decide what carries forward. It is one field on the agent, and pi is the default.
- The runtime is the durable loop, scheduling, the budget gate, sandboxed computers and the ledger. It is never a parameter; using Vetta is the runtime.
- Bringing your own harness means running an external coding agent as a process in a micro-VM. Your tools, MCP servers and connections still reach it over a session-scoped tool endpoint, and your policy still governs them.
- Pick by two published facts, not by taste: which capabilities the loop declares (approvals, structured output, streaming) and what an idle session on it holds.
- Cost per completed task is decided across harness, tools and runtime together, which is why the runtime stays managed even when the loop is yours.
## Two layers, one field
The question this post used to answer was "hosted runtime or bring your own runtime?" That framing is wrong for Vetta, and getting it right changes which decision you have to make.
Vetta is built in three layers. The **harness** is the agent loop: assemble a turn, call the model, parse the tool calls, decide what carries into the next turn. The **tools** are what the loop reaches for. The **runtime** is everything underneath: the durable loop and scheduler, model routing across completion windows, budget enforcement, sandboxed computers and the ledger. The [How Vetta works](/docs/how-vetta-is-built) page walks through all three.
Only one of those layers is yours to select, and it is the harness. It is a single field on the agent:
```json title="One field selects the loop"
{ "name": "Reconciler", "model": "zai-org/GLM-5.2-FP8", "harness": "pi" }
```
The runtime is not a parameter and there is nothing to select. Using Vetta's managed agents *is* the runtime. So the real choice is: run the default harness, or bring your own loop and run it on the same managed runtime?
## What the runtime does regardless
Whichever harness you pick, the layer beneath it does not change. That is the point of making `harness` a field rather than a fork of the product.
- **The durable loop.** The runtime wakes on a trigger (a user message, a scheduled [deployment](/primitives/deployments), a tool result), takes one bounded turn, commits the transcript and cursor to durable storage, and sleeps. A crash loses at most one turn, never the run. See [Runtime and durability](/docs/concepts/runtime) and [sessions](/primitives/sessions).
- **The budget gate.** Every agent carries a mandatory USD [budget](/primitives/budgets), and every call is quoted against the cap before it runs. Over the cap, it is refused.
- **Sandboxed computers.** A disposable [micro-VM](/primitives/computer) per agent, with a shell, a filesystem, networking and a managed browser. A paused micro-VM meters its stored disk and no vCPU.
- **Model routing and the completion window.** One model, three lanes: `immediate`, `priority`, `loose`. An unsupported lane is refused, never silently downgraded. See [completion windows](/primitives/completion-window).
- **The ledger.** Five-tier token accounting in integer micro-USD, per session and per agent.
The [policy layer](/docs/concepts/policies) sits here too: every tool call resolves `allow`, `ask` or `deny` and is priced before it runs, on every harness.
## The default harness: pi
`pi` is the default and the only harness with `ga` status today. It is a coding agent embedded in Vetta's composition, driving our sandbox tools directly. Because we assemble the turn, one of our tools is simply another entry in the toolset the model is offered, and per the [harness capabilities matrix](/docs/concepts/harness-capabilities) it declares all four published capabilities:
- `approvals`: the loop can hold a tool call open and wait for a person. Required if any tool's permission is `ask`, and required for `ask_operator`.
- `structured_output`: the loop can enforce `structured_output_required` and serve a typed delegation.
- `streaming_deltas`: the loop emits incremental `message.delta` events rather than one whole `message.completed`.
- `injected_tools`: the platform's tools reach the model.
It also receives every one of Vetta's own built-ins: web search and fetch, image and video generation, skill disclosure, `publish_file` and the managed browser. Those are offered on `pi` and `vetta` only.
Creating an agent on it:
```bash title="The default, spelled out"
vetta agent create \
--name Refunder \
--model zai-org/GLM-5.2-FP8 \
--harness pi \
--budget-usd 50 --max-task-usd 5 --budget-period month \
--window immediate \
--system "You process refunds."
```
If you omit `--harness`, you get `pi`.
## Bringing your own harness
The other published loops are `vetta`, `claude_code` and `hermes`, all in `preview`. `vetta` is our own loop, held to a small measured core, and the only one that runs in the session itself. `claude_code` and `hermes` are the bring-your-own case: an external coding agent, run as a process in a micro-VM, driven by its own command line and carrying its own toolset.
Bringing your own harness does not mean bringing your own infrastructure. The process runs on the same runtime; the durable loop, the budget gate, the event log, the ledger and your `tools` policy are unchanged.
What changes is the road our tools take to reach the model. On `pi` we hand them into the turn. On a harness whose agent is a CLI in a machine of its own, the loop is given a **session-scoped tool endpoint** at the start of every turn, scoped to that session by a credential minted for the turn, and calls our tools over it alongside its own file, shell and search tools. You configure nothing. The team tools (`send_to_agent`, `wait_for_agents`, `list_agents`, `board_read`, `board_write`), your MCP servers, your connected accounts and your apps all travel that road, so a [coordinator](/docs/team/coordinator) can run on any published harness and a [team](/primitives/subagents) may mix harnesses freely.
Two limits are real on that road, and both follow from those harnesses declaring `approvals: false`:
> A tool whose permission is `ask` is left out entirely, not gated. Nothing in that machine can hold a call open while a person decides, so a session whose toolset carries an `ask` is refused when it starts, naming `harness`.
> `wait_for_agents` does not pause the turn. A coordinator that fans work out parks once the turn goes idle. The cost is a few extra model calls, not a wrong answer.
There is a third: Vetta's own built-ins do not travel the second road. A `claude_code` or `hermes` agent has its CLI's own web, file and search tools instead, and a prompt that tells it to call `publish_file` will read as though the model ignored it.
## Two questions decide it
Two separate questions decide the choice, and both are answered by published fields rather than inferred from a bill.
**What must the loop be able to do?** If your toolset uses `ask`, you need `approvals`. If you require a typed answer, you need `structured_output`. If you want the answer as it is written, you need `streaming_deltas`. Every published harness declares `injected_tools`, so in practice this question is about the limits that come with `approvals: false`.
**What should an idle session cost?** This is the `execution` field. `isolate` runs the loop in the session object itself, so a session between turns holds no machine and bills storage only. `sandbox` runs the agent as a process on a micro-VM, which the session holds across turns. For long-horizon work that spends most of its life waiting, this is usually the deciding field.
| | `pi` | `vetta` | `claude_code` | `hermes` |
| --- | :---: | :---: | :---: | :---: |
| Status | `ga` | `preview` | `preview` | `preview` |
| Execution | `sandbox` | `isolate` | `sandbox` | `sandbox` |
| Idle session holds | a micro-VM | nothing | a micro-VM | a micro-VM |
| `approvals` | yes | no | no | no |
| `structured_output` | yes | declared, not yet served | no | no |
| `streaming_deltas` | yes | no | no | no |
| `injected_tools` | yes | yes | yes | yes |
| Vetta built-ins offered | yes | yes | no | no |
Nothing here is a quiet best effort. A session that asks for a capability its harness does not declare is refused when it starts, with a `400` naming `harness`. An `ask` toolset that silently became `allow` would remove your human-in-the-loop gate with nothing to notice it by.
The catalogue is served. `runnable` is the one field that varies by environment, because a harness that runs a process also needs a base image built for that deploy:
```ts title="Read the catalogue at runtime"
const harnesses = await client.harnesses.list();
for (const h of harnesses.data) {
console.log(h.base, h.execution, h.capabilities, h.runnable ? "available" : "not on this deploy");
}
```
A published harness with `runnable: false` is refused at session start with a `501` naming the field. The wire contract is [`GET /v1/harnesses`](/docs/api/harnesses).
## The cost-per-completed-task angle
Vetta is sold on cost per completed task, and the thesis behind it is that the model is a commodity: everything around the model decides what a finished piece of work costs. That is why the runtime stays managed even when the loop is yours. The four levers that earn the efficiency all live beneath the harness seam:
1. A micro-VM sandbox that pauses between turns, so idle bills storage only.
2. An alarm-driven durable loop: wake, turn, commit, sleep.
3. A three-tier completion window, chosen per task and fail-closed.
4. Model choice plus sub-agent delegation, so the cheapest capable model does each unit of work.
Bringing your own harness keeps all four. What it changes is the cost profile of the loop itself: how much context it carries per turn, how often it calls the model, whether it holds a machine while it waits. Two of those are visible before you spend a cent: `execution` tells you what an idle session holds, and the capability rows tell you whether a coordinator on that loop pays a few extra model calls at every fan-out because `wait_for_agents` cannot pause the turn.
The rest you measure. `harness` is chosen at create and does not change on a live agent, so an A/B is two agents from the same `.agent.yaml` differing in that one field, compared with `vetta agent spend ` broken down by component. Prompt, skills, budget and window carry across unchanged; the ledger is per session. We publish our own numbers on the [benchmark](/benchmark) page.
## Choosing
Keep the default harness when:
- any tool in the toolset is `ask`, or the agent uses `ask_operator`
- you require `structured_output_required` or a typed delegation
- a Vetta built-in is load-bearing: web search and fetch, media generation, `publish_file`, the managed browser
- you want a running transcript with `message.delta` events
Bring your own harness when:
- your team already lives in that coding agent and wants the same loop running unattended, under a budget
- every tool the agent needs is either its own or reaches it over the injected-tools road
- the toolset is `allow` and `deny` only, with no `ask`
- the deploy you are on reports it as `runnable`
Either way, the budget gate, the policy layer, the event log and the ledger are the same. There is no path that bypasses them, because there is no self-hosted runtime.
## Where to go next
The concept page on [harnesses](/docs/concepts/harnesses) explains what each loop is and where the tools come from on each; the [capabilities matrix](/docs/concepts/harness-capabilities) is the table above with the known gaps spelled out. To keep your existing framework and use Naïve's primitives from inside it, read [Naïve inside your agent framework](/blogs/naive-inside-your-agent-framework). For the whole stack, start with [Introducing Vetta](/blogs/introducing-vetta).
## FAQ
### What is the difference between a harness and the runtime in Vetta?
The harness is the agent loop: how a turn is assembled, how the model is called, how tool calls are parsed and what carries into the next turn. It is one field on an agent and you choose it. The runtime is the managed infrastructure under that seam: the durable wake, turn, commit, sleep loop, scheduling, model routing across completion windows, budget enforcement, sandboxed computers and the ledger. It is not selectable.
### What does bring-your-own harness mean?
It means choosing a harness whose agent is an external coding agent run as a process in a micro-VM, such as claude_code or hermes, instead of the default pi loop. The runtime, budget gate, policy layer and event log are the same either way. Vetta's team tools, MCP servers and connected accounts reach that process over a session-scoped tool endpoint that is written into the machine at the start of every turn.
### Does bringing my own harness bypass budgets or policy?
No. Budgets are enforced by the runtime before a call runs, and the tools policy governs every tool by the same names on every harness. On a harness that runs a CLI in its own machine, a tool set to deny is never even put in the list the agent is given.
### When should I keep the default harness?
Keep pi whenever a tool in your toolset is set to ask, whenever you require structured_output_required, whenever you want incremental message deltas, or whenever a Vetta built-in such as web search, the managed browser or publish_file is load-bearing. Those capabilities are declared per harness, and a session that asks for one its harness does not declare is refused at start.
### How do I see which harnesses my deployment can run?
Read GET /v1/harnesses or call client.harnesses.list(). Every entry carries base, status, execution, capabilities and a runnable flag that answers for the deploy you are talking to. A harness that is published but not runnable here is refused at session start with a 501 naming the field.
---
# Building AI agents into your SaaS: the multi-tenant playbook
URL: https://usenaive.ai/blogs/building-ai-agents-into-your-saas
Author: Dennis Zax
Published: 2026-07-03
Updated: 2026-09-07
Tag: Guide
> How to give every SaaS customer an isolated, governed agent: one organization per customer, a budgeted agent, allow/ask/deny policies, personas, a write-only vault, and webhooks back into your product.
## TL;DR
- Multi-tenant agents mean one organization per customer, not one shared agent with a system prompt that says whose data it may touch.
- Every organization's data is isolated and scoped by organization id on every request, and an API key reaches exactly the one organization it was minted in.
- An agent is a versioned config with a mandatory USD budget; a session is one durable run of it, priced before every model call.
- Per-tool allow / ask / deny policies are enforced at the tool-call boundary, so the risky steps pause for a human and the rest run.
- Personas give the agent a real email, phone number and domain to act from; the vault holds credentials the model never sees.
- Results come back into your product through signed webhooks and a typed structured_output, not by parsing transcripts.
## What a multi-tenant agent product actually needs
If you are adding agents to a SaaS product, the interesting question is not which model to use. It is what happens when a thousand customers each have an agent acting on their behalf, spending their money and holding their credentials. The model is the easy part. The hard part is the boundary around it: whose data the agent may read, what it may do without asking, how much it may spend, who it acts as, and how the result gets back into your product.
The tempting shortcut is one shared agent with a system prompt that says "only act for customer X". That works in a demo and fails in production: one prompt injection or one mixed-up variable leaks a tenant's data or spend into another's. Isolation has to be structural, enforced below the model.
This guide is the current playbook for building that on [Vetta](/docs), Naïve's managed agent. It replaces our earlier posts on multi-tenant agents and tenant isolation, which now redirect here.
## The building blocks
Six concerns show up in every multi-tenant agent product. Here is what teams build by hand, and the primitive that replaces it.
| Concern | The DIY stack | On Vetta |
|---|---|---|
| Isolation | Per-tenant scoping in every query and log line | One organization per customer, scoped by organization id on every request |
| Spend | Token metering plus a kill switch | A mandatory USD [budget](/primitives/budgets) on every agent, checked pre-flight |
| Permissions | Instructions in the system prompt | Per-tool `allow` / `ask` / `deny` policies enforced at the tool-call boundary |
| Acting as the customer | Shared bot inboxes and personal phones | A persona with its own [email](/primitives/email), [phone](/primitives/phone) and [domain](/primitives/domains) |
| Secrets | A secrets manager plus careful redaction | A write-only [vault](/primitives/vault) injected at the network boundary |
| Results | Polling and transcript parsing | Signed [webhooks](/primitives/webhooks) and typed [structured outputs](/primitives/structured-outputs) |
The rest of this post takes them in the order you will wire them.
## Step 1: one organization per customer
Everything on Vetta belongs to an [organization](/docs/platform/organizations): the billing entity that holds the credit balance and owns every agent, computer, skill and identity. Tenancy is logical multi-tenancy. Every organization's data is isolated per organization and scoped by organization id on every request, and an [API key](/docs/api/authentication) reaches exactly the one organization it is bound to.
So the tenancy model for a SaaS is simple: create an organization per customer, and keep the key that comes back in your own secrets store, keyed by your customer id.
```ts title="tenant.ts"
import { randomUUID } from "node:crypto";
import { createClient } from "@usenaive-sdk/vetta";
const root = createClient({
baseUrl: "https://api.vetta.sh",
apiKey: process.env.VETTA_API_KEY!,
fetch: globalThis.fetch,
idempotencyKey: () => randomUUID(),
});
// One organization per customer. The reply carries the organization and
// its first admin key; api_key.secret is returned here and never again.
const { organization, api_key } = await root.orgs.create({ name: "Acme" });
await secrets.put(`vetta:${customerId}`, api_key.secret);
// Every later call for this customer runs through a client bound to their key.
const acme = createClient({
baseUrl: "https://api.vetta.sh",
apiKey: api_key.secret,
fetch: globalThis.fetch,
idempotencyKey: () => randomUUID(),
});
```
The same thing from the CLI is `vetta org create --name "Acme"`. Keys can also carry scopes such as `agents:write` or `sessions:write`; a valid key acting outside its scopes gets a `403 forbidden`, not a partial result.
A bug in your prompt or your application code cannot cross tenants, because the key it is holding cannot see them.
## Step 2: an agent with a budget it cannot exceed
An [agent](/docs/capabilities/agent) is a reusable, versioned configuration: model, system prompt, tools, skills, harness, default completion window and a required budget. Every change mints a new version; running sessions keep the version they started on.
The budget is not optional. You cannot create an agent without a period cap, a per-task ceiling and a period, and the values are integer micro-USD on the wire (1 USD is 1,000,000).
```ts title="agent.ts"
const agent = await acme.agents.create({
name: "Acme Support",
model: "zai-org/GLM-5.2-FP8",
harness: "pi",
system: "You handle Acme's inbound support tickets.",
budget: {
cap_micro_usd: 50_000_000, // 50 USD per period
max_task_micro_usd: 2_000_000, // 2 USD per session
period: "month",
},
tools: {
default_config: { permission: "deny" },
configs: {
web_fetch: { enabled: true, permission: "allow" },
browser: { enabled: true, permission: "ask" },
},
},
});
```
Before every model call, Vetta prices the call and checks it against four limits in order: the organization balance, the agent's period cap, the task ceiling and the session budget. A call that would breach any of them is refused with a `budget.exceeded` event and the session pauses with a `budget_paused` stop reason instead of overrunning. Read the [budgets](/docs/concepts/budgets) docs for the full order of checks.
## Step 3: allow, ask, deny
The `tools` block above is the [policy](/docs/concepts/policies) layer. Each tool gets one of three permissions, and an unlisted tool takes `default_config.permission`:
- `allow` runs the tool without pausing.
- `ask` emits a `tool.confirm` event and pauses the session with an `awaiting_approval` stop reason until a human allows or denies the call.
- `deny` removes the tool from the model's available tools entirely; the model never sees it.
Policies are enforced at the tool-call boundary, before execution, not by asking the model to behave. Organization defaults and agent overrides are the two layers, and the most specific one wins.
A held session is `idle` with `stop_reason: "awaiting_approval"` and the blocked call in `pending_actions`. Answering it is one call from your own approval UI:
```ts
for (const action of session.pending_actions) {
await acme.sessions.confirmTool(session.id, {
tool_call_id: action.tool_call_id,
decision: "allow",
});
}
```
We cover the [approvals](/primitives/approvals) loop end to end in [How to add human approval to an AI agent](/blogs/how-to-add-human-approval-to-an-ai-agent).
## Step 4: give the agent someone to be
A customer's agent usually has to act as that customer: send email from their domain, receive a verification code. On Vetta that actor is an [identity](/docs/identity/overview), also called a persona: a named, durable actor that can own domains, email inboxes, phone numbers, OAuth connections and its own vault.
A persona's endpoints are concrete addresses and numbers, not boolean capabilities. The relationship with agents is many-to-many: one persona can be shared by several agents, and one agent can hold several personas and pick one per session.
```ts title="persona.ts"
const identity = await acme.identities.create({
name: "Acme Billing",
description: "Billing desk persona for Acme's support agent",
});
await acme.identities.attach(agent.id, identity.id);
const session = await acme.sessions.create({
agent_id: agent.id,
identity: identity.id,
message: "Reply to ticket #4821 and confirm the refund status.",
});
```
The CLI equivalents are `vetta identity create`, `vetta identity attach --agent --identity ` and `vetta session create --agent --identity `. See the [personas](/docs/identity/personas) docs for how endpoints and connections hang off an identity.
## Step 5: credentials the model never sees
Per-customer credentials are where DIY agent stacks leak. The moment a raw API token enters the prompt, a prompt injection can exfiltrate it. Vetta's [vault](/docs/identity/vault) is write-only: a credential's value travels once, on the create call, and is sealed server-side. There is no read or reveal route. The value is injected at the network boundary, outside the sandbox, when the agent reaches the server the credential is bound to, so nothing sensitive enters the model's context, the transcript or your logs.
```ts title="vault.ts"
const vault = await acme.vaults.create({
display_name: "Acme Billing",
identity_id: identity.id,
});
await acme.vaults.credentials.create(vault.id, {
kind: "static_bearer",
key: "helpdesk",
value: process.env.ACME_HELPDESK_TOKEN!,
mcp_server_url: "https://mcp.helpdesk.example/mcp",
});
```
The `static_bearer` and `mcp_oauth` kinds are keyed by `mcp_server_url` and injected when the agent connects to that server; the `env_var` kind, substituted on general sandbox egress for one bound host, is coming soon. Binding the vault to the persona keeps one customer's credentials attached to one customer's actor. Rotation is create-new then delete-old; there is no update method, on purpose. We wrote up the design in [Introducing Vault](/blogs/introducing-vault).
## Step 6: results back into your product
A [session](/docs/concepts/sessions) is one durable run of an agent. It can pause, resume, stream events, take follow-up input and be cancelled, and it carries its own budget. For a product integration you want a typed result, and a push when it is ready.
The typed result is a [structured output](/docs/capabilities/structured-outputs). Attach a JSON Schema on the agent or the session, and the document lands on the idle session as `structured_output`. Set `structured_output_required: true` to turn best effort into a hard requirement.
```ts title="session.ts"
const session = await acme.sessions.create({
agent_id: agent.id,
identity: identity.id,
message: "Triage ticket #4821 and report the outcome.",
metadata: { ticket_id: "4821" },
output_schema: {
type: "object",
properties: {
ticket_id: { type: "string" },
resolved: { type: "boolean" },
summary: { type: "string" },
},
required: ["ticket_id", "resolved"],
},
structured_output_required: true,
});
```
The push is a [webhook](/docs/capabilities/webhooks). Subscribe an HTTPS endpoint to the events you care about once per organization:
```ts
await acme.webhooks.create({
url: "https://app.example.com/hooks/vetta",
events: ["session.idle", "message.completed", "budget.exceeded"],
});
```
Each delivery is a signed JSON envelope, `{ id, type, created_at, data }`, and for `session.idle` the `data` carries the `session_id`, the `stop_reason` and the `structured_output`. Verify the HMAC-SHA256 signature in `Vetta-Signature` (`v1=`) over `{timestamp}.{raw_body}`, reject anything whose `Vetta-Timestamp` is more than 300 seconds old, and dedupe on the envelope `id`, which is stable across redeliveries. The `metadata` you set on the session is stored on it and echoed on its events, so a handler that reads the session back by `session_id` gets the `ticket_id` with it, without a lookup table.
## Revocation is part of the design
Because every piece of access is a first-class object, taking it away is a first-class call too. Revoke a customer's key with `keys.revoke`, detach a persona with `identities.detach`, delete a vault credential, or cancel a running session with `sessions.cancel`. None of these touch another customer's organization, because nothing in the path is shared between organizations. [How to revoke AI agent access instantly](/blogs/how-to-revoke-ai-agent-access-instantly) walks through each one.
## Pitfalls we still see
- **Isolation by prompt, not by platform.** "Only act for customer X" is a suggestion. An organization per customer, with a key that can only see that organization, is a boundary.
- **One key for every customer.** A single key with every tenant behind it turns one leak into a breach of every tenant. Mint per-organization keys and scope them.
- **`allow` as the default.** Start from `default_config: { permission: "deny" }`, allow the read-only tools, and put `ask` on anything that sends, pays or deletes.
- **Secrets in the context window.** If a credential can be printed by the model, it eventually will be. Vault it instead.
- **Parsing transcripts for results.** Attach an output schema and read `structured_output` from the `session.idle` webhook instead.
## Where to start
Pick one customer flow and make it multi-tenant end to end. Create an organization for one customer, create an agent with a small budget and a deny-by-default toolset, attach a persona, vault one credential, and run one session with an output schema and a webhook. Once that flow is isolated per customer, every other flow follows the same shape. The [TypeScript SDK](/docs/sdk/typescript) reference lists every method used above.
## FAQ
### How do I build multi-tenant AI agents into my SaaS?
Create one Vetta organization per customer and keep that organization's API key in your own secrets store, keyed by customer id. Everything the agent touches (agents, sessions, personas, vault, files, credit balance) lives inside that organization, and every request is scoped by organization id, so one customer's agent cannot read another's data through your code.
### How do I put a spending limit on a customer's agent?
Budgets are not optional on Vetta: an agent cannot be created without a cap, a per-task ceiling and a period. Every model call is priced before it runs and refused if it would breach the organization balance, the agent cap, the task ceiling or the session budget, and the session pauses with a budget_paused stop reason instead of overrunning.
### How do I keep customer credentials out of the model?
Store them as vault credentials. A credential's value travels once, on the create call, and is sealed server-side; there is no read or reveal route, and the value is injected at the network boundary when the agent reaches the server it is bound to, so it never enters the model's context. Rotation is create-new then delete-old.
### How does the agent's result get back into my product?
Attach a JSON Schema to the agent or the session. When the session goes idle, the typed document is on the session as structured_output and inside the session.idle webhook payload, so your handler can write the result straight into your database without prompt-parsing.
---
# Introducing Vault: write-only secrets your agent uses but never sees
URL: https://usenaive.ai/blogs/introducing-vault
Author: Dennis Zax
Published: 2026-07-01
Updated: 2026-09-07
Tag: Launch
> The Vault primitive stores API keys and tokens an agent needs but must never read. Values are sealed once, injected at the network boundary, and never returned by any route.
## TL;DR
- Vault is a write-only credential store: a secret travels once, on the create call, and no API route, SDK method, or CLI command ever returns it.
- The agent handles a placeholder reference, not the value. The real secret is injected outside the sandbox, at the network boundary, so it never enters the model's context, the transcript, or your logs.
- Three credential kinds: static_bearer and mcp_oauth for MCP servers (live today, matched by server URL), and env_var, bound to one exact destination host and substituted at egress (coming soon).
- Credentials are immutable. Rotation is create-new then delete-old, so last_injected_at always points at exactly one secret.
- Revocation is a delete. The value was never copied into the agent's environment, prompt, or transcript, so there is nothing else to chase; the agent's next call fails at the boundary.
Give an agent a real job and it needs real credentials: an API key for the payments provider, a token for the issue tracker, an OAuth grant for a customer's MCP server. The usual way to hand those over is to paste them into an environment variable or a system prompt. From that moment the secret lives in the model's context, in the transcript, in every log line that captured a tool call, and in the memory of whoever reads the run afterwards. A prompt injection or a careless `echo $KEY` is all it takes to ship it somewhere else.
Vault is our answer. It is the Trust primitive in Naïve's catalogue ([/primitives/vault](/primitives/vault)) and part of the identity layer of Vetta, the managed agent underneath. Its defining property is simple to state: **the agent uses the secret, and never sees it.**
## Write-only, by contract
A vault holds credentials an agent needs but must never read. Secret values are accepted exactly once, on the call that creates the credential, sealed server-side, and then never returned by any read. Not masked. Not redacted. Not returned.
This is enforced at the schema level rather than by policy. The credential object the API returns has `kind`, `key`, `host`, `mcp_server_url`, `placeholder_ref`, `expires_at`, `last_injected_at`, and `created_at`. It has no `value` field. The SDK's `credentials.list` could not hand a secret back even if a handler tried, and the CLI has no `reveal` command and, as the docs put it, never will. If you need the raw value, read it from wherever you originally got it.
The write path is also fenced. Creating a vault or adding a credential requires the `admin` scope; listing vaults and credential metadata only needs `agents:read`. An agent-facing key can confirm that a credential exists without ever being able to add, remove, or read one. And `POST /v1/vaults/{id}/credentials`, the one route that accepts a secret, is deliberately withheld from Vetta's own MCP tool catalogue, so it is not something a model can be handed as a tool.
## How injection works
The agent never handles the value. It handles a `placeholder_ref`, an opaque string of the form `vetta_ref_...`. The real secret is put back in outside the sandbox, at the network boundary, and only for the destination the credential was scoped to.
There are three credential kinds, each named by where the secret is allowed to go.
**`static_bearer`** is a fixed bearer token (an API key or a PAT) for an MCP server, keyed by `mcp_server_url`. When a session starts and the agent connects to a server whose URL matches one of the agent version's declared `mcp_servers`, the MCP connector adds the token on Naïve's side of the connection. The sandbox never receives it.
**`mcp_oauth`** is the same thing for MCP servers that use OAuth 2.0. It is injected the same way and refreshed automatically when a refresh block is supplied.
**`env_var`** is for CLIs, SDKs, and direct API calls that authenticate through an environment variable. Inside the sandbox the variable holds only the placeholder. When the agent runs something like `curl https://api.payments.example.com -H "Authorization: Bearer $PAYMENTS_API_KEY"`, the egress proxy checks the request host against the credential's bound `host`. If they match, the placeholder is swapped for the real key on the way out. If they do not match, the placeholder is sent as-is and the secret never leaves.
That last rule is what makes prompt injection survivable. A tricked agent that tries to `curl` the placeholder to `attacker.example.com` sends an inert string. A credential can never be exfiltrated to a host it was not scoped to, and `--host` is required precisely because the injector never wildcards.
One honest caveat. The two MCP kinds are live today. The `env_var` kind depends on all sandbox HTTPS egress being pinned through Vetta's egress proxy, which is coming soon; until it lands, the API refuses `env_var` credentials with `feature_not_configured` rather than storing a secret under a guarantee it cannot yet keep. We would rather refuse a write than pretend.
## Sealing a credential
One vault per identity is the usual shape. An identity is the persona an agent acts as, and a vault is bound to one persona (or left org-wide by omitting the identity). Values come from stdin by default, because an argv secret is visible in `ps` and lands in shell history.
```bash title="vetta vault"
vetta vault create --name "ava-vault" --identity idn_6sc5s97c5jt1ds84qngajv659p
# A static bearer token for an MCP server
printf '%s' "$TOKEN" | vetta vault set --vault vlt_... --mcp-bearer https://mcp.example.com/mcp
# An environment variable, bound to one exact destination host
printf '%s' "$PAYMENTS_KEY" | vetta vault set --vault vlt_... --env PAYMENTS_API_KEY --host api.payments.example.com
# Metadata only; no value is ever returned
vetta vault credentials --vault vlt_...
```
The same seven operations exist in the TypeScript SDK, and none of them reads a secret:
```typescript title="@usenaive-sdk/vetta"
const vault = await client.vaults.create({ display_name: "ava-vault", identity_id: ava.id });
await client.vaults.credentials.create(vault.id, {
kind: "static_bearer",
key: "Task tracker",
mcp_server_url: "https://mcp.example.com/mcp",
value: process.env.TRACKER_TOKEN!, // travels once, sealed server-side
});
const { data } = await client.vaults.credentials.list(vault.id); // key, kind, last_injected_at; never a value
```
If you run your project from a `naive.config.ts` and `naive up`, vaults are declared alongside identities and agents. Credential values are `{ from_env }` only, never a literal, and are read from your shell at apply time. Because a live value cannot be read back, `naive up` reconciles a credential's presence, not its value: a declared credential that already exists is `unchanged`, a missing one is created, and one you never declared is never touched.
```typescript title="naive.config.ts"
vaults: [
{
name: "Ava's keys",
identity: "Ava",
credentials: [
{ kind: "static_bearer", key: "GitHub", mcp_server_url: "https://mcp.github.example/sse", value: { from_env: "GITHUB_TOKEN" } },
],
},
],
```
## Rotation and revocation
There is no update route for a credential, and that is the contract rather than an omission. Rotation is two records and two ids: **set the new secret, then delete the old one.**
```bash title="Rotate"
printf '%s' "$NEW_TOKEN" | vetta vault set --vault vlt_... --mcp-bearer https://mcp.example.com/mcp
vetta vault rm --vault vlt_... --credential vcr_old...
```
The reason is audit, not ceremony. Each credential is immutable, so `last_injected_at` is always attributable to exactly one value. During an incident the question you need answered is "which secret was in use at the moment of this call?", and an in-place update would make that unanswerable at exactly the wrong time. `last_injected_at` is also the signal that tells you a credential was used, and when, without telling you what it is.
Revocation is a delete. The value was never copied into the agent's environment, prompt, or transcript, so there is no second copy in the sandbox to chase; the vault was the only place it lived. Deleting a vault soft-deletes the record and hard-deletes its secrets; the values are destroyed, not archived, and any agent relying on that injection starts failing at the egress boundary on its next call. Pair this with a short `expires_at` (an RFC 3339 timestamp) and a token cleans itself up without anyone remembering to. For the broader pattern of cutting an agent off, see [how to revoke AI agent access instantly](/blogs/how-to-revoke-ai-agent-access-instantly).
## At rest
Every value is protected with envelope encryption. A per-record data key encrypts the secret with AES-256-GCM, and that data key is itself wrapped by a key held in a managed key service, bound to your organization and to the identity that owns the vault. A vault maps to an identity and is referenced per session, so a credential is scoped to the persona that is allowed to act with it, and the wrapping key is bound to your organization and that identity.
## Where it fits
Vault covers one specific need: secrets an agent must act with but must never read. Two neighbours cover the rest.
- [Connections](/primitives/connections) hold third-party OAuth grants the identity has authorised. The agent connects to real services as its persona rather than through a shared bot token, and the tokens stay vaulted.
- [Approvals](/primitives/approvals) sit at the tool boundary. Every tool, including every MCP tool, carries an `allow`, `ask`, or `deny` policy, and MCP tools default to `ask`, so a newly exposed server tool never auto-runs even when its credential is present.
The combination is what lets an agent run unattended for months. Access is set up once by an operator with the `admin` scope, the agent acts under a policy that says what it may do, and nobody has to lend it a login. If you are embedding agents into your own product, this is also how you keep one customer's secrets away from another's: a vault per identity, one identity per customer, and the isolation comes from the platform rather than from your application code. We go deeper on that shape in [building AI agents into your SaaS](/blogs/building-ai-agents-into-your-saas).
## Get started
- The injection model: [usenaive.ai/docs/identity/vault](/docs/identity/vault)
- API reference: [usenaive.ai/docs/api/vaults](/docs/api/vaults)
- SDK: [usenaive.ai/docs/sdk/vaults](/docs/sdk/vaults)
- CLI: [usenaive.ai/docs/cli/vault](/docs/cli/vault)
- MCP connector and how credentials are matched to servers: [usenaive.ai/docs/capabilities/tools](/docs/capabilities/tools)
- Egress pinning status: [usenaive.ai/docs/computer/networking](/docs/computer/networking)
Read the vault page, seal one credential, and check `last_injected_at` after the first session. That is the whole loop: the agent did the work, and it still does not know the key.
## FAQ
### What is the Vault?
The Vault is Naïve's write-only credential store for agents. You seal an API key or token into a vault once, the agent references it by placeholder, and the platform injects the real value at the network boundary. No read path exists: every GET returns metadata only.
### Can an agent read a secret back out of the Vault?
No. There is no reveal route in the API, no reveal method in the SDK, and no reveal command in the CLI. The credential schema has no value field, so a list call could not return one even if a handler tried. If you need the secret, read it from wherever you originally got it.
### What stops a prompt-injected agent from leaking a key?
The agent only ever holds an opaque placeholder. An env_var credential is bound to one exact destination host; a request to any other host is sent with the inert placeholder, not the secret. MCP credentials are added on Naïve's side of the connection and never enter the sandbox.
### How do I rotate a credential?
Create the new credential, then delete the old one. There is deliberately no update route: each credential is immutable, so last_injected_at stays attributable to exactly one value and an audit can answer which secret was in use at the moment of a given call.
### How is the Vault encrypted at rest?
Each value is envelope-encrypted: a per-record data key encrypts the value with AES-256-GCM, and that data key is wrapped by a key held in a managed key service, bound to your organization and the identity that owns the vault.
---
# What is an autonomous company? A founder's guide
URL: https://usenaive.ai/blogs/what-is-an-autonomous-company
Author: Dennis Zax
Published: 2026-05-21
Updated: 2026-09-07
Tag: Guide
> An autonomous company is a repo: a naive.config.ts declaring the apps and the agent team, provisioned by naive up, run by a coordinator under per-agent budgets and approvals. Here is how one works.
## TL;DR
- An autonomous company is a business whose operating work is done by a team of agents, declared in one file and run under caps a person set and the agents cannot raise.
- On Naïve the unit is a template: a repository with a naive.config.ts that declares the apps, the agents, their tools, their budgets and their schedules. naive up provisions it into your organization.
- The team has a coordinator that owns the task and delegates bounded briefs to sub-agents, each in its own session with its own context, one level deep.
- Every model call and priced tool call is quoted against the budget before it runs. Anything that touches money, mail, publishing or identity can be set to ask, so the session freezes until you decide.
- You are not out of the loop. You approve what the policy says needs approval, and you edit the config; the rest runs without you.
## What is an autonomous company?
The home page says it in six words: build your dream company with agents. This is the longer version. An autonomous company is a business whose operating work is done by a team of agents. Not one chatbot with a long prompt: a roster of agents with different roles, tools and budgets, coordinated by one of them, running on a schedule and on tasks, with a person approving the steps that need a person.
Three things make that a company rather than a demo.
First, it is **declared, not improvised**. Who the agents are, what they may call, what they may spend, and when they wake is written in one file and applied to the platform. Nothing an agent does mid-run can raise its own ceiling.
Second, it is **bounded by money**. Every agent carries a USD budget, and every model call and priced tool call is quoted against it before it runs. A company that cannot overspend is one you can leave running overnight.
Third, it is **governed at the tool boundary**. Each tool resolves to allow, ask, or deny. The actions that matter, sending mail, publishing a post, moving money, acting as an identity, can be made to wait for you.
On Naïve, all three come from the same place: a template.
## A company is a repo
A **template** is a repository with a `naive.config.ts` in it. The repo holds the company's apps (a dashboard, sometimes a public site), the server behind them, and the config that declares the agent team. Clone it, bind the clone to your organization, and bring it up:
```sh title="Three commands"
naive template ai-media-channel # clone the template repo
naive claim --key sk_... # bind the clone to your organization
naive up # provision the apps, the agents, their budgets
```
`naive up` reads the config and reconciles it against the platform, resource by resource: skills, identities, vaults, apps, then agents, in that order so an agent can reference the rest. The report lists every resource as created, updated, unchanged, deleted or refused. Re-running is always safe: every resource is keyed by its name in the config, and nothing is deleted by omission except a cron dropped from an agent's `schedules`. `naive up --dry-run` shows what an apply would do without writing anything.
What the config declares, per the [CLI reference](/docs/cli/naive):
| Key | What it is |
| --- | --- |
| `apps[]` | The web apps the company runs: name, type (`fullstack` or `frontend_only`), the directory to deploy |
| `agents[]` | The team: each agent's `name`, `model`, `budget`, `system` prompt, `tools`, `skills`, `identity`, `schedules` and `intake` |
| `agents[].schedules[]` | Cron deployments, each with its own `budget_micro_usd` |
| `agents[].intake` | The message a new agent is sent once, on the apply that creates it |
| `skills[]`, `identities[]`, `vaults[]` | Playbooks the agents read on demand, the personas they act as, the credentials they use but never see |
The config is the source of truth. A field edited by hand in the dashboard is drift, and the next `naive up` converges it back. That is what makes "an agent cannot raise its own budget" a fact rather than a hope: the only thing that changes the company is a person committing a change and applying it. If you would rather not open a terminal, the available templates on [/templates](/templates) deploy from Studio, which does the same thing in the browser.
## What is in a template today
**Agency** templates run client work with a pipeline and deliverables; **Media** templates run a channel that produces and posts on a cadence.
- [AI Media Channel](/templates/ai-media-channel): a writer, a producer and a channel manager. The writer scripts to the channel's hook library, the producer renders and voices each short, the channel manager keeps the calendar full and holds every post as pending until you approve it. This is the workload [Social Bench](/benchmark) measures.
- [Clipping Channel](/templates/clipping-channel): long-form in, clips out. A scout scores the moments worth cutting, an editor reframes and captions them, an account manager publishes on cadence behind a review queue.
- [AI Automation Agency](/templates/ai-automation-agency): five agents (sales, architect, builder, qa, account manager) behind a CRM dashboard and a public site whose enquiry form feeds the pipeline.
- [SEO / GEO Agency](/templates/seo-geo-agency): a crew provisioned per active client, re-auditing rankings and AI-answer citations on a schedule.
Every one of them ships with the same budget line: $10 per agent per day, $2 per task. Newsletter, Paid Ads Agency and Recruiting Agency are catalogued and coming.
## The coordinator and its team
The team is a plain agent that has been given a roster. On the platform this is the `multiagent` object on an agent's configuration: `type: "coordinator"`, an `agents` list of one to twenty members pinned by version, and optionally `board: true`. There is no separate team object; versioning, budgets, sessions and events apply unchanged.
The **coordinator** owns the top-level task and the top-level context. It coordinates the roster two ways:
- **Delegation** is point-to-point and one-shot. The coordinator calls `send_to_agent` with a member's name and a brief. The member runs in its own session, with only that brief as its opening context, and its answer folds back into the coordinator's transcript as a tool result. The coordinator can fan out several briefs, then park once on `wait_for_agents` until they finish.
- **The board** is broadcast and durable. Any thread on the team calls `board_read` and `board_write`. Cards have four fixed statuses, `todo`, `doing`, `blocked`, `done`, and they outlive every session that touched them. If the coordinator's session ends, the next one picks up from the board.
Delegation is **one level deep**: a member is never handed `send_to_agent`, so it cannot delegate and cannot be talked into it. And the reason to delegate is not speed but context and cost. A member starts fresh with only its brief, so every downstream model call carries far fewer tokens than the coordinator dragging its whole history through every turn. The [team reference](/docs/team/overview) has the full mechanics; [sub-agents](/primitives/subagents) is the short version.
## Per-agent budgets
An agent is not creatable without a budget. It has three required fields: a period cap, a per-task ceiling, and the reset period (`day`, `week`, or `month`). On the wire these are integer micro-USD; the CLI's `--budget-usd` accepts a decimal and converts client-side.
Before every model call or priced tool call, the runtime computes a quote, an upper bound on what the call will cost at the session's completion window, and checks it against the organization balance, the agent's period cap, the task ceiling, and any session budget. If the quote would breach any of them, the call is refused, a `budget.exceeded` event is emitted, and the agent is told in-band so it can wrap up rather than crash.
In a team this composes. The coordinator's budget bounds the whole team, and every member's call is still quoted against the same organization balance and the coordinator's task ceiling, however many members fan out. Read [Budgets](/docs/concepts/budgets) for the reference and [Context & budgets](/docs/team/context-and-budgets) for how spend flows across a team.
## Approvals where money or identity is touched
Every capability flows through one place, the tool call, and that is where policy is enforced:
| Permission | What happens |
| --- | --- |
| `allow` | The tool runs without confirmation |
| `ask` | The runtime emits a `tool.confirm` event and pauses the tool until you approve or reject it |
| `deny` | The tool is not offered to the model at all |
`allow` is the wrong default for anything irreversible or externally visible. Sending mail, publishing a post, moving money, connecting a third-party account as a persona: set those to `ask`, or `deny` if the agent should never have the capability. That is what the templates do when they say a post is held as pending.
When an `ask` tool fires, the session goes idle with `stop_reason: "awaiting_approval"`. A held tool consumes no budget while it waits, and because the loop is durable it can sit on a confirmation for hours at storage cost only. You answer with `vetta session confirm`, and the decision records who approved it on the event and the audit trail. A policy can bound the wait with `ask_timeout_seconds` and an `on_timeout` of `deny` (the default), `allow`, or `escalate`.
Identity is gated the same way. An **identity** is a named persona an agent acts as: verified domains, inboxes, phone numbers, third-party connections authorized over OAuth, and a vault of credentials injected at the network boundary so the agent uses secrets it can never read. Connections run under the same allow / ask / deny filter as any other tool. See [Approvals](/primitives/approvals), [Vault](/primitives/vault), and the [policies reference](/docs/concepts/policies). We wrote up the approval loop in detail in [how to add human approval to an AI agent](/blogs/how-to-add-human-approval-to-an-ai-agent).
## What runs without you, and what does not
Runs without you: the schedules, which wake each agent, spend their own per-run budget, and go back to sleep. The intake message that starts a new agent working. Delegation and the board. Every budget check. Everything set to `allow`.
Waits for you: everything set to `ask`. Any edit to the company, which is a commit to `naive.config.ts` and a `naive up`. Authorizing a third-party connection over OAuth. Funding the organization balance that every quote is checked against.
We do not claim the company runs itself with no one watching. It runs within the caps you set and stops, cleanly, when it hits one. That is a different promise than hands-off, and it is the one we can keep.
The legal entity is a separate question from the operating company; we cover the US case in [introducing formation](/blogs/introducing-formation).
## Where to start
Pick the template on [/templates](/templates) closest to the company you want. Deploy it from Studio, or clone it and run the three commands. Watch the first intake session run. Then open `naive.config.ts`, change one thing (a budget, a schedule, a tool set to `ask`), run `naive up`, and read the report. That loop, edit, apply, observe, is how you run the company from then on.
If none of the templates fit, the same config works from zero: declare the agents you want, the tools they get, the budget they run under, and bring it up. For how the platforms in this space compare, see our [top 10 platforms for autonomous companies](/blogs/top-10-platforms-autonomous-companies).
An autonomous company is not a vision. It is a repository you can clone this afternoon, with a team already declared in it and a cap on what that team can spend. What it should do is still your call.
## FAQ
### What is an autonomous company in plain English?
A business where the day-to-day work is done by a team of agents rather than employees. On Naïve it is a repository with a naive.config.ts that declares the apps and the agent team; naive up provisions it, and a person stays in the loop for the actions the policy marks as needing approval.
### How is this different from using an AI assistant in my business?
An assistant answers when you prompt it. An autonomous company runs on a schedule and on its own tasks: a coordinator agent takes the work, delegates to sub-agents, and each of them runs in a durable session under a budget. You set the caps and answer approvals instead of typing prompts.
### Do I need to write code to start one?
No. The available templates on /templates deploy from Studio in the browser, and the same template can be cloned and brought up from the CLI with three commands. Editing the company later is editing naive.config.ts and running naive up again.
### How do I keep the agents from overspending?
An agent cannot be created without a budget: a period cap, a per-task ceiling, and a reset period. Every call is priced before it runs and refused if it would breach the cap. The templates ship at $10 per agent per day with a $2 per-task cap.
### What happens when an agent wants to do something risky?
Set that tool's policy to ask. The runtime pauses the tool, the session stops with awaiting_approval, and the held tool consumes no budget while it waits. You approve or reject it, and the decision records who answered. Set deny and the tool is never offered to the model at all.
---
# LLC formation for AI agents: a real US company, KYC to EIN
URL: https://usenaive.ai/blogs/introducing-formation
Author: Dennis Zax
Published: 2026-05-02
Updated: 2026-09-07
Tag: Launch
> Form a real US LLC for an agent's identity. Verified founders go in, a company with an EIN comes out, and the entity becomes what the persona's domains, inboxes, numbers and cards attach to.
## TL;DR
- The LLC primitive incorporates a real US company for an agent, in one funnel that runs from founder KYC to a filed entity with an EIN.
- It sits inside the identity model: an identity is a named persona, the legal entity is what that persona acts through, and domains, email, phone and cards hang off it.
- KYC is the gate. No company exists until every founder has passed a hosted identity check.
- Formation is white-label. Your agent talks to one primitive; the filing provider behind it is never part of your surface.
- Everything downstream is governed by policy: allow, ask or deny per action, fail-closed, with the audit log recording who did what.
## Why an agent needs a legal entity
Almost every autonomous business runs into the same wall on day one. The agent can research a niche, write, render, post and reply, but it cannot own anything. A domain registrar wants a registrant. A payments provider wants a business. A vendor wants an EIN on the W-9. The liability of whatever the agent does has to land somewhere, and "a script running on someone's laptop" is not an answer a state, a bank or a counterparty accepts.
Company formation was built for humans: notarized signatures, a physical registered office, a letter from the Secretary of State. An agent cannot sign paper or receive mail at a street address. So the entity ends up being formed by hand, out of band, and the agent inherits it as a pile of PDFs in someone's inbox. Nothing in the runtime knows the company exists.
The LLC primitive closes that gap. It incorporates a real US company for an agent, and it does so inside the identity model, so the entity is a first-class thing the rest of the persona attaches to rather than a document filed away somewhere.
## Where formation sits in the identity model
An [identity](/docs/identity/overview) is a named, described persona an agent acts as in the world. It has a `name` and a `description` so the model knows who it is being, and it owns concrete endpoints: the `domains` it sends and receives on, the `emails` and `phones` it answers as, the `connections` it has authorized over OAuth, and a write-only [vault](/docs/identity/vault) for the secrets it uses but never reads.
The relationship between agents and identities is many-to-many. One agent can hold several personas and pick one per session; one persona can be shared by several agents. Agents hold a persona through a grant, not a field on the agent, and the acting persona is named on the run, so choosing one rewrites nothing on the agent and mints no new agent version.
Formation is the layer underneath those endpoints. The order we designed for is:
1. An identity gets created: a persona with a name and a description.
2. The persona gets a **legal entity**: the LLC formed through this primitive.
3. The entity gets **[domains](/primitives/domains)**, then **[email](/primitives/email)** on a verified domain, then a **[phone](/primitives/phone)** number with carrier registration.
4. Money attaches last: **[cards](/primitives/cards)** with a hard spend cap the agent cannot raise.
Each of those steps is its own primitive with its own page. The endpoints are gated by the identity's [policy](/docs/identity/policies); the cards carry a cap the agent cannot raise. The entity is what makes them belong to something real. `billing@acme.com` is an inbox on a domain that a company owns, not an anonymous mailbox.
## The funnel: KYC to EIN
The primitive is one funnel with four gates. Every gate emits an output the next one consumes, and the whole thing runs as an async job you can watch alongside renders and deploys in [jobs](/primitives/jobs).
### 1. Verify the founders
No company can exist until the people behind it are verified. [KYC](/primitives/verification) is a hosted identity check for founders, and it is the gate in front of formation, not an optional add-on. The agent kicks off the check; each founder completes it in a hosted flow; the formation cannot be submitted until every member has passed. We wrote about the verification primitive in [Introducing KYC](/blogs/introducing-kyc).
### 2. Submit the formation
With founders verified, the agent submits the formation: the company name, the state to file in, and the description of the business. Submission is where money and legal consequence enter, so it is exactly the kind of action the policy layer is for. Every server-executed tool resolves to `allow`, `ask` or `deny` before it runs. With `ask`, the session pauses, emits a confirmation event and waits at storage cost until someone approves or denies. With `deny`, the tool is never offered to the model at all. Set the submission to `ask`, the way you would any other spending action: the agent prepares it, a person confirms it.
### 3. State filing
The filing goes to the state through the provider behind the primitive. Your agent does not know which one and does not need to. It sees a job moving through statuses, and the entity name it asked for.
### 4. EIN and documents
When the state approves, the funnel finishes with the EIN application and the formation documents. The output is an entity the runtime knows about: a name, a state, an EIN, and the paperwork, attached to the identity that asked for it.
That is the whole shape. KYC to EIN, one funnel, no human copying values between portals.
## White-label by design
We used to publish a post announcing which incorporation partner we had integrated. That post now redirects here, and it is worth saying why we retired it.
Every vendor behind Vetta is white-labeled. The sandbox provider, the model gateway, the payments processor, the email and telephony carriers, the formation provider: none of them appear in the API, the CLI, the SDK, the dashboard, the docs, or an error message. This is a hard rule for us, not a style preference.
For formation specifically it matters in three ways:
- **Your surface is stable.** If we change the provider behind the primitive, the primitive does not change. Your agent still submits a formation, still polls a job, still receives an EIN.
- **Your users see your product.** If you build incorporation into your own platform, the person forming the company sees your brand, not a chain of vendors.
- **The agent cannot route around it.** There is no vendor API key sitting in the agent's context to misuse. The agent calls a primitive; the credentials that make the filing happen are ours, not the agent's.
The trade-off is that we cannot tell you which firm files your paperwork, and we are comfortable with that. What we can tell you is that founders are KYC-verified before anything is submitted, and that the resulting entity is yours.
## Build the rest of the persona on the entity
Once the company exists, the identity around it is ordinary identity work, and that part is fully documented. Create the persona, grant it to an agent, and provision its endpoints:
```bash title="A persona, granted to an agent, with endpoints"
vetta identity create \
--name "Acme Labs" \
--description "Operating persona for Acme Labs LLC"
vetta identity attach --agent agt_... --identity idn_...
vetta identity email provision --identity idn_... --address hello@acme-labs.com --domain dom_...
vetta identity phone provision --identity idn_... --tier standard \
--legal-name "Acme Labs LLC" --tax-id 88-1234567 --country US --contact-phone +15555550123
```
The phone line is the first place the entity pays for itself. The `standard` messaging tier, the one with real throughput, requires a registered business and a tax ID; the `sole_prop` tier exists for personas that have neither. An LLC with an EIN qualifies for `standard` on day one.
The same flow from TypeScript:
```typescript title="Create and grant an identity"
import { randomUUID } from "node:crypto";
import { createClient } from "@usenaive-sdk/vetta";
const vetta = createClient({
baseUrl: "https://api.vetta.sh",
apiKey: process.env.VETTA_API_KEY!,
fetch: globalThis.fetch,
idempotencyKey: () => randomUUID(),
});
const identity = await vetta.identities.create({
name: "Acme Labs",
description: "Operating persona for Acme Labs LLC",
});
// agent is the result of vetta.agents.create(...)
await vetta.identities.attach(agent.id, identity.id);
```
`attach` is idempotent: re-running a provisioning script answers `attached: true, created: false` rather than failing, so `naive up` can reconcile identities by name on every run.
When the agent holds more than one persona, the acting one is chosen per session or per deployment, never by rewriting the agent:
```bash title="Pick the persona for a run"
vetta session create --agent Concierge --identity acme-labs
vetta deploy create --agent Concierge --identity acme-labs \
--cron "0 9 * * *" --budget-usd 5 --prompt "Answer anything waiting in the inbox."
```
Domain purchase and phone provisioning are approval-gated by default, so the first time an agent tries to buy `acme-labs.com` for its new LLC, a person confirms it. After that, the persona runs unattended. The full domain story is in [Introducing domains](/blogs/introducing-domain).
## Building an incorporation flow into your own product
Our old guide on building an agentic incorporation platform also redirects here, because the answer got simpler. You do not build the filing pipeline. You build the funnel around ours.
A working shape looks like this:
- **One identity per customer entity.** The persona is the unit of ownership. Its name and description tell the model who it is acting for; its endpoints are specific addresses and numbers, not booleans.
- **KYC as the front door.** Send founders through the hosted check before you let them name a company. The primitive enforces this anyway, but surfacing it early saves a failed submission.
- **Formation set to `ask`.** Let the agent prepare the submission and let a human approve the spend. The session holds at storage cost while it waits; nothing is billed for idle compute.
- **Provision endpoints in order.** Domain, then email on the verified domain, then phone. Each is a primitive with its own readiness gates, and a send attempted before a domain finishes verifying comes back retryable rather than failing.
- **Read the audit log.** Every control-plane action is attributed to a principal and queryable later, so "who approved this formation and when" is a query, not an archaeology project.
None of this requires vendor contracts on your side. The provider relationships, the carrier registrations and the filing credentials are ours; your integration is the primitives.
## Get started
- The primitive: [/primitives/formation](/primitives/formation)
- The gate in front of it: [/primitives/verification](/primitives/verification)
- Identity concepts: [usenaive.ai/docs/identity/overview](/docs/identity/overview) and [personas](/docs/identity/personas)
- The API: [usenaive.ai/docs/api/identities](/docs/api/identities), the SDK: [usenaive.ai/docs/sdk/identities](/docs/sdk/identities), the CLI: [usenaive.ai/docs/cli/identity](/docs/cli/identity)
Install the CLI with `npm i -g @usenaive-sdk/vetta-cli@^0.4.0`, create an identity, and give it a company to stand on.
## FAQ
### What does the LLC primitive do?
It incorporates a real US company for an agent's identity. The funnel runs from founder KYC through submission and state filing to an EIN and the formation documents, and the resulting entity is what the persona's domains, inboxes, phone numbers and cards attach to.
### Can an AI agent form a company on its own?
The agent drives the funnel, but the principals behind it are verified people. Every founder completes a hosted KYC check before a formation can be submitted, and the state filing is handled by the provider behind the primitive. You still own the legal and tax obligations of the entity, so involve counsel where you need to.
### Which vendor handles the filing?
We do not expose one. Formation is white-label: your agent, your users and your logs see a single primitive with a job status, not a third-party brand. If the provider behind it changes, nothing in your integration changes.
### Where does the company sit in the Vetta object model?
Under an identity. An identity is a named, described persona with a name, description, domains, emails, phones, connections and a vault. The legal entity is the layer that makes those endpoints belong to something real, and agents hold the identity through a grant rather than as a field on the agent.
### How do I get started?
Read the identity docs at usenaive.ai/docs/identity/overview, create an identity with the CLI or SDK, then start with KYC on /primitives/verification. The LLC primitive page at /primitives/formation covers the funnel.
---
# Introducing KYC: identity verification for your agent, done once
URL: https://usenaive.ai/blogs/introducing-kyc
Author: Dennis Zax
Published: 2026-05-01
Updated: 2026-09-07
Tag: Launch
> Verify the human behind an agent once, then let that identity hold domains, inboxes, phone numbers, cards and a company. Here is what verification unlocks, and what it never exposes to the agent.
## TL;DR
- KYC is the verification step of the Identity primitive: a hosted identity check a real person completes once, attached to one identity on Naïve.
- An identity is verified once. Everything it holds afterwards, web domains, inboxes, phone numbers, connections, a vault, a company, hangs off that one verified persona.
- Agents hold identities through a grant, not a field. One agent can act as several personas, one persona can be shared by several agents, and a session names which one it acts as.
- The agent sees a name, a description and the addresses it owns. It never sees a document, and the identity object has no field that could carry one.
- Every action an identity takes passes the policy layer before any external request is made, and fails closed if enforcement is unavailable.
Every real-world action an agent takes eventually meets a counterparty who asks the same question: who is actually behind this? A registrar wants a registrant. A carrier wants a registered sender before it lets a number text anyone. A state wants a named organizer on the filing. A card issuer wants a cardholder. None of them accept "an agent" as the answer.
KYC is how an agent on Naïve gets one. It is the verification step of the [Identity primitive](/primitives/verification): a real person completes a hosted identity check once, and the result is attached to an identity, the named persona the agent acts as. From then on, the primitives that need a real person behind them, a company first of all, build on that identity instead of asking again.
## The identity model in one paragraph
An [identity](/docs/identity/overview) is a named, described persona an agent acts as in the world. It has a `name` and a `description` so the model knows who it is being, and it owns concrete endpoints rather than booleans: `billing@acme.com` is a different identity from `support@acme.com`, and a phone number belongs to one identity, not to the org.
The pieces an identity bundles today:
| Piece | What it is |
| --- | --- |
| Web domains | Verified domains the identity sends and receives on, provisioned, brought-your-own, or purchased |
| Email | Real inboxes on a verified domain |
| Phone | Provisioned numbers with carrier-registered messaging |
| Connections | Third-party apps the identity has authorized over OAuth |
| Vault | A write-only credential store injected at the network boundary |
Verification is the piece that makes the persona a real principal. The person behind it is checked once; the company that follows is gated on that check, and the domain, inbox and number are issued to the same persona. On the site this is the Identity & legal group: [KYC](/primitives/verification), [LLC](/primitives/formation), [Domains](/primitives/domains), [Mail](/primitives/email), [Phone](/primitives/phone), plus [Cards](/primitives/cards) on the money side, spend-capped virtual cards planned to draw on the identity's wallet.
## Verified once, held many times
The relationship between agents and identities is many-to-many, and that is the reason verification only has to happen once.
- One agent can hold several identities, an inbound support persona and an outbound sales persona for example, and choose which to act as per task.
- One identity can be shared by several agents, a `billing@acme.com` persona handled by a triage agent and a refunds agent.
Agents reference identities; identities reference no agents. The grant hangs off the agent, and it is idempotent: attaching a persona that is already attached succeeds with `attached: true` and `created: false`, so a provisioning script can re-run safely. Detaching removes the grant and leaves the identity, and its verification, untouched.
```bash title="Create a persona, grant it to two agents"
vetta identity create --name "Acme Billing" \
--description "Handles invoices and refunds for Acme"
vetta identity attach --agent Refunder --identity "Acme Billing"
vetta identity attach --agent Triage --identity "Acme Billing"
```
The same thing from TypeScript:
```typescript title="The persona and the grant"
import { randomUUID } from "node:crypto";
import { createClient } from "@usenaive-sdk/vetta";
const vetta = createClient({
baseUrl: "https://api.vetta.sh",
apiKey: process.env.VETTA_API_KEY!,
fetch: globalThis.fetch,
idempotencyKey: () => randomUUID(),
});
const billing = await vetta.identities.create({
name: "Acme Billing",
description: "Handles invoices and refunds for Acme",
});
// refunder and triage are the agents from vetta.agents.create(...)
await vetta.identities.attach(refunder.id, billing.id);
await vetta.identities.attach(triage.id, billing.id);
```
When an agent holds more than one identity, the acting persona is named on the run, not baked into the agent. Selecting one mints no new agent version:
```bash title="Pick the persona per session"
vetta session create --agent Concierge --identity "Acme Billing"
```
For unattended work the persona goes on the deployment instead, and it is resolved and grant-checked when the schedule is written, so a bad reference fails while you are watching rather than at 03:00.
## What verification unlocks
A verified identity is the principal the rest of the Identity primitive builds on.
**A real company.** [Formation](/primitives/formation) takes an agent from KYC to EIN in one funnel: a real US company, with the verified person as the human behind the filing. We wrote about it in [Introducing Formation](/blogs/introducing-formation).
**A domain, an inbox, a number.** [Domains](/primitives/domains) gives the identity verified web domains it can send and receive on. [Mail](/primitives/email) puts real inboxes on those domains. [Phone](/primitives/phone) provisions numbers with carrier registration, which is exactly the kind of step that wants a named, verified party behind it. See [Introducing Domain](/blogs/introducing-domain) for the domain side.
**Authorized apps.** [Connections](/docs/identity/connections) let the identity authorize third-party apps over OAuth and act in them as itself: each connected account is bound to one identity, and the agent never handles the token. Which apps, and which tools inside them, is scoped by the identity's [policy](/docs/identity/policies).
**Secrets it can use but never read.** The [vault](/primitives/vault) is scoped to the identity. A credential is written once, referenced by name, and swapped in at the network boundary only for the host it is bound to. No route returns a value.
**Cards.** [Cards](/primitives/cards) are virtual cards with a hard spend cap the agent cannot raise. They draw on the [agent wallet](/docs/identity/wallet), which is carried by the identity and gated by the same policy as its email, phone and connections. The wallet is still marked coming soon in the docs, so treat its shape as direction rather than contract.
None of these primitives run a KYC check of their own. They read the identity.
## What it never exposes to the agent
Verification produces exactly one thing the runtime cares about: a persona that is known to have a real person behind it. It does not produce anything the agent can read.
When a session acts as an identity, the [persona docs](/docs/identity/personas) are precise about what is put in front of the model: the persona's name, description and the addresses it owns, for that run only. That is the whole surface. There is no document, no date of birth, no photograph in the context window, because none of it is part of the identity object.
You can check this against the [identity object in the API](/docs/api/identities). Its fields are `id`, `name`, `description`, `emails`, `phones`, `domains`, `connections`, `metadata`, `created_at` and `updated_at`. There is no field that could carry a document, and nothing in the reference returns one. The same design shows up in the vault: secret fields are write-only, reads return metadata only, and the model, the transcript and your logs only ever see a placeholder. We treat verification material the same way we treat a credential. The agent gets the effect of it, never the substance.
This matters for the obvious reason, that a prompt-injected agent cannot hand over what it was never given. It also matters for the operator. Verification is a hosted step a human completes, and the material stays on that side of the boundary.
## Every action is still gated
Verification says who the persona is. It does not say what the persona may do. That is the job of the [policy layer](/docs/identity/policies), and it runs on every identity action before any external request is made.
A policy scopes which primitives the identity may use (`email`, `phone`, `connections`, `vault`, `domains`), which connections it may authorize and which tools it may call inside them, and which actions need a human to say yes. Some are approval-gated out of the box: connecting a new third-party app, provisioning a phone number, and purchasing a domain. When an action needs approval the session emits a confirmation event and holds at storage cost until you respond.
An approval rule can also carry a threshold: an action whose quoted cost exceeds it forces approval even if the primitive is otherwise allowed. This composes with the agent's mandatory USD budget. The budget caps total spend; the policy governs which actions may spend at all. Enforcement is at the tool-call boundary and fails closed, so an identity can never reach a system or spend money the policy did not grant, verified or not.
## Why the check is not a tool the agent calls
The old way to add identity checks to a product was to integrate a verification vendor directly: build the redirect, store the result, own the PII handling, and repeat it in every product that needs it. That is a lot of surface area for a fact that is true exactly once per person.
Naïve is white-labeled end to end. There is no vendor name in the API, the CLI, the SDK or the dashboard, and the check is a hosted step for the person behind the identity, not a tool call the agent makes. A human completes it, the identity becomes the verified principal, and the agent inherits the standing without ever touching the flow. The agent's job is to act as the persona; ours is to make sure the persona is real.
## Get started
Create an identity, grant it to an agent, and give it an inbox to work from:
```bash title="From nothing to an inbox"
vetta identity create --name "Ava Sales" --description "Outbound SDR persona for the growth team"
vetta identity email provision --identity ava --address ava@acme-mail.com
vetta identity attach --agent Concierge --identity ava
vetta session create --agent Concierge --identity ava
```
Then read the reference: [Identity overview](/docs/identity/overview), [Personas](/docs/identity/personas), [Identity policies](/docs/identity/policies), [Credential vault](/docs/identity/vault), and the [Identities API](/docs/api/identities). The KYC primitive itself lives at [/primitives/verification](/primitives/verification), and the company it gates at [/primitives/formation](/primitives/formation).
## FAQ
### What is KYC on Naïve?
KYC is the verification step of the Identity primitive. A real person completes a hosted identity check once, and the result is attached to an identity, the named persona an agent acts as. From then on the identity can hold web domains, inboxes, phone numbers, connections, a credential vault and, through Formation, a real company.
### Does the agent get access to the verification documents?
No. When a session acts as an identity, the model is given the persona's name, description and the addresses it owns, nothing else. The identity object exposed by the API carries no document fields, and nothing in the API reference returns verification material.
### Do I have to re-verify for every agent?
No. Verification attaches to the identity, not the agent. Agents hold identities through a grant, so a verified persona can be attached to as many agents as you like, and detached without touching the verification.
### How does KYC relate to Formation?
Formation forms a real US company for an agent, and a verified human principal is the gate before the filing. The same verified identity is the one that later owns the company's domain, inbox and phone number, so you verify once and build the rest on top.
### Which vendor runs the check?
Naïve is white-labeled end to end. The check is hosted by Naïve and the result lives on your identity; you never integrate a third-party verification API, and no vendor name appears in the product.
---
# Introducing Domains: DNS, email and apps for your agent's identity
URL: https://usenaive.ai/blogs/introducing-domain
Author: Dennis Zax
Published: 2026-04-16
Updated: 2026-09-07
Tag: Launch
> Every organization gets a working system domain at signup. Connect a domain you already own, verify it, and hang inboxes and apps off it through the Vetta API, SDK and CLI.
## TL;DR
- A domain is the foundation an identity's inbox sits on and the anchor an app is served from. It belongs to the organization, and several personas can send from it.
- Three registrar kinds: system (auto-provisioned under Vetta's shared apex at signup), external (bring your own, verified through the DNS records we hand back), and purchased (bought through Vetta, approval-gated by policy).
- Verification is not one boolean. dns_status gates sending, inbound_status gates receiving, app_connect_status gates HTTP. Each track advances on its own.
- The API is six routes under /v1/domains, mirrored by client.domains in the SDK and vetta identity domain in the CLI. Verify is re-runnable.
- Records the platform owns (DKIM, _dmarc, inbound MX) are readable but never editable by the agent, so it cannot break its own deliverability.
## An agent needs somewhere to be reached
An agent that runs unattended needs a hostname. Its inbox needs one. The app it ships needs one. Its mail needs SPF, DKIM and MX records a receiving server will trust. Doing that by hand across registrars and DNS panels is slow, easy to get wrong, and puts a person back in the loop every time an agent needs a new address.
Domains is the Identity primitive that removes that step. It is documented at [usenaive.ai/docs/identity/domains](/docs/identity/domains) and sits on the [Domains](/primitives/domains) page of the catalog. This post covers the three registrar kinds, the three verification tracks, and the exact API, SDK and CLI shapes you drive them with.
## A domain belongs to the organization
A domain is an **organization** object, not an identity one. Two personas routinely send from the same domain, and the system domain is minted for the organization as a whole. An identity reaches a domain through the inboxes provisioned on it, which is why the routes live at `/v1/domains` rather than under an identity. Domains are shared infrastructure; inboxes and phone numbers are per persona; the [policy](/docs/identity/policies) layer decides which of them an agent may reach for at all.
## Three registrar kinds
A domain's `kind` records where it lives and who owns it.
| Kind | Who owns it | How it gets there |
| --- | --- | --- |
| `system` | Vetta | Auto-provisioned at signup as a subdomain under Vetta's shared apex, named after your organization slug. |
| `external` | You, at your registrar | Bring your own domain. Vetta returns the DNS records to add, then verifies them. |
| `purchased` | Vetta registers on your behalf | Bought through Vetta and approval-gated by policy by default. |
### System domains: a working domain before you do anything
Every organization gets a `system` domain automatically. Provisioning is fire-and-forget: Vetta creates the domain at the email layer with sending and receiving enabled, writes the SPF, DKIM, MX and DMARC records straight into its own shared apex zone, triggers verification, and polls briefly. A background sweep re-verifies any domain still `pending`, so a transient propagation delay resolves itself.
```bash title="The system domain is already there"
vetta identity domain list
# NAME KIND STATUS DNS_STATUS APP_CONNECT
# acme. system active provisioned pending
```
Because Vetta controls the zone there is no registrar step, which makes the system subdomain the fastest way to get an agent sending and receiving mail. Its zone is list-only: Vetta manages the records and any attempt to edit them returns `403`.
### External domains: bring your own
Connecting a domain you already own is a two-call flow. `connect` returns the DNS records to publish at your registrar; `verify` checks them and advances the status tracks.
```bash title="Connect, publish, verify"
vetta identity domain connect --domain acme-mail.com --kind external --receive
vetta identity domain records --domain dom_... # publish these, then:
vetta identity domain verify --domain dom_...
```
The record set is returned dynamically: typically a `TXT` for SPF, a `TXT` or `CNAME` for DKIM, an `MX` for inbound, sometimes an additional `CNAME`, plus a `_dmarc` `TXT`. Add every returned record exactly as given. Propagation takes minutes to hours depending on the registrar, which is why `verify` is idempotent: run it as often as you like until the tracks clear.
### Purchased domains
`purchased` is a domain bought through Vetta, where Vetta owns the registration and manages the zone for you. Purchasing requires human approval out of the box, alongside connecting a new third-party app and provisioning a phone number: the session emits a confirmation event and holds at storage cost until you respond.
One honest caveat. The API accepts `kind: "purchased"` in its schema, but today it answers `501 feature_not_configured` because there is no outbound payment path to a registrar. The kind, the policy gate and the zone semantics are specified; the checkout is not wired. Ship on `system` or `external` for now.
## Verification is three tracks, not one boolean
A domain advances along three independent tracks, and each gates a different capability.
| Field | Gates | Values |
| --- | --- | --- |
| `dns_status` | Sending mail (SPF/DKIM) | `pending_verification`, `provisioned`, `failed` |
| `inbound_status` | Receiving mail (MX) | `pending`, `verified`, `failed` |
| `app_connect_status` | Serving an app over HTTP | `pending`, `connected`, `failed` |
The domain record also carries an overall `status` (`pending_dns`, `active`, `failed`); creating an inbox requires `active`.
A domain can be fully verified for email while its app track is still `pending`. It can be sending (`dns_status: provisioned`) while inbound is `failed`. Calling `verify` after publishing only the SPF and DKIM records moves `dns_status` and leaves `inbound_status` where it was; an unpublished track is not an error, it simply stays pending. Read all three fields before calling a domain healthy.
## The email readiness gate
Inboxes cannot exist on an unverified domain. The rule in practice: a domain must be verified before an inbox on it can send.
| Action | Requires |
| --- | --- |
| Create an inbox | `status: active` |
| Send from the inbox | `dns_status: provisioned` |
| Receive at the inbox | verified for inbound at the email layer |
A send attempted before the domain finishes verifying is not a hard failure. It returns a retryable `job_not_ready`, and the durable runtime retries once verification completes rather than dropping the message.
Once the domain is `active`, an inbox is one call away. The address is `localpart@domain`, and every send from it is credit-checked against the budget.
```bash title="An inbox on the verified domain"
vetta identity email provision --identity idn_... --address ava@acme-mail.com --domain dom_...
```
The full inbox flow, including auto-derived localparts and the inbound wake, is on the [Mail](/primitives/email) primitive page and in the [email docs](/docs/identity/email).
## Protected records: the agent cannot break its own mail
Some records exist to keep deliverability and inbound routing intact, and the agent is not allowed to touch them:
- `_dmarc`
- `_domainkey` and `*._domainkey` (DKIM)
- the inbound `MX` and its associated `TXT`
In the API these come back with `managed: true`. An agent may read a managed record and must never edit or delete it, because removing the DKIM record breaks the signature on its own outbound mail. On a `system` domain the entire zone is list-only. On `external` and `purchased` domains you manage your other records, but the protected set stays off-limits. Reading the zone is always allowed on every kind: reading is never the dangerous half.
## The API, in six routes
The whole surface is small enough to list, documented at [usenaive.ai/docs/api/domains](/docs/api/domains).
| Route | Scope | What it does |
| --- | --- | --- |
| `POST /v1/domains` | `agents:write` | Connect a domain. Idempotent on `name`. |
| `GET /v1/domains` | `agents:read` | Cursor-paginated list. |
| `GET /v1/domains/{id}` | `agents:read` | One domain with all three tracks. |
| `POST /v1/domains/{id}/verify` | `agents:write` | Re-check DNS; returns the domain object. |
| `GET /v1/domains/{id}/records` | `agents:read` | The DNS records to publish, with `managed` flags. |
| `DELETE /v1/domains/{id}` | `agents:write` | Remove the domain. Inboxes on it stop delivering. |
`POST /v1/domains` takes `kind` (defaults to `external`), `name` (required for `external`, omitted for `system` so the API mints the subdomain), and `receive` (also request inbound MX records; defaults to `true`). Connecting a domain that already exists returns the existing record, which matters when an agent retries.
```bash title="Connect and verify over REST"
curl -fsSL https://api.vetta.sh/v1/domains \
-H "authorization: Bearer sk_live_..." \
-H "content-type: application/json" \
-d '{ "kind": "external", "name": "mail.acme.dev", "receive": true }'
curl -fsSL -X POST https://api.vetta.sh/v1/domains/dom_01H9DM.../verify \
-H "authorization: Bearer sk_live_..."
```
### The same six calls in TypeScript
The [TypeScript SDK](/docs/sdk/domains) mirrors the routes one to one under `client.domains`. It is deliberately thin: one page per `list` call, no retry policy of its own, and the same `Idempotency-Key` on every mutating verb that the CLI and dashboard send.
```typescript title="Connect, read the records, verify"
import { randomUUID } from "node:crypto";
import { createClient } from "@usenaive-sdk/vetta";
const client = createClient({
baseUrl: "https://api.vetta.sh",
apiKey: process.env.VETTA_API_KEY!,
fetch: globalThis.fetch,
idempotencyKey: () => randomUUID(),
});
const domain = await client.domains.create({ name: "acme-mail.com", kind: "external" });
// domain.records -> the DNS records to add at your registrar
const { data: records } = await client.domains.records(domain.id);
// re-runnable; each track advances on its own
const verified = await client.domains.verify(domain.id);
console.log(verified.dns_status, verified.inbound_status, verified.app_connect_status);
```
On the command line the same six calls are `vetta identity domain connect`, `list`, `show`, `verify`, `records` and `rm`, documented at [usenaive.ai/docs/cli/comms](/docs/cli/comms). Because domains are org-level, this sub-group has no `--identity` flag.
## Apps hang off a domain too
Email is the obvious consumer of a domain, but the third track exists for a reason. When an agent ships an [app](/primitives/apps), the hostname it serves from is one of your org domains. Connecting a domain to an app is pointing, never registering: the domain must already exist at `/v1/domains`, and its `app_connect_status` advances `pending → connected` as the hosting verifies it.
```typescript title="Point an existing domain at an app"
await client.apps.connectDomain(app.id, domain.id);
// later: client.apps.domains(app.id) lists what is connected
```
The REST shape is `POST /v1/apps/{id}/domains` with a `domain_id` body, `GET /v1/apps/{id}/domains` to list, and `DELETE /v1/apps/{id}/domains/{domain}` by name. In a `naive.config.ts` project, `naive up` connects an app's domains in the same reconcile that writes its secrets and ships its `deploy_dir`.
## Where this shows up in a real deployment
A [deployment](/primitives/deployments) runs an agent on a cron with a per-run budget, and can name the persona each fire speaks as. Give that persona an inbox on a verified domain and the scheduled agent is reachable with no person in the loop.
```bash title="A nightly run that speaks as a persona with an inbox"
vetta deploy create --agent nightly-triage \
--cron "0 9 * * *" \
--budget-usd 5 \
--window loose \
--prompt "Summarize what changed in main overnight." \
--identity ava-sales \
--on-idle https://acme.dev/hooks/vetta
```
Every fire is a normal session with the same events, files and budget enforcement. Without `--identity` a fire runs as no persona, which is rarely what you want for an agent that owns an inbox. The domain is what makes that persona addressable in the first place.
## How this composes with the rest of Identity
[KYC](/primitives/verification) verifies the founder, [LLC](/primitives/formation) gives the organization a real company, and Domains gives that company a hostname. On top of it, [Mail](/primitives/email) provisions inboxes, [Phone](/primitives/phone) provisions numbers, and [Profile](/primitives/profile) is the persona that speaks through them, each gated by the identity's policy.
Read the [Domains docs](/docs/identity/domains) for the full readiness tables, the [API reference](/docs/api/domains) for every field, and [Introducing Vault](/blogs/introducing-vault) for the credential store that pairs with a verified identity.
## FAQ
### What is the Domains primitive?
Domains is the Identity primitive that gives an organization a real hostname. Vetta provisions a system subdomain at signup, lets you connect a domain you own, and tracks whether that domain can send mail, receive mail and serve an app. Inboxes and app hostnames are then created on top of it.
### Do I need to buy a domain to get started?
No. Every organization gets a system domain automatically, created with sending and receiving enabled and its SPF, DKIM, MX and DMARC records written into Vetta's shared apex zone. You can provision an inbox on it without touching a registrar.
### Can I bring a domain I already own?
Yes. Create the domain with kind external, add the DNS records the API returns at your registrar, then call verify. Verify is idempotent, so you can run it as often as you like while DNS propagates.
### Can an agent edit my DNS?
Reading the zone is always allowed on every kind. On a system domain the whole zone is list-only. On external and purchased domains the protected set (DKIM, _dmarc and the inbound MX with its TXT) is off-limits to the agent; those records are marked managed: true in the API.
### Can Vetta buy the domain for me?
Purchased is a declared registrar kind and domain purchase is approval-gated by policy by default. The current API accepts kind purchased in its schema but answers 501 feature_not_configured, because there is no outbound payment path to a registrar yet. Use a system or external domain today.
---
# Top 10 platforms to build autonomous companies in 2026
URL: https://usenaive.ai/blogs/top-10-platforms-autonomous-companies
Author: Dennis Zax
Published: 2026-04-08
Updated: 2026-09-07
Tag: Comparison
> An even-handed 2026 review of ten platforms people use to build autonomous, agent-run companies: app builders, agent frameworks, and company templates that deploy an agent team.
## TL;DR
- Ten platforms, three groups: app builders that ship a product, agent frameworks that give you the loop, and company templates that deploy a working agent team.
- App builders (Lovable, Replit, Bolt) get you to software fast and stop at the software.
- Frameworks (Claude Code, OpenAI Agents SDK, LangGraph, CrewAI, Composio, n8n) give you orchestration or integrations; budgets, identity, and approvals are yours to build.
- Naïve is the one entry we make: pick a company template, run `naive up`, and an agent team runs it on Vetta, the managed agent underneath.
- If you need a product, pick a builder. If you need a custom agent system, pick a framework. If you need a company that runs on its own, start from a template.
## What we mean by an autonomous company
An autonomous company is a business where the day-to-day work is done by agents and a person sets direction and approves what leaves the building. That is a different job from building software. Software is the artifact; a company is the operation that keeps running after the artifact ships: the research, the drafts, the posts, the client updates, the invoices, and the bill for all of it.
Most tools sold under the "autonomous company" phrase do one slice of that. The honest way to compare them is to say which slice, so this list is split into three groups: app builders, agent frameworks, and company platforms. Naïve is in the third group and we make it; read that entry knowing where we sit.
> Third-party product names are trademarks of their respective owners, used for comparison only; no endorsement or affiliation is implied. Descriptions reflect each product's public positioning as of September 2026; check each vendor's site for current features and pricing.
## How we scored them
Five questions, the same for every entry:
1. **Product or operation.** Does it produce software, or does it run the work a business does every day?
2. **Unattended and bounded.** Can it run for hours or days without a person watching, at a cost you cap up front?
3. **Identity.** Does the agent act under its own persona (inbox, domain, phone, credentials), or does it borrow yours?
4. **A gate.** Is there a checkpoint before money is spent or something is published?
5. **Non-engineer ready.** Can someone who does not write code get it running?
## App builders: fast to a product, stop at the product
These turn a description into working software. None of them claims to run the business around it.
### 1. Lovable
Lovable generates full-stack web apps from natural-language prompts, aimed at people who do not write code. You describe the product, iterate in chat, and publish. It does not run agents on your behalf after the app ships; the operating is still yours.
### 2. Replit
Replit is a cloud development environment with an agent that builds, debugs, and deploys apps from prompts, with hosting in the same place. It is scoped to software: there is no persona, budget, or approval layer for business operations.
### 3. Bolt
Bolt, from StackBlitz, generates and runs full-stack apps in the browser. It is quick for MVPs and demos, and like the other two it builds the product, not the company around it.
## Agent frameworks: you get the loop, you build the company
Developer tools give you far more control. The trade is that budgets, identity, approvals, scheduling, and the durable state a long task needs are yours to assemble.
### 4. Claude Code
Anthropic's terminal-based coding agent. It reads and edits files, runs commands, and works through multi-step engineering tasks. Running a sales or content operation with it means wrapping it in your own scheduling, budget, and review layer. Worth noting: Vetta publishes `claude_code` as a harness, so a Claude Code loop can run as a process on a Vetta micro-VM with Vetta's budget and tool policy around it. One caveat: a loop that runs as a CLI cannot hold a tool call open for approval, so `ask` gates need the `pi` harness ([harnesses](/docs/concepts/harnesses)).
### 5. OpenAI Agents SDK
OpenAI's framework for building agent workflows: agents with instructions and tools, handoffs between them, guardrails, and tracing. It is a library, so hosting, durable state across hours, budgets, and identity are outside its scope.
### 6. LangGraph
LangChain's framework for stateful, graph-structured agent workflows, with a hosted deployment option. It is a strong fit when you want explicit control over how a long task branches and resumes. You still bring the business layer: who the agent is, what it may spend, and who approves what.
### 7. CrewAI
A framework for role-based teams of agents that collaborate on tasks, with an open-source core and an enterprise offering. Defining roles and hand-offs is its strength. The identity, spending, and approval infrastructure a company needs is left to you.
### 8. Composio
A managed integration layer: a large catalog of SaaS tools your agents can call, with authentication handled for you. It is a connector, not a runtime: it does not run the agent, hold its budget, or give it a persona.
### 9. n8n
A source-available workflow automation tool, self-hostable or hosted, with AI agent nodes alongside its classic triggers and integrations. It is a good fit when the work is event-driven and the steps are known. Open-ended, hours-long agent work with its own spend cap is not what it is shaped for.
## Company platforms: the team is the product
### 10. Naïve
We built Naïve so the unit you deploy is a company, not an app. You pick a [template](/templates), deploy it, and an agent team runs it.
A template is a crew declared in a public blueprint repository's `naive.config.ts`, alongside the apps it works in. The catalogue today: [AI Media Channel](/templates/ai-media-channel), [Clipping Channel](/templates/clipping-channel), [AI Automation Agency](/templates/ai-automation-agency), and [SEO / GEO Agency](/templates/seo-geo-agency), with Newsletter, Paid Ads Agency, and Recruiting Agency coming soon. Each one declares named roles (a writer, a producer, and a channel manager for the media channel), the tools each role is allowed, and the steps the work moves through.
Getting one running is a clone and three commands:
```bash title="Deploy a company template"
naive template media --template faceless # clone the media blueprint, pick the AI Media Channel crew
cd media
naive claim --key sk_... # bind the clone to your organization
naive up # provision the apps, the agents, their budgets
```
`naive up` reconciles the config against the platform and reports every resource as created, updated, unchanged, deleted, or refused. Re-running is safe, nothing is deleted by omission, and `--dry-run` shows the plan without writing ([naive CLI](/docs/cli/naive)). Each template page also has a deploy button that hands the same pair to the Studio, so the terminal is optional.
Underneath every template is **Vetta**, the managed agent at [vetta.sh](https://vetta.sh). An agent on Vetta is a versioned config: model, prompt, tools, skills, budget, and harness. That is what gives a template team the properties we scored on:
- **Unattended and bounded.** A session runs as a durable loop: wake, take one turn, commit, sleep. The agent's [computer](/primitives/computer) is a micro-VM that pauses between turns, so waiting bills storage rather than a hot machine. Every agent carries a mandatory USD [budget](/primitives/budgets) quoted before each call, and the run stops rather than overspending; the shipped templates cap each agent at 10 USD a day and 2 USD per task. Work that can wait runs on a cheaper [completion window](/primitives/completion-window).
- **Identity.** An agent acts under an identity with its own domain, inbox, and phone number, connects to third-party services as that persona, and uses credentials from a [vault](/primitives/vault) whose values are injected at the network boundary and never enter the prompt.
- **A gate.** Every tool call resolves to a [policy](/docs/concepts/policies) of `allow`, `ask`, or `deny`. `ask` holds the call until a person approves it, which is how the templates keep every post, proposal, and spend change pending until you clear it ([approvals](/primitives/approvals)). `social.post` defaults to `ask`.
- **A team, not a prompt.** A coordinator hands each member a bounded brief in its own session, or coordinates through a shared board ([sub-agents](/primitives/subagents), [teams](/docs/capabilities/team)). Members are pinned by version, so the team's behavior is reproducible.
- **Non-engineer ready.** The dashboard the template ships is where the queue lives and where you read what each agent did; pending calls are approved in the Studio. The config is code, but running a template does not require writing any.
The media template is also the workload our [Social Bench](/benchmark) measures: real accounts run autonomously for twelve weeks, across five harnesses and four models, with the results published.
## Side by side
| Platform | Product or operation | Unattended and bounded | Own identity | Gate before spend or publish | Non-engineer ready |
|----------|----------------------|:---:|:---:|:---:|:---:|
| Lovable | Product | No | No | No | Yes |
| Replit | Product | No | No | No | Partly |
| Bolt | Product | No | No | No | Yes |
| Claude Code | Coding tasks | Bring your own | No | Bring your own | No |
| OpenAI Agents SDK | Orchestration | Bring your own | No | Bring your own | No |
| LangGraph | Orchestration | Partly (hosted option) | No | Bring your own | No |
| CrewAI | Orchestration | Bring your own | No | Bring your own | No |
| Composio | Integrations | Not applicable | No | No | Partly |
| n8n | Workflows | Event-driven | No | Partly (manual steps) | Yes |
| Naïve (on Vetta) | Operation | Yes | Yes | Yes | Yes |
"Bring your own" means the framework does not stop you, but you are writing that layer. The Naïve row is our own product; the links above let you check each claim.
## What changed since the 2025 edition
Two entries left the list. Paperclip, an MIT-licensed framework for orchestrating agent runtimes, is a building block rather than a platform; [our earlier post on it](/blogs/on-paperclip-and-open-source) is still up. Create.xyz overlapped with the three builders that stayed. Naïve, listed twice in 2025 (for founders and for developers), is one entry now. Three joined: the OpenAI Agents SDK, LangGraph, and n8n, because they are what most teams reach for first when they build a custom agent system in 2026.
Our own entry changed most. Naïve used to be described as a primitives layer you assemble a company from, with any harness on top. It is now a catalogue of company templates you deploy, with Vetta as the managed agent that runs them. The primitives are still there ([primitives](/primitives)); the template is the thing you start from.
## How to pick
- **You need a product.** Lovable, Replit, or Bolt. Ship it, then decide who operates it.
- **You need a custom agent system and you have engineers.** Claude Code for engineering work, the OpenAI Agents SDK or LangGraph for orchestration, CrewAI for role-based teams, Composio for integrations, n8n for event-driven workflows. Build budgets, identity, and approvals yourself, or run the agent on Vetta and get them from the platform.
- **You need a company that runs on its own.** Start from a [template](/templates). If none fits, the same primitives and managed agent are underneath, and `naive init` scaffolds a config of your own.
The models will keep getting better for every entry here. What separates them is everything around the model: who the agent is, what it may spend, and who says yes before it acts.
## FAQ
### What is the difference between an app builder and an autonomous company platform?
An app builder turns a prompt into software you then have to operate. An autonomous company platform deploys agents that do the operating: a team with roles, budgets, schedules, an identity to act under, and an approval gate before anything is published or paid for. Naïve templates ship that whole shape; the app builders on this list ship the app.
### Can I use Naïve together with Claude Code, LangGraph, or CrewAI?
Yes, in two ways. Vetta, the managed agent Naïve runs on, lets you pick the harness per agent and publishes claude_code as one of them, so an agent can run a Claude Code loop inside Vetta's sandbox with Vetta's budget and tool policy around it (availability varies by environment, and a loop that runs as a CLI cannot hold an ask approval open). Separately, any framework can call Naïve primitives over the API or MCP, and a fullstack app in a template can serve its own MCP endpoint to the agents.
### Which platform is best for a non-technical founder?
For a product, Lovable, Replit, or Bolt. For a business that runs on its own, a Naïve template: the crew, its budgets, its schedules, and its dashboard are already declared, and the setup is three commands or a deploy from the template page into the Studio.
### Are these platforms open source?
CrewAI, LangGraph, and the OpenAI Agents SDK publish their core libraries under open-source licenses, and n8n is source-available and self-hostable; check each vendor for the current terms. The rest are commercial products. Naïve's blueprints are public repositories you clone; Vetta, the managed agent, is a hosted service.
### How should I evaluate a platform for an autonomous company?
Ask five things: does it produce software or run operations; can it run unattended for hours at a cost you can bound; does the agent act under its own identity or borrow yours; is there a gate before money is spent or something is published; and can someone who is not an engineer get it running. The table in this post scores the ten entries on exactly those.
---
# Naïve and Paperclip: attribution, MIT, and what we actually build
URL: https://usenaive.ai/blogs/on-paperclip-and-open-source
Author: Dennis Zax
Published: 2026-04-08
Updated: 2026-09-07
Tag: Engineering
> How Naïve attributes the open-source software it builds on, why MIT-licensed code in a commercial product is the point of the license, and where the line sits between upstream code and the stack we built ourselves.
## TL;DR
- Paperclip is credited by name in our Terms of Service, with the repository link, the MIT license, and the copyright line, plus a full copy of the license on our open-source licenses page.
- Residual upstream strings in a minified bundle are what building on open source looks like. They are evidence of usage, not of concealment.
- Naïve is not an orchestration wrapper. What we build is the company templates on usenaive.ai, the primitives they call, and Vetta, the managed agent that runs them across the harness, tools, and runtime layers.
- Paperclip is no longer part of our stack. The attribution stays, and the same model applies to every open-source project we still build on, including the default harness.
- Using MIT-licensed software commercially is the explicit intent of the MIT license. We comply with its terms, credit the projects, and publish our own templates as public repositories.
## Attribution exists, and always has
When we first published this post in April 2026, a claim was circulating that Naïve provided "zero attribution" for Paperclip, the MIT-licensed agent orchestration framework an early version of our product was built on. That claim was false then and it is false now, so we are leaving this post up and keeping it current.
Section 7.4 of our [Terms of Service](/terms) says:
> "The Service incorporates open source software components, including Paperclip, licensed under the MIT License. Copyright © 2025 Paperclip AI. Full license terms are available in the project repository and on our Open Source Licenses page. Nothing in these Terms restricts your rights under applicable open source licenses."
That section was in our Terms when the claim was made, and it is there today. It links to the Paperclip GitHub repository, names the license, and reproduces the copyright line. Our [open-source licenses page](/licenses) carries the full MIT license text for Paperclip alongside the notices for the other upstream projects we ship. That is what the MIT license asks for: keep the copyright notice and the permission notice with the software. We did, and we still do.
Paperclip was the most visible dependency, but it was one of many. The licenses page lists the rest, and every open-source project we build on next goes on it too.
## Residual strings are not a smoking gun
The other half of the claim was that leftover "Paperclip" references in our production bundle proved we were hiding something. They proved the opposite.
When you build on a large open-source project, string literals, CSS class names, and config defaults flow through from upstream. Not every internal identifier gets rebranded, because rebranding identifiers is work that produces nothing for users. Every large SaaS product ships hundreds of upstream references in its bundle. Open a minified bundle from any company you admire and you will find the names of the libraries they depend on.
Framing inherited artifacts as concealment misrepresents how software gets built. Those strings were evidence of usage, and the usage was disclosed in the one document every customer agrees to.
## What Naïve builds today
The more useful question was never "did you use Paperclip". It was "what did you build". Here is the honest answer for the product as it exists now, which is different from the product that existed in April.
Naïve is company templates run by agents. A [template](/templates) lives in a blueprint, a public repository with a `naive.config.ts` in it: the agent team, their prompts, their tool allow-lists, their schedules, and the apps the company runs on. You clone it, claim it against your organization, and `naive up` provisions the whole thing:
```bash
naive template agency
cd agency
naive claim --key sk_...
naive up
```
`naive up` reconciles what the config declares (skills, identities, vaults, apps, agents) against what exists, and reports every resource as created, updated, unchanged, deleted, or refused. Re-running it is always safe. The [naive CLI reference](/docs/cli/naive) has the full surface.
The agents those templates provision run on [Vetta](/blogs/introducing-vetta), our managed agent for long-horizon tasks. Vetta is three layers, and we operate all of them:
| Layer | What it is | Who chooses it |
| --- | --- | --- |
| Harness | The agent loop: assemble a turn, call the model, parse tool calls, decide what carries forward | You, per agent, via the `harness` field |
| Tools | What the loop reaches for: a computer, a browser, storage, skills, identity, connections, MCP servers. Every call resolves allow / ask / deny and is priced before it runs | You, via the agent's tool policy |
| Runtime | Durable session state and scheduling, model routing and the completion window, budget enforcement, sandboxed micro-VMs, the ledger | Nobody. Using Vetta is the runtime |
The thesis behind that split is that the model is a commodity and everything around it decides the cost of a finished task. We optimise the three layers together, and we publish the result on the [benchmark](/benchmark) page. [How Vetta works](/docs/how-vetta-is-built) walks through each layer.
Underneath the agents sit the primitives, which is the part of Naïve that never had anything to do with orchestration:
- **Identity and legal.** [KYC](/primitives/verification), a [real US company](/primitives/formation), [domains](/primitives/domains), a [custom-domain inbox](/primitives/email), and a [phone number](/primitives/phone) an agent can text from.
- **Money.** [Virtual cards](/primitives/cards) with a hard spend cap the agent cannot raise, and a USD [budget](/primitives/budgets) quoted before every call.
- **Hands.** A [real computer](/primitives/computer) with a shell, a filesystem, and a browser, plus [OAuth connections](/primitives/connections) into the apps a business already pays for.
- **Content.** Image, video, clips, audio, and [social publishing](/primitives/social) from one compose.
None of this existed in Paperclip. None of it depends on which harness an agent runs on. That was true when we used Paperclip and it is true now.
## Paperclip is no longer in our stack
We removed Paperclip from the product in the spring of 2026. We say this plainly because the honest version of an open-source story includes the part where you stop using something.
We chose Paperclip for the first version because it was MIT-licensed, well-engineered, and pointed in the direction we thought agent infrastructure was heading. We still respect the project and the people behind it. What changed is that the runtime layer became the thing we needed to own. A company that runs for months needs a session loop that wakes, takes one bounded turn, commits, and sleeps, so that an idle agent costs storage rather than a machine held hot. It needs a budget gate that refuses a call before it runs rather than reporting overspend the next morning. It needs tool policy that leaves a denied tool out of the model's view entirely. Those are runtime properties, and we built the runtime to have them.
Removing Paperclip did not remove the attribution. The Terms section is still there, the licenses page is still there, and they will stay until the last artifact of that code is gone from anything we ship. Attribution is a record of what you built on, not a marketing claim about what you use today.
## We still build on open source
Nothing about that decision made us less dependent on open source. The default harness a Vetta agent runs on, `pi`, is an open-source coding agent under the MIT license. We name it in our documentation, and it carries the same obligation we met for Paperclip: keep the copyright notice and the license text with the software. We chose not to write our own agent loop for the default case for the same reason we chose Paperclip at the start: a well-engineered open project beats a hand-rolled one, and the license exists so that people can build on it. The [harness documentation](/docs/concepts/harnesses) lists the loops an agent can run on, what each one holds while idle, and which capabilities it declares. We also ship our own loop, `vetta`, held to a small measured core and the only one that runs inside the session itself.
We also publish. The blueprint repositories our templates deploy from are public under [github.com/usenaive](https://github.com/usenaive). `naive template` clones one with the git history dropped, on purpose: the first commit of your company should be yours. You can read every prompt, every tool allow-list, and every schedule before an agent spends a cent under your organization. A template is not a black box you rent; it is code you own from the moment you clone it.
We believe the agent infrastructure layer should be composable and open. We say that as practitioners, not as a slogan. We build on MIT code, we credit it, and we publish the parts of our own work that others can build on.
## What the MIT license is for
Using MIT-licensed software in a commercial product is not a grey area. It is the explicit purpose of the license. The MIT license grants permission to use, copy, modify, merge, publish, distribute, sublicense, and sell copies of the software, with one condition: keep the copyright notice and the permission notice. Every company shipping a Node application, a React frontend, or a Postgres client does exactly this thousands of times over.
Authors who choose MIT are choosing this outcome. The projects we build on chose it, and we are grateful for the foundation they provide. Compliance means honouring the one condition, which we do, and it means being honest about what is upstream and what is ours, which is what this post is for.
## Where we stand
We are a small team building fast, and we will not get everything right. We welcome good-faith criticism, including about the code we shipped in April. But the narrative that we stripped attribution and presented someone else's work as our own was wrong on the facts. Attribution is in our Terms and on our licenses page. The product is company templates, the primitives underneath them, and a managed agent we built across every layer. And our commitment to open source is visible in what we build on and what we publish.
If you are weighing whether to run agents on a managed runtime or bring your own, [hosted vs bring-your-own runtime](/blogs/hosted-vs-bring-your-own-runtime-for-ai-agents) covers the tradeoffs. Questions about this post can go to [dennis@usenaive.ai](mailto:dennis@usenaive.ai).
## FAQ
### Is Naïve a fork of Paperclip?
No. Paperclip was one of several MIT-licensed open-source projects an early version of Naïve built on, used as an orchestration component. It is no longer part of the stack. What Naïve builds, the company templates, the primitives behind them, and the Vetta managed agent that runs them, was never part of Paperclip.
### Where is Paperclip attributed?
In the open-source section of our Terms of Service at usenaive.ai/terms, with a direct link to the Paperclip repository, the MIT license name, and the copyright line. The full license text is reproduced on usenaive.ai/licenses, next to the notices for the other open-source software we ship.
### What does Naïve build that Paperclip does not?
Company templates that provision a working agent team with one command, the primitives those teams call (company formation, KYC, domains, inboxes, phone numbers, virtual cards with hard caps, a real computer, connections, media and publishing), and Vetta: the managed agent with a durable session loop, USD budgets quoted before every call, completion windows, allow / ask / deny tool policy, identities, and a vault.
### Does Naïve still build on open source?
Yes. The default harness a Vetta agent runs on is an open-source coding agent under the MIT license, and our company templates are public repositories anyone can clone, read, and run under their own organization.
### Is using MIT-licensed software commercially a violation of open-source norms?
No. Commercial use is the explicit intent of the MIT license. The only obligations are to keep the copyright notice and the license text, which we do.
---