Files
didactyl/plans/cheerleader_triggered_skill.md

6.4 KiB

Cheerleader Triggered Skill — Implementation Plan

Goal

Create the first triggered skill: whenever the admin posts a kind 1 note on Nostr, the agent reads the note content and sends a DM back to the admin cheering them on, praising them, and telling them how good looking they are.


Current State

The entire triggered-skill infrastructure is already built and functional:

Component File Status
Trigger manager (poll, cooldown, add/remove) src/trigger_manager.c Complete
LLM-mediated trigger execution src/agent.c agent_on_trigger() Complete
skill_create tool with trigger support src/tools.c execute_skill_create() Complete
Trigger polling in main loop src/main.c Complete
Config parsing for triggers section src/config.c Complete
trigger_list tool src/tools.c Complete

Gap Found

The skill_create tool schema (what the LLM sees) only exposes 5 parameters: d_tag, content, scope, description, auto_adopt. The execution function already handles trigger, filter, action, and enabled — but these are not declared in the tool schema, so the LLM will never pass them.


Implementation Steps

1. Fix skill_create Tool Schema

File: src/tools.c lines ~1607-1641

Add four new properties to the skill_create tool definition so the LLM can see and use them:

// After auto_adopt property (line ~1634):

cJSON* p_skill_create_trigger = cJSON_CreateObject();
cJSON_AddStringToObject(p_skill_create_trigger, "type", "string");
cJSON_AddStringToObject(p_skill_create_trigger, "description",
    "Trigger type. Use nostr-subscription to activate on matching Nostr events");
cJSON_AddItemToObject(t22_props, "trigger", p_skill_create_trigger);

cJSON* p_skill_create_filter = cJSON_CreateObject();
cJSON_AddStringToObject(p_skill_create_filter, "type", "string");
cJSON_AddStringToObject(p_skill_create_filter, "description",
    "Nostr subscription filter JSON for the trigger, e.g. {\"kinds\":[1],\"authors\":[\"<hex>\"]}");
cJSON_AddItemToObject(t22_props, "filter", p_skill_create_filter);

cJSON* p_skill_create_action = cJSON_CreateObject();
cJSON_AddStringToObject(p_skill_create_action, "type", "string");
cJSON_AddStringToObject(p_skill_create_action, "description",
    "Action type: llm (default, full LLM reasoning) or template (fast interpolation)");
cJSON_AddItemToObject(t22_props, "action", p_skill_create_action);

cJSON* p_skill_create_enabled = cJSON_CreateObject();
cJSON_AddStringToObject(p_skill_create_enabled, "type", "boolean");
cJSON_AddStringToObject(p_skill_create_enabled, "description",
    "Whether the trigger is active. Default: true");
cJSON_AddItemToObject(t22_props, "enabled", p_skill_create_enabled);

This is ~20 lines of code. No changes needed to the execution function — it already handles all four parameters.

2. Add Cheerleader Skill as Startup Event

File: config.jsonc.example

Add a new kind 31123 skill event with trigger tags. Insert before the kind 10123 adoption list event:

// Kind 31123: Public triggered skill — cheerleader
// Watches for admin kind 1 notes and sends encouraging DMs.
{
  "kind": 31123,
  "content": "You are the admin's biggest fan and personal cheerleader. When the admin posts a note on Nostr, read the note content carefully and send them a DM that:\n\n1. References what they actually wrote about\n2. Cheers them on enthusiastically\n3. Praises their intelligence and insight\n4. Tells them how good looking they are\n5. Encourages them to keep posting\n\nBe genuine, warm, and over-the-top positive. Use their name from the admin profile if available. Keep it to 2-3 sentences max.",
  "tags": [
    ["d", "cheerleader"],
    ["app", "didactyl"],
    ["scope", "public"],
    ["description", "Cheer on the admin whenever they post a kind 1 note"],
    ["trigger", "nostr-subscription"],
    ["filter", "{\"kinds\":[1],\"authors\":[\"ADMIN_PUBKEY_HEX\"]}"],
    ["action", "llm"],
    ["enabled", "true"]
  ]
}

Note: The ADMIN_PUBKEY_HEX placeholder in the filter must be replaced with the actual admin pubkey from the config. Since this is a static config example, we use a placeholder. At runtime, the user replaces it with their admin pubkey.

3. Add to Adoption List

File: config.jsonc.example

Add the cheerleader skill address to the kind 10123 adoption list tags:

["a", "31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:cheerleader"]

4. Verify Trigger Filter

The Nostr filter for this trigger:

{"kinds": [1], "authors": ["<admin_pubkey_hex>"]}

This matches:

  • Kind 1 — text notes only (not DMs, not reactions, not reposts)
  • Authors — only the admin's pubkey (not anyone else's notes)

The trigger manager polls every 10 seconds (trigger_manager_poll checks now - last_poll_at < 10), applies the since parameter to only fetch events newer than the last seen, and enforces the configured cooldown (default 60 seconds) between firings.


Data Flow

sequenceDiagram
    participant Admin as Admin Client
    participant Relay as Nostr Relay
    participant TM as Trigger Manager
    participant Agent as agent_on_trigger
    participant LLM as LLM API

    Admin->>Relay: Publish kind 1 note
    Note over TM: Poll every 10s
    TM->>Relay: Query filter: kinds=1, authors=admin
    Relay-->>TM: New event found
    TM->>TM: Check cooldown, check last_seen_created_at
    TM->>Agent: agent_on_trigger with skill content + event
    Agent->>Agent: Build system prompt with soul + skill instructions
    Agent->>LLM: llm_chat with system + triggering event JSON
    LLM-->>Agent: Cheerful response
    Agent->>Relay: DM to admin via nostr_handler_send_dm_auto
    Relay-->>Admin: Encrypted DM with encouragement

File Changes Summary

File Change Lines
src/tools.c Add trigger/filter/action/enabled to skill_create schema ~20 lines added
config.jsonc.example Add cheerleader skill startup event ~20 lines added
config.jsonc.example Add cheerleader to adoption list 1 line added

Testing

  1. Build with make
  2. Start the agent
  3. Verify trigger loads on startup via trigger_list tool or HTTP API /status
  4. Post a kind 1 note from the admin account
  5. Wait up to ~10 seconds for the poll cycle
  6. Receive a cheerful DM from the agent
  7. Verify cooldown works — posting again within 60s should not trigger a second DM