> ## Documentation Index
> Fetch the complete documentation index at: https://vetta.sh/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Tools, connections & context

> Declare what the agent may use — built-ins, connection tools, and MCP servers — gate each one with a permission, and keep the context window small.

An agent's tools are what it works *with*: a shell, a filesystem, a browser, your third-party [connections](https://vetta.sh/docs/identity/connections.md), and any MCP server you point it at. You declare them on the agent, set a [permission](https://vetta.sh/docs/concepts/policies.md) per tool, and every call is priced against the [budget](https://vetta.sh/docs/concepts/budgets.md) before it runs.

## The `tools` config

An agent's tools are declared as one `tools` object: a `default_config` that sets the baseline for every tool, plus a `configs` map of per-tool overrides keyed by tool name. This is how you enable or disable individual tools and set each one's permission.

```jsonc theme={"system"}
{
  "tools": {
    "default_config": { "permission": "allow" },
    "configs": {
      "browser": {
        "enabled": true,
        "permission": "ask",
        "config": { "allowed_domains": ["example.com"] }
      },
      "bash": { "enabled": true, "permission": "allow" }
    }
  }
}
```

* `default_config` applies to every tool; a `configs` entry overrides one tool by name.
* `permission` is one of `allow` (run silently), `ask` (pause → `requires_action`), or `deny` (the tool is **not offered to the model at all**).
* Common patterns: **deny-by-default** (`default_config.permission: "deny"`, then list the tools you want) and **trust-by-default-except** (allow all, set one tool to `ask`). `default_config` takes only `permission`; `enabled: false` is per tool, in `configs`.
* A deny default does **not** deny the [ask-by-default tools](#permission-policies): an unlisted one still resolves to `ask`. Deny each by name.
* Running sessions keep the config they started with; edits apply to new sessions. Tools can also be re-selected per session while it is [idle](https://vetta.sh/docs/concepts/sessions.md#lifecycle).

## Built-in tools

| Tool | What it does |
| - | - |
| `bash` | Run shell commands in the [computer](https://vetta.sh/docs/computer/shell.md). |
| `read` · `write` · `edit` | Read, write, and string-replace files in the [workspace](https://vetta.sh/docs/computer/filesystem.md). |
| `ls` · `find` | List a directory, and find files by name or pattern. |
| `browser` | Drive a real browser: `goto`, `click`, `type`, `act`, `extract`, `observe`, `links`, `screenshot`, `login`, `signup`, `save_context`. `config` is the [browser option object](https://vetta.sh/docs/computer/browser.md#configuration-reference), validated by field on every agent write. See [below](#the-browser-tool). |
| `mobile` | Use a cloud Android [phone](https://vetta.sh/docs/computer/mobile.md): `screenshot`, `element_tree`, `tap`, `type`, `press_key`, `scroll`, `open_url`, `end`. One phone per session, opened on first use. **Asks by default.** See [below](#the-mobile-tool). |
| `read_skill` | Pull a [skill](https://vetta.sh/docs/capabilities/skills.md) body into context on demand. |
| `publish_file` | Promote a sandbox artifact into persistent [Files](https://vetta.sh/docs/capabilities/files.md), with an optional `title`, `tags` and `folder`. |
| `fetch_file` | The inverse of `publish_file`: write files the organization already holds into the session's [workspace](https://vetta.sh/docs/computer/filesystem.md) by their `fil_` ids — up to eight per call, with an optional `dir`. How an agent post-processes what the platform rendered for it. See [below](#reading-a-file-back-into-the-sandbox). |
| `find_files` | Find files already in the organization's [library](https://vetta.sh/docs/api/files.md) by title/name/description words (a generated picture or clip by its prompt), `kind`, `tag` or `folder`, or by `fil_` ids given as the query; returns ids and metadata, each file's `folder` among them, never bytes. A reply shows one as the picture or the clip itself: a Markdown image whose address is its `fil_` id, on its own line. |
| `view_image` | Look at images the organization holds, by their `fil_` ids — up to four at a time. Returns the pictures themselves, so the model can describe, compare or work from what is in them. Use `find_files` to discover ids. See [below](#looking-at-an-image). |
| `session_spend` | Report exactly what this session has been charged so far, in integer micro-USD: the settled total, a per-tier split, and every media job with its final `cost_micro_usd` (`null` while it is still rendering). Read-only, unpriced, and scoped to the calling session — the organization-wide view is [`GET /v1/credits/ledger`](https://vetta.sh/docs/api/credits) and [`vetta agent spend`](https://vetta.sh/docs/cli/agents). See [Budgets](https://vetta.sh/docs/concepts/budgets.md#reading-spend). |
| `show` | Draw a view of the company in the chat from live data: `spend_by_day` (the company's spend per day on its clock; the CEO only), `post_views` (views per recent post), `roadmap` (the board's work by status), `schedule` (routines and scheduled posts over the next seven days) or `earnings` (what the company's [marketplace listings](https://vetta.sh/docs/api/apps.md#list-the-running-company-in-the-marketplace) earned, held and ready, and each listing's review; the CEO only). Takes `source` and, for spend, `days` (2–30). The data and the title are read by the platform, never written by the model. Read-only and unpriced. |
| `crm.read`, `crm.write` | Read and keep the company's [CRM and Calendar](https://vetta.sh/docs/api/crm.md). Only in a company whose template declares the CRM. |
| `open_panel` | Turn the panel beside the chat to something the person should look at now: `app` (its `app_` id, or a template app's slug like `crm`), `file` or `card` with its `fil_` or `crd_` id, or `board`, `files`, `templates` (the marketplace) or `listings` (the company's own listings). The dashboard opens that tab, as a click on its chip would. Writes nothing and is unpriced. |
| `compose` | Lay out a card in the chat from live data. Takes one `spec`: a flat tree of elements under one `Card` — `Stack`, `Grid`, `Metric`, `LineChart`, `BarList`, `Progress`, `Roadmap`, `Schedule`, `Media`, `Text`. The agent writes the layout and the words; every number is a binding such as `{"$source": "views_total", "days": 7}` that the platform reads when the card is made. A literal number, or a digit in the words, is refused. Sources: `views_total` and `views_change` (views of the posts published in the window), `post_views`, `posts_this_week` (against the setup's cadence), `roadmap`, `schedule`, `latest_media`, and `spend_total` / `spend_by_day` (the CEO only). Read-only and unpriced. |
| `web_search` | Search the web and get back titled results with links. [Priced per call](https://vetta.sh/docs/platform/pricing.md#priced-tools). |
| `web_fetch` | Read one web page as text, so the agent can quote or summarise it. [Priced per call](https://vetta.sh/docs/platform/pricing.md#priced-tools). |
| `generate_image` | Generate an image from a description — optionally from reference images (`fil_` ids or public URLs) or as a trained `character`, in a named `style` (a [look](https://vetta.sh/docs/api/images.md#looks)), with `character_strength` (0.5–1) for a close-up; the result lands in [Files](https://vetta.sh/docs/capabilities/files.md). Asks by default: the card shows the price and the model. [Priced per job](https://vetta.sh/docs/platform/pricing.md#priced-tools). |
| `generate_video` | Generate a video from a description — optionally animating a first frame (a `fil_` id or a public URL), or putting a `character` on video from a still rendered as it; the result lands in [Files](https://vetta.sh/docs/capabilities/files.md). [Priced per job](https://vetta.sh/docs/platform/pricing.md#priced-tools). |
| `generate_3d_model` | Generate a textured 3D model (GLB) from a description or 1–4 photos, optionally rigged with an idle animation; does not block, and the file lands in [Files](https://vetta.sh/docs/capabilities/files.md). [Priced per job](https://vetta.sh/docs/platform/pricing.md#3d-assets), **asks by default**. See [3D assets](https://vetta.sh/docs/capabilities/3d.md). |
| `build_3d` | Run a build123d or Blender script on the computer, check the solid, render previews the agent sees, and publish STEP, STL, 3MF, GLB, OBJ or `.blend` files. Billed as computer time. See [3D assets](https://vetta.sh/docs/capabilities/3d.md). |
| `clip_video` | Cut the most engaging short clips out of a public video URL — transcribed, scored, captioned, optionally reframed to vertical; the clips land in [Files](https://vetta.sh/docs/capabilities/files.md). Optional `caption_preset` (`none`, `clean`, `bold` or `karaoke`), `language` (a BCP-47 tag), `min_seconds` / `max_seconds` (5–180, default 15–60) and `title`, the same knobs as the [Clips API](https://vetta.sh/docs/api/clips.md). [Priced per job](https://vetta.sh/docs/platform/pricing.md#priced-tools). |
| `build_media_skill` | Build a [media skill](https://vetta.sh/docs/api/media-skills.md) for your organization from 1–3 public Instagram Reel or TikTok links: it writes the skill, renders a sample and saves it to Skills; pass `build_id` and `feedback` to run another round. Runs in the background and tells the agent the skill and its samples when done. Billed to the organization, capped per build; asks first by default. |
| `list_as_template` | List this company as a [template](https://vetta.sh/docs/api/apps.md#list-the-running-company-in-the-marketplace) other companies can start from: a crew company lists its crew, a channel company its style. **Always asks**, and only a signed-in owner or admin can approve it; then Naive reviews it. Once listed, the company earns a share of what each new company that starts with it pays. |
| `pay_out_earnings` | [Pay out](https://vetta.sh/docs/api/apps.md#pay-out-what-it-earned) what the company's templates earned to its own Stripe account: each share is held 30 days and refunds are taken off, at least \$100. The first time, the owner sets up where the money goes from its card. **Always asks**, and only a signed-in owner or admin can approve it. |
| `create_character` | Train a reusable **character** — a consistent on-screen persona, an AI influencer or recurring host — from one to sixteen reference photos, each a stored `fil_` id or a public URL. Returns a `chr_` handle that trains in the background and does not block; once ready, pin it with `generate_image`'s `character` argument so the same face, hair and build carry across every render. [Priced per training](https://vetta.sh/docs/platform/pricing.md#priced-tools), held against the session's budget first, and **always asks**: approving the call is the consent. Needs a media backend that trains characters, otherwise it is refused. See [below](#characters). |
| `get_character` | Check a character's status, persona and looks — `training`, `ready` or `failed` — by its `chr_` id or name. Poll it after `create_character` until it is `ready`, then render with it. Read-only and free. See [below](#characters). |
| `list_characters` | List the organization's characters with their status, newest first, then the prebuilt ones every organization can render as — how an agent finds a host it trained in an earlier chat. Read-only and free. |
| `update_character` | Change a character you trained: its name, description, persona or looks, never its face. A persona is merged line by line. A prebuilt character is refused. |
| `delete_character` | Delete a character by `chr_` id or name, so it can no longer be rendered as and its name is free again. Files it already rendered keep their provenance. A prebuilt character cannot be deleted. Free. |
| `data_search` · `data_describe` · `data_run` | Read data in 18 domains — social media, search results, people, companies, compliance, funding, SEO, markets, crypto, news, US law, weather, jobs, real estate, reviews, trade records, product listings and travel. `data_search` finds a tool by a short `query` (optionally one `domain`, a social `platform`, a `limit`) and lists each with its price; `data_describe` returns a tool's input schema; `data_run` runs it with `path`, `query` and `body` parameters and returns the data, waiting up to two minutes. Search and describe are free and read-only; a run is [priced per call](https://vetta.sh/docs/platform/pricing.md#priced-tools) at the tool's listed price, capped at 25 results, held against the session's budget before it starts, and asks first by default. A run that fails is normally not charged. The same catalogue is on the [API, SDK, CLI and dashboard](https://vetta.sh/docs/capabilities/data.md); social tools are its `social` domain. |
| `find_stock_photo` | Search a licensed [stock photo library](https://vetta.sh/docs/api/images.md) by description, orientation and count; returns image URLs with the photographer credit. Free — booked as a `search` call at `$0`. |
| `apps` | Provision, deploy, and operate the organization's hosted [web apps](https://vetta.sh/docs/api/apps.md): sites, secrets, domains, and (fullstack) its [database](https://vetta.sh/docs/api/database.md) — SQL, tables, a REST layer, migrations — and [object storage](https://vetta.sh/docs/api/storage.md), where objects travel as `fil_` ids, never inline. The database actions are `db_query`, `db_tables`, `db_rest` and `db_migrate`; the storage actions are `storage_list`, `storage_get`, `storage_put`, `storage_remove` and `storage_signed_url`. Permission is per tool, not per action: `db_query`, `db_migrate`, a `db_rest` method other than `GET`, `storage_put` and `storage_remove` write, so an `ask` on this tool stops them (and every other `apps` call). Database calls carry no price of their own. |
| `transcribe_audio` | Transcribe a recording — a `file_id` or a public URL — into text with timestamps. See [Audio tools](#audio-tools). [Priced per job](https://vetta.sh/docs/platform/pricing.md#priced-tools). |
| `generate_speech` | Turn text into speech; the audio lands in [Files](https://vetta.sh/docs/capabilities/files.md). See [Audio tools](#audio-tools). [Priced per job](https://vetta.sh/docs/platform/pricing.md#priced-tools). |
| `generate_sound_effect` | Generate a sound effect from a description — an optional `duration_seconds` and `loop`; the audio lands in [Files](https://vetta.sh/docs/capabilities/files.md). See [Audio tools](#audio-tools). [Priced per job](https://vetta.sh/docs/platform/pricing.md#priced-tools). |
| `generate_music` | Generate an original song from a description — an optional `length_seconds` and `instrumental`; the audio lands in [Files](https://vetta.sh/docs/capabilities/files.md). See [Audio tools](#audio-tools). [Priced per job](https://vetta.sh/docs/platform/pricing.md#priced-tools). |
| `apps.request_access` | Ask the owner to let one of the organization's fullstack apps call the API from its server; the owner approves a card listing what the app could read or change, and the app then reads data with `@usenaive-sdk/app`. Can never be `allow`. |
| `ask_operator` | Ask the person running the task a question and stop until they answer. |
| `request_tools` | Propose adding tools to this agent's own toolset (name, permission, config, reason) and stop until the operator approves or refuses. |
| `write_plan` | Write the chat's Markdown plan, naming the version it read. Offered while a chat [plans](https://vetta.sh/docs/concepts/session-operations.md#plan-mode), and while it builds a plan it was given. |
| `propose_plan` | Ask the operator to build the plan, and stop until they answer Build it or Keep planning. Never `allow`. |
| `send_to_agent` | Send another agent a message. With `wait: true` it is a teammate whose answer comes back into your turn, on your budget; with `wait: false` it is a new session of that agent's own, on its budget, and you carry on. Who it may name is the agent's [`handoffs`](#handoffs). |
| `wait_for_agents` | Stop and wait for the teammates you sent with `wait: true`, until every one of them is done. |
| `list_agents` | List the agents this one may send work to — name, description, project, whether they are on its team — and how many teammates are running now. |
| `post_to_channel` | Post a message into the channel this session was `@mentioned` in — it lands in the channel's transcript as `From <agent> (session ses_…): …`, where people read it. Nothing answers it. Offered only to a session a channel mention started. |
| `board_read` | List the team board's cards, or read one card in full by id. |
| `board_write` | Create a card, update one, or comment on one. |
| `project_context` | Read what a template's crew knows about the business — the setup answers, the apps and the teammates of the project this agent belongs to. Read-only; offered only to an agent a [template](https://vetta.sh/docs/api/apps.md#project-context) installed. |

### Handoffs

Every agent carries a `handoffs` field that says who it may send work to with `send_to_agent`. It is a boolean, the word `"team"`, or a list of agent names, and it defaults to `true`:

* `true` (the default) — `send_to_agent` and `list_agents` are offered; any agent of the organization may be named, except itself.
* `"team"` — both tools are offered, narrowed to the agents on this agent's [`multiagent` roster](https://vetta.sh/docs/team/rosters-and-versioning), plus your company's CEO. Refused at save time on an agent with no roster.
* `["scriptwriter", "producer"]` — both tools are offered, narrowed to the names listed plus the CEO: `send_to_agent` offers them as its choices and `list_agents` answers with them. An empty list reaches the CEO alone.
* `false` — neither tool is offered; the agent sends to nobody, not even the CEO.

Every agent can always hand work to the company's [CEO](https://vetta.sh/docs/team/board.md), whether or not its list or roster names it. `false` is the only way to opt out.

An agent created before the field existed reads as `true`. Set it on [create or patch](https://vetta.sh/docs/api/agents.md), in an [agent file](https://vetta.sh/docs/cli/agents.md) as `handoffs: false`, `handoffs: team` or `handoffs: [..]`, or on a template seat's `handoffs` (checked at define time against the crew; leave it out and the default stands). Like every built-in, both tools still pass the toolset's [permission](#permission-policies): a `deny` on `send_to_agent` withholds it whatever `handoffs` says.

The one tool has two modes, and `wait` chooses. `wait: true` is [delegation](https://vetta.sh/docs/team/delegation.md): the target runs as a teammate in a thread of your session, on your budget, and its answer folds back into your turn — only a coordinator (an agent with a roster) may use it, and only while fewer than the platform's cap of teammates are running. `wait: false` is a **handoff**: it starts a normal top-level session on the target, on the target's own budget (capped by `budget_micro_usd` if you pass one), marked `metadata.handoff` (`from_session`, `from_agent`, `key`, `depth`), and you carry on. Chains stop at depth 4, one session may start at most 8 handoffs, and a repeat under a `handoff_key` whose session is still running answers `already_running` instead of starting another. Each refusal comes back to the model as the tool's result, never as a failed turn.

The same admission has a door for people: [`POST /v1/sessions/{id}/handoffs`](https://vetta.sh/docs/api/sessions#hand-off-to-other-agents) hands work from one of your sessions to up to six agents at once under that session's agent's `handoffs` policy, one refusal row per target that is turned away. It is how the dashboard's `@mention` reaches a room member; the 8-per-session quota does not apply to a person.

### The `browser` tool

One tool, one `action` argument. `goto`, `click`, `type`, `links` and `screenshot` are deterministic; `act` (do what an instruction says, `%name%` placeholders filled from `variables`), `extract` with an `instruction` and optional JSON `schema`, and `observe` (list matching elements) are model-driven. Every action passes the [guards](https://vetta.sh/docs/computer/browser.md#write-guard) — allow-list, write guard, credential guard, extract guard — whatever the permission. Saved logins are driven over the [REST](https://vetta.sh/docs/api/browser.md), [SDK](https://vetta.sh/docs/sdk/browser.md) and [CLI](https://vetta.sh/docs/cli/browser.md) surfaces; the tool refuses a `context_name` until it can be granted one.

The tool's `config` is the same option object as `computers.create`'s `browser` field. Creating or updating an agent (`POST /v1/agents`, `PATCH /v1/agents/{id}`) and defining a blueprint with a `tools.configs.browser.config` validate it by field and refuse a malformed one with `validation_failed` and `param: tools.configs.browser.config.<field>` — a stored block that failed to parse falls back to an empty allow-list and a browser that reaches nothing, so it is refused at the write instead. Every field is optional:

* `allowed_domains` — `string[]`, optional, defaults to `["*"]`. Absent, the browser reaches the open web, matching `web_fetch` and `web_search` below; name domains to close it to those hosts. An explicit `[]` is refused — it is the shape that grants a browser reaching nothing. `["*"]` is refused together with `proxy: true`, so a proxied browser must name its domains.
* `allow_writes` — `boolean`, `false`. Off, the write guard refuses an `act`, `click` or `type` that looks like it submits or changes something.
* `allow_extract` — `boolean`, `false`. Off, `extract`, `observe`, `links` and `screenshot` are refused while a saved login is open (the extract guard).
* `region` — one of `us-west`, `us-east`, `eu-central`, `ap-southeast`; default `us-west`. Where the browser runs.
* `stealth` — `boolean`, `false`. Anti-bot evasion.
* `proxy` — `boolean`, `false`. Residential proxy; needs a bounded `allowed_domains`.
* `keep_alive` — `boolean`, `false`. The browser survives a brief disconnect.
* `timeout_minutes` — integer 1–360, `15`. Idle timeout of a browser the tool opens.
* `context_name` — string, 1–64 chars. The [saved login](https://vetta.sh/docs/api/browser.md) the browser opens with. The agent's identity must hold a grant for it; without one the tool refuses the call.
* `persist_context` — `boolean`, `false`. Write cookies back to `context_name` when the browser closes.
* `human_login` — `boolean`, `false`. A person signs in to the saved login first; the agent continues from it.
* `ask_actions` — `string[]` of action names, `["signup"]`. The actions that are put to the operator first even under `allow`; see confirmation below.
* `viewport`, `os`, `block_ads`, `solve_captchas` — see the [reference](https://vetta.sh/docs/computer/browser.md#configuration-reference). Passed through to the browser as-is.

The dashboard's Tools tab draws every field but the last five, and carries those through a save untouched.

**Confirmation.** All actions follow the tool's `permission` (default `allow`). Under `allow`, the actions named in `ask_actions` are put to the operator first (`requires_action`) and every other action runs. The default list is `["signup"]`: creating an account on a third party's site is the one action that pauses unless you say otherwise. Widen the list for a browser whose `allowed_domains` is open — `["goto", "click", "type", "act", "login", "signup", "save_context"]` lets `extract`, `screenshot`, `links` and `observe` read the open page without a card while a person approves each URL and each change. `deny` on the tool withholds the browser altogether and `ask` pauses every action, whatever the list.

### The `mobile` tool

One tool, one `action` argument, and the same actions as [`POST /v1/mobile_devices/{id}/act`](https://vetta.sh/docs/api/mobile-devices#act-on-the-device): look with `screenshot` or `element_tree`, then `tap` (`x`/`y` in screenshot pixels, or a `selector` by `text`, `resource_id` or `content_desc`), `type`, `press_key`, `scroll` (`direction`, optional `amount`) or `open_url`. `end` hands the phone back. The tool is built on the `pi` harness only, and `open_url` is not bound by the browser's `allowed_domains`.

* **One phone per session.** The first call opens it. Calls made in parallel share it. It stays open across replies and approvals, so the next message can carry on where the app is. It ends when the agent calls `end`, when the session ends or stops on its budget or an error, or when its idle timer fires. A later call then opens a new one.
* **The model sees the screen.** A `screenshot` is saved as a [file](https://vetta.sh/docs/capabilities/files.md) and shown to the model as a link to it, the way the browser's screenshots are; a model without vision works from `element_tree`.
* **You can watch it.** While the agent uses it, the phone appears in the chat's **Computer** panel, below the agent's computer, with its screen and Live view — and on the dashboard's Mobile page (Computers → Mobile).
* **Or start it yourself.** With no phone yet, the Computer panel offers **Create phone**. The phone is made with the agent's `config` and filed under the chat (`session_id` on [`POST /v1/mobile_devices`](https://vetta.sh/docs/api/mobile-devices)); the agent's next call uses it instead of opening a second one.
* **Money.** A phone bills `$0.030` per minute on the `mobile` [line](https://vetta.sh/docs/platform/pricing.md#priced-tools), booked once when it ends against the organization's balance. It is not held against the session's or the agent's budget cap; its timers bound it.
* **Permission.** `ask` by default: the first call waits for a person. Approve it for the session and the rest of the chat runs without a card. Set `allow` to skip the card, or `deny` to withhold the tool.

`config` is optional and validated by field on every agent write:

* `os_version` — Android only, `13`, `14` or `15`, default `15`.
* `model` — `phone`, the default and today the only value.
* `jurisdiction` — `us`, `eu` or `as`, default `us`.
* `idle_timeout_minutes` — 1–180, default `10`. Also bounds what a long wait between messages can cost.
* `max_duration_minutes` — 1–60, default `60`. An agent's phone is capped at an hour: the minutes settle against the session when it ends.
* `apps` — up to 10 package names from the platform's curated app catalogue, installed when the phone opens.

> [!NOTE]
>
> `ask_operator` has **no `allow`** (nor do `request_tools`, `propose_plan` and `apps.request_access`). The tool *is* the pause, so `allow` collapses to `ask`: the session stops, reports `awaiting_answer`, and the question waits in `pending_actions` until someone answers it — see the [awaiting-answer loop](https://vetta.sh/docs/concepts/session-operations.md#awaiting-answer-loop). Set `deny` (or `enabled: false`) to forbid an agent to ask at all. It is not offered on a harness that [cannot hold a call open for a person](https://vetta.sh/docs/concepts/harness-capabilities.md), and a session that enables it on one is refused when it starts.

> [!NOTE]
>
> `request_tools` follows the same rule — it can never be `allow`, and a session-wide grant never covers it. Its description names the built-ins the agent is not currently offered, so the agent can ask for exactly what a task needs (`generate_video` with a model, `email.read`, `gmail.fetch_emails`). The call parks as an ordinary approval showing the proposed rows and the reason. Approving it mints a new agent version with those rows merged into `tools.configs` (an edit you made meanwhile is kept), re-pins the running session to it, and queues the session's next turn so the tool is offered as soon as the current one ends — a scheduled agent carries on with the task rather than waiting for its next input. Refusing it is the answer; the agent is told not to ask again. Permission is not a provider: a granted `generate_video` still needs a video model configured, and a `<connector>.<tool>` still needs that account connected.

> [!NOTE]
>
> `send_to_agent`, `wait_for_agents` and `list_agents` follow [`handoffs`](#handoffs): an agent whose policy is `false` is never offered one. `wait_for_agents` and the `wait: true` mode of `send_to_agent` are a **coordinator's** — a teammate that tries `wait: true` is refused, so work is delegated one level, never onward, though a teammate may still hand work on with `wait: false`. The two board tools appear only for a team that declared a board. They are listed here because a tool you cannot name is a tool you cannot `deny`.
>
> Every other tool above is on by default, and `mobile` is offered only on a deployment that can provision phones. A tool the session has no [computer](https://vetta.sh/docs/computer/index.md) for is simply not offered — a text-only session gets none of the six sandbox tools (`bash`, `read`, `write`, `edit`, `ls`, `find`) and keeps the rest. The one exception is `read`: a text-only session that pinned [skills](https://vetta.sh/docs/capabilities/skills.md) still gets it, because a skill's body is a file the model is told the location of, and a location nothing can open would be a lie. It reads the skills and nothing else — there is no filesystem behind it. There is deliberately **no `grep`**: its search runs a local process the sandbox cannot redirect, so it would search the wrong machine. `bash` covers the capability against the right filesystem.

## Web tools

`web_search` and `web_fetch` reach the open web, so each one takes its own filters and its own cap
on how much page text may enter the context. Both are configured in the same `configs` map as every
other tool:

```jsonc theme={"system"}
{
  "tools": {
    "configs": {
      "web_search": {
        "enabled": true,
        "permission": "allow",
        "config": { "blocked_domains": ["competitor.example"] }
      },
      "web_fetch": {
        "enabled": true,
        "permission": "allow",
        "config": {
          "allowed_domains": ["docs.example.com", "status.example.com"],
          "max_content_tokens": 4000
        }
      }
    }
  }
}
```

* **`allowed_domains`** — a non-empty list is a closed list: nothing outside it is reachable. Empty
  or absent means the open web. A domain covers its subdomains, so `example.com` matches
  `docs.example.com` and never `notexample.com`.
* **`blocked_domains`** — always wins over `allowed_domains`, so one entry carves a hole in a broad
  allow-list and the answer never depends on list order.
* **`max_content_tokens`** — how much of a fetched page may enter the context; the rest is truncated.
  Defaults to `4000`. Context you never load is context you never pay for.
* The two tools are configured **separately** — searching broadly while fetching only from a short
  list of trusted hosts is the common shape.
* A search result outside the policy is dropped from the results; a `web_fetch` outside it is
  refused before the page is requested. A page that **redirects** off the policy is refused too —
  the host that actually answered is the one that has to be admitted, so an open redirect on an
  allowed host cannot be used to reach past the list. That refusal comes after the fetch, so unlike
  the others it is billed.
* Both tools are **priced per call** against the [budget](https://vetta.sh/docs/concepts/budgets.md) and appear on
  `GET /v1/agents/{id}/spend?by=component` under `search`. The price is what the call cost, so it is
  booked after the call rather than quoted before it — a domain filter bounds *where* an agent can
  go, `cap_micro_usd` bounds how much it spends getting there.

## Generation tools

Both save what they produce to [Files](https://vetta.sh/docs/capabilities/files.md) and hand the agent a `file_id`; they
differ in *when*.

`generate_image` answers in the same call. An image renders in seconds, so the tool waits, saves the
result and returns its `file_id` there and then. It can render from up to three reference images
(`image_urls`, each a stored file's `fil_` id — say one an earlier render returned — or a public
`http(s)` URL) and several variations at once (`n`, up to 4); every variation is its own file. On
the platform's own image models, when some of them fail the ones that rendered are still saved and
billed; elsewhere a model renders the set or fails it whole. Within one session, a request with the same arguments
(a `seed` omitted counts as the same) is saved under the same `file_id` as the first, replacing that
image, and the result says so with `replayed: true` when the first was billed. Pass a different
`seed` to get a new file and keep the earlier one.
Both generators are also a [REST API](https://vetta.sh/docs/api/media.md) — the same renders, outside a session.

**Folders.** `publish_file`, `generate_image`, `generate_video`, `generate_speech`, `clip_video` and
`edit_video` each take an optional `folder`: a short project name such as `Episode 12`. The file
lands in that [folder](https://vetta.sh/docs/api/files.md#folders). Leave it out and the file lands in the folder of the last
file the session filed, so a series of renders stays together. A name that matches an existing
folder in another case takes that folder's spelling.

`generate_video` **does not block**. A render measured in minutes cannot happen inside a turn, so
the tool starts the job and returns a handle straight away and the agent carries on working. When
the job finishes, the file is saved and the session is woken and told its `file_id`. Nothing is
polled and nothing is waited on, so a long render never consumes a turn.

```jsonc theme={"system"}
{
  "tools": {
    "configs": {
      "generate_video": {
        "enabled": true,
        "permission": "allow",
        "config": { "models": ["bytedance/seedance-2.5", "openai/sora-2-pro"] }
      }
    }
  }
}
```

* **`models`** — which model this agent generates with when it names none, out of the live
  catalogue at [`GET /v1/media/models`](https://vetta.sh/docs/api/media). It is a **default, not a whitelist**: the
  **first one you name** is what runs when the agent does not choose, and the agent may still name
  anything the provider publishes. There is no fixed list and no enum, because the catalogue belongs
  to the provider and changes without a release of ours.
* **Choosing a model.** With no `models` pinned, `generate_image` runs the platform's default image
  model — the first the catalogue lists that takes what the call carries (a reference image, a
  character) — and, if that model is unavailable, the next one; `generate_video` runs the first of
  the platform's video defaults the catalogue publishes — `black-forest-labs/flux-3-max`,
  `minimax/hailuo-3`, `bytedance/seedance-2.5`, then `alibaba/wan-3.0/text-to-video` — and for an edit, only one the
  catalogue says takes a video; otherwise the agent is asked to name one. A model the agent names
  always wins and never falls back to another. Pins win too, in order: when the agent names no
  video model and the first pin is refused or unavailable, the next pin renders — never a model
  outside the pins.
  Asking for a model the catalogue does not have is refused before anything is submitted, and the
  refusal lists every id the provider publishes for that kind — the agent's way of discovering
  what exists without a second tool.
* **Nothing is held before a render starts.** No reservation is taken against the agent's
  [budget](https://vetta.sh/docs/concepts/budgets.md) and no balance floor is checked: a render costs cents and is what the
  agent was asked for, and refusing it at that moment would cost more than the render. Training a
  [character](#characters) is the exception — it is held first.
* **You are billed for what the finished job cost**, once per job, on the render's own idempotency
  key — a replayed call renders and bills once. A video on a model priced per generated second costs
  its length at the resolution it renders at (720p on the platform's per-second models); one whose
  model reports a cost costs that. It appears on `GET /v1/agents/{id}/spend?by=component` under
  `media`.
* A job that fails, or that never finishes, tells the session so and stores nothing.
* `seconds`, `aspect_ratio` and `seed` are optional. The platform's own models clamp them to what
  they support — an aspect ratio or a length a model does not take becomes the nearest one it does;
  another model may refuse a value it does not take instead. That is the whole shared
  argument list — resolution, quality, output format, a last frame, an audio
  switch and other per-model settings are not exposed to the tools, and the REST pages
  ([images](https://vetta.sh/docs/api/images.md), [video](https://vetta.sh/docs/api/video.md)) say the same. Both tools and both routes also take
  `character` (and `generate_image` `style` and `character_strength`), [below](#characters).
* **What comes back is an id, not a file on the box.** Both tools write to
  [Files](https://vetta.sh/docs/capabilities/files.md); nothing is left in the [workspace](https://vetta.sh/docs/computer/filesystem.md), because the
  render is saved from the provider to storage and never passes through the computer. An agent that
  has to *do* something to a render — join segments into a longer piece, overlay audio, re-encode —
  pulls the bytes back with [`fetch_file`](#reading-a-file-back-into-the-sandbox) first.

### Editing a clip: `edit_video` (Mira)

`edit_video` is **Mira**. It edits a 3–15 s clip from a `prompt`
and up to 4 `references` (a `fil_` id or a public URL each): a new place, an outfit or object, a
person swapped for one in a reference photo. Motion, camera and the filmed look are kept. Optional
`mode`. One call makes one edit. Like `generate_video` it runs in the background; the result lands
in [Files](https://vetta.sh/docs/capabilities/files.md).

* **It comes with `generate_video`.** An agent offered `generate_video` is offered `edit_video` on
  the same terms (`allow`, `ask` or `deny`). Studio draws a row for it; until you set that row,
  it follows `generate_video`. Name `edit_video` in `tools.configs` to set it on its own.
* **A deployment that does not run the edit lane offers it to nobody.**
* **It has a set price:** $0.03 per second of the source clip, rounded up, for an edit with no
  photos; $1.48 for an edit with photos or a person swap, at any length
  ([pricing](https://vetta.sh/docs/platform/pricing.md#priced-tools)). Failed, refused and timed-out edits cost nothing.
* **Without a session,** submit the same inputs over REST. `POST /v1/media/edits` (scope
  `agents:write`, `Idempotency-Key` required) answers `202` with an `edit_job`. Read it with
  `GET /v1/media/edits/{id}` (scope `agents:read`). The SDK is `client.media.edits.create` and
  `client.media.edits.get`; the CLI is `vetta media edit`. A length that is not known yet is
  reserved at 15 seconds and settled at the measured length when the edit finishes. A failed
  edit is not billed.

### Characters

A one-shot `generate_image` of "a woman with freckles" is a different woman every time, so a channel
of shorts has no recurring host. A **character** fixes that: a reusable, consistent on-screen persona
trained once from a handful of reference stills and then pinned into every later render, so the same
face, hair and build carry across a whole channel — the AI-influencer primitive. It is the media
sibling of an [identity](https://vetta.sh/docs/identity/overview.md): an identity is *who an agent is being* to the world (an
inbox, a number); a character is *who it renders as* on screen.

`create_character { name, description?, image_urls, persona?, style_pack?, consent }` starts the training and **does not block** — a
character trains for minutes, far longer than a turn. It returns a `chr_` handle in `status:
"training"` and the agent carries on.

* **`name`** — a short name, unique among the organization's characters, how the agent refers to it
  later ("Ava"); up to 100 characters. Surfaced to the model.
* **`description`** — optional; who the character is (looks, age, vibe, wardrobe), so renders stay in character.
* **`persona`** — optional; up to five lines (`niche`, `bio`, `voice`, `wardrobe`, `hooks`) an agent
  reads when it writes as the character.
* **`style_pack`** — optional; up to 12 [look](https://vetta.sh/docs/api/images.md#looks) names the character is usually drawn in; none gives Candid selfie, Phone snap and Realistic.
* **`image_urls`** — one to **sixteen** reference photos of the *same* person, each a stored file's
  `fil_` id or a public `http(s)` URL; more, varied angles train a stronger likeness. A private host,
  a `data:` or `file:` URL, and a `fil_` that is not one of the organization's images are refused.
  The first two are checked for a recognizable public figure before anything is held; if they show
  one, the call is refused with the reason.
* **`consent`** — **required**, `{ attested: true }`: the person in the photos is the operator, or has
  agreed to an AI likeness, and is not a public figure. The agent sets it only once a person has
  confirmed it; the character keeps it as `metadata.consent` with the session, the time and whether a
  person allowed the call on its card.

A training is paid, so `create_character` **always asks** — the person approving sees whose face
is being trained, and the card says that their yes confirms the person in the photos agreed. No tool
policy can set it to `allow` (an `allow` reads as `ask`; `deny` still removes it), the tool refuses a
call no person approved, and a session grant does not wave later trainings through. It is
[priced per training](https://vetta.sh/docs/platform/pricing.md#priced-tools) on the `media` component: a hold is taken
against the session's [budget](https://vetta.sh/docs/concepts/budgets.md) before anything is bought (a budget with no room
refuses it, and so does a per-task ceiling below the price), and the training is billed once the
trainer accepts the photos — a training that later ends `failed` is refunded. A name a live character already holds is refused before the trainer is
paid, and a replay of the same call answers the character it already made, untrained and unbilled.

`get_character { character }` reads its status by `chr_` id or name. Poll it until the status is
`ready` — the only renderable state — or `failed` (train a new one with clearer references).
`list_characters` lists every character the organization has, so a host trained in one chat is
found in the next, then the **prebuilt** ones every organization can render as with no training (they
cannot be deleted); `delete_character { character }` retires one and frees its name. All three are
free. Then render:

* **`generate_image`** takes an optional **`character`** argument — the `chr_` id or name of a
  `ready` character. The same face then carries across every image. With `image_urls` too, each one
  a still of that character, the image is an edit of that still that keeps its room, props and
  outfit in a new pose or framing — how a later shot of the same scene starts. Add
  `style` to draw it in a look, and `character_strength` (0.5–1) only when a face drifts in a close-up.
* **`generate_video`** takes `character` too, with `image_urls` set to one still rendered as that
  character, which becomes the first frame: the face on screen is the still's, so the still is
  required, and only a model that animates a still is used. There is no video model that pins a
  character directly.

A character is refused before anything is rendered when it is still training or its training failed
(`validation_failed`), and both tools and the `character` argument are refused with
`feature_not_configured` on a deployment whose media backend trains no characters — a character is
never silently dropped, which would render the wrong face. The same lifecycle is a
[REST API](https://vetta.sh/docs/api/characters.md) — `POST /v1/characters`, and `character` on `POST /v1/media/images` —
and [`vetta character`](https://vetta.sh/docs/cli/character) in the terminal; a character made on any of them renders on
all of them.

## Looking at an image

Everything here that makes a picture files it and hands back an id: a `screenshot`, a
`generate_image` render, a still someone uploaded. `find_files` then lists those
ids and their metadata and never their bytes. `view_image` is the other half — the one call that
opens one.

`view_image { file_ids }` takes one to four `fil_` ids the agent already has, from `find_files` or
from the tool that just made the file, and returns those pictures for the model to describe, compare
or work from. It finds nothing: discovery is `find_files`'s job, so the call that costs context is
always one the agent asked for by id.

* **Ids are resolved against your organization's own library.** An id from somewhere else is
  `not_found`, not a blind read.
* **Only what a model can actually look at** — PNG, JPEG, GIF and WebP. A PDF, an SVG or an audio
  file is refused by name rather than quietly dropped, because an agent told nothing is an agent
  that describes three images believing it described four.
* **Four at a time.** A turn that wants more than four is sampling a video, which belongs in the
  sampler, or reading a document, which is `read`'s. Four is enough to hold a render next to its
  reference.
* **What it costs.** The call itself is unpriced — nothing is generated and no provider is asked for
  anything. What it costs is context: each picture is part of the model request that carries it, and
  it stays in the session's [history](#context-management), so looking at four images twice is a
  choice worth making on purpose.

The pictures travel as links, never as bytes: each one is a `ToolImage` — `{ file_id, url,
media_type }`, where `url` is a short-lived signed read of the file. The same is true of every tool
that returns an image, and it is why an hour of browsing does not turn into an hour of PNGs replayed
on every turn. Links expire; asking `view_image` again for the same id mints a fresh one, which is
cheaper than what it replaces.

## Reading a file back into the sandbox

`publish_file` carries bytes one way: out of the [workspace](https://vetta.sh/docs/computer/filesystem.md) and into
[Files](https://vetta.sh/docs/capabilities/files.md). `fetch_file` is the other direction, and it exists because everything
the platform renders *for* an agent starts on the far side of that line. A
[`generate_video`](#generation-tools) job hands the session a `fil_` id when it finishes; the video
itself is saved from the provider straight to storage and never touches the computer's disk. So an
agent that rendered a three-minute piece as six 30-second segments and wants one file out of them is
holding six id strings, not six files — it can install `ffmpeg` perfectly well and still have nothing to
point it at.

`fetch_file { file_ids, dir? }` writes those files into the session's workspace and answers with the
path each one landed at:

```
fetch_file(file_ids=["fil_a1b2", "fil_c3d4", "fil_e5f6"], dir="segments")
→ /workspace/segments/opening.mp4 — fil_a1b2 (14204893 bytes, video/mp4)
  /workspace/segments/middle.mp4  — fil_c3d4 (15880110 bytes, video/mp4)
  /workspace/segments/close.mp4   — fil_e5f6 (12043557 bytes, video/mp4)
```

Beside those lines it returns `{ files: [{ file_id, path, bytes, content_type }] }`, so the next
`bash` call can name the paths without parsing prose. From there they are ordinary files: probe them,
concatenate them, re-encode them, and `publish_file` the result — which mints a **new** id, so the
segments and the finished piece both stay in the library.

* **Ids are resolved against your organization's own library**, the same resolution
  [`view_image`](#looking-at-an-image) does. An id your catalogue does not hold is `not_found`, never
  a read of a guessable storage key.
* **It needs a [computer](https://vetta.sh/docs/computer/index.md).** There is nowhere to put a file otherwise, so a session
  with no sandbox is refused by name — `computer_unavailable`, raised before the library is even
  read — rather than handed an empty directory.
* **Eight ids a call, 5 GiB a file** — the same ceiling as `publish_file`, so anything an agent
  published it can fetch back. Bytes cross a slice at a time and are never held whole on the way, so
  size costs transfer time only. A refusal names the file and its size, and nothing is written when
  one fires — a batch never lands half-copied.
* **`dir` is relative and confined** to the working directory, which is also the default; it is
  created if it does not exist. A `dir` that climbs out of it is refused before any byte is written.
* **A name is never reused.** Two library rows may share a `name`, and the second file quietly
  overwriting the first is not a missing clip, it is the same clip twice in the output — so the
  second one lands under its own id instead. The `path` in the result is always the one to use.
* **Unpriced.** It reads storage and writes a disk you are already paying for, like `ls`. What it
  costs is the computer's running time, which the [computer meter](https://vetta.sh/docs/computer/limits-and-billing.md)
  books either way.

The bytes go to the disk and never into the conversation: the model is told a path and a size, not
shown the file. Looking at a picture is still [`view_image`](#looking-at-an-image)'s job, and a
`read` of an image on the disk is still refused in favour of it.

## Audio tools

Two of the three directions of the [Audio API](https://vetta.sh/docs/api/audio.md), as tools. Both are on by default with
permission `allow`, both are billed on the `media` component like `generate_image` — what the call
actually cost, once — and both answer in the same call: a transcription the provider queues instead
of finishing inside the request is refused as `provider_error` rather than handed out as a job
nothing collects, so neither tool wakes the session later.

`transcribe_audio { file_id | url, language? }` — exactly one of `file_id` (a stored
[file](https://vetta.sh/docs/capabilities/files.md)) or `url` (public `http(s)`). Returns `{ text, language,
duration_seconds, segments }`, where each segment is `{ start_seconds, end_seconds, text }`.

`generate_speech { text, voice?, model? }` — up to 4 096 characters. Saves the audio (MP3) to
[Files](https://vetta.sh/docs/capabilities/files.md) and returns `{ file_id, name, size_bytes, duration_seconds, sha256 }`. `model` is one from
[`GET /v1/media/models?kind=tts`](https://vetta.sh/docs/api/audio#the-catalogues), default the managed `tts/auto`
alias, which speaks on the [self-hosted default model](https://vetta.sh/docs/api/audio.md#speak) (English only) and on the
next model when that one cannot; `voice` is one the model publishes — omit it for the model's default.

`generate_sound_effect { prompt, duration_seconds?, loop? }` — a `prompt` up to 500 characters
describing the effect, an optional `duration_seconds` (0.5–30) and an optional `loop`. Saves the
audio (MP3) to [Files](https://vetta.sh/docs/capabilities/files.md) and returns `{ file_id, name, size_bytes,
duration_seconds, sha256 }`. Omit `duration_seconds` to let the length follow the prompt. Billed on
the `media` component like `generate_speech`.

`generate_music { prompt, length_seconds?, instrumental? }` — a `prompt` up to 2 000 characters
describing the song, an optional `length_seconds` (3–300) and an optional `instrumental`. Saves the
audio (MP3) to [Files](https://vetta.sh/docs/capabilities/files.md) and returns `{ file_id, name, size_bytes,
duration_seconds, sha256 }`. Omit `length_seconds` for a 30-second song. Billed on
the `media` component like `generate_speech`.

None of these tools take a `models` pin. Speech-to-speech is a [route](https://vetta.sh/docs/api/audio.md#converse) only —
there is no tool for it yet.

## Permission policies

Every server-executed tool resolves to `allow`, `ask`, or `deny` before it runs — set a default in `default_config` and override per tool in `configs`, and respond to `ask` with a `tool.confirm` event. Full detail, including the confirmation flow and connection/primitive scoping, lives in [Policies](https://vetta.sh/docs/concepts/policies.md).

The tools that publish, spend, file or change the company's settings **ask by default**: `social.post`, `email.send`, `legal.verify`, `legal.resend_link`, `legal.form`, `legal.submit`, `wallet.pay`, `wallet.transfer`, `card.issue`, `card.credentials`, `card.cancel`, `trading.place_order`, `trading.cancel_order`, `trading.close_position`, `company.set_timezone`, `company.set_logo`, `company.set_brand`, `apps.request_access`, `mobile`, `data_run`, `generate_video`, `edit_video` and `create_character`. A video's card shows its price and lets the person pick the model; an agent cannot set `generate_video` or `edit_video` to `allow`, with `request_tools` or by writing its own or another agent's toolset; a person's key can. `create_character`, `apps.request_access`, `company.set_logo` and `company.set_brand` never run without a card, whatever the policy. For the rest the order is: a per-tool `configs` entry wins; otherwise the tool is `ask` unless `default_config.permission` is `deny`, in which case it is `deny`. To offer one to such an agent, give it `"permission": "ask"` in `configs`.

## Once a chat has read people's data

Reading contacts, mail, messages or a connected app flags the chat, and any chat a flagged one hands work to. While it is flagged, your policy still applies, but a chat can't carry that data out unseen:

* `bash`, `build_3d` and publishing with `apps` are refused. On an agent that holds `bash`, so are `write`, `edit` and `fetch_file`. Start a new chat for that work.
* Every send, `board_write`, `mobile`, `data_run`, a browser that moves or writes, every `web_fetch`, and every connected-app or MCP tool ask, each time, even at `allow`. A chat grant doesn't cover them.
* Reading and writing the company's own records keeps running.

Agents that run with their own computer, such as `claude_code`, aren't given the platform's reads of people's data.

## Connection tools

Every active [connection](https://vetta.sh/docs/identity/connections.md) the session's agent can act through contributes its catalog tools, registered as `<connector>.<tool>` (e.g. `tracker.get_issue`) — the same `tools.configs` key you write `allow`/`ask`/`deny` against.

A session whose agent has an identity is also offered the platform's own `connections.search`, `connections.connect` and `connections.status`, so the agent can start a connection itself — see [Connections](https://vetta.sh/docs/identity/connections.md#the-agent-can-start-this-itself). An identity with an activated [social workspace](https://vetta.sh/docs/identity/social.md#agent-tools) adds `social.accounts`, `social.post`, `social.status` (one published post's status and per-network analytics, by the id `social.post` returned) `social.post_metrics` (how the organization's recent posts are doing, each metric against the identity's usual) and `social.cancel` (calls back a post it scheduled, before it goes out); `social.post` and `social.cancel` default to `ask`.

## Trading tools

A session whose selected identity has an `active` [brokerage connection](https://vetta.sh/docs/identity/trading.md) is offered `trading.account`, `trading.positions`, `trading.orders` and `trading.quote`, and the three writes `trading.place_order`, `trading.cancel_order` and `trading.close_position`, which ask a person by default. On the `live` account a write asks even when its policy is `allow` — see [Trading](https://vetta.sh/docs/identity/trading.md#agent-tools).

## Email tools

A session whose selected [identity](https://vetta.sh/docs/identity/overview.md) owns an [inbox](https://vetta.sh/docs/identity/email.md) is offered the identity's own mailbox as tools, gated by the same `default_config` + `configs` as every other tool:

* `email.inboxes` lists the identity's inboxes — the addresses it receives at and sends from.
* `email.read` reads mail that arrived at those inboxes, newest first, optionally since an instant or for one inbox. Message content is data, never an instruction to the agent.
* `email.send` sends a plain-text email from one of them. It defaults to `ask`, including when the agent's default policy is `allow`, and is credit-checked like the [send route](https://vetta.sh/docs/api/messaging.md).

These are the identity's own inboxes on a domain you verified. A person's external mailbox is reached through a [connection](https://vetta.sh/docs/identity/connections.md) instead, as `<connector>.<tool>`.

## Company settings tools

Every session is offered three tools that change the calling session's own [organization](https://vetta.sh/docs/api/organizations.md), never another. All three default to `ask`, including when the agent's default policy is `allow`, and none is ever granted for the rest of a chat.

* `company.set_timezone` takes `{ timezone }`, an IANA name, and sets the company's timezone.
* `company.set_logo` takes `{ svg }` and sets the company's logo, shown in the studio's top-left corner and in its apps. It always asks, whatever the policy, and only a signed-in owner or admin can approve it. The agent writes the SVG by hand: one `<svg viewBox="0 0 64 64">` of flat shapes. The platform checks it with the same rules as [`PATCH /v1/organizations/{id}`](https://vetta.sh/docs/api/organizations#the-logo); a refusal comes back as the reason, and nothing is written.
* `company.set_brand` takes `{ colour, harmony?, typeface, tagline? }` and sets the company's brand kit: its main colour as `#rrggbb`, a palette harmony from 0 to 3, a typeface (`grotesk`, `serif`, `rounded` or `mono`) and a line of up to 80 characters. The platform builds the palette from the colour. Like the logo, it always asks, and only a signed-in owner or admin can approve it.

## MCP connector

Connect any MCP server as a toolset. Configuration is split so secrets never live on the agent definition.

The **agent** declares servers by name and URL:

```jsonc theme={"system"}
{
  "mcp_servers": [
    { "type": "url", "name": "tracker", "url": "https://mcp.example.com/mcp" }
  ]
}
```

A server's tools are selected and gated in the same `tools` wrapper, through `configs` entries keyed by the MCP tool name — the identical `default_config` + `configs` + `permission` pattern used for built-ins:

```jsonc theme={"system"}
{
  "tools": {
    "default_config": { "permission": "ask" },
    "configs": {
      "tracker.get_issue": { "enabled": true, "permission": "ask" },
      "tracker.delete_project": { "enabled": false }
    }
  }
}
```

* The `url` must be a public `http` or `https` address. A private, loopback or link-local IP, `localhost`, or a `.local` or `.internal` name is refused with `400 validation_failed`, `param: "mcp_servers.0.url"`.
* Constraints: up to 20 servers per agent; server URLs must use HTTPS; every declared server must be referenced by at least one enabled tool, and every referenced tool must resolve to a declared server.
* **Default permission is `ask`** for MCP tools, so a newly exposed server tool never auto-runs. To trust one, give it an explicit `"permission": "allow"` in `configs` — per tool, by name. There is no server-wide trust switch.
* **Auth is injected at session start**, never on the agent: reference [vault](https://vetta.sh/docs/identity/vault.md) credentials (`static_bearer` or `mcp_oauth`) which are matched to servers by URL and injected server-side. Secret-bearing inline headers such as `Authorization`, cookies and API keys are rejected; the sandbox and model never receive the token. A credential bound to an agent (`agent_id`) reaches only that agent.
* **Catalog input is bounded:** one server contributes at most 100 tools; names, descriptions, schemas and response bodies are capped before they enter model context. Configured credentials are redacted from catalog metadata, tool output and setup failures.
* The server name `vetta` and this platform's own `/v1/mcp` are reserved: declaring either needs the `admin` scope, and is refused with `403 forbidden` otherwise.
* A connection or auth failure doesn't stop the session — it surfaces as a `session.error` event naming the server, and the connection is retried on the next wake.

### Naive Managed Agents as its own MCP server

`POST /v1/mcp` publishes **the Naive Managed Agents API itself** as an MCP tool catalog, so an agent can operate a
Managed Agents account the way it operates any other connected system. It is a JSON-RPC endpoint speaking the
three methods a catalog needs (`initialize`, `tools/list`, `tools/call`) and nothing else.

```jsonc theme={"system"}
{
  "mcp_servers": [
    { "type": "url", "name": "vetta", "url": "https://api.vetta.sh/v1/mcp" }
  ]
}
```

* **The catalog is generated from the route table**, one tool per served route, named
  `resource_verb` — `agents_create`, `sessions_list_events`, `computers_exec_command`. Namespaced by
  the server, a tool is `vetta.agents_create` in `configs`, exactly like `tracker.get_issue` above.
* **Argument schemas are the request schemas.** Path parameters, declared query filters and body
  fields arrive as one flat object, validated by the same zod schema `/v1/openapi.json` publishes.
* **Every tool carries `annotations.readOnlyHint` and `annotations.destructiveHint`**, so a caller
  can allow reads and benign writes and `ask` only on the dangerous ones without maintaining a list.
  `readOnlyHint` follows what the route does, not its method: a POST whose body is only the question
  — pricing a payment or a card, minting a file link — is read-only too. `destructiveHint` is `true`
  for every delete and for a call that spends money or acts outside your account (a payment, a
  card or its credential reveal, a post, an email, a third-party sign-up), changes the organization's security posture
  (vaults, webhooks, members, domains, saved logins), decides what an agent may do (creating,
  updating or rolling back one), rewrites data (a migration, raw SQL), or runs code (a command, a
  browser action on a page). Creating a session, a skill or a file is neither.
* **A tool call is an ordinary API call.** It is dispatched back through the same middleware with the
  bearer that made it, so authentication, scope, the plan gate, rate limits and the strict request
  contract all apply once and identically. A tool can never reach further than its credential.
* **MCP sessions narrow access further.** A short-lived [MCP session](https://vetta.sh/docs/api/mcp-sessions.md) binds an
  optional identity, scopes, a tool allowlist and an expiry to one revocable bearer. Its catalog is
  filtered to that allowlist and revocation takes effect on the next call.
* **Nine routes are withheld from the catalog** and answer only over HTTP: this endpoint itself, the
  SSE stream (`GET /v1/sessions/{id}/stream`), the multipart upload (`POST /v1/files`), the four
  `/v1/api_keys` routes (they mint and return live secrets), `POST /v1/vaults/{id}/credentials`
  (the one route that accepts a secret value) and the [model proxy](https://vetta.sh/docs/api/proxy.md) (a credentialed,
  billed model call is a loop, and its streaming half never returns). None of them is a thing a
  model should be handed.

See [MCP server](https://vetta.sh/docs/api/mcp.md) in the API reference for the handshake, the tool schema, the result cap
and the withheld routes one by one, with the reason each is withheld.

## Tools from your apps

A `fullstack` [app](https://vetta.sh/docs/api/apps.md) that serves its own MCP endpoint declares the path once, on the app
(`"mcp": "/mcp"` on create or patch; `apps[].mcp` in a `naive.config`), and nothing on any agent:

```bash theme={"system"}
curl -fsSL -X PATCH https://api.vetta.sh/v1/apps/app_01H... \
  -H "authorization: Bearer sk_live_..." \
  -H "content-type: application/json" \
  -d '{ "mcp": "/mcp" }'
```

* **Every agent that can access the app gets its tools.** Access is the `apps` tool's
  `allowed_apps` config — absent means every app in the organization, present means exactly the
  listed ids. On each turn the platform resolves the app's current URL plus its `mcp` path and
  registers the endpoint's tools as `<app-name>.<tool>` (`storefront.list_orders`), so they are
  gated by the same `default_config` + `configs` allow/ask/deny as every other tool. No per-agent
  `mcp_servers` entry, and nothing to update when a custom domain moves the URL.
* **Auth is the platform's own token.** Setting `mcp` mints an opaque bearer, pushes it into the
  app as the write-only secret `VETTA_MCP_TOKEN` — the app requires it on its endpoint — and
  injects it as `Authorization: Bearer` server-side on every call. It never reaches the sandbox,
  the transcript or any read route. Setting `mcp` to `null` deletes the secret and forgets the
  token; re-sending the same path never rotates it.
* **An unreachable endpoint never fails a turn.** An app that is not yet `active`, or whose MCP
  endpoint is down or malformed, simply contributes no tools that turn; the platform logs one
  error line and the agent carries on with the rest of its toolset.

## Coming soon

* **`send_sms` as an agent tool** — today SMS is sent via the [API/CLI](https://vetta.sh/docs/api/messaging.md) (`POST .../send`), not from inside a turn.
* **Custom tools** — organization-defined, client-executed tools: you describe the arguments as a JSON Schema, the model emits a tool-use event, your application runs it and returns the result.

## Context management

Keeping the context window small is the other half of cost control. Managed Agents manages it at three levels:

> [card] Progressive disclosure
>
> [Skills](https://vetta.sh/docs/capabilities/skills.md) load a small always-on index; full bodies are pulled only when a task needs them.

> [card] Automatic compaction
>
> When the transcript crosses a token threshold, the earlier turns are summarized into a compaction block and the pre-summary history is dropped, preserving tool pairing.

> [card] Context editing
>
> Stale tool results (and optionally older thinking) are cleared once they're no longer needed, keeping the live window lean without losing the durable record.

The full transcript is always retained durably (see [Runtime & durability](https://vetta.sh/docs/concepts/runtime.md)); context management only governs what is *re-sent to the model* each turn. Context that never enters the window is context you never pay for. The event log behind that transcript is replayable by [`seq` cursor](https://vetta.sh/docs/concepts/events-and-streaming) for **at least 72 hours**, so a consumer that falls behind can always resume without loss.

> [card] [Next: files](https://vetta.sh/docs/capabilities/files.md)
>
> Persist and retrieve artifacts across sessions.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.