Skills
Packaging a Skill: instructions, reference files and scripts an Agent can load and run.
A Skill is a folder: a SKILL.md with front matter, plus any reference files and scripts it
needs. To see a complete, valid project with one, run npx @elizon/cli example skill (offline, no
account).
---
# Front matter. `name` and `description` are what an Agent sees before it opens the Skill.
name: price-calculation
description: Works out the net, VAT and gross amounts of an order line. Use it whenever a price with or without VAT is asked for, instead of doing the arithmetic yourself.
# The tools the Skill asks for. [] asks for none, so the Skill needs no approval at install.
# Leaving the key out is not the same as []: it is carried as null.
allowed-tools: []
---
# Price calculation
Never work out a VAT amount yourself: run the script and quote the numbers it prints.
1. Run `scripts/total.py` with three arguments: the quantity, the unit price before VAT, and the
VAT rate in percent. For 3 items at 19.99 with 21% VAT:
`run_skill_script("price-calculation", "scripts/total.py", ["3", "19.99", "21"])`.
2. It prints `{"net": "59.97", "vat": "12.59", "gross": "72.56"}`. Quote those numbers exactly.
3. If you do not know the VAT rate, ask for it. Do not assume one.
How amounts are rounded is in `reference/rounding.md`.
Replace this text with your own procedure before shipping.# Rounding
- Every amount is rounded to two decimals, half up: 0.125 becomes 0.13.
- The VAT is worked out on the rounded net amount, then rounded. The gross amount is net plus VAT.
Replace this with the rounding rule your business actually uses."""Prints the net, VAT and gross amounts of one order line, as JSON.
Usage: total.py <quantity> <unit price before VAT> <VAT rate in percent>
"""
import json
import sys
from decimal import ROUND_HALF_UP, Decimal, InvalidOperation
def cents(value: Decimal) -> Decimal:
return value.quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)
def main(args: list[str]) -> int:
if len(args) != 3:
print("usage: total.py <quantity> <unit price> <VAT rate in percent>", file=sys.stderr)
return 2
try:
quantity, unit_price, rate = (Decimal(arg) for arg in args)
except InvalidOperation:
print(f"not a number: {args}", file=sys.stderr)
return 2
net = cents(quantity * unit_price)
vat = cents(net * rate / 100)
print(json.dumps({"net": str(net), "vat": str(vat), "gross": str(net + vat)}))
return 0
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))In the manifest, ref points at the folder, with a trailing slash:
skills:
- name: price-calculation
ref: ./skills/price-calculation/Front matter
| Front matter | Meaning |
|---|---|
name | The Skill's name, at most 64 characters. If it is missing, the manifest entry's name is used. |
description | When an Agent should use it. Required: build refuses a Skill without one. |
allowed-tools | The tools the Skill asks for. See Allowed tools. |
Anything else in the front matter stays in SKILL.md as text and is not read.
Allowed tools
allowed-tools is a YAML list ([bash, python]) or a comma-separated string (bash, python). There
are three cases, and the first two are carried differently:
- Key absent: carried as
null, which the platform records as deny-all: the Skill asks for no tool permission. It is never turned into[]. A key with no value (allowed-tools:) is YAMLnulland counts as absent. [](or an empty string,allowed-tools: ""): the Skill asks for no tools. It needs no approval.- A list: the Skill asks for those tools. A Skill that asks for any tool needs an administrator's approval before it can be activated, and at install the wizard shows every tool it names.
The platform's tool policy is bash, python, web_search, file_read, file_write and
http_request. Any other name is a scan finding (see Scanning).
allowed-tools is a declaration that drives approval and the scan. It does not open the sandbox:
every script runs under the same restrictions (see Scripts), whatever the list says.
The portable form
build turns the folder into one portable Skill definition, stored in the .pkg as JSON. These are
all of its fields, and what build takes each one from when ref is a folder:
| Field | From a folder | Notes |
|---|---|---|
name | front matter name | 1–64 characters. |
description | front matter description | Not empty. |
license | — (null) | Text or null. |
compatibility | — (null) | Text up to 500 characters, or null. Display only: nothing parses it. |
metadata | — (null) | An object or null. |
allowedTools | front matter allowed-tools | null (key absent), [], or the list. null and [] stay distinct. |
files | every file in the folder, SKILL.md included | At least one. Each entry has a path (relative to the Skill) and content. |
license, compatibility and metadata have no front matter spelling, so a folder always gives
null. To set them, point ref at a YAML file that holds the portable form itself (below) instead
of a folder; validate scans and build packs it the same way.
From a folder, files holds every regular file under it, with /-separated paths relative to the
folder. Symbolic links are skipped. Each content is the file's text, read as UTF-8, so a Skill
carries text files: instructions, reference data and scripts. files is sorted by path in byte
order, so two builds of the same folder are identical. The definition never carries a review state:
no scan result, no approval, no status.
name: price-calculation
description: Works out the net, VAT and gross amounts of an order line. Use it whenever a price with or without VAT is asked for, instead of doing the arithmetic yourself.
license: null
compatibility: null
metadata: null
allowedTools: []
files:
- path: SKILL.md
content: "---\n# Front matter…"
- path: reference/rounding.md
content: "# Rounding\n…"
- path: scripts/total.py
content: '"""Prints the net, VAT and gross amounts…'How an Agent uses a Skill
An Agent sees the name and description of every active Skill bound to it, and gets two tools:
get_skill_instructions(skill_name)returns the body ofSKILL.mdand the list of the Skill's files, so the Agent knows which scripts exist.run_skill_script(skill_name, script_path, args)runs one of the Skill's scripts in a sandbox and returns JSON:exit_code,stdout,stderr(only when the exit code is not 0),timed_outandfiles(the files the script wrote).
stderr keeps only its last 4 KB. When the result would be too long, stdout is cut from the end
and the result says so with stdout_truncated: true and stdout_total_chars.
A call that is refused (an unknown Skill, a bad script_path or args) or that cannot run returns
{"error": "..."} instead, with the reason.
script_path is a path from the file list, relative to the Skill folder, without ... args is a
list of strings, passed as the script's command-line arguments, each exactly as given (quoted, so
nothing in it is interpreted by a shell): at most 32 of them, each at most 1,024 characters and
8 KB in total.
To give a Skill to an Agent, list it in the Agent's definition as skills: [skills.<name>] (see
Agents → Skills). Install
attaches the Skill to that Agent; no Studio step binds them. After install the Skill is a draft:
the operator activates it (after the scan, and the approval if it asks for tools) before the Agent
can load it. Name the Skill in the Agent's instructions so the Agent knows when to use it.
Scripts
A script is a file in the Skill whose extension the sandbox has a runtime for:
| Extension | Runs with |
|---|---|
.py | Python 3.12 (python3) |
.sh | Alpine Linux sh |
.js | Node.js 20 (node) |
Any other file is data the scripts can read. Each run is a fresh container:
- The working directory is
/workspace, and every file of the Skill is there at its own path (scripts/total.py,reference/rounding.md). Everything outside/workspaceis read-only. - No network. Only the language's standard library is available, and nothing can be installed.
- No secrets. Nothing from the platform's own environment is passed to the script.
- Limits. Three come from the instance's sandbox settings (defaults shown): 30 seconds wall
clock (
SANDBOX_TIMEOUT_S; a run for an Agent never waits longer than 30 seconds), 256 MB of memory (SANDBOX_MEMORY_MB), and 512 KB of standard output and error together (SANDBOX_OUTPUT_BYTES). Two are fixed: 1 CPU and 128 processes. A script over the output limit is stopped and its output is cut; one over the time limit returnstimed_out: true. - New files a script writes in
/workspaceare returned infiles, up to 50 MB in total (measured base64-encoded). Files whose name starts with.are not returned.
Reading a Data Table from a script
A script can read a Data Table when the Agent it runs for has that table bound with Available to
skill scripts switched on (expose_to_scripts: true on the binding; see
Data Tables for Agents). The rows are then in the workspace at
data/<table-slug>.jsonl, one JSON object per row. The slug is the table name in lower case with
every run of characters other than a-z and 0-9 turned into -, leading and trailing -
trimmed, and cut to 64 characters (table if nothing is left), so "Price List" is
data/price-list.jsonl; when two tables share a slug, the next ones get -2, -3, and so on.
import json
with open("data/price-list.jsonl") as rows:
prices = {row["Sku"]: row["Price"] for row in map(json.loads, rows)}The file is a read-only snapshot taken the first time a script runs in a turn. A script cannot write the table, and a table that is not bound, or not switched on, is never there. A table over the byte budget (50 MB by default) is never cut short: the script run fails and names the table.
Size
A Skill's files may total at most 10 MB, and no single file may exceed 5 MB. A Skill also
travels as one entry of the .pkg, so its whole portable definition must fit in 5 MB. build
refuses a larger Skill (PORTABLE_SKILL_TOO_LARGE) because it could never install.
Scanning
validateruns a local static scan of every Skill and reports findings as warnings. It reads every.py,.js,.tsand.shfile and flags a URL to a host outside the platform's allowlist, obfuscated code (eval(, base64 decoding, long hex strings), access to credentials or the environment (os.environ,process.env,$HOME,~/.aws,/etc/passwd), and, inSKILL.md's front matter, anallowed-toolsentry outside the tool policy. The rest ofSKILL.mdand other data files are not scanned for URLs or credentials.validateandbuildrefuse an email address or a secret reference in the front matter'sname,descriptionorallowed-tools(see Carried text). TheSKILL.mdbody, reference files and scripts are the Skill's content and are not scanned for them.buildrefuses a Skill folder without aSKILL.mdor adescription, and one over the size limits.- At install, the installing Organization re-scans the Skill with its own policy, and that result is the one that counts. The wizard shows each finding and every tool the Skill names, and the operator must acknowledge them to install it.
- Skills from a Package are installed as drafts.