Files
didactyl/plans/triggered_skills.md

7.0 KiB

Triggered Skills — Implementation Plan

Overview

Extend the existing skill system so that skills can carry Nostr subscription triggers. When matching events arrive, didactyl executes the skill automatically — either via template interpolation (fast, no LLM) or LLM-mediated reasoning (full agent loop).

See docs/TOOLS_AND_SKILLS.md for the full architecture.


Implementation Steps

1. Trigger Manager Module

Create src/trigger_manager.c and src/trigger_manager.h — the core component that manages dynamic Nostr subscriptions tied to skills.

Data structures:

#define TRIGGER_MAX_ACTIVE 16
#define TRIGGER_COOLDOWN_SECONDS 60

typedef struct {
    char skill_d_tag[65];
    char skill_content[4096];   // the action template or LLM prompt
    int action_type;            // 0 = llm, 1 = template
    char filter_json[2048];     // the Nostr subscription filter
    int enabled;
    time_t last_fired;
    nostr_pool_subscription_t* subscription;
} active_trigger_t;

typedef struct {
    active_trigger_t triggers[TRIGGER_MAX_ACTIVE];
    int count;
    didactyl_config_t* cfg;
    pthread_mutex_t mutex;
} trigger_manager_t;

API:

int trigger_manager_init(trigger_manager_t* mgr, didactyl_config_t* cfg);
int trigger_manager_load_from_skills(trigger_manager_t* mgr);
int trigger_manager_add(trigger_manager_t* mgr, const char* skill_d_tag, 
                        const char* content, const char* filter_json, 
                        int action_type, int enabled);
int trigger_manager_remove(trigger_manager_t* mgr, const char* skill_d_tag);
int trigger_manager_update(trigger_manager_t* mgr, const char* skill_d_tag,
                           const char* content, const char* filter_json,
                           int action_type, int enabled);
int trigger_manager_active_count(trigger_manager_t* mgr);
char* trigger_manager_status_json(trigger_manager_t* mgr);
void trigger_manager_cleanup(trigger_manager_t* mgr);

2. Template Engine

Create a simple template interpolation engine in src/trigger_manager.c (or a separate src/template.c if it grows).

Functionality:

  • Parse placeholders like {content}, {pubkey}, {author_display_name} from a template string
  • Extract values from a triggering Nostr event (cJSON object)
  • Produce an interpolated output string
  • Parse action prefixes: DM admin:, DM <pubkey>:, POST:, LOG:

3. Trigger Event Callback

When a subscribed event arrives:

static void on_trigger_event(cJSON* event, const char* relay_url, void* user_data) {
    active_trigger_t* trigger = (active_trigger_t*)user_data;
    
    // Check cooldown
    if (time(NULL) - trigger->last_fired < TRIGGER_COOLDOWN_SECONDS) return;
    trigger->last_fired = time(NULL);
    
    if (trigger->action_type == 1) {
        // Template: interpolate and execute
        char* output = template_interpolate(trigger->skill_content, event);
        template_execute_action(output);  // parse prefix, DM/POST/LOG
        free(output);
    } else {
        // LLM: build context and run agent loop
        trigger_run_llm_action(trigger, event, relay_url);
    }
}

4. Skill Loading on Startup

In main.c, after agent_init() and skill adoption list is available:

  1. Query own kind 10123 adoption list
  2. For each adopted skill address, query the skill event
  3. Check for trigger tag — if present, extract filter, action, enabled tags
  4. Register with trigger manager
  5. Trigger manager creates Nostr subscriptions

5. Extend skill_create for Live Trigger Registration

When skill_create is called with trigger tags:

  1. Publish the skill event as normal
  2. If trigger tags are present, also register with the trigger manager immediately
  3. No restart required — the subscription goes live right away

When skill_remove is called for a triggered skill:

  1. Remove from adoption list as normal
  2. Also unregister from trigger manager, tearing down the subscription

6. Extend Agent for Trigger-Initiated Conversations

The agent currently only handles DM-initiated conversations via agent_on_message(). Add a new entry point:

void agent_on_trigger(const char* skill_d_tag,
                      const char* skill_content,
                      cJSON* triggering_event,
                      const char* relay_url);

This builds an LLM conversation with:

  • System context (soul)
  • A system message explaining this is a triggered skill execution
  • The skill content as instructions
  • The triggering event as user context
  • Full tool access (same as admin tier)

The LLM response actions (DMs, posts, etc.) are executed via tools as normal.

7. New Tool: trigger_list

Add a tool so the LLM can inspect active triggers:

{
  "name": "trigger_list",
  "description": "List all active triggered skills with their filters and status",
  "parameters": { "type": "object", "properties": {} }
}

8. Config Extension

Add trigger-related limits to config:

{
  "triggers": {
    "enabled": true,
    "max_active": 16,
    "cooldown_seconds": 60,
    "llm_rate_limit_per_minute": 10,
    "template_rate_limit_per_minute": 60
  }
}

9. Integration into Main Loop

The trigger manager subscriptions are serviced by the same nostr_handler_poll() call in the main loop — no changes needed to the poll loop itself, since all subscriptions share the relay pool.


File Changes Summary

File Change
src/trigger_manager.c NEW — trigger manager, template engine, event callbacks
src/trigger_manager.h NEW — trigger manager API
src/main.c Add trigger_manager_init, trigger_manager_load_from_skills after agent_init
src/agent.c Add agent_on_trigger entry point for LLM-mediated trigger actions
src/agent.h Declare agent_on_trigger
src/tools.c Extend skill_create/skill_remove to register/unregister triggers; add trigger_list tool
src/config.h Add triggers_config_t struct
src/config.c Parse triggers config section
docs/TOOLS_AND_SKILLS.md Already written — full architecture reference

Implementation Order

  1. trigger_manager.h / trigger_manager.c — core module with data structures and API stubs
  2. Template engine — interpolation and action prefix parsing
  3. Trigger event callback — on_trigger_event with cooldown
  4. Startup loading — query adoption list, find triggered skills, create subscriptions
  5. agent_on_trigger — LLM-mediated trigger execution path
  6. Live registration — extend skill_create/skill_remove for immediate trigger management
  7. trigger_list tool — LLM visibility into active triggers
  8. Config parsing — triggers section with limits
  9. Rate limiting — enforce LLM and template rate limits
  10. Testing — manual trigger creation and verification

Dependencies

  • Existing nostr_handler_query_json() for loading skills
  • Existing nostr_relay_pool_subscribe() for creating trigger subscriptions
  • Existing agent_on_message() pattern for the LLM-mediated path
  • Existing skill_create / skill_remove tools for lifecycle hooks