Skip to content

Skills

Skills are the core behavioural unit of an agent. Each skill carries metadata (name, description, tags, schema, examples) and an optional async handler. Skills appear on the agent’s discovery card and can optionally be invoked by the LLM via a read_skill tool.

agent-core is a default feature — no extra flags needed:

[dependencies]
agent_sdk = { package = "pf_agent_sdk", path = "../../crates/agent_sdk" }
  1. Create the agent

    let mut agent = Agent::new("my-agent")?;
  2. Register skills via the fluent builder

    Use agent.add_skill("id") for metadata-only skills or agent.skill("id", handler) to attach a handler in one call. Finalize every skill with .register().

    use serde_json::{json, Value};
    // Metadata-only skill (no handler — appears on the card)
    agent
    .add_skill("summarize")
    .display_name("Summarize")
    .description("Condense long documents into key points")
    .tags(&["nlp", "summarization"])
    .register()?;
    // Skill with handler
    agent
    .skill("lookup", |params: Value| async move {
    let query = params["query"].as_str().unwrap_or("");
    Ok(json!({ "results": [format!("Result for '{query}'")] }))
    })
    .description("Search the knowledge base")
    .schema(json!({
    "type": "object",
    "properties": {
    "query": { "type": "string" }
    },
    "required": ["query"]
    }))
    .examples(vec!["Look up Rust async patterns".into()])
    .register()?;
  3. Wire the agent to a protocol surface

    Skills are protocol-independent. Protocol adapters (A2A, MCP) read SkillDefinition values and convert them. See A2A Serving for the next step.

use agent_sdk::Agent;
use serde_json::{json, Value};
fn main() -> agent_sdk::SdkResult<()> {
let mut agent = Agent::new("skill-demo")?;
// 1. Simple echo skill with handler
agent
.skill("echo", |params: Value| async move {
Ok(json!({ "echoed": params }))
})
.description("Echoes back the input as JSON")
.tags(&["utility", "debug"])
.register()?;
// 2. Skill with a JSON schema and LLM invocability
agent
.skill("weather", |params: Value| async move {
let city = params["city"].as_str().unwrap_or("unknown");
Ok(json!({ "city": city, "temp_c": 22, "condition": "sunny" }))
})
.display_name("Get Weather")
.description("Returns current weather for a city")
.schema(json!({
"type": "object",
"properties": {
"city": { "type": "string", "description": "City name" }
},
"required": ["city"]
}))
.example("What's the weather in Berlin?")
.llm_callable(true)
.register()?;
// 3. Internal-only skill (hidden from discovery card)
agent
.skill("health_check", |_params: Value| async move {
Ok(json!({ "status": "ok" }))
})
.description("Internal liveness check")
.expose(false)
.register()?;
println!("Registered skills: {:?}", agent.list_skills());
Ok(())
}
MethodDescriptionDefault
.handler(fn)Attach an async handler (Fn(Value) → Future<Result<Value, String>>)None (metadata-only)
.display_name(s)Human-readable nameSkill ID
.description(s)Short descriptionAuto-generated
.schema(json)JSON Schema for input parametersNone
.examples(vec)Example promptsNone
.example(s)Append a single example
.tags(&[..])Categorisation tagsNone
.tag(s)Append a single tag
.input_modes(&[..])Accepted MIME types["application/json", "text/plain"]
.output_modes(&[..])Produced MIME types["application/json", "text/plain"]
.json_only()Shorthand: set both modes to application/json
.text_only()Shorthand: set both modes to text/plain
.instructions(s)Behavioural guidance injected into LLM contextNone
.expose(bool)Visible on the discovery cardtrue
.llm_callable(bool)LLM can invoke via read_skill toolfalse
.register()Finalize and register — must be called
TypeModulePurpose
SkillEntryBuilderagent_sdk::agent::skillFluent builder returned by Agent::add_skill / Agent::skill
SkillDefinitionagent_sdk::agent::skillProtocol-independent skill metadata
SkillContextagent_sdk::agent::skillResolved skill state for LLM message injection
SkillRegistryagent_sdk::agent::skillInternal registry managing handlers and definitions
SkillHandleragent_sdk::agent::skillArc<dyn Fn(Value) → Pin<Box<dyn Future<Output = Result<Value, String>>>>>
SkillErroragent_sdk::agent::skillNotImplemented or ExecutionFailed