
A **skill** is a `SKILL.md` package — frontmatter plus instructions — that teaches the model *how you want a task done*. Skills guide **reasoning**; the sandbox-backed tools perform **execution**. Nexo does not implement skill loading; it composes the [`ruby_llm-skills`](https://github.com/kieranklaassen/ruby_llm-skills) gem so you attach a skill with one macro and no loader setup.

---

## A skill package

Drop a package under `app/skills/` (or scaffold one — see below):

```
app/skills/
└── triage/
    ├── SKILL.md          # frontmatter (name, description) + process steps
    └── references/       # supporting docs the skill can cite
```

```markdown
---
name: triage
description: Triage incoming issues by severity and route them to the right owner.
---

# Triage

## Process
1. Classify the issue severity.
2. Route to the right owner.
```

Reference it with the `skills` macro — its instructions are layered on top of the agent's own, in declaration order:

```ruby
require "nexo"

class TriageAgent < Nexo::Agent
  model ENV.fetch("NEXO_MODEL")   # any ruby_llm model — never a hardcoded vendor default
  skills :triage                  # one macro, no loader wiring
end

TriageAgent.new.chat   # chat built with the base sandbox tools + the skill's instructions
```

---

## Scaffold a skill

Scaffold a new skill package with the generator (creates a valid `SKILL.md` plus a `references/` directory):

```sh
rails g nexo:skill triage
#   create  app/skills/triage/references/.keep
#   create  app/skills/triage/SKILL.md
```

---

## An optional dependency

`ruby_llm-skills` is an **optional** dependency — required lazily only when you use a skill. Without it installed, `require "nexo"` still loads; touching a skill raises a clear `Nexo::MissingDependencyError` telling you to add `gem "ruby_llm-skills"`. Referencing a skill that does not exist raises `Nexo::Error` naming the missing `SKILL.md` path.

---

## Skill tools stay gated

A skill contributes **instructions only**. A loaded skill ships no independent tools, and Nexo deliberately does not attach `ruby_llm-skills`' progressive-disclosure tool (which reads files outside the sandbox). The model reaches a skill's `references/`/`scripts/` files through Nexo's own permission-gated, sandbox-backed tools — so **attaching a skill never widens what an agent can do** beyond its configured sandbox/permission mode.

> **Safe by default:** skills add reasoning, never authority. A `:read_only` agent with a skill is still `:read_only`.

---

## Live example

A runnable example points a code-reviewer agent at a local Ollama model, attaches a `ruby-code-review` skill, and accounts tokens per prompt:

```sh
NEXO_MODEL=gemma3:12b ruby -Ilib examples/code_reviewer.rb
```

The skill package it uses lives at `examples/skills/ruby-code-review/SKILL.md` in the repo.

> [View `examples/code_reviewer.rb` on GitHub →](https://github.com/maquina-app/nexo/blob/main/examples/code_reviewer.rb)


---

## Next steps

<div class="not-prose mt-8 grid grid-cols-1 gap-4 sm:grid-cols-2">
  <a href="../loops/" class="group relative rounded-2xl border border-zinc-200 p-6 hover:border-[#50B1FD]/50 dark:border-zinc-800 dark:hover:border-[#50B1FD]/50 transition">
    <h3 class="font-sora font-semibold text-zinc-900 dark:text-white group-hover:text-[#50B1FD] transition">
      Loops
    </h3>
    <p class="mt-2 text-sm text-zinc-600 dark:text-zinc-400">
      The engine that runs the skilled agent's turns.
    </p>
  </a>

  <a href="../tools/" class="group relative rounded-2xl border border-zinc-200 p-6 hover:border-[#50B1FD]/50 dark:border-zinc-800 dark:hover:border-[#50B1FD]/50 transition">
    <h3 class="font-sora font-semibold text-zinc-900 dark:text-white group-hover:text-[#50B1FD] transition">
      Tools
    </h3>
    <p class="mt-2 text-sm text-zinc-600 dark:text-zinc-400">
      The sandbox-backed tools a skill teaches the model to use.
    </p>
  </a>
</div>
