DocsSkillsCreating Skills

Creating Skills

Build a custom tool in 5 minutes using Lua or Rhai.

Step 1: Create the Directory

mkdir -p ~/.ryvos/skills/word_count

Step 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

FieldRequiredDefaultDescription
nameYesTool name (alphanumeric + underscores)
descriptionYesWhat the tool does (shown to LLM)
languageYes"lua" or "rhai"
entrypointYesScript file relative to the skill directory
timeout_secsNo30Max execution time
tierNo"t2"Security tier (t0-t4)
input_schema_jsonYesJSON Schema for the tool input

Prerequisites

[prerequisites]
required_env = ["API_KEY"]                   # Must be set
required_os = "linux"                        # linux, macos, windows

If 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:

FunctionDescription
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 }