Skip to contents

What is a skill?

A skill is a folder with a SKILL.md file inside it. The file starts with a short header giving the skill a name and a one-sentence description of when it applies, followed by instructions written in plain Markdown. Some skills also carry supporting files alongside SKILL.md: reference documents, templates, or worked examples the instructions point to.

AI coding assistants such as Claude Code and Codex look for skills in a known folder in your project. When you ask for something that matches a skill’s description, the assistant reads the instructions and follows them. Without a skill, the assistant has to guess how you like things done. With one, it knows.

Posit’s Quarto authoring skill is a good example. Its description says to use it whenever you are working with .qmd files or Quarto projects, and its instructions and reference files cover callouts, cross-references, citations, and the other details an assistant tends to get wrong.

Skills live on GitHub, but installing one usually means npm or copying folders by hand. {ally} does it from R.

Installation

# install.packages("pak")
pak::pak("rfortherestofus/ally")

Install your first skill

Open the project you want the skill in, copy the link to the skill’s SKILL.md from your browser, and paste it into install_skill():

library(ally)

install_skill(
  "https://github.com/posit-dev/skills/blob/main/quarto/quarto-authoring/SKILL.md"
)
#> ✔ Installed "quarto-authoring" to '~/projects/report/.agents/skills/quarto-authoring'
#> ℹ Codex and other agents read '.agents/skills/' directly.
#> ✔ Copied to "Claude Code" ('~/projects/report/.claude/skills')

That is the whole workflow. Open the project in Claude Code or Codex, ask for help with a Quarto document, and the assistant picks the skill up on its own.

Where the files go

install_skill() puts the skill in two places inside the project:

Path What it is
.agents/skills/<skill>/ The canonical copy. Codex and a growing number of agents read it directly.
.claude/skills/<skill>/ A second copy for Claude Code, which only looks in its own folder.

Both folders are plain directories, so they can be committed to git and shared with collaborators.

A walkthrough you can run

Everything below runs against a throwaway project and a skill written on the fly, so it works offline and touches nothing else on your computer.

library(ally)

# A minimal skill: a folder with a SKILL.md inside it
skill <- fs::dir_create(fs::path(tempdir(), "r-style-guide"))
writeLines(
  c(
    "---",
    "name: r-style-guide",
    "description: House style for R code in this project.",
    "---",
    "",
    "Follow the tidyverse style guide. Use the base pipe `|>`."
  ),
  fs::path(skill, "SKILL.md")
)

# An empty project to install it into
project <- fs::dir_create(fs::path(tempdir(), "my-project"))

Install from the local path. The project is set as the working directory just for this call:

withr::with_dir(project, install_skill(skill))
#> ✔ Installed "r-style-guide" to '/tmp/RtmpUpCAQd/my-project/.agents/skills/r-style-guide'
#> ℹ Codex and other agents read '.agents/skills/' directly.
#> ✔ Copied to "Claude Code" ('/tmp/RtmpUpCAQd/my-project/.claude/skills')

Both copies are now in place:

fs::dir_tree(project, all = TRUE)
#> /tmp/RtmpUpCAQd/my-project
#> ├── .agents
#> │   └── skills
#> │       └── r-style-guide
#> │           ├── .ally-source.json
#> │           └── SKILL.md
#> └── .claude
#>     └── skills
#>         └── r-style-guide
#>             ├── .ally-source.json
#>             └── SKILL.md

installed_skills() lists the skills in the project (see See what is installed for what each column means):

withr::with_dir(project, installed_skills(scope = "project"))
#> # A tibble: 1 × 10
#>   name          description      installed_by installed_from installed          
#>   <chr>         <chr>            <chr>        <chr>          <dttm>             
#> 1 r-style-guide House style for… ally         /tmp/RtmpUpCA… 2026-09-23 19:38:19
#> # ℹ 5 more variables: updated_on_github <dttm>, edited <lgl>, scope <chr>,
#> #   found_in <chr>, path <chr>

And remove_skill() deletes both copies:

withr::with_dir(project, remove_skill("r-style-guide"))
#> ✔ Removed copy at '/tmp/RtmpUpCAQd/my-project/.claude/skills/r-style-guide'
#> ✔ Removed canonical copy at '/tmp/RtmpUpCAQd/my-project/.agents/skills/r-style-guide'

This project or every project

By default a skill is installed into the current working directory, so it travels with the project. Pass scope = "user" to install it into your home folder instead, where every project on the computer can use it:

install_skill("posit-dev/skills/quarto/quarto-authoring", scope = "user")
#> ✔ Installed "quarto-authoring" to '~/.agents/skills/quarto-authoring'
#> ℹ Every project on this computer can use skills in '~/.agents/skills/'.
#> ✔ Copied to "Claude Code" ('~/.claude/skills')

Every function in {ally} takes the same scope argument. installed_skills() looks in both places unless you pick one.

Ways to name a skill

install_skill() accepts a few forms of source:

# A full GitHub URL, pasted straight from the browser
install_skill(
  "https://github.com/posit-dev/skills/tree/main/quarto/quarto-authoring"
)

# The link to the SKILL.md itself also works
install_skill(
  "https://github.com/posit-dev/skills/blob/main/quarto/quarto-authoring/SKILL.md"
)

# A repository whose SKILL.md sits at the top level is named after the repository
install_skill("https://github.com/statzhero/tidy-r-skill")

# GitHub shorthand: owner/repo/path/to/skill, with an optional @ref
install_skill("posit-dev/skills/quarto/quarto-authoring")
install_skill("owner/repo/skills/my-skill@dev")

# A local path, handy while writing a skill of your own
install_skill("~/my-skills/r-style-guide")

GitHub skills are fetched from the repository’s zip archive: a single download that needs no token and never counts against the GitHub API rate limit, so a room full of people on one network can install at once. Public repositories only.

See what is installed

installed_skills() lists every skill an agent can load on your computer, not just the ones {ally} installed. It looks in the shared .agents/skills/ folder and in each agent’s own folder (.claude/skills/, .codex/skills/, .cursor/skills/), in the current project and in your home folder:

installed_skills()
#> # A tibble: 3 × 10
#>   name  description installed_by installed_from installed           updated_on_github   edited scope
#>   <chr> <chr>       <chr>        <chr>          <dttm>              <dttm>              <lgl>  <chr>
#> 1 my-n… How I like… <NA>         <NA>           2026-09-23 12:14:57 NA                  NA     user
#> 2 quar… Use when t… ally         posit-dev/ski… 2026-09-23 12:14:56 2026-05-01 08:28:25 FALSE  user
#> 3 r-cl… Build comm… ally         posit-dev/ski… 2026-09-23 12:14:57 2026-04-20 13:43:24 FALSE  user
#> # ℹ 2 more variables: found_in <chr>, path <chr>

Each row is one skill. A skill found in several folders, such as the two copies install_skill() makes, appears once.

Column What it tells you
name The skill’s folder name.
description The start of the description from the SKILL.md header, which agents read to decide when to use the skill. The full text is in the SKILL.md at path.
installed_by "ally", "skills CLI" for skills installed with npx skills, or NA when nothing recorded it.
installed_from The GitHub repository and folder, or local path, the skill was installed from.
installed When this copy was installed or last updated, in your time zone.
updated_on_github When the skill’s folder last changed on GitHub.
edited Whether any copy of the skill has changed since {ally} installed it. NA for skills {ally} did not install.
scope "project" or "user".
found_in The folders that hold the skill, such as ".agents, .claude".
path The skill’s folder.

Some rows are mostly NA, like my-notes above. Those are skills that were copied into a folder by hand or by a tool that keeps no record, so there is no way to tell where they came from. Their installed time is when their folder was created, and without a GitHub repository there is nothing to check for updates. Install them again with install_skill() and {ally} records their source from then on.

updated_on_github comes from each repository’s public commit feed, so, like installing, it needs no token and does not count against the GitHub API rate limit. It takes a moment for each GitHub skill. Pass check_github = FALSE to skip it, when you are offline for instance.

Claude Code plugins bring their own skills, which live elsewhere and are not listed.

Keeping skills current

Skills change. Authors fix mistakes, add reference material, and adapt their instructions as the tools they describe evolve. The Quarto authoring skill, for instance, records a version number in its SKILL.md header and has been revised several times. An installed skill is a snapshot of the moment you installed it; it does not update itself.

{ally} records where each skill came from in a small .ally-source.json file next to the SKILL.md, so getting the newest version is one call:

update_skill("quarto-authoring")
#> ℹ Updating "quarto-authoring" from "posit-dev/skills/quarto/quarto-authoring@main"
#> ✔ Installed "quarto-authoring" to '~/projects/report/.agents/skills/quarto-authoring'
#> ℹ Codex and other agents read '.agents/skills/' directly.
#> ✔ Copied to "Claude Code" ('~/projects/report/.claude/skills')

update_skill() fetches the skill again from its original source, replaces the canonical copy, and refreshes the Claude Code copy.

Replacing a skill would throw away any changes you have made to it, so {ally} checks first. install_skill() records a fingerprint of the skill’s files, and if either copy has been edited since then, update_skill() stops and names the edited files instead:

update_skill("quarto-authoring")
#> Error in `update_skill()`:
#> ! Not overwriting "quarto-authoring", which has local changes.
#> ✖ '~/projects/report/.agents/skills/quarto-authoring' has been edited since it was installed.
#> ℹ Copy anything you want to keep, then run again with `force = TRUE` to overwrite it.

Save your changes somewhere else, then run update_skill("quarto-authoring", force = TRUE) to get the new version. install_skill() does the same check, and also stops rather than overwrite a skill folder that {ally} did not install. The edited column of installed_skills() shows which skills have changes.

Skills installed before {ally} recorded fingerprints show NA in the edited column and are not protected. Updating them once records a fingerprint from then on.

To bring every skill in a project up to date at once:

skills <- installed_skills(scope = "project")
for (skill in skills$name[skills$installed_by %in% "ally"]) {
  update_skill(skill)
}

Only skills that {ally} installed know their source, so the loop skips the others. A skill with local changes stops the loop with an error, so nothing is lost.

To see which skills have changed since you installed them, compare two columns of installed_skills(). installed is when you installed or last updated a skill, and updated_on_github is when its folder last changed on GitHub:

skills <- installed_skills(scope = "project")
skills[which(skills$updated_on_github > skills$installed), ]

Skills installed with scope = "user" are updated the same way, with scope = "user" added to each call.

If you edit a skill by hand in .agents/skills/, or delete the Claude Code copy by accident, link_skills() refreshes the copies for every installed skill without fetching anything:

Make your edits in .agents/skills/, the copy every agent’s copy is made from. If the Claude Code copy has been edited, link_skills() skips that skill with a warning rather than overwrite it; pass force = TRUE to overwrite it anyway.

Prefer a single set of files? Pass link = TRUE to install_skill(), update_skill(), or link_skills() and {ally} puts a relative symlink at .claude/skills/<skill> pointing back to the canonical copy instead of a second copy. Where symlinks are not supported (typically Windows without developer mode) it falls back to copying and tells you so.

Where to find skills

  • Ally from R for the Rest of Us will offer a curated collection of skills for R users working with AI, each with the link to paste into install_skill(). Ally is not yet open for registration, so the collection is not available today, but it is coming soon.
  • posit-dev/skills is Posit’s collection, covering R packages, Shiny, Quarto, and more.
  • Write your own. A folder with a SKILL.md is all it takes, and install_skill() accepts a local path while you iterate.