Skip to main content
A skill is what an agent needs for one task: a description of when to use it, the instructions to follow, the tools it calls and the knowledge bases it reads. You define it once in a project and version it the way you version an agent.
Skills load on cascade voice calls and in text conversations: the Chat API, SMS and WhatsApp text. Speech-to-speech agents ignore them.

Create a skill

The response is version 1, a draft. Creating a skill with a name the project already has makes the next version of that skill, also a draft. A refusal names what it refused: a tool by its place and name (tools[1] 'get_invoices' webhook_url: ...), a knowledge base outside the project by its place and id. What each mode does on a call is in A skill’s knowledge bases. A tool’s webhook_secret is encrypted at rest and every read returns it as "***", as an agent’s is.

Lifecycle

Skills follow agent versioning: A published version never changes; to change a live skill, create a new version, edit it, and publish it.
Publishing or rolling back a skill changes every agent that follows its live version, on each agent’s next call, without republishing those agents. Who uses a skill lists them before you publish; pin a version to hold an agent back. See ADR-0030.

List and read

The list is newest first in the paginated envelope (total, page, limit up to 200). name lists one skill’s versions, and state (draft, published or archived) filters by state: ?state=published lists the live version of every skill. GET /v1/skills/{skill_id} reads one version. Skills belong to their project: another project’s skill is a 404 on every route.

Attach skills to an agent

List the skills an agent may use by name in its config:
Each name resolves to the skill’s live published version once, when a call starts, so a publish mid-call does not change a running call, and the next call runs whatever is live then.

Pin a version

An entry may pin one version of the skill by its id, the id a skill read returns:
A pinned entry runs exactly that version on every call, whatever is published or rolled back later. It still runs once a newer publish archives it; a draft cannot be pinned, since it can still change. The pin is an id rather than a version number because a rollback makes an older number live again, and an id always names the same version. To move a pinned agent to a newer version, change the pin in the agent’s config. Saving the agent (POST /v1/agents, PUT /v1/agents/{agent_id}) checks its skills against the version each resolves to now, and refuses with a 422 that names both sides: For a pinned entry the tools checked are the pinned version’s, not the live one’s. details carries the entry (field, skill; for a pin, field is skills[i].version_id and version_id is set) and, for a collision, tool and collides_with. load_skill is offered automatically, so listing it in the agent’s own tools is refused too. A skill version published after the agent was saved is not checked against it: a tool of that version whose name the agent already uses is skipped on the call, as is a skill an inline call-init agent names that has no published version, or pins a version that is not one of that skill’s in the project.

Who uses a skill

Every draft and published agent version of the project that lists the skill — and, for a skill library, of every project linked to it — by agent name and version, with its project_id, the skill version it resolves to now and pinned: for a pinned entry its pinned version, for any other the live published one, or null when none is published. This is who a publish or a rollback reaches: every entry with pinned: false. An archived agent version, a deleted agent’s included, is not listed. A name the project has no version of is a 404. A project linked to a library lists none of its own skills’ agents: its agents read the library’s.

Delete a skill

DELETE /v1/skills/{skill_id} deletes one version. While any agent version listed above names the skill, it is a 409 whose details.agents names them (agent_id, project_id, name, version, state), a linked project’s agents included, whatever the version deleted, pinned or not: an agent following the live version can be moved to any version of the skill by a publish or rollback, and a pinned one runs its version on every call. Remove the skill from their skills first. A deleted version is gone from every read; the calls that ran it keep their record of it.

Load a skill on a call

Until a skill is loaded the model sees only its name and description, in a section added to the system prompt, and a built-in tool, load_skill:
None of the skills’ tools are offered yet. When the conversation needs a skill the model calls load_skill with {"name": "verify_identity"}, and the skill’s instructions join the prompt as their own section, its tools join the tool list, and the model answers with them:
The conversation so far stays in the context: the caller does not repeat themselves. A load costs one more model response, once per skill per call. A skill’s instructions and its tools’ descriptions render the call’s template variables ({{name}}), as the agent’s own prompt does. Its webhook tools run like the agent’s own, recorded in tool invocations. When two tools share a name, the first of built-in, the agent’s own tools, loaded skills’ tools in load order, and MCP tools wins, in what the model is offered and in what runs. A skill tool with the name of one of the agent’s tools is skipped. An agent with no skills is given no section and no load_skill. Skills are not loaded on speech-to-speech agents: an S2S agent that lists skills carries a skills_ignored_on_s2s entry in its read’s warnings, and its calls run without them, logging why.

A skill’s knowledge bases

A skill’s knowledge_bases take effect when it is loaded, by their mode:
Before the load neither is in the prompt or searchable through the skill. A skill unloaded to make room (below) takes its documents out of the prompt and its knowledge bases out of the search. The documents are read when the call starts, with the skill, so a load reads nothing; a document added mid-call is in the next call. With several tool-mode knowledge bases, top_k and similarity_threshold come from the first searched: the agent’s own, then the skills’ in load order. A tool of the agent’s, a skill’s or an MCP server’s named query_knowledge keeps the name, and the skills’ knowledge bases are then not searchable.

How many skills a call holds

max_loaded_skills in the agent config caps how many skills a call holds loaded at once: 3 by default, from 1 to 10.
Loading a skill past the cap unloads the one loaded longest ago, in the same switch: its section leaves the prompt and its tools leave the tool list as the new skill’s arrive, and the load_skill result tells the model which skill went. An unloaded skill stays in the list of skills the model can load, so it can load it again. Loading a skill that is already loaded changes nothing: the result says it is already loaded, and the prompt and the tools stay as they are.

Skills across a handoff

When a call is handed to another agent, with the handoff_to_agent tool or POST /v1/calls/{id}/handoff, the call takes that agent’s skills: each resolved as at a call’s start (its pinned or live published version in the call’s project, rendered with the call’s template variables), none loaded. The model is given the target agent’s list and load_skill when it has skills, and neither when it has none. Skills the first agent loaded are not carried over, even when the target lists them too, and the target’s own max_loaded_skills applies from then on.

Skills in a text conversation

A text conversation — the Chat API, an SMS or a WhatsApp text — holds skills the way a call does, across its messages. The prompt lists the agent’s skills and the reply’s tool loop offers load_skill. A load in one round of the loop gives the next round the skill’s section and tools, so the reply that loaded explain_bill can call get_invoices before it goes out:
  • Resolved once per session. A session’s first message resolves the agent’s skills (pinned or live published versions, in the project) and the session keeps those versions: a skill published mid-conversation reaches the next session, not this one. Instructions and tool descriptions render the session’s template variables as they stand on each message.
  • Loaded for the session. What a message loads stays loaded for the session’s later messages. A new session starts with nothing loaded.
  • The same rules as a call. max_loaded_skills unloads the skill loaded longest ago, a skill already loaded loads as nothing, an unknown name is answered with the skills that can be loaded, and tools take the same precedence. A loaded skill’s prompt-mode documents join its section and its tool-mode knowledge bases are searched through query_knowledge.
  • Two messages at once. Two messages of one session answered at the same time keep both of their loads, within max_loaded_skills.
A text load is recorded as a load_skill row in the session’s tool invocations (GET /v1/chat/sessions/{id}/tool-invocations), like any text tool. A text session has no event log, so there are no skill.loaded events.

Call-init inline agents

An inline agent returned by call-init has no stored row, so nothing checks its skills when it is saved. They resolve against the call’s project like a stored agent’s: the skills the project has can be loaded on the call, and a name the project does not have (or has no published version of) is logged as a warning and skipped. The call starts either way.

Skills in evals

An eval run resolves its target’s skills once, in the run’s project, before its first iteration, and every iteration runs those versions. An inline target can name skills too. The run’s resolved_config records them under resolved_skills, and a skill that did not resolve is a skills_skipped warning on the run. load_skill is never mocked, because it changes the agent under test rather than reaching anything outside the eval. A function_call expectation on it checks that the agent loads the skill when it should:
The skill’s own tools are mocked like any tool, and a call to one with no mock is refused under mock_only. Tag a skill’s scenarios (say skill:explain_bill) and run them against each agent GET /v1/skills/{name}/agents lists.

What a call records

The versions a call resolved are saved on the call when it starts, so the call stays explainable after a skill is published again. A handoff adds the target agent’s versions. Each load is a skill.loaded event on the call’s event log (GET /v1/calls/{id}/events), and each unload to stay within max_loaded_skills a skill.unloaded event with reason limit, recorded before the load that made room. Both are also delivered to webhook subscribers:
The call read (GET /v1/calls/{id} and the list) and call.ended carry skills: every skill version the call resolved, in the agent’s order (after a handoff, the target agent’s next; a version both agents resolved is listed once), with whether the agent pinned it and whether the call loaded it at any point. A skill the call never loaded is listed too, and a skill unloaded later still reads loaded: true: its skill.unloaded event says when it went. A call whose agent has no skills has an empty list.
An eval run records neither (its own record is resolved_config, above). Skill names, versions and reasons are identifiers, never redacted. skill.loaded and skill.unloaded are in the events kind of retention; once a call’s events expire, loaded is null, and the versions stay.

A skill library

A project can read its skills from another project, its skill library, so agents in several projects share one set of skills. Only the platform credential links a project to a library or unlinks it:
{"library_project_id": null} unlinks it; the field is required, so an empty body is a 422 rather than an unlink. The response is the project, which shows the link as skill_library on every read:
An API key, admin included, is refused (401): a link lets one project read another’s skills, so only the platform, which created both, may make one. The link is refused with a 422 for the project itself, a project that does not exist or is deleted, a library that is itself linked, and a project other projects already read from: a library is one level deep. Each change is an audit log entry on the linked project, action: "skill_library_set", by actor: "platform". A linked project’s skills come only from its library. Its own skills are not consulted, never as a fallback: one name means one skill for every agent that reads from the library. So, in a linked project:
  • Saving an agent checks its skills against the library: a name the library has no published version of, a tool collision with the library version’s tools, and a pin on a version that is not one of the library’s are the 422s above, saying “in this project’s skill library”.
  • A call resolves the agent’s skills in the library: a stored agent, a call-init inline agent and a handoff’s target alike. A name the library does not hold, or a pin outside it, is a warning and a skipped skill; the call starts either way.
  • A text conversation (below) and an eval run resolve the agent’s skills in the library too: a session loads the library’s skills and keeps them across messages, and a run records the library’s versions in resolved_config.resolved_skills.
  • A skill’s knowledge bases belong to the project that holds the skill: a library skill’s knowledge_bases are the library’s, and its prompt-mode documents and tool-mode search reach the linked project’s calls and conversations once it is loaded.
  • A library that is deleted holds no skills: a call skips them with a warning, and a save that lists one is a 422. A library that is purged unlinks its projects, which then read their own skills again.
A call’s record is the same as for a project’s own skills: skills on the call read names the library’s versions.

Audit

Every create, edit, publish, rollback and delete is an audit log entry with resource_type: "skill". A publish or rollback also writes an archived entry for the version it took out of service.