use molo::agent::{Agent, ReActAgent};
use molo::provider::{FakeProvider, FakeReply};
use molo::skill::{Skill, SkillRegistry};
use molo::{Message, ToolCall, ToolRegistry};
use std::sync::Arc;
#[derive(Clone)]
struct SharedFake(Arc<FakeProvider>);
impl SharedFake {
fn requests(&self) -> Vec<molo::ChatRequest> {
self.0.requests()
}
}
#[molo::async_trait]
impl molo::Provider for SharedFake {
async fn chat(
&self,
request: molo::ChatRequest,
) -> Result<molo::ChatResponse, molo::ProviderError> {
self.0.chat(request).await
}
async fn stream_chat(
&self,
request: molo::ChatRequest,
) -> Result<
futures::stream::BoxStream<'static, Result<molo::StreamEvent, molo::ProviderError>>,
molo::ProviderError,
> {
self.0.stream_chat(request).await
}
}
const CODE_REVIEW_SKILL: &str = r#"---
name: code-review
description: Review code changes against team conventions and output a list of issues sorted by severity
allowed-tools:
- Bash(git:*)
---
Review process:
1. Read the scope of the changes and the diff;
2. Check each file for issues: correctness / naming / documentation;
3. Output the list of issues sorted by severity.
"#;
const RELEASE_NOTES_SKILL: &str = r#"---
name: release-notes
description: Generate release notes from the commit history
---
Rules:
- Group entries into "New features / Fixes / Maintenance";
- One short line per entry, no details.
"#;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
println!(
"== Path 1: progressive disclosure (menu always present + load_skill reads on demand) =="
);
let registry = SkillRegistry::new();
registry.add(Skill::parse(CODE_REVIEW_SKILL)?);
registry.add(Skill::parse(RELEASE_NOTES_SKILL)?);
let fake = SharedFake(Arc::new(FakeProvider::new([
FakeReply::ToolCalls {
content: "the task matches the code-review skill, load it first".into(),
calls: vec![ToolCall {
id: "c1".into(),
name: "load_skill".into(),
arguments: r#"{"name":"code-review"}"#.into(),
}],
},
FakeReply::Text("Reviewed per the skill process; found 2 issues.".into()),
])));
let mut agent = ReActAgent::new(
fake.clone(),
ToolRegistry::new(),
"You are a code review assistant",
)
.with_skills(registry);
let answer = agent.run("review the changes under docs/").await?;
println!("answer: {answer}");
let requests = fake.requests();
if let Message::System(system) = &requests[0].messages[0] {
println!("-- skill menu in the first round's system prompt:");
for line in system.lines().filter(|l| l.starts_with("- ")) {
println!(" {line}");
}
}
for message in &requests[1].messages {
if let Message::ToolResult { content, .. } = message {
println!("-- skill body returned by load_skill (excerpt):");
for line in content.lines().take(2) {
println!(" {line}");
}
}
}
println!();
println!(
"== Path 2: explicit user activation (activate_skill; the body is not decided by the model) =="
);
let registry = SkillRegistry::new();
registry.add(Skill::parse(RELEASE_NOTES_SKILL)?);
let fake = SharedFake(Arc::new(FakeProvider::new([FakeReply::Text(
"OK, generated the release notes per the rules.".into(),
)])));
let mut agent = ReActAgent::new(
fake.clone(),
ToolRegistry::new(),
"You are a release assistant",
)
.with_skills(registry);
assert!(agent.activate_skill("release-notes"));
let answer = agent.run("generate the release notes").await?;
println!("answer: {answer}");
if let Message::System(system) = &fake.requests()[0].messages[0] {
println!("-- system prompt contains the skill body directly (pre-activated section):");
for line in system.lines().filter(|l| l.starts_with("[")) {
println!(" {line}");
}
}
println!();
println!(
"== Path 3: multi-source directory discovery (from_dirs, user directory + project directory merged) =="
);
let dir = std::env::temp_dir().join(format!("molo-skill-demo-{}", std::process::id()));
let user_dir = dir.join("user-skills");
let project_dir = dir.join("project-skills");
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(user_dir.join("code-review")).unwrap();
std::fs::write(user_dir.join("code-review/SKILL.md"), CODE_REVIEW_SKILL).unwrap();
std::fs::create_dir_all(user_dir.join("translate")).unwrap();
std::fs::write(
user_dir.join("translate/SKILL.md"),
"---\nname: translate\ndescription: Translate text\n---\nRules: keep the original meaning and formatting.",
)
.unwrap();
std::fs::create_dir_all(project_dir.join("code-review")).unwrap();
std::fs::write(
project_dir.join("code-review/SKILL.md"),
"---\nname: code-review\ndescription: Review code changes against team conventions\n---\nProject-specific review steps.",
)
.unwrap();
std::fs::create_dir_all(project_dir.join("bad-skill")).unwrap();
std::fs::write(
project_dir.join("bad-skill/SKILL.md"),
"---\nname: BadSkill\ndescription: Invalid skill name\n---\n",
)
.unwrap();
let discovered = SkillRegistry::from_dirs(&[user_dir, project_dir]).await;
println!(
"discovered {} skills (same-name merged, broken skills skipped):",
discovered.skills().len()
);
for skill in discovered.skills() {
println!(" - {}: {}", skill.name(), skill.description());
}
let code_review = discovered
.get("code-review")
.expect("code-review must exist");
println!("-- code-review body comes from the project level (overriding the user level):");
for line in code_review.body().lines().take(2) {
println!(" {line}");
}
let _ = std::fs::remove_dir_all(&dir);
Ok(())
}