Skip to main content
An agent’s tools are what it works with: a shell, a filesystem, a browser, your third-party connections, and any MCP server you point it at. You declare them on the agent, set a permission per tool, and every call is priced against the budget 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.
  • 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.

Built-in tools

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. 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, and a session that enables it on one is refused when it starts.
The last five are a team’s tools: an agent with no roster is never offered one, a teammate is never offered send_to_agent — work is delegated one level, never onward — and 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 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 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:
  • 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 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 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. 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.
  • models — which models this agent may generate with, out of the live catalogue at GET /v1/media/models. It narrows; there is no fixed list to choose from and no enum, because the catalogue belongs to the provider and changes without a release of ours. Omit it and the agent may use anything the provider publishes. Name models and the agent is held to them, with the first one you name as its default.
  • 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. A refusal lists ids the agent can actually use. Asking for a model outside the pin, or one the catalogue does not have, is refused before anything is submitted.
  • Before a job starts, the room left under the agent’s max_task_usd (budgets) is held. A generation job’s cost cannot be known until it finishes, so what the reservation does is admit the job rather than price it: an agent with no real headroom left is refused and nothing is spent. The check happens before the render, not after it.
  • You are billed for what the finished job actually cost, once per job, and the rest of the reservation is released the moment the job settles. 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.

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.

Connection tools

Every active connection 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.

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:
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:
  • 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 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.
  • The catalog is generated from the route table, one tool per served route, named resource_verbagents_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, so a caller can allow reads and ask on writes without maintaining a list.
  • 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 (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 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 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:
  • 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_email / send_sms as agent tools — today email and SMS are sent via the API/CLI (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:

Progressive disclosure

Skills load a small always-on index; full bodies are pulled only when a task needs them.

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.

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); 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 for at least 72 hours, so a consumer that falls behind can always resume without loss.

Next: files

Persist and retrieve artifacts across sessions.