Elizon Docs
Features & SpecsPortable definitions

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).

SKILL.md
rounding.md
total.py
skills/price-calculation/SKILL.md
---
# 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.
skills/price-calculation/reference/rounding.md
# 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.
skills/price-calculation/scripts/total.py
"""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:

elizon.yaml
skills:
  - name: price-calculation
    ref: ./skills/price-calculation/

Front matter

Front matterMeaning
nameThe Skill's name, at most 64 characters. If it is missing, the manifest entry's name is used.
descriptionWhen an Agent should use it. Required: build refuses a Skill without one.
allowed-toolsThe 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 YAML null and 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:

FieldFrom a folderNotes
namefront matter name1–64 characters.
descriptionfront matter descriptionNot 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.
allowedToolsfront matter allowed-toolsnull (key absent), [], or the list. null and [] stay distinct.
filesevery file in the folder, SKILL.md includedAt 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.

The portable form of the example, as a YAML file (file contents shortened)
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 of SKILL.md and 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_out and files (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:

ExtensionRuns with
.pyPython 3.12 (python3)
.shAlpine Linux sh
.jsNode.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 /workspace is 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 returns timed_out: true.
  • New files a script writes in /workspace are returned in files, 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.

Reading a bound table
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

  • validate runs a local static scan of every Skill and reports findings as warnings. It reads every .py, .js, .ts and .sh file 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, in SKILL.md's front matter, an allowed-tools entry outside the tool policy. The rest of SKILL.md and other data files are not scanned for URLs or credentials.
  • validate and build refuse an email address or a secret reference in the front matter's name, description or allowed-tools (see Carried text). The SKILL.md body, reference files and scripts are the Skill's content and are not scanned for them.
  • build refuses a Skill folder without a SKILL.md or a description, 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.