A skill is a SKILL.md file that teaches the agent how to
do something using shell commands. Skills are markdown documentation:
the agent reads them and runs bash to execute. No code, no
compilation, no installation.
corteza has three ways to add tools, and they target different audiences:
| Form | Audience | Config key | Vignette |
|---|---|---|---|
| Package skills | R packages as tools | skill_packages |
vignette("package-as-skill") |
SKILL.md files |
Portable, shell-based | skill_paths |
this one |
| R skills | R functions, registered directly | skill_paths (.R
files) |
this one |
A SKILL.md is plain markdown with YAML frontmatter:
---
name: weather
description: Get current weather and forecasts (no API key required)
metadata: {"requires":{"bins":["curl"]}}
---
# Weather
Get weather using wttr.in:
```bash
curl -s "wttr.in/London?format=3"
```
## Options
- `?format=3`: one-line format
- `?0`: current weather only| Field | Required | Description |
|---|---|---|
name |
yes | snake-case identifier |
description |
yes | One-liner that goes into the system prompt |
metadata |
no | Optional metadata; the
requires block declares external deps |
The metadata.requires field is openclaw-compatible, so
skills written for openclaw work in corteza without modification (and
vice versa). Conceptually requires.bins is to a skill what
SystemRequirements is to an R package: a declaration of
external binaries the skill needs (curl, jq,
etc). requires.env lists environment variables (API keys,
tokens). corteza stores both for documentation; it doesn’t gate skill
loading on them.
| Scope | Path |
|---|---|
| Global | tools::R_user_dir("corteza", "data")/skills/ |
| Project | .corteza/skills/ |
Both nested (my-skill/SKILL.md) and flat
(my-skill.md) layouts work. Project-local skills override
global ones with the same name.
The agent doesn’t call skills directly. It reads the markdown into
the system prompt at session start, then generates bash
commands when the conversation calls for them.
bash tool runs itR one-liners run via Rscript:
---
name: r-eval
description: Execute one-shot R code
metadata: {"requires":{"bins":["Rscript"]}}
---
# R one-liners
```bash
Rscript --vanilla -e 'mean(1:100)'
```
## Multi-line
```bash
Rscript --vanilla -e '
df <- mtcars[1:5, 1:3]
print(df)
'
```This skill is stateless: each Rscript
call starts a fresh R session.
For stateful R (objects persist across turns),
corteza’s built-in run_r tool maintains a long-lived R
session in the MCP server process. The agent picks run_r
for interactive analysis and Rscript for portable
one-shots.
| Use case | Approach |
|---|---|
| One-off calculation | Stateless |
| Data pipeline | Stateless, write intermediate state to files |
| Interactive analysis | Stateful (run_r) |
| Package development | Stateful (run_r) |
| Portable skill | Stateless |
corteza ships with tools that don’t have a SKILL.md:
| Tool | Purpose | Stateful? |
|---|---|---|
run_r |
Execute R in the persistent session | Yes |
run_r_script |
Execute R in a subprocess | No |
r_help |
Query R documentation via saber | No |
installed_packages |
List installed R packages | No |
You don’t need to write these as skills; they’re always available.
mkdir -p .corteza/skills/my-skill
$EDITOR .corteza/skills/my-skill/SKILL.mdAdd frontmatter and documentation, then restart the session. Skills load at startup, not on the fly.
?format=3 is
opaque; “one-line format” is not.metadata.requires.bins. The requires.env block
lists API keys that need to be set.Skills are just files. Commit them, symlink them, copy them.
# Personal collection of skills, used across projects
ln -s ~/skills $(Rscript -e 'cat(file.path(tools::R_user_dir("corteza","data"),"skills"))')
# Or just clone a collection into the project
git clone <your-skills-repo-url> .corteza/skills