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:
opts.skill-resolver(when non-null) wins outright.- Otherwise
opts.skillsis materialized viafor-agentand wrapped inin-memory-resolver. - When neither is set, returns
null—run-loopskips 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:
- If
skill.body-fnis non-null, call it withctxand return the resulting string. - 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 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 withread_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 byread_skill. May be loaded from a resource via::hot::resource/load-str.body-fn— alternative tobody: a function(ctx: Map) -> Strthat 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-inread_skill/apply_skillresolvebodyorbody-fnrather 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 theVec<Skill>to advertise this turn.ctxis a map carrying the turn'smessages,systemprompt, andmodel; 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 theread_skillandapply_skillbuilt-in tools when the model asks for a specific skill body. Should returnnullfor unknown names. Note this is decoupled fromlist-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-loopadds a fourthsearch_skillstool 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)— likein-memory-resolverbut with a substring-ranksearch-fnover name/description/when. Pair with a small or emptylist-fnoutput 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.