Skip to content

Skills & SKILL.md

Skills are portable instruction bundles rooted in a SKILL.md file. A skill is not just “extra prompt text” - it is a packaging format for:

  1. Trigger metadata (name, description)
  2. Executable workflow guidance (the skill body)
  3. Optional bundled resources (scripts/, references/, assets/)
  4. Optional dependency and UI metadata (agents/openai.yaml in Codex, or equivalent metadata in other systems)

In practice, skills are a context-budget and execution-control system as much as a prompting system. The hard engineering problems are:

  • Discovery scope and precedence: local project skills vs user/global skills vs system-provided skills
  • Trigger correctness: avoiding false positives/negatives and ambiguous name collisions
  • Token discipline: loading only metadata by default and deferring heavy content
  • Dependency bootstrapping: env vars and MCP prerequisites without silently failing
  • Permission boundaries: allowing useful automation without granting blanket access
  • Supply-chain trust: remote skill download and update behavior

For this page, “reference implementations” include:

  • Aider (no first-class skill system)
  • Codex (Rust, first-class SKILL.md runtime)
  • OpenCode (TypeScript/Bun, first-class skill tool + directory scanner)
  • OpenTUI (no runtime skill engine, but ecosystem-level skill packaging mention)
  • Claude Agent Skills docs (public product specification and operational best practices)

Claude docs used for the SKILL.md spec and best-practice guidance:


Reference: references/aider/ at commit b9050e1d

Aider does not implement a first-class skill runtime:

  • No SKILL.md parser
  • No skill directory discovery layer (.claude/skills, .agents/skills, etc.)
  • No skill metadata registry
  • No skill injection envelope into conversation history
  • No skill dependency model (env vars/MCP dependencies declared by skill)

Aider’s extensibility remains:

  • command-line configuration
  • coder modes (--edit-format, --architect, etc.)
  • project docs and read-only context inclusion
  • custom shell commands (--lint-cmd, --test-cmd, /run)

This gives flexibility, but no portable “skill package” abstraction. Team-specific procedures can be documented in project files, but there is no reusable/triggerable skill lifecycle equivalent to Codex/OpenCode/Claude.

If OpenOxide wants Claude/Codex-style skills, Aider is primarily a negative reference here: useful for understanding what breaks when skills are absent (repeat prompt boilerplate, no structured discovery, no dependency prompting), but not for implementation patterns.


Reference: references/codex/ at commit 4ab44e2c

Codex has a complete, multi-layer skill subsystem in Rust:

  • codex-rs/core/src/skills/loader.rs
  • codex-rs/core/src/skills/manager.rs
  • codex-rs/core/src/skills/injection.rs
  • codex-rs/core/src/skills/env_var_dependencies.rs
  • codex-rs/core/src/mcp/skill_dependencies.rs
  • codex-rs/core/src/skills/permissions.rs
  • codex-rs/core/src/skills/system.rs
  • codex-rs/core/src/project_doc.rs
  • codex-rs/core/src/instructions/user_instructions.rs

At a high level:

  1. Session start loads skills through SkillsManager and composes a “skills index section” into user instructions.
  2. Turn start re-resolves skills for current cwd and selects explicit mentions from structured inputs and text tokens.
  3. Dependency prompts/installers run for selected skills (env vars + MCP).
  4. Selected SKILL.md files are read and injected as user-role <skill>...</skill> messages.
  5. Sampling continues with those injected instructions.

skills/loader.rs defines roots from config layers and cwd/project topology:

  • repo scope roots
  • user scope roots
  • system scope roots
  • admin scope roots
  • intermediate .agents/skills directories from project root to cwd

Important behavior:

  • Root scopes are ranked (Repo > User > System > Admin) for dedupe precedence.
  • Canonicalized paths are used where possible.
  • Duplicate skill paths are removed.
  • Scans are breadth-first with explicit limits (MAX_SCAN_DEPTH, MAX_SKILLS_DIRS_PER_ROOT).

Codex follows symlinked directories for repo/user/admin scopes, but not for system scope, since system skills are generated by Codex itself (skills/loader.rs:343+).

SkillsManager caches outcomes per cwd:

  • skills_for_config(&Config) seeds cache during spawn
  • skills_for_cwd(cwd, force_reload) and skills_for_cwd_with_extra_user_roots(...) serve API/runtime paths
  • clear_cache() invalidates the map

This cache is also visible in app-server behavior (skills/list with forceReload).

parse_skill_file() reads SKILL.md, extracts YAML frontmatter, and requires:

  • name
  • description

Both fields are:

  • normalized to single-line whitespace
  • length-validated
  • rejected when missing/empty

Codex loads additional metadata from agents/openai.yaml adjacent to SKILL.md:

  • interface (display name, short description, icons, brand color, default prompt)
  • dependencies.tools[] (e.g., env_var, mcp)
  • policy.allow_implicit_invocation
  • permissions (compiled into runtime permission profile)

Loader behavior is intentionally fail-open for optional metadata: invalid optional metadata logs warnings and does not block base skill loading.

skills/system.rs embeds sample skills at compile time using include_dir, then installs them into:

  • CODEX_HOME/skills/.system

Install is marker/fingerprint guarded:

  • compares an embedded-directory fingerprint against .codex-system-skills.marker
  • skips reinstall when unchanged
  • rewrites system cache when fingerprint differs

Embedded examples include:

  • skill-creator
  • skill-installer

with supporting agents/, scripts/, references/, and assets/ folders under:

  • codex-rs/core/src/skills/assets/samples/

4) Skills as Session-Level Instruction Inventory

Section titled “4) Skills as Session-Level Instruction Inventory”

At spawn time (codex.rs), Codex computes:

  • loaded_skills.allowed_skills_for_implicit_invocation()

Then get_user_instructions() (project_doc.rs) appends a rendered “Skills” section (skills/render.rs) after AGENTS/user instructions, including:

  • available skill names/descriptions/paths
  • explicit trigger/use rules
  • progressive-disclosure guidance

This is key: metadata is always available to the model, but full skill bodies are deferred.

Per turn, run_turn() calls:

  • collect_explicit_skill_mentions(...) in skills/injection.rs

Selection algorithm:

  1. Structured UserInput::Skill resolved by path first
  2. Text mentions parsed ($skill-name and [$name](path) forms)
  3. Path-linked mentions preferred over plain-name fallback
  4. Plain-name fallback requires unambiguous name and no connector-slug conflict
  5. Disabled skill paths are excluded
  6. Deduping by path and name is enforced

This prevents accidental selection for ambiguous skill names and connector collisions.

build_skill_injections() reads selected SKILL.md files and emits user-role messages via SkillInstructions:

<skill>
<name>...</name>
<path>...</path>
... SKILL.md content ...
</skill>

Implementation path:

  • skills/injection.rs builds ResponseItems
  • instructions/user_instructions.rs defines From<SkillInstructions> for ResponseItem
  • run_turn() records injected items before sampling

Failures to read files are emitted as warning events and metrics; other skills continue.

skills/env_var_dependencies.rs:

  • collects dependencies where dependencies.tools[].type == "env_var"
  • checks session-scoped dependency env cache, then process env
  • prompts user via request_user_input for missing variables (is_secret = true)
  • stores values in-memory for current session only

mcp/skill_dependencies.rs:

  • extracts missing MCP dependencies from selected skills
  • prompts for installation unless in full-access mode
  • can persist MCP config edits to global config
  • supports OAuth login flow for installed MCP servers where required
  • tracks prompted dependencies to avoid repetitive prompts in one session

skills/permissions.rs compiles optional manifest permissions into a constrained runtime permissions profile:

  • filesystem read/write path normalization
  • optional network access
  • macOS-specific seatbelt extension fields
  • default approval policy set to Never within that compiled profile

This enables skill-authored permission narrowing/expansion patterns, but introduces trust/safety concerns discussed below.

Codex app-server v2 exposes skill operations in codex_message_processor.rs and documented in app-server/README.md:

  • skills/list
  • skills/remote/read (under development)
  • skills/remote/write (under development)
  • skills/config/write

skills/list supports:

  • multiple cwd queries
  • per-cwd extra user roots (absolute paths only)
  • force-reload cache bypass

skills/remote.rs supports:

  • listing remote public skills
  • downloading zip payloads
  • path-safe extraction (safe_join, component validation)
  • whitelist extraction via server-provided file map

Current app-server docs explicitly mark remote read/write endpoints as under development.


Reference: references/opencode/ at commit 7ed44997

OpenCode implements skills with both:

  • a discovery registry (src/skill/skill.ts)
  • a model-callable tool (src/tool/skill.ts)

Plus API/CLI/TUI surfaces.

  1. Skill.state() scans configured roots and builds { name -> skill info }.
  2. Skill names are exposed as slash-command-like commands via Command.state().
  3. The skill tool can be called by the model to load full skill content.
  4. Loaded content is returned inside <skill_content ...> output and becomes part of conversation context through normal tool-result flow.

Skill.Info in skill.ts:

  • name
  • description
  • location
  • content

Parsed from SKILL.md frontmatter/body using ConfigMarkdown.parse.

Skill.state() builds registry by scanning in this order:

  1. global external dirs (~/.claude/skills/**/SKILL.md, ~/.agents/skills/**/SKILL.md)
  2. project-level external dirs found by upward search from instance directory
  3. .opencode/{skill,skills}/**/SKILL.md across config directories
  4. extra skills.paths[] from config
  5. remote discovery sources from skills.urls[]

On duplicate name:

  • logs warning
  • later scan overrides earlier entry

This “last write wins” behavior is practical but can silently shadow skills.

skill/discovery.ts fetches a skill index:

  • <base>/index.json

Expected shape:

  • skills: [{ name, description, files[] }]

It downloads listed files into cache (Global.Path.cache/skills/<name>/...) and only returns directories containing SKILL.md.

Caching behavior:

  • file fetch skips already-existing destination files
  • repeat pulls typically become no-op for unchanged files

SkillTool in tool/skill.ts:

  • dynamically describes available skills in tool description XML block
  • validates params with zod ({ name: string })
  • asks permission (permission: "skill", pattern = selected name)
  • returns structured output:
    • <skill_content name="...">
    • raw skill content
    • base directory URI
    • sampled file listing (<skill_files>)

This is a direct, explicit load model: full body comes through a tool call instead of hidden prompt mutation.

OpenCode integrates skills into permission and agent systems:

  • config schema includes permission.skill rules (config.ts)
  • SkillTool evaluates agent permission with PermissionNext.evaluate("skill", skill.name, ...)
  • default agent permissions include allow-rules for skill directories in external_directory

This avoids a common failure mode where skill instructions reference bundled files that the active agent cannot read.

command/index.ts adds each skill as a command when no command name collision exists:

  • command source: "skill"
  • template returns skill content

server/server.ts exposes:

  • GET /skill

returning Skill.Info[].

cli/cmd/debug/skill.ts:

  • opencode debug skill

prints all discovered skills as JSON.

TUI dialog components (dialog-skill.tsx) fetch from /skill and support searchable selection.

OpenCode has extensive tests in:

  • test/skill/skill.test.ts
  • test/skill/discovery.test.ts
  • agent permission test coverage in test/agent/agent.test.ts

Covered scenarios include:

  • discovery from .opencode/skill, .opencode/skills
  • discovery from .claude/skills and .agents/skills (project + global)
  • missing frontmatter skip behavior
  • remote discovery from Cloudflare .well-known/skills
  • reference-file download alongside SKILL.md
  • cache behavior on repeated pulls

Reference: references/opentui/ at commit f4712b9a

OpenTUI does not implement a runtime skill system in framework code (no SKILL.md parser/runtime in packages/ source tree). Skills appear only as ecosystem distribution guidance in README:

  • install an “OpenTUI skill” for AI assistants
  • no in-framework skill discovery/injection/dependency lifecycle

So OpenTUI is not a direct architectural reference for implementing skills in OpenOxide core; it is only a packaging ecosystem signal.


Claude’s public docs are the strongest public spec for SKILL.md authoring conventions and progressive disclosure behavior:

  1. Three-level loading model:
    • lightweight metadata always visible
    • skill body loaded when invoked
    • bundled resources loaded on demand
  2. Strict skill folder contract:
    • SKILL.md required
    • predictable adjacent resources
  3. Progressive disclosure as a token strategy:
    • keep core body concise
    • push heavy details into referenced files
  4. Trigger hygiene:
    • descriptions should clearly encode when to use the skill
    • avoid vague metadata that overfires
  5. Operational best practices:
    • use scripts for deterministic repetitive tasks
    • avoid oversized monolithic instructions
    • include clear “when to read which file” guidance

OpenCode and Codex independently converge on many of these same patterns (directory-based discovery, metadata-first, delayed full-body loading, optional bundled references/scripts).


Reference: Claude Code Skills docs (fetched 2026-02-21)

Claude Code has the most mature skill system of the four references, built on the Agent Skills open standard. Custom slash commands (.claude/commands/) have been merged into skills — both paths create the same /name shortcut, but skills/ take precedence when both exist.

  1. At session start, skill descriptions are loaded from all discovery roots and injected as lightweight metadata.
  2. Skills consume up to 2% of the context window for descriptions (configurable via SLASH_COMMAND_TOOL_CHAR_BUDGET). Skills that exceed the budget are excluded (visible via /context).
  3. When invoked (user /slash-command or Claude auto-invocation), the full SKILL.md body is loaded and injected.
  4. Skills with context: fork execute in an isolated subagent context — the skill body becomes the subagent’s task.
  5. Skills passed to subagents via the skills: frontmatter field are fully preloaded into the subagent’s context at launch (not on-demand).

Four scope levels, highest priority wins on name collision:

LevelLocationPriority
EnterpriseManaged settingsHighest
Personal~/.claude/skills/<name>/SKILL.md
Project.claude/skills/<name>/SKILL.md
Plugin<plugin>/skills/<name>/SKILL.mdLowest (namespaced as plugin-name:skill-name)

Automatic discovery from nested .claude/skills/ directories supports monorepo setups. Skills from --add-dir directories support live change detection (no restart needed).

FieldTypePurpose
namestringDisplay name and /slash-command. Lowercase, hyphens, max 64 chars
descriptionstringWhen to use. Claude uses this for auto-invocation decisions
argument-hintstringAutocomplete hint shown in UI, e.g., [issue-number]
disable-model-invocationbooltrue = only the user can invoke (hides from Claude’s auto-invoke)
user-invocableboolfalse = hidden from / menu, Claude-only background knowledge
allowed-toolslistTools permitted without approval when skill is active
modelstringModel override while skill is active
contextstringfork = run in isolated subagent context
agentstringSubagent type for context: fork (default: general-purpose)
hooksobjectLifecycle hooks scoped to skill’s lifetime (cleaned up when skill finishes)

The combination of disable-model-invocation and user-invocable creates three distinct behavioral modes:

ConfigUser invokeClaude invokeContext load
(defaults)YesYesDescription every request, full on invoke
disable-model-invocation: trueYesNoNothing until user invokes
user-invocable: falseNoYesDescription every request, full on invoke

Skill bodies support several substitution patterns:

  • $ARGUMENTS — all args passed after the slash command
  • $ARGUMENTS[N] or $N — positional args (0-indexed)
  • ${CLAUDE_SESSION_ID} — current session ID
  • !`command` — shell preprocessing: the command runs before the skill body is sent to Claude, and its stdout replaces the backtick expression

Shell preprocessing is unique to Claude Code among all references — it enables dynamic context injection (e.g., !`git log --oneline -5` to inject recent commits).

Skills can declare hooks in frontmatter that are scoped to the skill’s lifetime:

---
name: deploy
hooks:
Stop:
- hooks:
- type: command
command: "echo 'Deploy skill finishing'"
once: true
---

The once: true field causes the hook to fire exactly once per session then be removed. This is only supported in skill frontmatter, not in agent frontmatter or settings files.

Key Design Differences from Codex and OpenCode

Section titled “Key Design Differences from Codex and OpenCode”
AspectCodexOpenCodeClaude Code
Discovery roots4 scopes (Repo > User > System > Admin)Config + upward search4 scopes (Enterprise > Personal > Project > Plugin)
Name collisionScope rank precedenceLast-write-winsHigher-priority wins + plugin namespace
Loading modelMetadata-first, body on mentionMetadata-first, body via tool callDescriptions at start, full on invoke
Invocation$skill-name, [$name](path)/skill-name via tool/skill-name or Claude auto-invocation
Invocation controlallow_implicit_invocationN/Adisable-model-invocation + user-invocable
Shell preprocessingNoNoYes (!`command` syntax)
Fork executionNoNocontext: fork runs in subagent
Hook scopingNoNoHooks in skill frontmatter, scoped to lifetime
Context budgetN/AN/A2% of window, configurable
Dependenciesenv_var + MCP (explicit)env_var + MCP (explicit)None (self-contained)

Codex and OpenCode both expose potential collision scenarios:

  • two skills with same name in different roots
  • connector/app slug matching skill name
  • command namespace collisions with skill names

Hard requirement: disambiguation by explicit path and predictable precedence rules.

OpenCode’s last-write-wins duplicate behavior is convenient but can hide unexpected overrides. Codex’s scope ranking mitigates some ambiguity but still needs user visibility in UI/API.

If entire skills auto-load, context cost explodes quickly. Both Codex and Claude guidance reinforce metadata-first loading. Skills must be structured for deferred reads.

A skill that assumes env vars or MCP servers without prompting creates brittle failures. Codex’s env-var + MCP dependency prompts are a strong reference and should be treated as baseline behavior.

Remote downloads add integrity and provenance risk:

  • tampered index payloads
  • skill updates changing behavior unexpectedly
  • unreviewed dependency instructions

At minimum: path-safe extraction, deterministic caching, origin transparency, and explicit user approval for install/enable.

A skill may instruct the model to run scripts or read references that current policy blocks. OpenCode’s default permission tweaks for skill directories and Codex’s optional skill permission profiles both address this from different angles.

Per-cwd caches speed lookup, but stale registry state causes confusing “skill not found” or old-content injection. Force-reload pathways and filesystem-triggered invalidation are important.


OpenOxide should implement skills as a first-class subsystem, not a prompt hack.

Proposed crate split:

  • openoxide-skills:
    • discovery
    • parsing and validation
    • metadata model
    • mention matching
    • injection rendering
    • dependency resolvers
  • openoxide-skill-registry (optional if split desired):
    • remote index handling
    • cache and install manager
  • integrate with:
    • openoxide-agent (turn loop)
    • openoxide-config (roots, enable/disable, trusted sources)
    • openoxide-permissions (skill-scoped permission profile support)
pub struct SkillMetadata {
pub name: String,
pub description: String,
pub short_description: Option<String>,
pub path: PathBuf,
pub scope: SkillScope, // Repo | User | System | Admin
pub policy: SkillPolicy,
pub interface: Option<SkillInterface>,
pub dependencies: Option<SkillDependencies>,
pub permissions: Option<PermissionsProfile>,
}

Adopt Codex-like precedence with explicit policy:

  1. repo roots (.openoxide/skills, .agents/skills along project-root->cwd chain)
  2. user roots ($OPENOXIDE_HOME/skills, $HOME/.agents/skills, optionally $HOME/.claude/skills for compatibility)
  3. system roots (embedded defaults cache)
  4. admin roots (system config location)

Rules:

  • canonicalize paths
  • dedupe by path
  • deterministic sorting (scope rank -> name -> path)
  • bounded traversal depth and dir count
  • controlled symlink policy by scope

Required:

  • YAML frontmatter with name, description
  • markdown body

Optional adjacent metadata:

  • agents/openoxide.yaml (primary)
  • agents/openai.yaml compatibility reader for portability

Field parity target (v1):

  • interface.*
  • dependencies.tools[] (env_var, mcp; leave room for future kinds)
  • policy.allow_implicit_invocation
  • optional permissions profile

Implement two-stage flow:

  1. Always-visible metadata section in initial instructions (names + descriptions + paths + usage rules)
  2. Explicit skill load/injection on mention or structured input

Mention resolver requirements:

  • structured selection by path first
  • text mentions ($name and [$name](path))
  • plain-name fallback only when unambiguous
  • connector/app slug conflict suppression
  • disabled-path filtering
  • stable ordering

Use a clear XML-like envelope for loaded skills:

<skill>
<name>...</name>
<path>...</path>
...body...
</skill>

Keep this as user-role content items so history filtering can identify and compact skill instructions cleanly.

  • collect all env_var dependencies for selected skills
  • resolve from session cache -> process env
  • prompt for missing values (secret input mode)
  • session-memory storage by default; optional encrypted persistence later
  • detect missing servers
  • prompt install/skip with remember-for-session state
  • install through config edit pipeline
  • optional OAuth flow handoff where transport requires auth

Baseline recommendation:

  • do not auto-expand permissions blindly from skill metadata
  • require explicit user opt-in for manifest-derived permission profile
  • always show effective delta before applying

Optional modes:

  • strict mode: ignore skill permission manifests entirely
  • trusted-source mode: allow manifests from system/admin scopes only

If implemented:

  • index schema with explicit file lists
  • path-safe extraction only
  • source pinning and lock metadata
  • optional signature verification
  • install requires approval
  • clear status UI: source URL, version/hash, install path, enabled state

Minimum user-facing surface:

  • skills/list
  • skills/reload
  • skills/enable / skills/disable
  • skills/install (future, remote/local)
  • debug view for:
    • resolved roots
    • effective precedence
    • parse errors
    • disabled paths

TUI:

  • searchable skill picker
  • show scope badges (repo, user, system, admin)
  • show dependency warnings before invocation

Must-have tests:

  1. discovery precedence and dedupe across scopes
  2. duplicate-name ambiguity behavior
  3. mention parser edge cases ($, links, env-var lookalikes)
  4. skill injection serialization
  5. env-var prompt flow
  6. mcp dependency detection/install prompt flow
  7. cache behavior + force reload
  8. path traversal/symlink safety checks in loader and installer
  9. compatibility parsing for Claude/Codex-style skill folder layouts

Likely Rust dependencies:

  • serde, serde_yaml
  • walkdir or ignore
  • path_clean/dunce
  • aho-corasick or custom parser for mention extraction
  • schemars (if skill metadata schema export is desired)
  • tokio for async reads/prompts/install orchestration

Implement two frontmatter fields for invocation control:

  • disable_model_invocation: bool — when true, only user can invoke (e.g., /deploy). Skill metadata is not sent to the model at all.
  • user_invocable: bool — when false, skill is hidden from the / menu but Claude can auto-invoke based on description match.

These two fields create a three-way matrix (both-true, user-only, model-only) that covers all real-world use cases.

Support a context: fork field that executes the skill body in an isolated subagent context:

pub enum SkillContext {
/// Default: inject skill body into current conversation.
Inline,
/// Run skill body as a subagent task, return summary to parent.
Fork {
agent_type: Option<String>,
},
}

This enables long-running or tool-heavy skills (e.g., “review all files in src/”) without polluting the main conversation context.

Support !`command` syntax in skill bodies. Before the skill content is sent to the model, shell expressions are evaluated and replaced with their stdout. This enables dynamic context like recent git history, environment info, or project-specific metadata without requiring the model to make tool calls.

Cap skill descriptions at a configurable percentage of the context window (default 2%):

pub struct SkillBudget {
/// Maximum percentage of context window for skill descriptions.
pub max_pct: f32, // default: 0.02
/// Absolute fallback if context size is unknown.
pub fallback_chars: usize, // default: 16_000
}

Skills that exceed the budget are excluded with a warning visible via a /context or similar debug command. This prevents skill metadata from crowding out actual conversation content.

When the plugin system lands, plugin skills must be namespaced as plugin-name:skill-name to prevent cross-plugin name collisions. Non-plugin skills keep their bare names.

Skills can declare hooks in frontmatter that are scoped to the skill’s lifetime:

pub struct SkillHooks {
/// Hooks that are registered when skill activates and removed when it deactivates.
pub hooks: HashMap<HookEvent, Vec<HookHandler>>,
}
pub struct HookHandler {
pub hook_type: HookType,
pub command: String,
/// If true, hook fires once per session then is removed.
pub once: bool,
}
  1. Skills are a runtime subsystem, not a markdown convention.
  2. Metadata-first, content-later loading is mandatory for token discipline.
  3. Explicit path-based selection must outrank name heuristics.
  4. Dependency resolution is first-class, not ad-hoc.
  5. Permission expansion from skill manifests must be explicit and inspectable.
  6. Registry/install features must be designed with supply-chain safety from day one.
  7. Invocation control (disable_model_invocation + user_invocable) is first-class — skills are not all-or-nothing.
  8. Context fork enables heavy skills to run in isolated subagent context without polluting the main conversation.
  9. Shell preprocessing enables dynamic context injection before model sees the skill body.
  10. Context budget prevents skill metadata from crowding out conversation content.
  11. Plugin namespacing prevents cross-plugin name collisions from day one.