Skills System#
Skills are reusable, domain-specific pieces of knowledge injected into agent prompts before each task. They encode successful proof strategies, domain conventions, and common pitfalls learned from previous runs.
eurekaclaw/skills/
├── registry.py SkillRegistry (load + store skills)
├── injector.py SkillInjector (retrieve + format for prompts)
├── install.py SkillInstaller (install seed skills or skills from ClawHub)
└── evolver.py SkillEvolver (distill skills from proceeded session)
Skill File Format#
Each skill is a Markdown file with YAML frontmatter:
---
name: ucb_regret_analysis
version: "1.0"
tags: [bandit, regret, ucb, concentration]
agent_roles: [theory, survey]
pipeline_stages: [theory]
description: How to decompose and bound UCB1 regret using concentration inequalities
source: seed # seed | distilled | manual
created_at: 2026-01-01T00:00:00
usage_count: 0
success_rate: null # float 0-1, or null if unknown
---
# UCB Regret Analysis
When bounding UCB1 regret, decompose into:
1. Suboptimal arm pulls where confidence bound held (good event)
2. Pulls where the bound failed (bad event, controlled by concentration)
Use Hoeffding for sub-Gaussian rewards, Bernstein when variance is known...
Skills are stored in one of three locations depending on their origin (see Skill Lifecycle below).
SkillRegistry#
File: eurekaclaw/skills/registry.py
class SkillRegistry:
def __init__(skills_dir: Path | None = None) -> None
Loading#
def load_all() -> list[SkillRecord]
Load all skills from registered directories. Load order (later overrides earlier):
Seed skills in
eurekaclaw/skills/seed_skills/Domain plugin skills (extra directories from
get_skills_dirs())User skills in
~/.eurekaclaw/skills/(highest priority)
def add_skills_dir(path: Path) -> None
Register an extra directory to load skills from (used by domain plugins).
def reload() -> None
Reload all skills from disk (e.g., after distillation writes new files).
Retrieval#
def get(name: str) -> SkillRecord | None
Retrieve a skill by exact name.
def get_by_tags(tags: list[str]) -> list[SkillRecord]
Return all skills that have at least one of the given tags.
def get_by_role(role: str) -> list[SkillRecord]
Return all skills whose agent_roles includes role.
def get_by_pipeline_stage(stage: str) -> list[SkillRecord]
Return all skills for a given pipeline stage.
Storage#
def upsert(skill: SkillRecord) -> None
Write skill to disk and register in memory. Creates or overwrites the .md file in ~/.eurekaclaw/skills/.
SkillInjector#
File: eurekaclaw/skills/injector.py
Retrieves the most relevant skills for a task and formats them for injection into agent system prompts.
class SkillInjector:
def __init__(
registry: SkillRegistry,
selected_skills: list[str] | None = None,
) -> None
selected_skills — optional list of skill names to pin. Pinned skills are always included at the front of the top-k result, before any usage-sorted optional skills. If a pinned skill name is not found in the registry, a warning is logged and it is silently skipped.
Retrieval#
def top_k(
task: Task,
role: str,
k: int = 5,
strategy: Literal["tag", "semantic", "hybrid"] = "tag"
) -> list[SkillRecord]
Retrieval strategies:
Strategy |
Description |
|---|---|
|
Filter by matching |
|
Embedding-based similarity using |
|
Tag filter (3×k candidates), then text similarity ranking |
Pinned-skill priority: When selected_skills is non-empty, the tag (and by extension hybrid) retrieval splits candidates into:
Must-have — skills in both
selected_skillsand the role/stage set → always placed firstOptional — remaining candidates → sorted by
usage_countdescending
The combined list is then truncated to k.
Formatting#
def render_for_prompt(skills: list[SkillRecord]) -> str
Returns an XML block injected into the agent system prompt:
<skills>
<skill name="ucb_regret_analysis">
# UCB Regret Analysis
...
</skill>
<skill name="concentration_inequalities">
...
</skill>
</skills>
Data Models#
File: eurekaclaw/types/skills.py
class SkillMeta(BaseModel):
name: str
version: str = "1.0"
tags: list[str] = []
agent_roles: list[str] = [] # e.g., ["theory", "survey"]
pipeline_stages: list[str] = [] # e.g., ["theory", "experiment"]
description: str = ""
source: Literal["seed", "distilled", "manual"] = "seed"
created_at: datetime
usage_count: int = 0
success_rate: float | None = None
class SkillRecord(BaseModel):
meta: SkillMeta
content: str # Markdown body after frontmatter
file_path: str = "" # absolute path to the .md file
embedding: list[float] | None = None # populated on first semantic retrieval
@property
def full_markdown(self) -> str: ... # frontmatter + content
Skill Distillation (Post-Run Learning)#
After each successful session, ContinualLearningLoop.post_run() distills new skills from the session:
ContinualLearningLoop.post_run()
├── extract failures (FailedAttempt[]) from TheoryState
├── deduplicate — only unique failure patterns (skip low-novelty)
├── compress successes — proof text trimmed to 300 chars
├── SkillEvolver.distill_from_session()
│ → new SkillRecord .md files in ~/.eurekaclaw/skills/
└── (rl/madmax modes) ProcessRewardModel scoring
SkillEvolver.distill_from_session() uses the main LLM to:
Identify generalizable patterns from successful proofs
Write a new skill Markdown file with appropriate tags and roles
Set
source: distilledin frontmatter
New skills are immediately available in the next session via SkillRegistry.reload().
Seed Skills (MAB Domain)#
The MAB domain plugin ships four seed skills:
Skill |
Tags |
Description |
|---|---|---|
|
bandit, regret, ucb |
UCB1 regret decomposition via concentration |
|
bandit, thompson, bayesian |
Thompson Sampling regret analysis |
|
bandit, lower-bound, information |
Lai-Robbins and Fano-based lower bounds |
|
bandit, simulation, experiment |
How to run and interpret bandit simulations |
Installing Seed Skills#
eurekaclaw install-skills # copy seeds to ~/.eurekaclaw/skills/
eurekaclaw install-skills --force # overwrite existing copies
eurekaclaw install-skills <skillname> # install a skill from ClawHub
The <skillname> form fetches a skill from the ClawHub registry using the clawhub CLI (must be installed separately). Example:
eurekaclaw install-skills steipete/github
This command is a convenience for inspection and manual editing only. Agents do not require it — seed skills are always available directly from the package.
Skill Lifecycle#
Understanding where skills physically live prevents confusion about why ~/.eurekaclaw/skills/ may not contain every skill an agent can see.
Three storage locations#
Location |
Who writes there |
When |
|---|---|---|
|
Package developers (you) |
Committed to the repo; bundled with |
|
Domain plugin authors |
Registered via |
|
|
On demand; user-editable |
Load order at runtime#
Every time SkillRegistry._load() runs, it reads all three sources in this order. A skill with the same name in a later source overrides the earlier one:
1. eurekaclaw/skills/seed_skills/**/*.md (lowest priority)
2. domain plugin skill dirs (extra_dirs) (medium priority)
3. ~/.eurekaclaw/skills/**/*.md (highest priority — overrides seeds)
Consequence: adding a file to seed_skills/ makes it immediately visible to agents on the next run, without copying anything to ~/.eurekaclaw/skills/. The absence of a seed skill from ~/.eurekaclaw/skills/ does not reduce agent capability.
How new skills are generated#
Skills enter the system through three paths:
2. LLM distillation (automatic, post-run)#
After each successful session, SkillEvolver.distill_from_session() is called with up to 5 FailedAttempt records and 5 ProofRecord successes from the session. It calls the fast model with a distillation prompt and parses the response into a new SkillRecord.
The new skill is written to ~/.eurekaclaw/skills/<name>.md via SkillRegistry.upsert() with:
source: distilledname: distilled_<session_id[:8]>_<random_hex>Tags, roles, and stages extracted from the LLM response
It is immediately available to subsequent sessions without any restart.
Session completes
└── SkillEvolver.distill_from_session(failures, successes)
└── LLM call (fast model, max_tokens=1024)
└── _parse_skill_response()
└── SkillRegistry.upsert() → ~/.eurekaclaw/skills/<name>.md
3. ClawHub skills#
eurekaclaw install-skills <author>/<skillname>
Downloads the skill from the ClawHub registry via the clawhub CLI and places it in ~/.eurekaclaw/skills/. Requires clawhub to be installed (pip install clawhub or equivalent).
4. Manual user skills#
Place any .md file with valid YAML frontmatter directly in ~/.eurekaclaw/skills/. It will be loaded on the next session. Use source: manual in the frontmatter to distinguish from distilled skills.
Skill stats update#
After each session, SkillRegistry.update_stats(name, success) rewrites the skill file with updated usage_count and success_rate (exponential moving average, α=0.3). This only affects skills that already exist in ~/.eurekaclaw/skills/ — seed skills in the package are never modified on disk by running sessions.
Why ~/.eurekaclaw/skills/ may look empty#
A fresh installation with no sessions run yet will have an empty ~/.eurekaclaw/skills/ directory. This is normal. The agents are not “missing” any skills — they read seed skills and domain plugin skills directly at runtime. ~/.eurekaclaw/skills/ fills up over time through:
eurekaclaw install-skills(one-time copy for inspection/editing)Completed sessions (automatic distillation)
Manual placement of custom
.mdfiles