Agent Skills
How an Agent uses the Skills bound to it — reading a skill's instructions and running its scripts as tool calls.
A Skill is a folder of instructions (SKILL.md) plus optional scripts and data files. Bind
one or more Active Skills to an Agent and the Agent can use them in any run: Preview chat, the
public API, a workflow AI Agent node, or as a Team member.
What the Agent sees
Every bound, Active Skill adds one line to the Agent's instructions:
[skill: demo-bank-api] Mock loan-servicing API ... (call get_skill_instructions("demo-bank-api") before using it; run its scripts with run_skill_script)and the Agent gets two tools:
| Tool | Arguments | Returns |
|---|---|---|
get_skill_instructions | skill_name | The skill's SKILL.md body and the paths of all its files. |
run_skill_script | skill_name, script_path, args (opt.) | A JSON object: exit_code, stdout, stderr, timed_out, files. |
The model calls these like any other tool, so a skill works even when the Agent answers in
another language or never says the skill's name. skill_name is the name on the skill's line;
the Agent can only reach the Skills bound to it.
An Agent with no bound Active Skill gets neither tool.
Running a script: the argv contract
run_skill_script runs one script from the skill in a sandbox, with every file of the skill
in its working directory (so data/customers.json next to scripts/bank_api.py is readable by a
relative path). args is a list of strings, one entry per command-line argument:
{
"skill_name": "demo-bank-api",
"script_path": "scripts/bank_api.py",
"args": ["verify-customer", "--egn-last4", "8836", "--dob", "1985-01-30"]
}runs python3 scripts/bank_api.py verify-customer --egn-last4 8836 --dob 1985-01-30. Each entry
reaches the script as exactly one argument: quotes, spaces, ;, $(...) and newlines are passed
through literally, never interpreted by a shell.
| Script extension | Runs with |
|---|---|
.py | python3 |
.sh | sh |
.js | node |
The result:
{
"exit_code": 0,
"stdout": "{\"challenge_id\": \"ch.CUST-1004\"}",
"timed_out": false,
"files": []
}stderr is included only when exit_code is not 0, so the model can read an error such as an
argparse usage message and fix its call. When the result would be longer than the tool result
limit, only stdout is shortened (its beginning is kept): the result is still valid JSON, with
"stdout_truncated": true and stdout_total_chars set to the full length. files lists the download URL of each file the script
wrote. A script reads its arguments with sys.argv (or argparse); the SKILL_PARAMETERS
environment variable is {} for a tool call.
Limits
A call that breaks one of these is refused before anything runs, and the model gets the reason:
| Limit | Value |
|---|---|
script_path | A file of the skill; relative, no ..; .py, .sh or .js |
Number of args | 32 |
| Length of one argument | 1,024 characters |
| All arguments together | 8 KiB |
| Characters | No NUL character |
| Run time | At most 30 seconds (then timed_out: true) |
The sandbox has no network and no environment
Scripts run with no network access, and nothing from the platform's environment is passed in
except SKILL_PARAMETERS: no API keys, no secrets. A script can't call an external API; ship the
data it needs as files in the skill (like data/customers.json above).
Results on later turns
Script results stay available to the Agent on later turns of the same session (within the
Agent's history setting), so a multi-step flow works across messages: verify-customer returns a
challenge_id on one turn, and confirm-otp --challenge-id ... uses it on the next. The chat
transcript shows only the Agent's answers, not the tool calls.
Each turn's skill tool calls are stored on that turn's assistant message
(metadata.skillToolCalls: tool, skill, script, arguments, exit code, and the first 4 KiB of
output), so you can check whether an Agent actually ran a skill.
Usage
Every script run that reached the sandbox counts as one skill_execution run in Usage. A refused
call counts nothing.