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:
- Query own kind 10123 adoption list
- For each adopted skill address, query the skill event
- Check for
triggertag — if present, extractfilter,action,enabledtags - Register with trigger manager
- Trigger manager creates Nostr subscriptions
5. Extend skill_create for Live Trigger Registration
When skill_create is called with trigger tags:
- Publish the skill event as normal
- If trigger tags are present, also register with the trigger manager immediately
- No restart required — the subscription goes live right away
When skill_remove is called for a triggered skill:
- Remove from adoption list as normal
- 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
trigger_manager.h/trigger_manager.c— core module with data structures and API stubs- Template engine — interpolation and action prefix parsing
- Trigger event callback — on_trigger_event with cooldown
- Startup loading — query adoption list, find triggered skills, create subscriptions
- agent_on_trigger — LLM-mediated trigger execution path
- Live registration — extend skill_create/skill_remove for immediate trigger management
- trigger_list tool — LLM visibility into active triggers
- Config parsing — triggers section with limits
- Rate limiting — enforce LLM and template rate limits
- 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_removetools for lifecycle hooks