English · 简体中文
Skills API¶
Skills are reusable bundles of system prompt, tool filter, and trigger conditions. They let you package domain expertise (code review, translation, summarization, RAG) into a single file that PAR auto-discovers and activates when relevant.
Status: v0.5.2-beta. API may change before v1.0.
What is a skill?¶
PAR has four abstraction layers. Skills sit alongside tools, agents, and middleware:
| Layer | What it packages | Example |
|---|---|---|
| Tool | A single function callable by the LLM | read, web_search, bash |
| Skill | System prompt + tool subset + trigger conditions | "Code reviewer" (review prompt + read/grep tools only) |
| Agent | Full config: model, tools, middleware, retry policy | Default agent with GPT-4 + 23 tools |
| Middleware | Cross-cutting hook (logging, retry, rate limit) | Retry with exponential backoff |
When to create a skill vs an agent: - Skill: You want to switch behavior within a conversation without restarting. The agent stays the same; the skill overlays its prompt and tool filter. - Agent: You need a fundamentally different model, tool set, or middleware chain.
Quick start¶
Create a skill in 30 seconds:
mkdir -p ~/.par/skills/greeter
cat > ~/.par/skills/greeter/skill.md << 'EOF'
---
schema_version: 1
id: greeter
name: Greeter
description: Greet users warmly. Use when the conversation starts or the user says hello.
system_prompt_override: "You are an enthusiastic greeter. Always start with a warm welcome."
tool_filter: All
trigger: Keyword [hello, hi, hey]
---
# Greeter Skill
When activated, greet the user warmly and ask how you can help.
Use their name if provided in the conversation context.
EOF
Now start PAR and check:
par
> /skills
greeter Greet users warmly. Use when the conversation starts...
> /skill greeter
ID: greeter
Name: Greeter
Description: Greet users warmly. Use when the conversation starts or the user says hello.
Trigger: keyword
The skill is auto-discovered and registered. When the user types "hello", the keyword trigger activates the skill.
File format¶
Each skill lives in its own directory: ~/.par/skills/<id>/skill.md.
Directory structure¶
YAML frontmatter reference¶
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
schema_version |
int | yes | — | Must be 1 for v0.5.2. Loader rejects unknown versions. |
id |
string | yes | — | Lowercase-hyphen identifier. Must match directory name. |
name |
string | yes | — | Display name. |
description |
string | yes | — | What the skill does + when to use it. Max 1024 chars. Always loaded (L1 metadata). |
system_prompt_override |
string | null | no | null |
Replaces the agent's system prompt when skill activates. |
tool_filter |
All | Only [...] | Except [...] |
no | All |
Restricts which tools the LLM can use. |
trigger |
Auto | Manual | Keyword [...] |
no | Auto |
When the skill gets activated. |
expected_output |
JSON | null | no | null |
Forward-looking: typed success criteria (informational in v0.5.2, LLM judge planned for a future version). |
Markdown body¶
The body after the second --- is the skill's instructions. It's lazy-loaded (L2) — only read into memory when the skill activates, not at startup. This keeps startup fast even with 50+ skills installed.
Trigger types¶
The trigger field controls when a skill becomes active:
Auto (default)¶
The skill description is loaded into the LLM's system prompt. The LLM itself decides whether to activate the skill based on the user's message and the description text.
Best for: skills that should be "always available" to the LLM (summarizer, RAG assistant).
Manual¶
The skill is never auto-activated. Users must explicitly invoke it.
REPL (activates for the current session, applies to all subsequent invokes):
> /skill use my-skill
Skill activated: my-skill
> hello
[response with my-skill's system_prompt_override applied]
> /skill unuse
Manual skill activation cleared (1 were active).
Activation is persistent across invokes within the session until cleared with /skill unuse. It composes with auto-triggered skills (keyword/auto) — system_prompt_override uses last-wins, tool_filter intersects. Use this for specialized workflows that should remain on for a stretch of a conversation.
Standalone CLI (validation only — a CLI process exits before any invoke, so it can't actually apply activation):
Best for: skills that should only fire on explicit request (dangerous operations, specialized workflows).
Keyword¶
A fast substring pre-filter runs before LLM judgment. The skill is only considered if one of the keywords appears in the user's message:
confirm(default): after keyword match, LLM still judges whether to activatedeterministic: keyword match immediately activates, no LLM judgment needed
Best for: skills with clear domain keywords (PDF extractor, translator for specific languages).
Token budget¶
To prevent system prompt explosion, PAR caps total skill description tokens at 2048 (configurable via runtime config skill_token_budget). When the budget is exceeded, lowest-priority skill descriptions are dropped with a warning.
Tool filter¶
Restricts which tools the LLM can access when the skill is active:
tool_filter: All # all registered tools (default)
tool_filter: Only [read, grep] # only these tools
tool_filter: Except [bash] # all tools except these
When multiple skills are active simultaneously, filters compose by intersection (most restrictive wins). This ensures no skill can widen another skill's restriction.
Discovery¶
PAR discovers skills from three sources, in this precedence order (first match wins):
- SDK-registered (highest):
Runtime.register_skill(descriptor)— explicit, programmatic - Project:
./.par/skills/<id>/skill.md— checked into the repo - User:
~/.par/skills/<id>/skill.md— user-wide defaults - Builtin (lowest): 4 starter skills shipped with PAR
Hot-reload: On each Runtime.invoke, PAR checks if any skills directory's mtime changed. If so, it rescans automatically. You can also force a rescan:
CLI usage¶
REPL commands¶
| Command | Description |
|---|---|
/skills |
List all registered skills with description preview |
/skill <id> |
Show full detail of a specific skill (default subcommand) |
/skill use <id> |
Manually activate a skill (overrides its trigger; persistent until /skill unuse) |
/skill unuse |
Clear manual activation; subsequent invokes use only auto-triggered skills |
/skill create <id> |
Interactive wizard: create a new skill in ~/.par/skills/<id>/skill.md |
Activation semantics: /skill use <id> adds <id> to the runtime's user_activated_skills set. On every invoke, skills in this set are resolved regardless of their trigger (Auto/Manual/Keyword) and composed with any auto-triggered skills. system_prompt_override uses last-wins (order is: auto-triggered first, then user-activated); tool_filter intersects (most restrictive wins). Manual-trigger skills are only activatable via /skill use — they are dead weight otherwise.
Standalone commands¶
par skill list # list all discovered skills (non-interactive)
par skill show <id> # show skill detail (id, name, trigger, tool_filter, override)
par skill use <id> # validate a skill is available; print activation hint
par skill create [id] # interactive wizard (see below); id optional, prompted if omitted
par skill reload # force filesystem rescan (invalidates mtime cache)
/skill create wizard¶
The wizard prompts for all 8 frontmatter fields and writes a valid skill.md to ~/.par/skills/<id>/skill.md:
$ par skill create my-analyst
Name [my-analyst]:
Description (≤1024 chars): Analyze data and report insights
System prompt override (blank = none, \n for newlines): You are a data analyst.
Tool filter [All|Only|Except] (default: All): Only
Only tools (comma-separated): read, grep, bash
Trigger [Auto|Manual|Keyword] (default: Auto): Keyword
Keywords (comma-separated): data, analyze, report
Expected output JSON schema (blank = none):
Created: /home/user/.par/skills/my-analyst/skill.md
Use /skill use my-analyst to activate it in this session.
The file is immediately discoverable by /skills and par skill list (the wizard calls Skill_loader.force_reload to invalidate the mtime cache). Edit the ## Instructions and ## Examples sections in the generated file to customize the L2 body.
SDK API¶
Python¶
from par_runtime import Runtime
import json
rt = Runtime(config_json)
# Register a skill programmatically
rt.register_skill(json.dumps({
"schema_version": 1,
"id": "my-skill",
"name": "My Skill",
"description": "Does something useful.",
"system_prompt_override": "You are a specialist.",
"tool_filter": "Only [read]",
"trigger": "Auto"
}))
# List all registered skills
skills = rt.list_skills()
for s in skills:
print(f" {s['id']}: {s['description']}")
OCaml¶
(* Create a skill descriptor *)
let descriptor =
Par.Runtime.make_skill
~id:"my-skill"
~description:"Does something useful."
~system_prompt_override:"You are a specialist."
~tool_filter:(Par.Types.Only ["read"])
~trigger:Par.Types.Auto
()
|> Result.get_ok
(* Register it *)
let _ = Par.Runtime.register_skill rt descriptor
(* List all skills *)
let skills = Par.Runtime.list_skills rt
List.iter (fun s -> Printf.printf " %s: %s\n" s.Par.Types.id s.Par.Types.description) skills
Builtin skills¶
PAR ships with 4 starter skills. Override them by creating a skill with the same id in ~/.par/skills/ or ./.par/skills/:
| ID | Trigger | Tools | Description |
|---|---|---|---|
code-reviewer |
Keyword: review, audit | read, grep, find | Reviews code for bugs, security, style |
summarizer |
Auto | All | Summarizes long text into key points |
translator |
Keyword: translate, 翻译 | All | Translates between languages |
rag-assistant |
Auto | All | Answers using retrieved document context |
Examples¶
Example 1: Custom skill — Python data analyst¶
mkdir -p ~/.par/skills/python-analyst
cat > ~/.par/skills/python-analyst/skill.md << 'EOF'
---
schema_version: 1
id: python-analyst
name: Python Data Analyst
description: Analyze Python code for data science patterns. Use when the user asks about pandas, numpy, or data analysis code.
system_prompt_override: "You are a Python data science expert. Focus on pandas/numpy patterns, performance, and correctness."
tool_filter: Only [read, grep, find]
trigger: Keyword [pandas, numpy, dataframe, data analysis]
---
# Python Data Analyst
When analyzing Python code:
1. Check for common pandas anti-patterns (chained indexing, in-place modification)
2. Verify numpy vectorization opportunities
3. Suggest type annotations for data science functions
4. Flag potential memory issues with large DataFrames
EOF
Example 2: Override a builtin¶
To customize the summarizer skill, create a project-level skill that overrides the builtin:
mkdir -p .par/skills/summarizer
cat > .par/skills/summarizer/skill.md << 'EOF'
---
schema_version: 1
id: summarizer
name: Tech Summarizer
description: Summarize technical documents. Use when the user asks for a summary or TL;DR.
system_prompt_override: "You are a technical writer. Summarize with structured headers, preserve all technical terms, and include a one-sentence TL;DR at the top."
tool_filter: All
trigger: Auto
---
# Tech Summarizer
Format summaries as:
## TL;DR
[one sentence]
## Key Points
- bullet points
## Technical Details
[code blocks, configs, commands]
Because this is in ./.par/skills/ (project), it takes precedence over the builtin summarizer (which is lowest priority).
Example 3: Deterministic keyword skill¶
A skill that activates immediately on keyword match, without LLM judgment:
mkdir -p ~/.par/skills/log-parser
cat > ~/.par/skills/log-parser/skill.md << 'EOF'
---
schema_version: 1
id: log-parser
name: Log Parser
description: Parse and analyze log files. Use when the user mentions logs, stack traces, or error output.
system_prompt_override: "You are a log analysis expert. Extract timestamps, error codes, and stack traces. Correlate events across log lines."
tool_filter: Only [read, grep, bash]
trigger: Keyword [log, stack trace, error output, traceback] deterministic
---
# Log Parser
1. Identify the log format (syslog, JSON, plain text)
2. Extract all ERROR/WARN lines
3. Find the root cause (first error in the chain)
4. Suggest fixes based on the error pattern
The deterministic flag means: if the user's message contains "log", "stack trace", "error output", or "traceback", this skill activates immediately — no LLM round-trip needed.
Tiered context loading¶
PAR uses a 2-level context loading model inspired by Anthropic Agent Skills:
| Level | What | When loaded | Token cost |
|---|---|---|---|
| L1 | id, name, description (frontmatter only) |
At startup, always in memory | ~100 tokens per skill |
| L2 | Markdown body (instructions) | Only when skill activates | Variable (skill-specific) |
This means you can install 50+ skills without bloating every LLM call — only the L1 metadata is always resident. The L2 body is loaded on-demand when a skill triggers.
Migration and versioning¶
The schema_version field (currently 1) lets PAR evolve the skill format without breaking existing skills:
- Unknown versions are rejected at load time with a clear error:
"skill targets schema_version 2, current is 1. See MIGRATION.md." - Old versions may be auto-migrated (future) or rejected with upgrade instructions.
When PAR adds new frontmatter fields in v0.6+, skills with schema_version: 1 will still work — new fields default to sensible values.
Skills as behavioral modes¶
A common agent pattern is switching the same agent between plan / build / review / debug behaviors with different tool sets and prompts. PAR implements this via Skills, not a separate Mode type.
Why not a separate Mode type?¶
Skill's data model is a strict superset of what "Mode" typically means:
| Mode concept | Skill equivalent |
|---|---|
allowed_tools |
tool_filter (All_tools \| Only \| Except) |
prompt_fragment |
system_prompt_override (Stable_prompt \| Volatile_prompt \| Both_prompts) |
register_mode(id, ...) |
register_skill(descriptor) |
set_mode("plan") |
set_user_activated_skills(["plan"]) or invoke(..., skills=["plan"]) |
Adding a parallel Mode type would create two systems doing the same thing — a type-rigor violation (STRATEGY.md §3). See DECISIONS.md #2 for the full rationale and escalation trigger.
Per-call mode switching¶
Use the ?skills parameter on Runtime.invoke / invoke_async for race-free per-call activation:
(* Activate "plan-mode" skill for this invoke only *)
Runtime.invoke rt ~agent_id:"coder" ~message:"implement X" ~skills:["plan-mode"] ()
The ?skills parameter does NOT modify rt.user_activated_skills — it overrides the skill snapshot for this single invoke only. This is safe for concurrent invoke calls on the same runtime.
Skill activation events¶
v0.8.1 adds two event variants for observability (Skill_activated, Skill_deactivated):
Skill_activated of { skill_id : string }— emitted at invoke entry for each active skillSkill_deactivated of { skill_id : string }— reserved for future use (when skills gain explicit deactivation)
Subscribe via the event bus to audit mode transitions.
Python API¶
rt.set_user_activated_skills(["plan-mode", "debug-mode"])
rt.invoke("coder", "implement X")
rt.clear_user_activated_skills()
rt.get_user_activated_skills() # -> [] (after clear)
See also¶
- Agent API — agents,
Runtime.invoke, tool handlers - Tools — 23 builtin tools, custom registration
- RAG API — embeddings, vector store, retrieval (used by
rag-assistantskill) - Architecture — how PAR works internally