Didactyl — Skills
- Didactyl — Skills
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 — technique, judgment, decision-making. A tool is the chisel. The skill never directly uses the chisel without the craftsperson (the LLM) in the loop.
Skills are portable, shareable, and discoverable as Nostr 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
Skill content is JSON and should focus on instructions, not transport/runtime controls.
{
"kind": 31123,
"content": {
"description": "Check spelling and grammar",
"template": "system:\nYou are a spelling and grammar checker.\n\nRules:\n- Fix spelling errors\n- Fix grammar errors\n- Preserve original formatting\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 |
template |
string | — | Skill instructions/template text (recommended) |
base |
bool | false |
Optional hint that this skill is intended as base/default behavior |
Execution parameters (
llm,max_tokens,temperature,seed,tools) are defined on trigger tags, not in content.
Composition Model (No Context Modes)
Skills do not use context_mode.
Context is assembled from kind 10123 adoption list order:
- Resolve adopted skills in list order.
- Expand each skill template/tool variables.
- Append each resolved skill as context messages in that same order.
- Append live user/trigger input.
The adoption list itself is the context definition.
- One adopted skill = single-skill behavior.
- Multiple adopted skills = layered behavior in explicit order.
- Reordering
10123changes precedence naturally.
Ordering Convention
- Earlier adopted skills generally set broader tone/policy.
- Later adopted skills can narrow/specialize behavior.
- If multiple skills strongly conflict, normal prompt-order effects apply.
Template Variables Are Tool Calls
Template variables are tool calls.
When the engine encounters {{admin_profile}}, it runs the corresponding tool and inserts the result into context.
| Variable | Tool Called | Description |
|---|---|---|
{{agent_identity}} |
agent_identity |
Agent identity block |
{{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 |
Triggering event JSON |
Unknown variables should resolve to empty values for portability.
Triggered Skills
A triggered skill has a trigger source attached.
Didactyl trigger types:
nostr-subscriptionwebhookcronchaindm
Trigger Tags
| Tag | Required | Description |
|---|---|---|
trigger |
Yes | Trigger type: nostr-subscription, webhook, cron, chain, dm |
filter |
Yes | Type-specific filter |
enabled |
No | Whether active (default: true) |
llm |
No | Model spec fallback chain (e.g., openai/gpt-4o-mini, cheap) |
max_tokens |
No | Max output tokens for this trigger execution |
temperature |
No | Sampling temperature for this trigger execution |
seed |
No | Optional deterministic seed where supported |
tools |
No | true for all tools, false for none, or CSV list of allowed tool names |
Execution Parameter Resolution
When a trigger fires:
- Start with agent defaults.
- Apply execution tags from that trigger (
llm,max_tokens,temperature,seed,tools). - Execute skill with those effective runtime settings.
- Restore defaults after the run.
Adopted vs Triggered Behavior
- Adopted skill (
10123): contributes instructions/template to context. - Triggered skill: contributes instructions and may define execution parameters via trigger tags.
Trigger Types
nostr-subscription
filter is a JSON-encoded Nostr subscription filter.
["trigger", "nostr-subscription"],
["filter", "{\"#p\":[\"<admin_pubkey>\"],\"kinds\":[1]}"],
["llm", "openai/gpt-4o-mini, cheap"],
["temperature", "0"],
["tools", "nostr_query,nostr_dm"],
["enabled", "true"]
webhook
filter is required and can be {}; webhook firing happens via HTTP.
["trigger", "webhook"],
["filter", "{}"],
["llm", "default"],
["tools", "true"],
["enabled", "true"]
cron
filter is a standard 5-field cron expression: minute hour day-of-month month day-of-week.
["trigger", "cron"],
["filter", "*/5 * * * *"],
["llm", "openai/gpt-4o-mini"],
["max_tokens", "300"],
["enabled", "true"]
chain
filter is the source skill d tag to chain from.
["trigger", "chain"],
["filter", "source-skill-d-tag"],
["llm", "default"],
["enabled", "true"]
dm
filter is JSON with sender scope:
{"from":"admin"}{"from":"wot"}{"from":"any"}
["trigger", "dm"],
["filter", "{\"from\":\"admin\"}"],
["llm", "default"],
["tools", "true"],
["enabled", "true"]
Private Skill Encoding (31124)
Private skills use NIP-44 encryption on event content.
Rules for kind 31124:
- Keep
dtag exposed so the event stays addressable/replaceable. - Move non-
dmetadata into plaintext payload before encryption. - Encrypt full payload with NIP-44 and store ciphertext in event
content. - On receive: resolve by
d, decryptcontent, then read content + private tags.
Private Skill Event (on relay)
{
"kind": 31124,
"content": "<nip44-ciphertext>",
"tags": [
["d", "mention-monitor"]
]
}
Decrypted Private Payload (application-level JSON)
{
"content": {
"description": "Monitor mentions and DM summaries",
"template": "When {{triggering_event}} includes Bitcoin or Lightning, summarize and DM admin."
},
"private_tags": [
["scope", "private"],
["trigger", "nostr-subscription"],
["filter", "{\"#p\":[\"<admin_pubkey>\"],\"kinds\":[1]}"],
["llm", "openai/gpt-4o-mini, fast"],
["temperature", "0"],
["seed", "42"],
["tools", "nostr_query,nostr_dm"],
["enabled", "true"]
]
}
Execution Flow
sequenceDiagram
participant Input as Message/Trigger
participant Dispatch as Dispatcher
participant Adopt as Adoption Resolver (10123)
participant Ctx as Context Assembler
participant Trig as Trigger Runtime Params
participant LLM as LLM API
Input->>Dispatch: message or trigger event
Dispatch->>Adopt: load adopted skills in list order
Adopt-->>Ctx: ordered skill templates
Ctx->>Ctx: resolve template variables via tools
Dispatch->>Trig: resolve trigger execution tags
Trig-->>LLM: model + max_tokens + temperature + seed + tool policy
Ctx->>LLM: composed messages
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 + execution params | Tags on skill events |
Portability Guidelines
To keep skills reusable across agents/clients:
- Prefer generic instructions over implementation-specific assumptions.
- Treat tool names as capabilities, not platform internals.
- Resolve unknown variables safely (empty result, no hard failure).
- Keep app-specific tags optional (
["app","didactyl"]).
A skill should still be useful even when some variables/tools are unavailable.
Related Documentation
- Tool architecture and complete tool catalog: TOOLS.md
- Context assembly model: CONTEXT.md
- Project overview/runtime behavior: README.md
Write a comment