Light Dark

Functions

build-index

fn (skills: Vec): Str

Build a system-prompt fragment that lists every skill available to the agent, formatted so an LLM can decide which skills to read next. Prepended with a short header describing the read_skill contract.

Example

index ::ai::skill/build-index([tone, escalation])
// => \"\"\"
//    Available skills you can read with read_skill(name):
//      - customer-tone: Apply our voice (when: reply, marketing)
//      - escalation: Decide when to escalate (when: refund, angry)
//    \"\"\"

builtin-apply-skill

fn (skills: Vec): ::ai::tool/Tool

Construct the apply_skill built-in tool over the given skill set. Like read_skill but lets the model pass a ctx map that reaches the skill's body-fn (when present).

builtin-apply-skill-r

fn (resolver: SkillResolver): ::ai::tool/Tool

Resolver-aware variant of builtin-apply-skill. Like builtin-read-skill-r but lets the model pass a ctx map that reaches the skill's body-fn (when present).

builtin-list-skills

fn (skills: Vec): ::ai::tool/Tool

Construct the list_skills built-in tool over the given skill set. Used internally by builtins; exposed for callers that want to wire individual built-ins by hand.

builtin-list-skills-r

fn (resolver: SkillResolver): ::ai::tool/Tool

Resolver-aware variant of builtin-list-skills. The bound list_skills tool consults the resolver's list-fn on every invocation, so resolvers that change their output over time (e.g. a hybrid resolver that re-ranks after new context arrives) stay in sync with what the model sees.

builtin-read-skill

fn (skills: Vec): ::ai::tool/Tool

Construct the read_skill built-in tool over the given skill set. Returns the resolved body for the named skill, or an error string when the name is unknown.

builtin-read-skill-r

fn (resolver: SkillResolver): ::ai::tool/Tool

Resolver-aware variant of builtin-read-skill. Routes name lookups through resolver.read-fn, so a store-backed resolver can serve skills it never surfaced in the index.

builtin-search-skills-r

fn (resolver: SkillResolver): ::ai::tool/Tool

Built-in search_skills tool. Only constructed when the resolver declares a search-fn; built around resolver.search-fn(query, opts) so any backing strategy (substring, embeddings, vendor APIs) works without changes here.

builtins

fn (skills: Vec): Vec<::ai::tool/Tool>

Return the three built-in tools (list_skills, read_skill, apply_skill) bound over skills. Concatenated into the agent's tools list by ::ai::chat/run-loop whenever the agent declares one or more skills.

Equivalent to builtins-with-resolver(in-memory-resolver(skills)) minus the optional search_skills tool. Kept for backwards compatibility with callers that wire skill toolsets directly.

builtins-with-resolver

fn (resolver: SkillResolver): Vec<::ai::tool/Tool>

Build the per-turn skill toolset from a SkillResolver. Always emits list_skills, read_skill, and apply_skill; appends a fourth search_skills tool when the resolver provides a search-fn. Used by ::ai::chat/run-loop whenever an agent declares either opts.skills or opts.skill-resolver.

effective-resolver

fn (skill-resolver: SkillResolver?, skills: Vec?): SkillResolver?

Resolve which SkillResolver ::ai::chat/run-loop should use on a given call. Precedence:

  1. opts.skill-resolver (when non-null) wins outright.
  2. Otherwise opts.skills is materialized via for-agent and wrapped in in-memory-resolver.
  3. When neither is set, returns nullrun-loop skips the skill index and skill builtins entirely.

find-skill

fn (skills: Vec, name: Str): Skill?

Look up a skill by name, returning null when no match.

for-agent

fn (entries: Vec): Vec

Normalize an agent's skills: list (a mix of Hot function refs and pre-built Skill records) into a Vec<Skill> ready for ::ai::chat/run-loop. Per the design, agents must list every skill function explicitly — there is no namespace glob.

Example

skills ::ai::skill/for-agent([customer-tone, escalation-policy])

from-fn

fn (f: Fn): Skill

Build a Skill from a Hot function annotated with meta {skill: {...}}. The annotation map is merged with sensible defaults: name falls back to the qualified function name, when defaults to [].

Example

tone
meta {skill: {description: "Customer tone", when: ["reply"], body: "..."}}
fn () { {} }

skill ::ai::skill/from-fn(tone)
// => Skill({name: "::myapp/tone", description: "Customer tone", ...})

in-memory-resolver

fn (skills: Vec): SkillResolver

Default SkillResolver: list-fn returns the full skills vector every turn, read-fn does an in-memory name lookup, and search-fn is null. Mirrors the pre-resolver behaviour of ::ai::chat/run-loop exactly, so wrapping opts.skills in this resolver is a no-op for the model.

index-line

fn (skill: Skill): Str

Render a single skill as one line in the resolver index, e.g. " - customer-tone: Apply our voice (when: reply, marketing)". Trigger phrases are comma-joined; missing fields are omitted.

list-skills-of

fn (skills: Vec): Vec

Build the body for a list_skills tool call: a vector of {name, description, when} maps suitable for an LLM to scan.

load-body

fn (skill: Skill, ctx: Map): Str

Resolve the body of a Skill for a given invocation context.

Resolution order:

  1. If skill.body-fn is non-null, call it with ctx and return the resulting string.
  2. Otherwise return skill.body (or "" if absent).

Errors from body-fn propagate as Result.Err.

Example

text ::ai::skill/load-body(skill, {topic: "refund"})

ns alias

Alias of ::ai::skill/

Skills are LLM-judgment-routed prompt augmentations: named bundles of instructions (and optionally bundled tools) that an agent can pull into its context on demand. Prompt-only skills can be built directly as Skill({...}) records. Executable or function-backed skills can also be declared by adding meta {skill: {...}} to a Hot function; ::ai::skill/from-fn uses the compiler-harvested metadata to build the same Skill shape at runtime.

Three built-in tools (list_skills, read_skill, apply_skill) are auto-added by ::ai::chat/run-loop so the model can discover and pull skills on demand without exhausting context up front.

Example

customer-tone
meta {skill: {
    description: "Apply our customer voice rules",
    when: ["customer reply", "marketing copy", "support email"],
    body: \"\"\"
    Use a warm, plain-language tone. Avoid jargon. Always close
    with a clear next step.
    \"\"\"
}}
fn () { {} }

generated-tone Skill({
    name: "generated-tone",
    description: "A prompt-only generated skill",
    when: ["generated guidance"],
    body: "Use this body directly."
})

search-skills-of

fn (skills: Vec, query: Str, opts: Map): Vec

Rank skills against a free-text query using a lightweight case-insensitive substring scorer. opts.limit caps the result (default 10); opts.min-score filters out weak hits (default 1). Returns the highest-scoring Skill records, ties broken by input order.

searchable-resolver

fn (skills: Vec, opts: Map): SkillResolver

SkillResolver over a fixed Vec<Skill> that also exposes a substring-rank search-fn. run-loop will surface a fourth search_skills tool so the model can query for relevant skills on demand.

opts.always-on (default []) are skills returned from list-fn every turn regardless of search; the rest of skills are reachable only via search_skills and read_skill. This keeps the system-prompt index small while still letting the model find the long tail.

Types

Skill

Skill type {
    name: Str,
    description: Str,
    when: Vec?,
    body: Str?,
    body-fn: Fn?,
    tools: Vec<::ai::tool/Tool>?,
    requires: Vec?,
    fn: Fn?
}

A reusable prompt augmentation. Most fields are optional:

  • name — unique identifier the model uses with read_skill / apply_skill. Defaults to the qualified Hot function name.
  • description — short summary the model uses to decide when to pull the skill. Required for any useful skill.
  • when — trigger phrases that hint when this skill applies. Joined into the resolver index injected into the system prompt.
  • body — inline instructions returned by read_skill. May be loaded from a resource via ::hot::resource/load-str.
  • body-fn — alternative to body: a function (ctx: Map) -> Str that produces context-aware instructions.
  • tools — bundled tools that become available to the model whenever the skill is read.
  • requires — names of other skills this one depends on.
  • fn — back-reference to the Hot function the skill was harvested from. Preserved for callers that need to round-trip to the source function; built-in read_skill / apply_skill resolve body or body-fn rather than calling this function.

SkillResolver

SkillResolver type {
    list-fn: Fn,
    read-fn: Fn,
    search-fn: Fn?
}

Pluggable strategy for picking which skills are visible to the model on a given turn. ::ai::chat/run-loop calls into a resolver at three fixed points:

  • list-fn(ctx) — once per turn, when building the system-prompt skill index. Returns the Vec<Skill> to advertise this turn. ctx is a map carrying the turn's messages, system prompt, and model; resolvers may inspect any of these to decide what to surface (e.g. embed the last user message and rank a store).
  • read-fn(name) — invoked from the read_skill and apply_skill built-in tools when the model asks for a specific skill body. Should return null for unknown names. Note this is decoupled from list-fn: a store-backed resolver may know about more skills than it surfaces in any single turn.
  • search-fn(query, opts) — optional. When set, run-loop adds a fourth search_skills tool that lets the model query the resolver directly. Useful when the index is too large to inline every turn.

Built-in factories:

  • in-memory-resolver(skills) — current behaviour, no search.
  • searchable-resolver(skills) — like in-memory-resolver but with a substring-rank search-fn over name/description/when. Pair with a small or empty list-fn output to scale to many skills without inflating the system prompt.

Custom resolvers (Postgres, Pinecone, LLM-judged routing, etc.) are constructed by passing your own Fn slots to SkillResolver({...}) directly.