Repository navigation
Use skills from other agent harnesses #1277
Description
Skills written for other agent harnesses should work in codeaf. This issue is the plan for the first version, settled in the thread below: parity with the other harnesses first, nothing self-evolving.
What we are building
- Read skills from where they already live. A skill is an agentskills.io folder: a directory holding a
SKILL.mdwithnameanddescriptionin its frontmatter, and optionallyscripts/,references/andassets/beside it. codeaf finds them in the standard folders and never asks you to move or rewrite one. - Every skill is yours to invoke, any time, by its own name.
/<skill-name> [what you want]puts the skill's instructions in front of the model for this turn. Every discovered skill is a slash command, and the picker under the draft lists them beside codeaf's own commands. - A subset you choose is auto-invoked, and you see it happen. For the skills you turn on, the model sees a one-line catalog (name, description, size) and pulls a skill's body in itself when the work matches. Every such pull draws a row in the transcript naming the skill. Nothing auto-loads because it happened to be on disk.
- You choose where. A skill is turned on globally, for a project, or for one conversation. Narrower wins: a skill on globally can be turned off in one project, and one off globally can be turned on in one conversation.
- Adding a skill is one command.
/skill add <folder>or/skill add <git url>copies it into codeaf's own skills folder, global or project./skillis the management command: add, remove, on, off, andusefor a skill whose name a built-in command already owns. - Skills work anywhere the model works. In the chat and inside a task node alike. Delegating to a task is possible, never required.
- A Skills tab in
/settingslists everything discovered as one catalog, its size, and its on/off per scope. It does not say which folder a skill came from; a skill in codeaf's own folder carries one mark, an imported one carries none.
The format
One SKILL.md per folder. Frontmatter fields: name (1–64 chars, lowercase letters, digits and hyphens), description (1–1024 chars), optional license, compatibility, metadata, allowed-tools. The body is Markdown and there is no format restriction on it. We read leniently the way the spec's client guide recommends: a name that does not match its folder or exceeds 64 chars loads with a warning shown in the Skills tab, a missing description or unparseable frontmatter skips the skill and the tab says why. The one common malformation, an unquoted description: containing a colon, is repaired before the YAML parse.
allowed-tools is read and shown on the skill's row, and pre-approves nothing. A tool call made while following a skill goes through the same approval settings as any other call, so a skill can never widen what you have allowed.
Three tiers of cost, and we pay each only when it is earned:
| Tier | What | When | Cost |
|---|---|---|---|
| catalog | name + description + size | every request, for skills turned on | ~50–100 tokens per skill |
| instructions | the SKILL.md body |
when invoked or auto-invoked | the body, once per conversation |
| resources | scripts/, references/, assets/ |
when the body points at them | read on demand with read/bash |
Where we read on day one
Project scope (the git root of where codeaf was opened, else the folder itself), in this order:
<project>/.codeaf/skills/ codeaf's own
<project>/.agents/skills/ the cross-client convention
<project>/.claude/skills/
<project>/.codex/skills/
<project>/.cursor/skills/
<project>/.gemini/skills/
<project>/.opencode/skills/
<project>/.goose/skills/
User scope, same order under ~:
~/.codeaf/skills/ ~/.agents/skills/ ~/.claude/skills/ ~/.codex/skills/ ~/.cursor/skills/ ~/.gemini/skills/ ~/.opencode/skills/ ~/.goose/skills/
Every folder is scanned silently, with no import step and no question; a folder that does not exist is skipped without a word. A skill is a direct child directory holding SKILL.md; nothing is walked deeper. The result is ONE catalog. The scan order above is the precedence: the first skill seen under a name is the skill, and a later folder's skill with the same name is ignored. So a project's skill shadows a user's, and .codeaf/skills shadows every other harness's at the same level. ~/.codeaf/skills/ is also where the resident keeps its promoted commands; those folders have no SKILL.md and the scan does not see them. A folder attached with /folder contributes its project-scope skills too, listed under its own path.
How a skill reaches the model
Invoked by you. /<skill-name> [words] sends the skill body plus your words as the turn. The transcript shows the line you typed, not the body. If the skill is already in this conversation's context, only your words are sent and the skill is named as already loaded. The slash table today is a fixed list, so this needs a second, dynamic source of commands that the picker ranks and the dispatcher consults after the fixed table. codeaf's own commands always win a name; a skill named like one of them is reached through /skill use <name> [words] instead, which does exactly what its own slash would have done, and its row in the tab says so. /skills opens the Skills tab.
Auto-invoked. Skills turned on for this scope are listed in the system prompt as a catalog, and one tool, skill, whose name argument is constrained to those names, returns the body wrapped so the model can tell it from conversation:
<skill name="pdf-processing" dir="/home/you/.agents/skills/pdf-processing">
...body...
resources: scripts/extract.py, references/REFERENCE.md
</skill>
Relative paths in the body resolve against dir; the skill's folder is allow-listed for read so bundled files do not each cost a permission prompt.
You always see an auto-invocation. The model pulling a skill in is a tool call like any other, so it draws its own row in the transcript at the moment it happens, naming the skill and what it cost:
◆ used skill pdf-processing · 3.1 KB
The row is never folded into the model's prose and never omitted, so a person can always tell a turn that followed a skill from one that did not. Inside a task the same row appears on the task page among the node's steps. When no skill is turned on, the catalog and the tool are absent, not empty (the absent-not-broken law). The catalog changes only when you change a setting or attach a folder, so it rides message[0] with the standing orders and folders and does not re-price the transcript per turn. A loaded skill is not summarised away by compaction.
Inside a task. A task node renders its own system prompt, so it gets the same catalog and the same tool for its scope. propose_task gains an optional skills list to pin named skills into a node's brief, so the chat can hand skilled work to a task with the skill attached. A worker on the bash belt gets the catalog and reads SKILL.md with the shell it has.
Where the choice is kept
| Scope | File | Shape |
|---|---|---|
| global | ~/.codeaf/skills.json, beside config.json in the profile |
on: [names], off: [names] |
| project | <project>/.codeaf/skills.json |
the same two lists, written by the product like autonomy.json, gitignore it or commit it as you like |
| conversation | the conversation's meta.json |
the same two lists |
The global file is its own file rather than rows in config.json: a list of skill names is not a setting the registry can describe, and a config.json key nothing reads is one the unread-key notice would flag.
Effective state for a skill: conversation says on/off, else project, else global, else off. Every write goes through one engine door so the Skills tab and /skill on|off cannot disagree.
Adding a skill
/skill add ~/some/folder copies it into ~/.codeaf/skills/<name>/
/skill add ~/some/folder --project into <project>/.codeaf/skills/<name>/
/skill add https://github.com/o/r clones, finds every SKILL.md, copies each
/skill add https://github.com/o/r/tree/main/skills/pdf one folder out of a repo
The URL form clones to a temp directory with the git on PATH and copies out; nothing is executed. A name already present asks before it overwrites. /skill remove <name> removes only from a codeaf folder, never from another harness's.
The Skills tab
One row per discovered skill:
◆ pdf-processing 3.1 KB on here · on globally
brand-voice 12 KB off
data-analysis 8.4 KB on for this project
The mark on the first row says it lives in codeaf's own folder; the other two were found in another harness's folder, and the tab does not say which.
Enter cycles the row's state for the current conversation; a second key sets it for the project or globally. A row with a warning shows it dimly, and a skill declaring allowed-tools shows the list. The size is what an invocation costs, in bytes and an estimated token count, so the choice is yours with the number in front of you.
Not in this version
Kept from the design note in the thread for later, once the basics have been used: per-skill checks attached to task acceptance, a cost and outcome ledger per skill, demotion of skills that show no value, cue write-back, trust levels beyond "you turned it on", and forging skills from repeated runs. Editing a skill when it is wrong is the first tool, and the rest should earn its place from real use.
Build order
- Discover and parse.
internal/skills: the ordered scan into one catalog, first-seen-wins by name, lenient frontmatter, warning records, size, the native mark. Pure, tested against fixtures undertestdata/. - Invoke.
/<skill-name> [words]: the dynamic command source beside the fixed table, ranked by the picker and consulted by the dispatcher, body sent as the turn's text; the manual page and command-table gates. - Choose and auto-invoke. Scope storage in the three files, the catalog in message[0], the
skilltool with its belt-fact sentence, the transcript row every auto-invocation draws, dedup and compaction protection. - Settings tab. The Skills tab,
/skill on|off [--project|--global], warnings and shadowing shown. - Add and remove. Folder and git URL, project or global.
- Tasks.
propose_task.skills, catalog and tool in a node, the bash belt readsSKILL.md.
The six steps land as ONE pull request against dev, in that order of commits, with one change entry. docs/CHAT-V3.md records that v3 deliberately dropped "skills & rules" in favour of routed memory; step 3's entry invalidates that.
Settled since the first draft
- A project's skills from a freshly cloned repository are invocable by you and never auto-invoked until turned on. The scope model gives this without a trust setting.
allowed-toolspre-approves nothing; a skill's tool use is judged by the existing approval settings like any other.- OpenCode and Goose folders are read on day one. Every folder is scanned silently into one catalog; there is no import step. First seen wins a name, in the scan order above.
- A skill is invoked by its own name as a slash command, not through
/skill <name>./skillmanages skills: add, remove, on, off, anduseas the fallback when a built-in command owns the name. - An auto-invocation is always visible: one transcript row per pull, naming the skill and its size.
Drafted with CodeAF · reviewed and owned by the author
Activity
santoshkumarradha commentedon Sep 21, 2026
Design note: skills as attachments, not injections
The issue names the hard part correctly: the question is not whether we can read a skill file, it is when a skill earns its place in a run. This is the design that falls out of how CodeAF already works.
The one law: the conversation holds the shelf; the work carries the skills.
Skill bodies never load into the chat. Chat carries only a catalog: names, one-line docs, and cost cards, windowed to what is relevant right now, with search for the long tail. When work needs a skill, the skill is attached to the task node doing the work, where context is fresh, bounded, and disposable. If chat itself needs skilled work done, it hands that piece to a quick task with the skill attached and keeps only the result. The conversation stays clean enough to think.
A skill here is five things, not one file:
Attachment is per node, several at a time, ordered. Real work is compositional: the render leaf of a task graph gets the video skill, the copy leaf gets the brand skill, and order is precedence. A skill can always be pinned ("use the pdf skill"), retrieval proposes from cues and scope, and a worker can pull a skill itself mid-run when it discovers the need. Pinned always outranks retrieved.
Three ways a skill is consumed, chosen by its cost card:
What keeps it honest: every run records cost and check outcomes per attached skill. Skills whose attachments keep failing checks get rewritten. Skills that show no marginal value against similar runs without them are demoted to pin-only. Misses (a run that should have used a skill and didn't) write their cues back, so selection improves from real use. And a skill's check runs only when its payload was actually consumed; bodies are served from the store, so consumption is a fact, not a report. An attached-but-unused skill costs one doc line, nothing more.
flowchart TD A["You ask for something"] --> C{"Chat (sees the shelf,<br/>never the bodies)"} C -->|"pocket-sized step"| C2["Chat does it inline"] C -->|"everything else"| N["Task node,<br/>skills attached by digest"] P["You pin a skill"] -. "always wins" .-> N R["Retrieval on cues + scope"] -. "proposes" .-> N N --> M{"Each attached skill is consumed by"} M -->|"read"| M1["Body pulled in on demand"] M -->|"run"| M2["Executable on PATH"] M -->|"delegate"| M3["Runs as a sub-task,<br/>only the result returns"] M1 --> K["Node checks: the task's own<br/>+ every consumed skill's"] M2 --> K M3 --> K K --> L[("Ledger: cost, pass/fail,<br/>marginal value")] L -->|"earns its tokens"| U["Attached more readily"] L -->|"no marginal value"| D["Demoted to pin-only"] L -->|"misses"| W["Cue write-back,<br/>selection improves"]What this means for skills written for other harnesses: they stay where they are. The standard folders and formats are read unmodified, an imported skill works immediately at provisional trust, and it earns full trust by passing a trial rather than by assertion. Because bodies only ever enter disposable task contexts, imported skills get the same sandboxing as everything else the system runs. A skill collection becomes more valuable here, not merely compatible.
Position on the open questions in the issue:
Build order follows the loop: shelf and attachment first, checks and the ledger next, forge last.
drafted with CodeAF
santoshkumarradha commentedon Sep 21, 2026
CC @ZeroPoint95 take ?
ZeroPoint95 commentedon Sep 21, 2026
@santoshkumarradha The grading/performance ledger stuff is cool but I don't know that it's worth the complexity. It's usually easier to just edit a skill if there is something wrong with it, unless you are in a situation where your agents need to absorb a lot of niche knowledge over time.
I think it's hard to find a real use case where you both need a self-evolving set of skills and have enough useful information to actually do the evolution in a productive way.
Basically I would suggest starting with a simpler implementation to achieve parity with other harnesses:
skillstab insettings(or standalone) would be useful hereAnyways open to discussion as always :)
santoshkumarradha commentedon Sep 21, 2026
Gtg with me. Feel free to update issues sur. @ZeroPoint95
AbirAbbas commentedon Sep 23, 2026
Cross-link: #1399 opens the
npx skills(vercel-labs/skills) listing for codeaf once this issue is in a release. That listing makes codeaf a "universal" agent there, sonpx skillswrites skills to<project>/.agents/skillsand~/.agents/skills. The scan ininternal/skillsalready reads both folders.One thing relevant to codeaf's own folder: #1399 declares
~/.agents/skillsas codeaf's global folder upstream, not~/.codeaf/skills. Upstream'sskills remove -grunsrm -rf <globalSkillsDir>/<name>for every agent in its table without checking that the path is a skill, and~/.codeaf/skillsalso holds the resident'sbin/shelf.skills: OpenCode's and Goose's folders are read too
skills: OpenCode's and Goose's folders are read too (#1691)
skills: OpenCode's and Goose's folders are read too
skills: remove the first design so #1409's lands on a clean tree