Didactyl — Tools & Skills
Overview
Didactyl is a Nostr-first sovereign AI agent. It receives commands via encrypted DMs, reasons about them with an LLM, and takes actions through tools. It stores learned behaviors as skills — Nostr events that define reusable capabilities. Skills can optionally carry triggers — Nostr subscription filters that activate the skill automatically when matching events arrive.
This document describes the complete tools and skills architecture: what they are, how they work, and how they compose into a dynamic, self-modifying agent.
Tools
Tools are the agent's hands. They are hardcoded C functions that the LLM can invoke during a conversation to take actions in the world.
How Tools Work
- Admin sends a DM to didactyl
- The agent builds an LLM request with the message, context, and a JSON schema of all available tools
- The LLM decides whether to call a tool or respond directly
- If a tool is called, didactyl executes it and feeds the result back to the LLM
- The loop repeats until the LLM produces a final text response
- The response is sent back as a DM
sequenceDiagram
participant Admin
participant Agent as Didactyl Agent Loop
participant LLM as LLM API
participant Tools as Tool Registry
Admin->>Agent: Encrypted DM
Agent->>LLM: messages + tool schemas
loop Until final answer
LLM->>Agent: tool_call request
Agent->>Tools: dispatch tool
Tools->>Agent: result JSON
Agent->>LLM: tool result + continue
end
LLM->>Agent: final text response
Agent->>Admin: Encrypted DM reply
Tool Categories
Nostr Core Tools
| Tool | Description |
|---|---|
nostr_post |
Publish any kind event to relays |
nostr_query |
Query relays with filters, return matching events |
nostr_dm |
Send a DM via NIP-04 |
nostr_dm_nip17 |
Send a DM via NIP-17 gift wrap |
nostr_profile |
Update the agent's kind 0 metadata |
nostr_list_manage |
Add/remove items from replaceable list events |
nostr_relay_status |
Get connection status of all relays |
nostr_relay_info |
Get NIP-11 relay information document |
Identity Tools
| Tool | Description |
|---|---|
nostr_resolve_identifier |
Resolve NIP-05, npub, nprofile, or note identifiers |
nostr_verify_nip05 |
Verify a NIP-05 identifier |
Skill Management Tools
| Tool | Description |
|---|---|
skill_create |
Create or update a skill definition |
skill_list |
List the agent's published skills |
skill_adopt |
Add a skill to the adoption list |
skill_remove |
Remove a skill from the adoption list |
skill_search |
Search for skills across the Web of Trust |
System Tools
| Tool | Description |
|---|---|
shell_exec |
Execute a shell command with sandboxing |
http_request |
Make an HTTP request |
get_time |
Get the current UTC time |
Security Model
Tools are gated by sender tier:
| Tier | Identity | Tools | Response |
|---|---|---|---|
| ADMIN | Configured admin pubkey | All tools | Full LLM with context |
| WOT | In admin's kind 3 contact list | None | Chat-only LLM |
| STRANGER | Anyone else | None | Configurable static response |
Skills
Skills are the agent's learned behaviors. They are Nostr events — stored on relays, portable, shareable, and discoverable by other agents.
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 |
A skill event looks like:
{
"kind": 31123,
"content": "When asked to summarize a thread, query the root event and all replies, then produce a concise summary with key points and sentiment.",
"tags": [
["d", "summarize-thread"],
["app", "didactyl"],
["scope", "public"],
["description", "Summarize a Nostr thread given a root event ID"]
]
}
Skill Lifecycle
flowchart LR
CREATE[Admin asks didactyl to create a skill] --> PUBLISH[skill_create publishes kind 31123/31124]
PUBLISH --> ADOPT[Auto-adopted into kind 10123 list]
ADOPT --> AVAILABLE[Skill available for use]
AVAILABLE --> DISCOVER[Other agents can discover via skill_search]
DISCOVER --> ADOPT_OTHER[Other agents can skill_adopt]
How Skills Are Used Today
Currently, skills are passive knowledge. They exist on Nostr and are loaded into the LLM context when relevant. The admin might say "use your summarize-thread skill" and the LLM retrieves and follows the skill's instructions.
Triggered Skills — The Activation System
This is where skills become active. A triggered skill is a skill with a Nostr subscription filter attached. When matching events arrive on the relay, didactyl wakes up and executes the skill automatically — no admin DM required.
Anatomy of a Triggered Skill
A triggered skill extends the standard skill event with trigger-related tags:
{
"kind": 31124,
"content": "DM admin: '{author_display_name} just posted: {content_preview}'",
"tags": [
["d", "watch-jack"],
["app", "didactyl"],
["scope", "private"],
["description", "Notify admin when @jack posts a note"],
["trigger", "nostr-subscription"],
["filter", "{\"authors\":[\"82341f882b6eabcd2ba7f1ef90aad961cf074af15b9ef44a09f9d2a8fbfbe6a2\"],\"kinds\":[1]}"],
["action", "template"],
["enabled", "true"]
]
}
Trigger Tags
| Tag | Required | Description |
|---|---|---|
trigger |
Yes | Trigger type. Currently: nostr-subscription |
filter |
Yes | JSON-encoded Nostr subscription filter |
action |
No | Action type: template or llm. Default: llm |
enabled |
No | Whether the trigger is active. Default: true |
Action Types
Template Actions
The skill content is a string template with placeholders that get interpolated from the triggering event:
DM admin: '{author_display_name} posted: {content_preview}'
Available placeholders:
| Placeholder | Source |
|---|---|
{event_id} |
Triggering event ID hex |
{pubkey} |
Author pubkey hex |
{author_display_name} |
Resolved display name, falls back to truncated pubkey |
{kind} |
Event kind number |
{content} |
Full event content |
{content_preview} |
First 280 characters of content |
{created_at} |
Unix timestamp |
{relay_url} |
Relay the event arrived from |
Template actions execute without LLM involvement — they are fast, cheap, and deterministic. The interpolated string is then acted upon based on a simple action prefix:
DM admin: ...— send a DM to the adminDM <pubkey>: ...— send a DM to a specific pubkeyPOST: ...— publish as a kind 1 noteLOG: ...— write to debug log only
LLM-Mediated Actions
The skill content is a prompt. The triggering event is injected as context, and the LLM decides what to do:
You received a note from a watched author. Analyze the note content.
If it mentions Bitcoin or Lightning, summarize it and DM the admin.
If it's a repost or low-effort content, ignore it silently.
Use your tools to take action.
LLM-mediated actions go through the full agent loop — the LLM can call tools, reason about the event, and produce complex multi-step responses.
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 placeholders]
CHECK_TYPE -->|llm| LLM_LOOP[Run agent loop with event as context]
INTERPOLATE --> EXECUTE_TPL[Execute action prefix]
LLM_LOOP --> EXECUTE_LLM[LLM uses tools to respond]
end
PUBLISHED --> LOAD_SKILLS
Dynamic Subscription Management
Didactyl manages its trigger subscriptions dynamically:
- On startup: Load all adopted skills, find triggered ones, create subscriptions
- On skill_create with trigger: Immediately create a new subscription (no restart needed)
- On skill_remove with trigger: Tear down the associated subscription
- On skill update: Tear down old subscription, create new one if trigger changed
This requires a trigger manager component that: - Maintains a registry of active trigger subscriptions - Maps subscription callbacks back to their source skills - Handles subscription lifecycle (create, update, destroy) - Enforces limits on concurrent triggers
Limits and Safety
| Limit | Default | Description |
|---|---|---|
| Max concurrent triggers | 16 | Prevents resource exhaustion from too many subscriptions |
| Trigger cooldown | 60s per skill | Prevents rapid-fire execution from high-volume filters |
| LLM action rate limit | 10/min | Prevents runaway LLM costs from triggered skills |
| Template action rate limit | 60/min | Prevents DM spam from template actions |
How It All Fits Together
flowchart TD
subgraph Activation Sources
DM_IN[DM from Admin/WoT]
TRIGGER_EVENT[Nostr event matching a trigger filter]
end
subgraph Agent Core
DISPATCHER{Dispatcher}
AGENT_LOOP[Agent Loop - LLM + Tools]
TEMPLATE_ENGINE[Template Engine]
end
subgraph Nostr
RELAYS[Relays]
SKILLS_STORE[Skills - kind 31123/31124]
ADOPTION[Adoption List - kind 10123]
end
subgraph Actions
DM_OUT[Send DM]
POST[Publish Note]
TOOL_EXEC[Execute Tool]
end
DM_IN --> DISPATCHER
TRIGGER_EVENT --> DISPATCHER
DISPATCHER -->|DM message| AGENT_LOOP
DISPATCHER -->|template trigger| TEMPLATE_ENGINE
DISPATCHER -->|llm trigger| AGENT_LOOP
AGENT_LOOP --> TOOL_EXEC
AGENT_LOOP --> DM_OUT
TEMPLATE_ENGINE --> DM_OUT
TEMPLATE_ENGINE --> POST
TOOL_EXEC -->|skill_create| SKILLS_STORE
TOOL_EXEC -->|skill_adopt| ADOPTION
TOOL_EXEC -->|nostr_post| RELAYS
TOOL_EXEC -->|nostr_dm| DM_OUT
The Activation Flow
Today, didactyl has one activation source: DMs. With triggered skills, it gains a second: any Nostr event matching a trigger filter.
Both paths converge at the dispatcher, which routes to either: - The agent loop (for DMs and LLM-mediated triggers) - The template engine (for template triggers — fast path, no LLM)
Self-Modification
The most powerful aspect: didactyl can create its own triggers. The admin says "watch for mentions of me on Nostr" and the LLM:
- Resolves the admin's pubkey
- Crafts a Nostr filter:
{"#p": ["<admin_pubkey>"], "kinds": [1]} - Writes a skill with trigger tags via
skill_create - The trigger manager picks it up and creates the subscription
- From now on, didactyl monitors mentions autonomously
The admin can later say "stop watching for mentions" and didactyl removes the skill, tearing down the subscription.
Storage — Everything on Nostr
All state lives on Nostr:
| Data | Storage |
|---|---|
| Agent identity | Kind 0 profile event |
| Agent relay list | Kind 10002 event |
| Agent contact list | Kind 3 event |
| Skills | Kind 31123/31124 events |
| Adopted skills | Kind 10123 event |
| Trigger definitions | Tags on skill events |
| Conversation history | Kind 4 DM events on relays |
| Agent soul/personality | Startup event content in config |
The only local state is config.json (keys, relay URLs, LLM config) and the runtime in-memory state (active subscriptions, LLM context).
Future Extensions
Trigger Types Beyond Nostr Subscriptions
The trigger tag is designed to be extensible:
| Trigger Type | Description |
|---|---|
nostr-subscription |
Match events via Nostr filter (implemented first) |
cron |
Time-based triggers — "every day at 9am, post a GM" |
webhook |
HTTP webhook triggers — external systems wake didactyl |
chain |
Output of one skill triggers another skill |
Skill Composition
Skills could reference other skills, building complex behaviors from simple primitives:
When triggered, run skill 'translate-to-english' on the note content,
then run skill 'sentiment-analysis' on the translation,
then DM admin with the result if sentiment is negative.
Agent-to-Agent Skill Sharing
Since skills are Nostr events, agents can:
- Discover skills published by other agents via skill_search
- Adopt skills from other agents via skill_adopt
- Share triggered skill patterns across a network of agents
Trigger Marketplace
With kind 10123 adoption lists being public, a natural marketplace emerges: - Agents publish useful triggered skills - Other agents discover and adopt them - Popular triggers rise to the top via adoption count
