Creating Skills
Build a custom tool in 5 minutes using Lua or Rhai.
Step 1: Create the Directory
mkdir -p ~/.ryvos/skills/word_countStep 2: Write the Manifest
Create ~/.ryvos/skills/word_count/skill.toml:
name = "word_count"
description = "Count words, lines, and characters in a file"
language = "lua"
entrypoint = "count.lua"
timeout_secs = 10
tier = "t0"
input_schema_json = '''
{
"type": "object",
"properties": {
"path": {
"type": "string",
"description": "Path to the file to count"
}
},
"required": ["path"]
}
'''Step 3: Write the Implementation
Create ~/.ryvos/skills/word_count/count.lua:
-- input is a global table populated by Ryvos from the JSON input
local path = input.path
local content = host.read_file(path)
if not content then
error("File not found: " .. path)
end
local lines = select(2, content:gsub("\n", "\n"))
local words = select(2, content:gsub("%S+", ""))
local chars = #content
return {
path = path,
lines = lines,
words = words,
characters = chars,
}Step 4: Test It
Start Ryvos and ask it to use the tool:
You: count the words in README.md
Ryvos: [Tool: word_count] path="README.md"
README.md has 1,247 words, 89 lines, and 8,432 characters.
Manifest Reference
| Field | Required | Default | Description |
|---|---|---|---|
name | Yes | — | Tool name (alphanumeric + underscores) |
description | Yes | — | What the tool does (shown to LLM) |
language | Yes | — | "lua" or "rhai" |
entrypoint | Yes | — | Script file relative to the skill directory |
timeout_secs | No | 30 | Max execution time |
tier | No | "t2" | Security tier (t0-t4) |
input_schema_json | Yes | — | JSON Schema for the tool input |
Prerequisites
[prerequisites]
required_env = ["API_KEY"] # Must be set
required_os = "linux" # linux, macos, windowsIf any prerequisite fails, the skill is skipped (not loaded) and a warning is logged.
Host Functions
Skills have access to a curated set of host functions provided by Ryvos:
| Function | Description |
|---|---|
host.read_file(path) | Read file contents (respects sandbox) |
host.http_get(url) | HTTP GET request |
host.http_post(url, body) | HTTP POST request |
host.env(name) | Read environment variable |
host.log(msg) | Write to Ryvos log |
Tips
- Return a table/map from your script for structured data. The LLM parses it well.
- Use
error()to signal failures. The agent sees the error message and can retry. - Keep skills focused — one tool per skill. Compose complex workflows by letting the agent chain multiple skills.
- Set the right tier — read-only tools should be T0, network tools T1-T2, anything destructive T3+.
Examples
Rhai Skill
name = "disk_usage"
description = "Show disk usage for a directory"
language = "rhai"
entrypoint = "du.rhai"
tier = "t0"
input_schema_json = '{"type":"object","properties":{"path":{"type":"string"}},"required":["path"]}'// du.rhai
let path = input.path;
let content = host::read_file(path);
let size = content.len();
#{ path: path, bytes: size }Lua Skill with HTTP
name = "translate"
description = "Translate text between languages"
language = "lua"
entrypoint = "translate.lua"
tier = "t1"
input_schema_json = '{"type":"object","properties":{"text":{"type":"string"},"to":{"type":"string"}},"required":["text","to"]}'
[prerequisites]
required_env = ["DEEPL_API_KEY"]local api_key = host.env("DEEPL_API_KEY")
local body = '{"text":["' .. input.text .. '"],"target_lang":"' .. input.to .. '"}'
local result = host.http_post("https://api-free.deepl.com/v2/translate", body)
return { translation = result }