Files
didactyl/plans/skills_demo_page.md

17 KiB

Skills Demo Page — Design Document

Purpose

A standalone web page that demonstrates Didactyl skills running in a browser context. The page presents a minimal word-processing interface where users can load text, discover skills from Nostr relays, execute them against the text via an LLM, and edit/republish skill definitions.

This document is the complete specification for building the page. The front-end project consuming this document has its own LLM calling capability and Nostr connectivity.


Concepts

What is a Skill?

A skill is a Nostr event (kind 31123 for public, 31124 for private) that defines a self-contained LLM execution unit. Each skill specifies:

  • A template with system and user prompt sections
  • An LLM specification (model preferences with fallback chain)
  • Parameters like temperature and max_tokens
  • A description for human display

Skills are replaceable events keyed by their d tag (the d_tag). Publishing a new event with the same d tag replaces the previous version.

Skill Event Structure

{
  "kind": 31123,
  "pubkey": "<author hex pubkey>",
  "created_at": 1709750000,
  "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- Return ONLY the corrected text, no explanations\\n\\nuser:\\n{{message}}\"}",
  "tags": [
    ["d", "spellcheck"],
    ["app", "didactyl"],
    ["scope", "public"],
    ["description", "Spelling and grammar checker"],
    ["t", "text-processing"],
    ["t", "skills-demo"]
  ]
}

The content field is a JSON string containing these fields:

Field Type Required Description
description string yes Human-readable description
context_mode string no full for demo skills (skill provides its own template)
llm string no LLM model preference with fallback chain
tools bool no false for text-processing skills
max_tokens int no Max tokens for LLM response
temperature float no Sampling temperature
template string yes The prompt template with system: and user: sections

Template Format

Templates use a simple section-based format:

system:
You are a spelling and grammar checker.

Rules:
- Fix spelling errors
- Fix grammar errors

user:
{{message}}

The {{message}} placeholder is replaced with the user's text at execution time.

Skill Address Format

Skills are referenced by their NIP-33 address: <kind>:<author_pubkey_hex>:<d_tag>

Example: 31123:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:spellcheck


Demo Skills

Four skills are published by the Didactyl agent (pubkey 52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8). All are tagged with t:skills-demo for easy discovery.

1. Spellcheck — d:spellcheck

system:
You are a spelling and grammar checker.

Rules:
- Fix spelling errors
- Fix grammar errors
- Preserve original formatting and paragraph structure
- Preserve line breaks exactly as they appear
- Ignore technical terms: API, JSON, HTTP, nostr, pubkey, npub, nsec, NIP
- Canadian English preferred
- Return ONLY the corrected text, no explanations or commentary

user:
{{message}}
  • temperature: 0.1
  • max_tokens: 4000

2. Condense — d:condense-5

system:
You are a text condensing editor.

Rules:
- Reduce the text length by approximately 5%
- Preserve the original meaning and tone
- Preserve paragraph structure
- Remove redundant words and phrases
- Tighten sentences without changing voice
- Do not add new information
- Return ONLY the condensed text, no explanations or commentary

user:
{{message}}
  • temperature: 0.3
  • max_tokens: 4000

3. Translate to Japanese — d:translate-ja

system:
You are a professional translator specializing in English to Japanese translation.

Rules:
- Translate the entire text from English to Japanese
- Use natural, fluent Japanese appropriate for the content register
- Preserve paragraph structure and formatting
- Preserve any technical terms in their commonly accepted Japanese form
- If a term has no standard Japanese equivalent, keep the English term in katakana
- Return ONLY the Japanese translation, no explanations or commentary

user:
{{message}}
  • temperature: 0.3
  • max_tokens: 4000

4. Convert to Poem — d:convert-to-poem

system:
You are a creative poet.

Rules:
- Convert the given text into a poem
- Capture the key themes and meaning of the original text
- Use vivid imagery and poetic devices
- Aim for 12-20 lines
- Free verse is acceptable but light rhyme is welcome
- Return ONLY the poem, no explanations or commentary

user:
{{message}}
  • temperature: 0.9
  • max_tokens: 2000

Discovering Skills from Nostr

Query Filter

To find the demo skills, query relays with this filter:

{
  "kinds": [31123],
  "#t": ["skills-demo"],
  "limit": 20
}

To find skills by a specific author:

{
  "kinds": [31123],
  "authors": ["52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8"],
  "#t": ["skills-demo"],
  "limit": 20
}
  • wss://relay.damus.io
  • wss://nos.lol
  • wss://relay.primal.net

Parsing a Skill Event

  1. Receive the event from the relay
  2. Parse event.content as JSON to get the skill definition object
  3. Extract description, template, temperature, max_tokens
  4. Extract the d tag value from event.tags as the skill d_tag
  5. Extract the description tag from event.tags as a display label
  6. The skill is ready to display and execute

Page Layout

+------------------------------------------------------------------+
|  SKILLS DEMO                                          [dark/light]|
+------------------------------------------------------------------+
|                                    |                              |
|  +------------------------------+ | +---------------------------+|
|  | TEXT EDITOR                   | | | SKILLS PANEL              ||
|  |                               | | |                           ||
|  | [large editable textarea     ]| | | Loading skills...         ||
|  | [filled with default text    ]| | |                           ||
|  | [                            ]| | | [x] spellcheck            ||
|  | [                            ]| | |     Spelling and grammar  ||
|  | [                            ]| | |     checker               ||
|  | [                            ]| | |                           ||
|  | [                            ]| | | [ ] condense-5            ||
|  | [                            ]| | |     Reduce text by 5%     ||
|  | [                            ]| | |                           ||
|  | [                            ]| | | [ ] translate-ja          ||
|  | [                            ]| | |     Translate to Japanese ||
|  | [                            ]| | |                           ||
|  | [                            ]| | | [ ] convert-to-poem       ||
|  |                               | | |     Convert to poem       ||
|  +------------------------------+ | | |                           ||
|                                    | | [Run Selected Skill]      ||
|  +------------------------------+ | | [Edit Skill]              ||
|  | STATUS BAR                    | | |                           ||
|  | Ready / Running... / Done     | | | ─── Skill Details ───    ||
|  +------------------------------+ | | template: ...             ||
|                                    | | temperature: 0.1          ||
|                                    | | max_tokens: 4000          ||
|                                    | +---------------------------+|
+------------------------------------------------------------------+

Left Column — Text Editor (65% width)

  • A large <textarea> or contenteditable div, minimum 500px tall
  • Pre-filled with default sample text (see Default Text section below)
  • Fully editable by the user
  • When a skill runs, the text content is sent as {{message}}
  • After the LLM responds, the textarea content is replaced with the result
  • An undo button restores the previous text (keep a history stack)

Right Column — Skills Panel (35% width)

  • Skills list: Radio buttons or selectable cards, one skill selected at a time
  • Each skill card shows:
    • Skill d_tag (the d tag)
    • Description (from the description tag or content field)
    • Author pubkey (truncated, e.g., 52a3e8...0acb8)
  • Run Selected Skill button: executes the selected skill against the current text
  • Edit Skill button: opens the skill editor (see Skill Editor section)
  • Skill Details section: shows the full template, temperature, max_tokens for the selected skill
  • Refresh Skills button: re-queries relays for updated skill events

Status Bar

  • Shows current state: Ready, Running skill: spellcheck..., Done (1.2s), or Error: ...
  • Displays token usage if available from the LLM response

Skill Execution Flow

sequenceDiagram
    participant User
    participant Page as Demo Page
    participant LLM as LLM Provider

    User->>Page: Select skill + click Run
    Page->>Page: Parse skill template
    Page->>Page: Split template into system/user sections
    Page->>Page: Replace message placeholder with textarea content
    Page->>LLM: Send system prompt + user message
    Note over Page,LLM: Use skill temperature and max_tokens
    LLM-->>Page: Response text
    Page->>Page: Push current text to undo stack
    Page->>Page: Replace textarea with response
    Page->>User: Show completion status

Template Parsing

The template field uses a simple format with system: and user: section markers:

system:
<system prompt content>

user:
<user prompt content with {{message}} placeholder>

Parsing algorithm:

  1. Split the template string on the line user: (case-sensitive, must be at start of line)
  2. Everything before user: is the system section; strip the leading system: prefix
  3. Everything after user: is the user section
  4. In the user section, replace {{message}} with the textarea content
  5. Trim whitespace from both sections

Pseudocode:

function parseSkillTemplate(template, userText) {
  const userMarkerIndex = template.indexOf('\nuser:\n');
  if (userMarkerIndex === -1) {
    // Fallback: entire template is system, user text is the message
    return {
      system: template.replace(/^system:\n/, '').trim(),
      user: userText
    };
  }

  let systemPart = template.substring(0, userMarkerIndex);
  systemPart = systemPart.replace(/^system:\n/, '').trim();

  let userPart = template.substring(userMarkerIndex + '\nuser:\n'.length);
  userPart = userPart.replace('{{message}}', userText).trim();

  return { system: systemPart, user: userPart };
}

LLM Call

With the parsed system and user prompts, call the LLM:

const response = await callLLM({
  messages: [
    { role: 'system', content: parsed.system },
    { role: 'user', content: parsed.user }
  ],
  temperature: skill.temperature || 0.7,
  max_tokens: skill.max_tokens || 2000
});

The page's own LLM calling capability handles the actual API request. The skill just provides the prompts and parameters.


Skill Editor

When the user clicks Edit Skill, a modal or inline editor opens for the selected skill.

Editor Fields

Field Input Type Description
Slug text input (readonly for existing, editable for new) The d tag identifier
Description text input One-line human description
Template large textarea (monospace) The full system:/user: template
Temperature number input (0.0 - 2.0, step 0.1) LLM sampling temperature
Max Tokens number input (100 - 8000) Maximum response tokens

Editor Actions

  • Save & Publish: Builds a new kind 31123 event and publishes to relays
  • Cancel: Closes editor without changes
  • New Skill: Opens editor with blank fields for creating a new skill

Publishing a Skill

sequenceDiagram
    participant Editor as Skill Editor
    participant Signer as Nostr Signer
    participant Relay as Nostr Relays

    Editor->>Editor: Build skill content JSON
    Editor->>Editor: Build event with kind 31123 + tags
    Editor->>Signer: Sign event
    Signer-->>Editor: Signed event
    Editor->>Relay: Publish to connected relays
    Relay-->>Editor: OK confirmation
    Editor->>Editor: Update local skill list

Building the event:

function buildSkillEvent(d_tag, description, template, temperature, maxTokens) {
  const content = JSON.stringify({
    description: description,
    context_mode: 'full',
    llm: 'default',
    tools: false,
    max_tokens: maxTokens,
    temperature: temperature,
    template: template
  });

  return {
    kind: 31123,
    content: content,
    tags: [
      ['d', d_tag],
      ['app', 'didactyl'],
      ['scope', 'public'],
      ['description', description],
      ['t', 'text-processing'],
      ['t', 'skills-demo']
    ],
    created_at: Math.floor(Date.now() / 1000)
  };
}

The event must be signed with the user's Nostr private key (via NIP-07 browser extension or the app's own signer) before publishing to relays.


Default Text

Pre-fill the textarea with this sample text that exercises the demo skills well:

The Sovereign Agent

In the not-too-distant future, software agents will roam freely across decentralized networks, communicating through cryptographic protocols that no central authority can censor or control. These agents will learn new capabilities not from app stores or corporate APIs, but from each other — sharing skills as freely as humans share ideas.

Imagine an agent that wakes up on any computer in the world. You enter twelve words, and there it is — your agent, with all its memories, skills, and personality intact. It doesn't live on a server you rent. It doesn't depend on a company staying in business. It exists wherever you need it, sovereign and portable.

The key insight is that skills are the new apps. Instead of installing software, agents adopt skills — small, self-contained instructions that teach them how to perform specific tasks. A spelling checker. A translator. A poet. Each skill is just a Nostr event, discoverable by anyone, adoptable by any agent.

This is not science fiction. The protocols exist today. Nostr provides the communication layer. Bitcoin provides the economic layer. Cryptography provides the trust layer. What remains is to build the agents themselves — and to set them free.

There are some erors in this text that a good spellchecker should catch. The writting could also be more concise, and it would be intresting to see it translated or converted into verse.

Note: The last paragraph intentionally contains spelling errors (erors, writting, intresting) to demonstrate the spellcheck skill.


Undo/Redo

Maintain a text history stack:

const textHistory = [];
let historyIndex = -1;

function pushHistory(text) {
  // Truncate any forward history
  textHistory.splice(historyIndex + 1);
  textHistory.push(text);
  historyIndex = textHistory.length - 1;
}

function undo() {
  if (historyIndex > 0) {
    historyIndex--;
    return textHistory[historyIndex];
  }
  return null;
}

function redo() {
  if (historyIndex < textHistory.length - 1) {
    historyIndex++;
    return textHistory[historyIndex];
  }
  return null;
}
  • Push the current text to history before replacing it with the LLM response
  • Push the initial default text as the first history entry
  • Show Undo/Redo buttons below the textarea

Responsive Behavior

  • On screens wider than 900px: two-column layout as shown
  • On screens narrower than 900px: stack vertically — text editor on top, skills panel below
  • The textarea should have a minimum height of 400px and resize vertically

Error Handling

Scenario Behavior
No relays connected Show "No relay connection" in skills panel with retry button
No skills found Show "No skills found. Try different relays or create a new skill."
Skill content parse error Show "Invalid skill format" badge on the skill card, disable Run
LLM call fails Show error in status bar, do not replace textarea content
Publish fails Show error in editor, keep editor open for retry

Summary of Nostr Operations

Operation Kind Direction When
Discover skills 31123 Read from relays Page load + refresh
Execute skill N/A (local LLM call) User clicks Run
Publish skill 31123 Write to relays User saves in editor

The page does NOT need to interact with the Didactyl agent's HTTP API. All skill discovery and publishing happens directly via Nostr relays. Skill execution happens locally using the page's own LLM capability.