Files
didactyl/docs/TOOLS_AND_SKILLS.md

15 KiB

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

  1. Admin sends a DM to didactyl
  2. The agent builds an LLM request with the message, context, and a JSON schema of all available tools
  3. The LLM decides whether to call a tool or respond directly
  4. If a tool is called, didactyl executes it and feeds the result back to the LLM
  5. The loop repeats until the LLM produces a final text response
  6. 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 Event & Messaging Tools

Tool Description
nostr_post Publish a Nostr event to connected relays
nostr_delete Request deletion of one or more previously published events (NIP-09 kind 5)
nostr_react React to a Nostr event with like/dislike/emoji (NIP-25 kind 7)
nostr_query Query events from relays using a Nostr filter
nostr_dm_send Send a NIP-04 encrypted DM
nostr_dm_send_nip17 Send a private DM using NIP-17 gift wrap protocol

Nostr Identity & Utility Tools

Tool Description
nostr_profile_get Look up a Nostr profile (kind 0 metadata) by pubkey
nostr_nip05_lookup Look up or verify a NIP-05 identifier (user@domain)
nostr_encode Encode a Nostr entity into nostr: URI (npub, note, nprofile, nevent, naddr)
nostr_decode Decode a Nostr bech32/nostr: URI into components
nostr_relay_status Get connection status and statistics for all relays
nostr_relay_info Fetch NIP-11 relay information document
nostr_encrypt Encrypt plaintext using NIP-44 for a recipient
nostr_decrypt Decrypt NIP-44 ciphertext from a sender
nostr_list_manage Add/remove tag tuples in replaceable list events (NIP-51 style)

Skills & Trigger Tools

Tool Description
skill_create Create or update a skill definition as kind 31123/31124 and optionally auto-adopt it
skill_list List this agent's published skills, optionally filtered by scope
skill_adopt Adopt a skill by adding its address to kind 10123 adoption list
skill_remove Remove a skill address from kind 10123 adoption list
skill_search Search public skills by query/author and optionally rank by adoption popularity
trigger_list List active triggered skills and their runtime status

LLM / Model Management Tools

Tool Description
model_get Get current active LLM runtime configuration (excluding API key)
model_set Update active LLM configuration and persist it to config.json
model_list List available model IDs using provider OpenAI-compatible /models endpoint

System & Runtime Tools

Tool Description
my_version Return current Didactyl version and metadata from build macros
http_fetch Fetch HTTP(S) resources with optional method, headers, timeout, and body
shell_exec Execute a shell command and return stdout/stderr
file_read Read a local file as text from the configured working directory
file_write Write text content to a local file in the configured working directory
tool_list List available tools with name, description, and JSON parameter schema

Content Publishing Conveniences

Tool Description
nostr_post_readme Publish README.md as kind 30023 with deterministic d-tag readme.md
nostr_file_md_to_longform_post Read a markdown file and publish it as kind 30023 longform post (defaults d-tag to lowercase filename)

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

Adopted skills are now always-on contextual knowledge for admin DM handling: the agent resolves the local adoption list (kind 10123), caches referenced skills, and injects their instructions into the LLM system context each turn.

To keep context stable and safe, skill injection is bounded by hard caps (per-skill truncation and total skill-context budget), and excess skills are omitted with an explicit budget notice.


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 admin
  • DM <pubkey>: ... — send a DM to a specific pubkey
  • POST: ... — publish as a kind 1 note
  • LOG: ... — 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:

  1. On startup: Load all adopted skills, find triggered ones, create subscriptions
  2. On skill_create with trigger: Immediately create a new subscription (no restart needed)
  3. On skill_remove with trigger: Tear down the associated subscription
  4. 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:

  1. Resolves the admin's pubkey
  2. Crafts a Nostr filter: {"#p": ["<admin_pubkey>"], "kinds": [1]}
  3. Writes a skill with trigger tags via skill_create
  4. The trigger manager picks it up and creates the subscription
  5. 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