v0.0.22 - Add relay-waited --test-tool flow, longform markdown post tool, and README CLI debugger docs

This commit is contained in:
Your Name
2026-03-01 20:00:01 -04:00
parent a446f25400
commit 052c11863f
15 changed files with 2047 additions and 19 deletions

393
docs/TOOLS_AND_SKILLS.md Normal file
View File

@@ -0,0 +1,393 @@
# 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
```mermaid
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:
```json
{
"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
```mermaid
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:
```json
{
"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
```mermaid
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
```mermaid
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