12 KiB
Didactyl — Skills
See also: TOOLS.md
Overview
A skill is a self-contained LLM execution unit stored as a Nostr event. Each skill defines what the LLM sees (context template), which model runs it (LLM specification with fallbacks), what tools are available, and whether the agent's identity (soul) is included.
Skills are portable, shareable, and discoverable — they live on Nostr relays as standard events.
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.
inject
The skill's instructions are appended to the agent's soul context. The soul is always present.
┌─────────────────────────────────────┐
│ Soul (agent identity + rules) │
│ │
│ ┌─────────────────────────────┐ │
│ │ Skill instructions │ │
│ ├─────────────────────────────┤ │
│ │ Admin context, history, etc │ │
│ └─────────────────────────────┘ │
│ │
│ User message │
└─────────────────────────────────────┘
full
The skill provides its own context template. The soul is not included unless the template explicitly references {{soul}}. This is for specialized, focused tasks that don't need the agent's identity.
┌─────────────────────────────────────┐
│ Skill-defined system prompt │
│ (from skill template) │
│ │
│ User message │
└─────────────────────────────────────┘
A full mode skill can still include the soul if desired:
system:
{{soul}}
You are now in translation mode. Translate the following to {{target_language}}.
Respond ONLY with the translation.
user:
{{message}}
override
The skill replaces the soul's system prompt but keeps the standard context assembly structure (admin context, history, tools, etc.).
Summary
| Mode | Soul | Template | Tools | Use Case |
|---|---|---|---|---|
inject |
✅ Always | Soul's template + skill instructions appended | Agent default | Behavioral rules, knowledge |
full |
❌ Unless {{soul}} in template |
Skill provides template | Skill specifies | Spellcheck, translation, focused analysis |
override |
Replaced by skill | Soul's template structure | Agent default | Different personality, same capabilities |
Template Variables
A skill template can reference these context parts:
| Variable | Source |
|---|---|
{{soul}} |
The agent's identity/system context |
{{admin_profile}} |
Admin kind 0 profile |
{{admin_notes}} |
Admin recent notes |
{{admin_relays}} |
Admin relay list |
{{adopted_skills}} |
Other adopted skill instructions |
{{dm_history}} |
Recent DM conversation |
{{message}} |
Current user message |
{{triggering_event}} |
For triggered skills, the event JSON |
{{tools}} |
Available tool schemas |
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 Nostr subscription filter attached. When matching events arrive, the skill executes automatically.
Trigger Tags
| Tag | Required | Description |
|---|---|---|
trigger |
Yes | Trigger type: nostr-subscription |
filter |
Yes | JSON-encoded Nostr subscription filter |
action |
No | template or llm (default: llm) |
enabled |
No | Whether active (default: true) |
Template Actions
Fast, deterministic, no LLM. The skill content is a template with placeholders from the triggering event:
DM admin: '{author_display_name} posted: {content_preview}'
Placeholders: {event_id}, {pubkey}, {author_display_name}, {kind}, {content}, {content_preview}, {created_at}, {relay_url}
Output prefixes: DM admin: ..., DM <pubkey>: ..., POST: ..., LOG: ...
LLM-Mediated Actions
The skill content defines the execution context. The triggering event is available as {{triggering_event}}. The skill's LLM spec, context mode, and tool access all apply.
{
"kind": 31124,
"content": {
"description": "Analyze mentions and notify admin",
"context_mode": "full",
"llm": "openai/gpt-4o-mini, cheap",
"tools": ["nostr_dm_send"],
"template": "system:\nYou monitor Nostr mentions. When the triggering event mentions Bitcoin or Lightning, summarize it and DM the admin. Otherwise, ignore silently.\n\nTriggering event:\n{{triggering_event}}"
},
"tags": [
["d", "mention-monitor"],
["trigger", "nostr-subscription"],
["filter", "{\"#p\":[\"<admin_pubkey>\"],\"kinds\":[1]}"],
["action", "llm"],
["enabled", "true"]
]
}
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 --> SUBSCRIBE[Create Nostr subscriptions for each filter]
end
subgraph Execution
EVENT_IN[Matching event arrives] --> LOOKUP[Find associated skill]
LOOKUP --> CHECK_TYPE{Action type?}
CHECK_TYPE -->|template| INTERPOLATE[Interpolate + execute prefix]
CHECK_TYPE -->|llm| RESOLVE[Resolve LLM + assemble context + run]
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
The Soul and Skills
The soul is the agent's default identity and context template. Skills interact with it based on their context mode:
inject— soul always present; skill instructions layered on topfull— soul absent unless template includes{{soul}}override— skill replaces soul prompt, keeps standard context structure
A spelling checker runs with no soul — purely functional, minimal context, cheap model. A complex analysis skill includes the full soul and all context parts. The soul is the default, not a requirement.
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 |
| Template action rate limit | 60/min | Prevents DM spam |
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 |
|---|---|
cron triggers |
Time-based triggers |
webhook triggers |
HTTP webhook triggers |
chain triggers |
Output of one skill triggers another |
| Skill composition | Pipeline multiple skills |
| Agent-to-agent sharing | Discover and adopt skills across agents |
| Trigger marketplace | Popular triggers rise via adoption count |