12 KiB
Didactyl — Skills
See also: CONTEXT.md · TOOLS.md
Overview
A skill is a set of instructions for the LLM stored as a Nostr event. Skills teach the agent how to accomplish tasks — the LLM reads the instructions, reasons about them, and uses tools to take action.
Think of it like a woodshop: a skill is knowing how to carve — the technique, the judgment, the decision-making. A tool is the chisel. The skill never directly uses the chisel without the craftsperson (the LLM) in the loop. If you want a hardcoded program that runs without reasoning, that's a tool or an external program — not a skill.
Skills are portable, shareable, and discoverable — they live on Nostr relays as standard events. Every skill execution involves the LLM.
Skill Events
| Kind | Purpose | Replaceable? |
|---|---|---|
31123 |
Public skill definition | Yes, by d-tag |
31124 |
Private skill definition | Yes, by d-tag |
10123 |
Skill adoption list | Yes, single per pubkey |
Skill Content
The skill event's content field is a JSON object that defines the complete execution specification:
{
"kind": 31123,
"content": {
"description": "Check spelling and grammar",
"context_mode": "full",
"llm": "openai/gpt-4o-mini, cheap",
"tools": false,
"max_tokens": 2000,
"temperature": 0.1,
"template": "system:\nYou are a spelling and grammar checker.\n\nRules:\n- Fix spelling errors\n- Fix grammar errors\n- Preserve original formatting\n- Ignore: API, JSON, HTTP, nostr, pubkey, npub, nsec, NIP\n- Canadian English preferred\n- Return ONLY the corrected text, no explanations\n\nuser:\n{{message}}"
},
"tags": [
["d", "spellcheck"],
["scope", "public"],
["description", "Spelling and grammar checker"]
]
}
Content Fields
| Field | Type | Default | Description |
|---|---|---|---|
description |
string | — | Human-readable description |
context_mode |
string | inject |
inject, full, or override |
llm |
string | default |
LLM fallback chain |
tools |
bool/array | true |
false = no tools, true = all tools, array = specific tool names |
template |
string | — | Context template (required for full mode) |
soul |
bool | true |
Whether to include the agent's soul in context |
max_tokens |
int | — | Override max tokens for this skill |
temperature |
float | — | Override temperature for this skill |
Context Modes
Skills control how the LLM context window is assembled using the context_mode field:
| Mode | Description | Use Case |
|---|---|---|
inject |
Skill instructions appended to the agent's default context | Behavioral rules, knowledge additions |
full |
Skill provides its own complete context template | Spellcheck, translation, focused analysis |
override |
Skill replaces the default system prompt, keeps standard context structure | Different personality, same capabilities |
Default is inject. See the agent's soul/personality documentation for details on how context assembly works.
Template Variables Are Tool Calls
Template variables are tool calls. When the template engine encounters a variable like {{admin_profile}}, it runs the corresponding tool and inserts the result into the context that gets sent to the LLM.
This is the unified resolution model: the soul prompt template uses tool: section directives, and skill templates use {{tool_name}} inline syntax, but both resolve through tools_execute(). There is no separate variable-resolver layer — every variable is backed by a tool.
| Variable | Tool Called | Description |
|---|---|---|
{{soul}} |
(built-in) | The agent's identity/system context |
{{admin_profile}} |
nostr_admin_profile |
Admin kind 0 profile |
{{admin_notes}} |
nostr_admin_notes |
Admin recent notes |
{{admin_relays}} |
nostr_admin_relays |
Admin relay list |
{{adopted_skills}} |
adopted_skills |
Other adopted skill instructions |
{{dm_history}} |
(expand directive) | Recent DM conversation |
{{message}} |
(built-in) | Current user message |
{{triggering_event}} |
trigger_event |
For triggered skills, the event JSON |
This means new data sources are added by adding tools, not by extending the template engine. If you can call it as a tool, you can use it as a template variable.
LLM Specification with Fallback Chains
Each skill specifies which LLM to use with CSS font-family-style fallbacks. The runtime tries each model in order; if one is unavailable (API error, rate limit, not configured), it falls back to the next.
Format
model-spec := model-ref ["," model-ref]*
model-ref := provider "/" model-name
| category-alias
Examples
| Skill | LLM Spec |
|---|---|
| Spelling checker | openai/gpt-4o-mini, cheap |
| Complex analysis | anthropic/claude-sonnet-4-20250514, openai/gpt-4o, smart |
| Translation | openai/gpt-4o-mini, fast |
| Default behavior | default |
Category Aliases
Aliases map to models in the agent's config:
{
"llm": {
"provider": "openai",
"model": "gpt-4o",
"aliases": {
"fast": "openai/gpt-4o-mini",
"cheap": "openai/gpt-4o-mini",
"smart": "anthropic/claude-sonnet-4-20250514"
}
}
}
If all specified models fail, the agent's default model is used as a last resort.
sequenceDiagram
participant Skill as Skill Spec
participant Resolver as LLM Resolver
participant API1 as Model 1
participant API2 as Model 2
participant Default as Default Model
Skill->>Resolver: "anthropic/claude-sonnet, openai/gpt-4o-mini, cheap"
Resolver->>API1: try anthropic/claude-sonnet
alt Available
API1-->>Resolver: ✅
else Unavailable
API1-->>Resolver: ❌
Resolver->>API2: try openai/gpt-4o-mini
alt Available
API2-->>Resolver: ✅
else Unavailable
Resolver->>Default: resolve "cheap" alias
end
end
Triggered Skills
A triggered skill has a trigger source attached. Didactyl supports nostr-subscription, webhook, cron, and chain trigger types.
Trigger Tags
| Tag | Required | Description |
|---|---|---|
trigger |
Yes | Trigger type: nostr-subscription, webhook, cron, or chain |
filter |
Yes | Type-specific filter — see Trigger Types below |
enabled |
No | Whether active (default: true) |
Note: The
actiontag is deprecated. All triggered skills are LLM-mediated. If you need a fast hardcoded action without LLM reasoning, use a tool or an external program — not a skill. Skills are instructions for the LLM; the LLM is always in the loop.
Triggered Skill Execution
When a trigger fires, the skill content is sent to the LLM as instructions along with the triggering event JSON. The LLM reads both, reasons about what to do, and uses tools to take action.
This is the same execution model as DM-invoked skills — the only difference is the input source. A DM-invoked skill receives a user message; a triggered skill receives a triggering event.
{
"kind": 31124,
"content": "You monitor Nostr mentions. When the triggering event mentions Bitcoin or Lightning, summarize it and DM the admin. Otherwise, ignore silently.",
"tags": [
["d", "mention-monitor"],
["trigger", "nostr-subscription"],
["filter", "{\"#p\":[\"<admin_pubkey>\"],\"kinds\":[1]}"],
["enabled", "true"]
]
}
A cron-triggered skill:
{
"kind": 31124,
"content": "DM the admin a short heartbeat status message with the current time.",
"tags": [
["d", "heartbeat"],
["trigger", "cron"],
["filter", "*/5 * * * *"],
["enabled", "true"]
]
}
Trigger Types
nostr-subscription
filter is a JSON-encoded Nostr subscription filter.
["trigger", "nostr-subscription"],
["filter", "{\"#p\":[\"<admin_pubkey>\"],\"kinds\":[1]}"]
webhook
filter is required for schema compatibility and can be a simple placeholder like {}; webhook firing happens via HTTP.
["trigger", "webhook"],
["filter", "{}"]
cron
filter is a standard 5-field cron expression: minute hour day-of-month month day-of-week.
["trigger", "cron"],
["filter", "0 * * * *"]
chain
filter is the source skill d tag to chain from.
["trigger", "chain"],
["filter", "source-skill-d-tag"]
Trigger Lifecycle
flowchart TD
subgraph Creation
ADMIN_CMD[Admin: 'Warn me when @jack posts'] --> LLM_REASON[LLM resolves pubkey + builds skill]
LLM_REASON --> SKILL_CREATE[skill_create with trigger tags]
SKILL_CREATE --> PUBLISHED[Skill published to Nostr]
end
subgraph Activation
STARTUP[Didactyl starts up] --> LOAD_SKILLS[Load adopted skills from kind 10123]
LOAD_SKILLS --> FIND_TRIGGERS[Find skills with trigger tags]
FIND_TRIGGERS --> REGISTER[Register trigger by type]
REGISTER --> NOSTR_SUB[nostr-subscription: create Nostr subscriptions]
REGISTER --> CRON_REG[cron: keep expression for poll loop]
REGISTER --> WEBHOOK_REG[webhook: route via /api/trigger/:d_tag]
REGISTER --> CHAIN_REG[chain: wait for source skill completion]
end
subgraph Execution
EVENT_IN[Matching Nostr event] --> LOOKUP[Find associated skill]
WEBHOOK_IN[POST /api/trigger/:d_tag] --> LOOKUP
CRON_TICK[cron poll match] --> LOOKUP
LOOKUP --> RESOLVE[Resolve LLM + assemble context + run with tools]
RESOLVE --> CHAIN_CHECK[Check chain triggers]
CHAIN_CHECK --> CHAIN_FIRE[Fire matching chain triggers]
end
PUBLISHED --> LOAD_SKILLS
Execution Flow
sequenceDiagram
participant Input as Message/Trigger
participant Dispatch as Dispatcher
participant Skill as Skill Resolver
participant LLM_Res as LLM Resolver
participant Ctx as Context Assembler
participant LLM as LLM API
Input->>Dispatch: message or trigger event
Dispatch->>Skill: which skill handles this?
alt No specific skill
Skill-->>Dispatch: use default soul context
Dispatch->>LLM_Res: resolve "default" LLM
else Skill with context_mode=inject
Skill-->>Dispatch: soul + skill instructions
Dispatch->>LLM_Res: resolve skill.llm or "default"
else Skill with context_mode=full
Skill-->>Dispatch: skill template only
Dispatch->>LLM_Res: resolve skill.llm
end
LLM_Res->>LLM_Res: try models in fallback order
LLM_Res-->>Dispatch: resolved model
Dispatch->>Ctx: assemble context per mode
Ctx->>LLM: request with resolved model + context
LLM-->>Input: response
Limits and Safety
| Limit | Default | Description |
|---|---|---|
| Max concurrent triggers | 16 | Prevents resource exhaustion |
| Trigger cooldown | 60s per skill | Prevents rapid-fire execution |
| LLM action rate limit | 10/min | Prevents runaway LLM costs |
Storage on Nostr
| Data | Storage |
|---|---|
| Skills | Kind 31123/31124 events |
| Adopted skills | Kind 10123 event |
| Trigger definitions | Tags on skill events |
Future Extensions
| Extension | Description |
|---|---|
| Skill composition | Pipeline multiple skills |
| Agent-to-agent sharing | Discover and adopt skills across agents |
| Trigger marketplace | Popular triggers rise via adoption count |