pub struct SkillRegistry { /* private fields */ }Expand description
Skill registry: holds a collection of skills, responsible for lookup and disclosure by name.
Internally an ordered name → skill map guarded by a read-write lock
(read-heavy, O(1) lookup by name, registration order preserved);
add / remove take &self, so the application can hold the registry
handle and hot-swap — add/remove does not depend on
construction-time ordering, and the next read (menu / lookup) takes
effect immediately.
Cloning is a deep copy: each registry holds its own independent skill collection, so add/remove do not affect each other.
§Panics
If the internal read-write lock gets poisoned (a method panics while still holding it), subsequent calls panic. Normal operation (no public method enters a panic path) does not trigger this.
§Example
use molo::skill::{Skill, SkillRegistry};
let registry = SkillRegistry::new();
let skill = Skill::parse("---\nname: greet\ndescription: Say hello\n---\nHello!")?;
registry.add(skill);
assert_eq!(registry.menu(), "- greet: Say hello");
assert_eq!(
registry.get("greet").map(|skill| skill.body().to_string()),
Some("Hello!".to_string())
);
// re-registering the same name: replaces the original skill, position
// unchanged
let v2 = Skill::parse("---\nname: greet\ndescription: Say hello\n---\nGood morning!")?;
registry.add(v2);
assert_eq!(
registry.get("greet").map(|skill| skill.body().to_string()),
Some("Good morning!".to_string())
);
assert_eq!(registry.skills().len(), 1);Implementations§
Source§impl SkillRegistry
impl SkillRegistry
Sourcepub fn new() -> SkillRegistry
pub fn new() -> SkillRegistry
Create an empty registry.
Sourcepub fn add(&self, skill: Skill) -> &SkillRegistry
pub fn add(&self, skill: Skill) -> &SkillRegistry
Register a skill; a same-named skill replaces the original
(position unchanged), returning self for chaining.
Registration is an explicit operation: a skill must first pass
validation via Skill::parse / Skill::from_dir; invalid
skills cannot enter the registry.
Sourcepub fn remove(&self, name: &str) -> bool
pub fn remove(&self, name: &str) -> bool
Remove a skill (by name); returns true when removed, false
when the skill does not exist.
This is the developer’s physical management interface (upgrading /
retiring skills); session-level “invisibility” filtering uses
SkillLayer::with_enabled_skills, and metadata can stay in the
registry.
Sourcepub fn get(&self, name: &str) -> Option<Skill>
pub fn get(&self, name: &str) -> Option<Skill>
Get a skill by name (cloned, so the lock-held reference does
not escape; O(1) lookup by name); returns None when missing.
Sourcepub async fn from_dir(path: &Path) -> Result<SkillRegistry, SkillError>
pub async fn from_dir(path: &Path) -> Result<SkillRegistry, SkillError>
Scan a directory and discover all skill directories within it
(reading SKILL.md from each subdirectory).
Lenient discovery: bad skills (parse failure / directory name
mismatch / no SKILL.md) are skipped with the reason logged via
tracing::warn, without taking down the whole registry; only an
unreadable root directory itself returns an error.
§Errors
An unreadable root directory → SkillError::Io.
Sourcepub async fn from_dirs<P>(paths: &[P]) -> SkillRegistry
pub async fn from_dirs<P>(paths: &[P]) -> SkillRegistry
Discover skills from multiple directories and merge (multi-source loading).
Typical scenario: user-level and project-level skill directories as two sources (concrete paths are the caller’s decision; this library does not hardcode directory conventions). Directories are scanned in argument order, and later-loaded skills with the same name override earlier ones (argument order is priority: put the project level last so it overrides the user level).
Lenient discovery: missing directories, unreadable directories
and bad skills are all skipped with the reason logged via
tracing::warn, without taking down other sources; if all sources
are unusable, an empty registry is returned.
§Example
use molo::skill::SkillRegistry;
// neither source exists: skipped leniently, resulting in an empty
// registry
let skills =
SkillRegistry::from_dirs(&["molo-nonexistent-a", "molo-nonexistent-b"]).await;
assert!(skills.skills().is_empty());Disclosure block: one line - {name}: {description} per skill, in
registration order.
This is the first step of progressive disclosure — the model uses it to decide whether to load a skill by name; one line per skill keeps the resident system-prompt cost fixed and negligible.
Trait Implementations§
Source§impl Clone for SkillRegistry
impl Clone for SkillRegistry
Source§fn clone(&self) -> SkillRegistry
fn clone(&self) -> SkillRegistry
std RwLock has no Clone: copy the contents under the lock and rebuild.
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read moreSource§impl Debug for SkillRegistry
impl Debug for SkillRegistry
Source§impl Default for SkillRegistry
impl Default for SkillRegistry
Source§fn default() -> SkillRegistry
fn default() -> SkillRegistry
Source§impl Extend<Skill> for SkillRegistry
impl Extend<Skill> for SkillRegistry
Source§fn extend<I>(&mut self, iter: I)where
I: IntoIterator<Item = Skill>,
fn extend<I>(&mut self, iter: I)where
I: IntoIterator<Item = Skill>,
Source§fn extend_one(&mut self, item: T)
fn extend_one(&mut self, item: T)
extend_one)Source§fn extend_reserve(&mut self, additional: usize)
fn extend_reserve(&mut self, additional: usize)
extend_one)