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
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.
List and read
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:Pin a version
An entry may pin one version of the skill by its id, theid a skill read returns:
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
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:
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:
{{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’sknowledge_bases take effect when it is loaded, by their mode:
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.
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 thehandoff_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 offersload_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_skillsunloads 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’sprompt-mode documents join its section and itstool-mode knowledge bases are searched throughquery_knowledge. - Two messages at once. Two messages of one session answered at the same time keep both of their loads, within
max_loaded_skills.
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 itsskills 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. Aninline 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:
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 askill.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:
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.
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:
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
skillsagainst 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 the422s 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_basesare the library’s, and itsprompt-mode documents andtool-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.
skills on the call read names the library’s versions.
Audit
Every create, edit, publish, rollback and delete is an audit log entry withresource_type: "skill". A publish or rollback also writes an archived entry for the version it took out of service.