v0.0.43 - Unify self-authored event cache for skills and adoption list

This commit is contained in:
Your Name
2026-03-05 18:00:44 -04:00
parent 36e4998e85
commit 67a9d78f94
7 changed files with 187 additions and 124 deletions
+2 -2
View File
@@ -51,11 +51,11 @@ Agents learn capabilities through skills — Nostr events that any agent can di
Didactyl will support local inference, which is very privacy preserving. Remote inference does however have it's advantages, and in those cases Didactyl supports using Bitcoin Lightning and eCash inference providers.
## Current Status — v0.0.42
## Current Status — v0.0.43
**Active build — this project is barely working. Experiment at your own risk.**
> Last release update: v0.0.42 — Add self-skill subscription cache and make skill_list read from cache
> Last release update: v0.0.43 — Unify self-authored event cache for skills and adoption list
- Connects to configured relays with auto-reconnect and relay state transition logging
- Publishes configured startup events per relay as each relay becomes connected
+57 -44
View File
@@ -1632,25 +1632,7 @@ static int refresh_adopted_skills_cache_if_needed(void) {
memset(tmp, 0, sizeof(tmp));
int tmp_count = 0;
cJSON* adopt_filter = cJSON_CreateObject();
cJSON* kinds = cJSON_CreateArray();
cJSON* authors = cJSON_CreateArray();
if (!adopt_filter || !kinds || !authors) {
cJSON_Delete(adopt_filter);
cJSON_Delete(kinds);
cJSON_Delete(authors);
return -1;
}
cJSON_AddItemToArray(kinds, cJSON_CreateNumber(10123));
cJSON_AddItemToObject(adopt_filter, "kinds", kinds);
cJSON_AddItemToArray(authors, cJSON_CreateString(g_cfg->keys.public_key_hex));
cJSON_AddItemToObject(adopt_filter, "authors", authors);
cJSON_AddNumberToObject(adopt_filter, "limit", 1);
char* adoption_json = nostr_handler_query_json(adopt_filter, 2000);
cJSON_Delete(adopt_filter);
char* adoption_json = nostr_handler_get_self_events_by_kind_json(10123);
cJSON* adoption_events = adoption_json ? cJSON_Parse(adoption_json) : NULL;
free(adoption_json);
@@ -1680,34 +1662,65 @@ static int refresh_adopted_skills_cache_if_needed(void) {
continue;
}
cJSON* skill_filter = cJSON_CreateObject();
cJSON* sk_kinds = cJSON_CreateArray();
cJSON* sk_authors = cJSON_CreateArray();
cJSON* d_values = cJSON_CreateArray();
if (!skill_filter || !sk_kinds || !sk_authors || !d_values) {
cJSON* skill_events = NULL;
if (strcmp(skill_author, g_cfg->keys.public_key_hex) == 0) {
char* skill_json = nostr_handler_get_self_events_by_kind_json(skill_kind);
if (skill_json) {
cJSON* all_events = cJSON_Parse(skill_json);
free(skill_json);
if (all_events && cJSON_IsArray(all_events)) {
skill_events = cJSON_CreateArray();
if (skill_events) {
int all_n = cJSON_GetArraySize(all_events);
for (int ai = 0; ai < all_n; ai++) {
cJSON* ev = cJSON_GetArrayItem(all_events, ai);
cJSON* ev_pubkey = ev ? cJSON_GetObjectItemCaseSensitive(ev, "pubkey") : NULL;
cJSON* ev_tags = ev ? cJSON_GetObjectItemCaseSensitive(ev, "tags") : NULL;
cJSON* ev_d = find_tag_value_string_local(ev_tags, "d");
if (!ev_pubkey || !cJSON_IsString(ev_pubkey) || !ev_pubkey->valuestring ||
strcmp(ev_pubkey->valuestring, skill_author) != 0 ||
!ev_d || !cJSON_IsString(ev_d) || !ev_d->valuestring ||
strcmp(ev_d->valuestring, skill_slug) != 0) {
continue;
}
cJSON* dup = cJSON_Duplicate(ev, 1);
if (dup) {
cJSON_AddItemToArray(skill_events, dup);
}
}
}
}
cJSON_Delete(all_events);
}
} else {
cJSON* skill_filter = cJSON_CreateObject();
cJSON* sk_kinds = cJSON_CreateArray();
cJSON* sk_authors = cJSON_CreateArray();
cJSON* d_values = cJSON_CreateArray();
if (!skill_filter || !sk_kinds || !sk_authors || !d_values) {
cJSON_Delete(skill_filter);
cJSON_Delete(sk_kinds);
cJSON_Delete(sk_authors);
cJSON_Delete(d_values);
continue;
}
cJSON_AddItemToArray(sk_kinds, cJSON_CreateNumber(skill_kind));
cJSON_AddItemToObject(skill_filter, "kinds", sk_kinds);
cJSON_AddItemToArray(sk_authors, cJSON_CreateString(skill_author));
cJSON_AddItemToObject(skill_filter, "authors", sk_authors);
cJSON_AddItemToArray(d_values, cJSON_CreateString(skill_slug));
cJSON_AddItemToObject(skill_filter, "#d", d_values);
cJSON_AddNumberToObject(skill_filter, "limit", 1);
char* skill_json = nostr_handler_query_json(skill_filter, 2000);
cJSON_Delete(skill_filter);
cJSON_Delete(sk_kinds);
cJSON_Delete(sk_authors);
cJSON_Delete(d_values);
continue;
if (skill_json) {
skill_events = cJSON_Parse(skill_json);
free(skill_json);
}
}
cJSON_AddItemToArray(sk_kinds, cJSON_CreateNumber(skill_kind));
cJSON_AddItemToObject(skill_filter, "kinds", sk_kinds);
cJSON_AddItemToArray(sk_authors, cJSON_CreateString(skill_author));
cJSON_AddItemToObject(skill_filter, "authors", sk_authors);
cJSON_AddItemToArray(d_values, cJSON_CreateString(skill_slug));
cJSON_AddItemToObject(skill_filter, "#d", d_values);
cJSON_AddNumberToObject(skill_filter, "limit", 1);
char* skill_json = nostr_handler_query_json(skill_filter, 2000);
cJSON_Delete(skill_filter);
if (!skill_json) {
continue;
}
cJSON* skill_events = cJSON_Parse(skill_json);
free(skill_json);
if (!skill_events || !cJSON_IsArray(skill_events) || cJSON_GetArraySize(skill_events) <= 0) {
cJSON_Delete(skill_events);
continue;
+2 -2
View File
@@ -12,8 +12,8 @@
// Using DIDACTYL_ prefix to avoid conflicts with nostr_core_lib VERSION macros
#define DIDACTYL_VERSION_MAJOR 0
#define DIDACTYL_VERSION_MINOR 0
#define DIDACTYL_VERSION_PATCH 42
#define DIDACTYL_VERSION "v0.0.42"
#define DIDACTYL_VERSION_PATCH 43
#define DIDACTYL_VERSION "v0.0.43"
// Agent metadata
#define DIDACTYL_NAME "Didactyl"
+67 -15
View File
@@ -401,7 +401,7 @@ static void self_skill_cache_upsert_event_locked(cJSON* event) {
}
int kind_val = (int)kind->valuedouble;
if (kind_val != 31123 && kind_val != 31124) {
if (kind_val != 31123 && kind_val != 31124 && kind_val != 10123) {
return;
}
@@ -410,7 +410,7 @@ static void self_skill_cache_upsert_event_locked(cJSON* event) {
}
const char* d_tag = find_tag_value_local(tags, "d");
if (!d_tag || d_tag[0] == '\0') {
if ((kind_val == 31123 || kind_val == 31124) && (!d_tag || d_tag[0] == '\0')) {
return;
}
@@ -440,13 +440,16 @@ static void self_skill_cache_upsert_event_locked(cJSON* event) {
continue;
}
const char* ex_d = find_tag_value_local(ex_tags, "d");
if (!ex_d || strcmp(ex_d, d_tag) != 0) {
int ex_kind_val = (int)ex_kind->valuedouble;
if (ex_kind_val != kind_val || strcmp(ex_pubkey->valuestring, pubkey->valuestring) != 0) {
continue;
}
if ((int)ex_kind->valuedouble != kind_val || strcmp(ex_pubkey->valuestring, pubkey->valuestring) != 0) {
continue;
if (kind_val == 31123 || kind_val == 31124) {
const char* ex_d = find_tag_value_local(ex_tags, "d");
if (!ex_d || !d_tag || strcmp(ex_d, d_tag) != 0) {
continue;
}
}
cJSON* ex_created_at = cJSON_GetObjectItemCaseSensitive(existing, "created_at");
@@ -1267,6 +1270,7 @@ int nostr_handler_subscribe_self_skills(void) {
cJSON_AddItemToArray(kinds, cJSON_CreateNumber(31123));
cJSON_AddItemToArray(kinds, cJSON_CreateNumber(31124));
cJSON_AddItemToArray(kinds, cJSON_CreateNumber(10123));
cJSON_AddItemToObject(filter, "kinds", kinds);
cJSON_AddItemToArray(authors, cJSON_CreateString(g_cfg->keys.public_key_hex));
cJSON_AddItemToObject(filter, "authors", authors);
@@ -1291,7 +1295,7 @@ int nostr_handler_subscribe_self_skills(void) {
return -1;
}
DEBUG_INFO("[didactyl] self-skill subscriptions active for pubkey %.16s...", g_cfg->keys.public_key_hex);
DEBUG_INFO("[didactyl] self-event subscriptions active for pubkey %.16s...", g_cfg->keys.public_key_hex);
return 0;
}
@@ -1591,6 +1595,12 @@ static int publish_kind_event_to_relays(int kind,
relay_count,
sent);
if (sent > 0) {
pthread_mutex_lock(&g_self_skill_mutex);
self_skill_cache_upsert_event_locked(event);
pthread_mutex_unlock(&g_self_skill_mutex);
}
cJSON_Delete(event);
return sent > 0 ? 0 : -1;
}
@@ -1782,22 +1792,64 @@ const char* nostr_handler_get_system_context(void) {
return g_system_context;
}
char* nostr_handler_get_self_skill_events_json(void) {
cJSON* dup = NULL;
char* nostr_handler_get_self_events_by_kind_json(int kind) {
cJSON* out = cJSON_CreateArray();
if (!out) {
return NULL;
}
pthread_mutex_lock(&g_self_skill_mutex);
if (g_self_skill_events && cJSON_IsArray(g_self_skill_events)) {
dup = cJSON_Duplicate(g_self_skill_events, 1);
int n = cJSON_GetArraySize(g_self_skill_events);
for (int i = 0; i < n; i++) {
cJSON* ev = cJSON_GetArrayItem(g_self_skill_events, i);
cJSON* ev_kind = ev ? cJSON_GetObjectItemCaseSensitive(ev, "kind") : NULL;
if (!ev_kind || !cJSON_IsNumber(ev_kind) || (int)ev_kind->valuedouble != kind) {
continue;
}
cJSON* dup = cJSON_Duplicate(ev, 1);
if (dup) {
cJSON_AddItemToArray(out, dup);
}
}
}
pthread_mutex_unlock(&g_self_skill_mutex);
if (!dup) {
return strdup("[]");
char* json = cJSON_PrintUnformatted(out);
cJSON_Delete(out);
return json ? json : strdup("[]");
}
char* nostr_handler_get_self_skill_events_json(void) {
cJSON* out = cJSON_CreateArray();
if (!out) {
return NULL;
}
char* out = cJSON_PrintUnformatted(dup);
cJSON_Delete(dup);
return out;
pthread_mutex_lock(&g_self_skill_mutex);
if (g_self_skill_events && cJSON_IsArray(g_self_skill_events)) {
int n = cJSON_GetArraySize(g_self_skill_events);
for (int i = 0; i < n; i++) {
cJSON* ev = cJSON_GetArrayItem(g_self_skill_events, i);
cJSON* ev_kind = ev ? cJSON_GetObjectItemCaseSensitive(ev, "kind") : NULL;
if (!ev_kind || !cJSON_IsNumber(ev_kind)) {
continue;
}
int k = (int)ev_kind->valuedouble;
if (k != 31123 && k != 31124) {
continue;
}
cJSON* dup = cJSON_Duplicate(ev, 1);
if (dup) {
cJSON_AddItemToArray(out, dup);
}
}
}
pthread_mutex_unlock(&g_self_skill_mutex);
char* json = cJSON_PrintUnformatted(out);
cJSON_Delete(out);
return json ? json : strdup("[]");
}
const char* nostr_handler_get_startup_display_name(void) {
+1
View File
@@ -30,6 +30,7 @@ typedef struct {
int nostr_handler_init(didactyl_config_t* config);
int nostr_handler_subscribe_admin_context(void);
int nostr_handler_subscribe_self_skills(void);
char* nostr_handler_get_self_events_by_kind_json(int kind);
int nostr_handler_subscribe_dms(dm_callback_t callback, void* user_data);
int nostr_handler_send_dm(const char* recipient_pubkey_hex, const char* message);
int nostr_handler_send_dm_auto(const char* recipient_pubkey_hex, const char* message);
+1 -18
View File
@@ -893,24 +893,7 @@ static int fetch_adoption_list_tags(tools_context_t* ctx, cJSON** out_tags, char
*out_tags = NULL;
*out_content = NULL;
cJSON* filter = cJSON_CreateObject();
cJSON* kinds = cJSON_CreateArray();
cJSON* authors = cJSON_CreateArray();
if (!filter || !kinds || !authors) {
cJSON_Delete(filter);
cJSON_Delete(kinds);
cJSON_Delete(authors);
return -1;
}
cJSON_AddItemToArray(kinds, cJSON_CreateNumber(10123));
cJSON_AddItemToObject(filter, "kinds", kinds);
cJSON_AddItemToArray(authors, cJSON_CreateString(ctx->cfg->keys.public_key_hex));
cJSON_AddItemToObject(filter, "authors", authors);
cJSON_AddNumberToObject(filter, "limit", 1);
char* events_json = nostr_handler_query_json(filter, 8000);
cJSON_Delete(filter);
char* events_json = nostr_handler_get_self_events_by_kind_json(10123);
cJSON* tags = cJSON_CreateArray();
char* content = strdup("");
+57 -43
View File
@@ -252,24 +252,7 @@ int trigger_manager_load_from_skills(trigger_manager_t* mgr) {
return -1;
}
cJSON* adopt_filter = cJSON_CreateObject();
cJSON* kinds = cJSON_CreateArray();
cJSON* authors = cJSON_CreateArray();
if (!adopt_filter || !kinds || !authors) {
cJSON_Delete(adopt_filter);
cJSON_Delete(kinds);
cJSON_Delete(authors);
return -1;
}
cJSON_AddItemToArray(kinds, cJSON_CreateNumber(10123));
cJSON_AddItemToObject(adopt_filter, "kinds", kinds);
cJSON_AddItemToArray(authors, cJSON_CreateString(mgr->cfg->keys.public_key_hex));
cJSON_AddItemToObject(adopt_filter, "authors", authors);
cJSON_AddNumberToObject(adopt_filter, "limit", 1);
char* adoption_json = nostr_handler_query_json(adopt_filter, 2000);
cJSON_Delete(adopt_filter);
char* adoption_json = nostr_handler_get_self_events_by_kind_json(10123);
if (!adoption_json) {
return 0;
}
@@ -308,34 +291,65 @@ int trigger_manager_load_from_skills(trigger_manager_t* mgr) {
continue;
}
cJSON* skill_filter = cJSON_CreateObject();
cJSON* sk_kinds = cJSON_CreateArray();
cJSON* sk_authors = cJSON_CreateArray();
cJSON* d_values = cJSON_CreateArray();
if (!skill_filter || !sk_kinds || !sk_authors || !d_values) {
cJSON* skill_events = NULL;
if (strcmp(pubkey, mgr->cfg->keys.public_key_hex) == 0) {
char* skill_json = nostr_handler_get_self_events_by_kind_json(kind);
if (skill_json) {
cJSON* all_events = cJSON_Parse(skill_json);
free(skill_json);
if (all_events && cJSON_IsArray(all_events)) {
skill_events = cJSON_CreateArray();
if (skill_events) {
int all_n = cJSON_GetArraySize(all_events);
for (int ai = 0; ai < all_n; ai++) {
cJSON* ev = cJSON_GetArrayItem(all_events, ai);
cJSON* ev_pubkey = ev ? cJSON_GetObjectItemCaseSensitive(ev, "pubkey") : NULL;
cJSON* ev_tags = ev ? cJSON_GetObjectItemCaseSensitive(ev, "tags") : NULL;
cJSON* ev_d = find_tag_value_string(ev_tags, "d");
if (!ev_pubkey || !cJSON_IsString(ev_pubkey) || !ev_pubkey->valuestring ||
strcmp(ev_pubkey->valuestring, pubkey) != 0 ||
!ev_d || !cJSON_IsString(ev_d) || !ev_d->valuestring ||
strcmp(ev_d->valuestring, slug) != 0) {
continue;
}
cJSON* dup = cJSON_Duplicate(ev, 1);
if (dup) {
cJSON_AddItemToArray(skill_events, dup);
}
}
}
}
cJSON_Delete(all_events);
}
} else {
cJSON* skill_filter = cJSON_CreateObject();
cJSON* sk_kinds = cJSON_CreateArray();
cJSON* sk_authors = cJSON_CreateArray();
cJSON* d_values = cJSON_CreateArray();
if (!skill_filter || !sk_kinds || !sk_authors || !d_values) {
cJSON_Delete(skill_filter);
cJSON_Delete(sk_kinds);
cJSON_Delete(sk_authors);
cJSON_Delete(d_values);
continue;
}
cJSON_AddItemToArray(sk_kinds, cJSON_CreateNumber(kind));
cJSON_AddItemToObject(skill_filter, "kinds", sk_kinds);
cJSON_AddItemToArray(sk_authors, cJSON_CreateString(pubkey));
cJSON_AddItemToObject(skill_filter, "authors", sk_authors);
cJSON_AddItemToArray(d_values, cJSON_CreateString(slug));
cJSON_AddItemToObject(skill_filter, "#d", d_values);
cJSON_AddNumberToObject(skill_filter, "limit", 1);
char* skill_json = nostr_handler_query_json(skill_filter, 2000);
cJSON_Delete(skill_filter);
cJSON_Delete(sk_kinds);
cJSON_Delete(sk_authors);
cJSON_Delete(d_values);
continue;
if (skill_json) {
skill_events = cJSON_Parse(skill_json);
free(skill_json);
}
}
cJSON_AddItemToArray(sk_kinds, cJSON_CreateNumber(kind));
cJSON_AddItemToObject(skill_filter, "kinds", sk_kinds);
cJSON_AddItemToArray(sk_authors, cJSON_CreateString(pubkey));
cJSON_AddItemToObject(skill_filter, "authors", sk_authors);
cJSON_AddItemToArray(d_values, cJSON_CreateString(slug));
cJSON_AddItemToObject(skill_filter, "#d", d_values);
cJSON_AddNumberToObject(skill_filter, "limit", 1);
char* skill_json = nostr_handler_query_json(skill_filter, 2000);
cJSON_Delete(skill_filter);
if (!skill_json) {
continue;
}
cJSON* skill_events = cJSON_Parse(skill_json);
free(skill_json);
if (!skill_events || !cJSON_IsArray(skill_events) || cJSON_GetArraySize(skill_events) <= 0) {
cJSON_Delete(skill_events);
continue;