> ## 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](/docs/identity/connections), and any MCP server you point it at. You declare them on the agent, set a [permission](/docs/concepts/policies) per tool, and every call is priced against the [budget](/docs/concepts/budgets) 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: **enable-only** (`default_config.enabled: false`, then enable individually) and **trust-by-default-except** (allow all, set one tool to `ask`).
* 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](/docs/concepts/sessions#lifecycle).

## Built-in tools

| Tool                      | What it does                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `bash`                    | Run shell commands in the [computer](/docs/computer/shell).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| `read` · `write` · `edit` | Read, write, and string-replace files in the [workspace](/docs/computer/filesystem).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| `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](/docs/computer/browser#configuration-reference), validated by field on every agent write. See [below](#the-browser-tool).                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| `read_skill`              | Pull a [skill](/docs/capabilities/skills) body into context on demand.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `publish_file`            | Promote a sandbox artifact into persistent [Files](/docs/capabilities/files), with an optional `title` and `tags`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| `fetch_file`              | The inverse of `publish_file`: write files the organization already holds into the session's [workspace](/docs/computer/filesystem) 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](/docs/api/files) by title/name words, `kind` or `tag`; returns ids and metadata, never bytes.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| `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`](/docs/api/credits) and [`vetta agent spend`](/docs/cli/agents). See [Budgets](/docs/concepts/budgets#reading-spend).                                                                                                                                                                                                                                                                                                                |
| `web_search`              | Search the web and get back titled results with links. [Priced per call](/docs/platform/pricing#priced-tools).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| `web_fetch`               | Read one web page as text, so the agent can quote or summarise it. [Priced per call](/docs/platform/pricing#priced-tools).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| `generate_image`          | Generate an image from a description; the result lands in [Files](/docs/capabilities/files). [Priced per job](/docs/platform/pricing#priced-tools).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| `generate_video`          | Generate a video from a description; the result lands in [Files](/docs/capabilities/files). [Priced per job](/docs/platform/pricing#priced-tools).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| `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](/docs/capabilities/files). 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](/docs/api/clips). [Priced per job](/docs/platform/pricing#priced-tools).                                                                                                                                                                                                                                                                                                        |
| `find_stock_photo`        | Search a licensed [stock photo library](/docs/api/images) 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](/docs/api/apps): sites, secrets, domains, and (fullstack) its [database](/docs/api/database) — SQL, tables, a REST layer, migrations — and [object storage](/docs/api/storage), 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](/docs/platform/pricing#priced-tools).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| `generate_speech`         | Turn text into speech; the audio lands in [Files](/docs/capabilities/files). See [Audio tools](#audio-tools). [Priced per job](/docs/platform/pricing#priced-tools).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| `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.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| `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`, either for all of them or for the first answer.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| `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_…): …` and wakes the coordinator. 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](/docs/api/apps#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](/docs/team/rosters-and-versioning). Refused at save time on an agent with no roster.
* `["scriptwriter", "producer"]` — both tools are offered, narrowed to the names listed: `send_to_agent` offers them as its choices and `list_agents` answers with them.
* `false` (or an empty list) — neither tool is offered; the agent sends to nobody.

An agent created before the field existed reads as `true`. Set it on [create or patch](/docs/api/agents), in an [agent file](/docs/cli/agents) 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](/docs/team/delegation): 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`](/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](/docs/computer/browser#write-guard) — allow-list, write guard, credential guard, extract guard — whatever the permission. Saved logins are driven over the [REST](/docs/api/browser), [SDK](/docs/sdk/browser) and [CLI](/docs/cli/browser) 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](/docs/api/browser) 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](/docs/computer/browser#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.

<Note>
  `ask_operator` is the one tool with **no `allow`**. 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](/docs/concepts/session-operations#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](/docs/concepts/harness-capabilities), and a session that enables it on one is refused when it starts.
</Note>

<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>

<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. A tool the session has no [computer](/docs/computer/index) 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](/docs/capabilities/skills) 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.
</Note>

## 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](/docs/concepts/budgets) 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](/docs/capabilities/files) 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 reference images (`image_urls`,
public `http(s)` only) and several variations at once (`n`, up to 4); every variation is its own file.
Both generators are also a [REST API](/docs/api/media) — the same renders, outside a session.

`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`](/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 cheapest model the
  provider publishes a price for, and `generate_video` asks the agent to name one — video carries no
  published price, so there is no cheapest to fall back to and we will not pick a favourite for you.
  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 job starts.** A render's cost cannot be known until it finishes — video
  models publish no price at all — so no reservation is taken against the agent's
  [budget](/docs/concepts/budgets) and no balance floor is checked. A render costs cents and is what the
  agent was asked for; refusing it at that moment would cost more than the render.
* **You are billed for what the finished job actually cost**, once per job, on the render's own
  idempotency key. 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; a model clamps them to what it supports. That
  is the whole 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](/docs/api/images),
  [video](/docs/api/video)) say the same.
* **What comes back is an id, not a file on the box.** Both tools write to
  [Files](/docs/capabilities/files); nothing is left in the [workspace](/docs/computer/filesystem), 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.

## 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](/docs/computer/filesystem) and into
[Files](/docs/capabilities/files). `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](/docs/computer/index).** 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, 64 MiB a file.** A minute of generated video is 10–20 MB, so the bound clears a
  handful of segments and still refuses the single file that would fill a box's disk. 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](/docs/computer/limits-and-billing)
  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](/docs/api/audio), 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](/docs/capabilities/files)) 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](/docs/capabilities/files) and returns `{ file_id, name, size_bytes, duration_seconds, sha256 }`. `model` is one from
[`GET /v1/media/models?kind=tts`](/docs/api/audio#the-catalogues), default the managed `tts/auto`
alias; `voice` is one the model publishes — omit it for the model's default.

Neither tool takes a `models` pin. Speech-to-speech is a [route](/docs/api/audio#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](/docs/concepts/policies).

## Connection tools

Every active [connection](/docs/identity/connections) 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](/docs/identity/connections#the-agent-can-start-this-itself). An identity with an activated [social workspace](/docs/identity/social#agent-tools) adds `social.accounts` and `social.post`; `social.post` defaults to `ask`.

## Email tools

A session whose selected [identity](/docs/identity/overview) owns an [inbox](/docs/identity/email) 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](/docs/api/messaging).

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

## 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 }
    }
  }
}
```

* Constraints: up to 20 servers per agent; 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](/docs/identity/vault) credentials (`static_bearer` or `mcp_oauth`) which are matched to servers by URL and injected server-side. The sandbox never receives the token.
* 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.

### Vetta as its own MCP server

`POST /v1/mcp` publishes **the Vetta API itself** as an MCP tool catalog, so an agent can operate a
Vetta 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 own key.
* **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](/docs/api/proxy) (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](/docs/api/mcp) 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](/docs/api/apps) 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](/docs/api/messaging) (`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. Vetta manages it at three levels:

<CardGroup cols={3}>
  <Card title="Progressive disclosure" icon="layers">
    [Skills](/docs/capabilities/skills) load a small always-on index; full bodies are pulled only when a task needs them.
  </Card>

  <Card title="Automatic compaction" icon="minimize-2">
    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>

  <Card title="Context editing" icon="eraser">
    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.
  </Card>
</CardGroup>

The full transcript is always retained durably (see [Runtime & durability](/docs/concepts/runtime)); 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](/docs/concepts/events-and-streaming) for **at least 72 hours**, so a consumer that falls behind can always resume without loss.

<Card title="Next: files" icon="folder" href="/docs/capabilities/files">
  Persist and retrieve artifacts across sessions.
</Card>
