From 3eddc032c1d12962cafd8468193dd9fbcb3b34c2 Mon Sep 17 00:00:00 2001 From: Your Name Date: Mon, 9 Mar 2026 18:31:15 -0400 Subject: [PATCH] v0.0.59 - Use tool-capable multi-turn execution for triggered skills --- README.md | 4 +-- src/agent.c | 99 +++++++++++++++++++++++++++++++++++++++++++++++------ src/main.h | 4 +-- 3 files changed, 92 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 83180c1..2e114c5 100644 --- a/README.md +++ b/README.md @@ -53,11 +53,11 @@ Skills support context modes (`inject`, `full`, `override`) and per-skill LLM fa 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.58 +## Current Status — v0.0.59 **Active build — this project is barely working. Experiment at your own risk.** -> Last release update: v0.0.58 — Disable pool dedup on trigger subscriptions to restore kind-1 firing +> Last release update: v0.0.59 — Use tool-capable multi-turn execution for triggered skills - Connects to configured relays with auto-reconnect and relay state transition logging - Publishes configured startup events per relay as each relay becomes connected diff --git a/src/agent.c b/src/agent.c index d7bba57..1065d8d 100644 --- a/src/agent.c +++ b/src/agent.c @@ -1962,20 +1962,97 @@ void agent_on_trigger(const char* skill_slug, snprintf(user_prompt, user_len, "Triggering event JSON:\n%s", event_json); free(event_json); - append_context_log(g_cfg->admin.pubkey, "llm_trigger", user_prompt); - - char* response = llm_chat(system_prompt, user_prompt); - free(system_prompt); - free(user_prompt); - - if (!response) { - const char* fallback = "Triggered skill execution failed: LLM unavailable."; - (void)nostr_handler_send_dm_auto(g_cfg->admin.pubkey, fallback); + char* tools_json = tools_build_openai_schema_json(&g_tools_ctx); + if (!tools_json) { + free(system_prompt); + free(user_prompt); + (void)nostr_handler_send_dm_auto(g_cfg->admin.pubkey, "Triggered skill execution failed: tools unavailable."); return; } - (void)nostr_handler_send_dm_auto(g_cfg->admin.pubkey, response); - free(response); + cJSON* messages = cJSON_CreateArray(); + if (!messages || !cJSON_IsArray(messages) || + append_simple_message(messages, "system", system_prompt) != 0 || + append_simple_message(messages, "user", user_prompt) != 0) { + cJSON_Delete(messages); + free(tools_json); + free(system_prompt); + free(user_prompt); + (void)nostr_handler_send_dm_auto(g_cfg->admin.pubkey, "Triggered skill execution failed: prompt initialization failed."); + return; + } + + append_context_log(g_cfg->admin.pubkey, "llm_trigger", user_prompt); + free(system_prompt); + free(user_prompt); + + int max_turns = g_cfg->tools.max_turns > 0 ? g_cfg->tools.max_turns : 6; + char* final_answer_owned = NULL; + + for (int turn = 0; turn < max_turns; turn++) { + char* messages_json = cJSON_PrintUnformatted(messages); + if (!messages_json) { + break; + } + + append_context_log(g_cfg->admin.pubkey, "llm_trigger_with_tools_messages", messages_json); + + llm_response_t resp; + int rc = llm_chat_with_tools_messages(messages_json, tools_json, "auto", &resp); + free(messages_json); + if (rc != 0) { + (void)nostr_handler_send_dm_auto(g_cfg->admin.pubkey, "Triggered skill execution failed: LLM request failed."); + cJSON_Delete(messages); + free(tools_json); + return; + } + + if (resp.tool_call_count <= 0) { + const char* answer = resp.content ? resp.content : "No response content."; + final_answer_owned = strdup(answer); + llm_response_free(&resp); + break; + } + + if (append_assistant_tool_calls_message(messages, &resp) != 0) { + llm_response_free(&resp); + break; + } + + for (int i = 0; i < resp.tool_call_count; i++) { + llm_tool_call_t* tc = &resp.tool_calls[i]; + char* tool_result = tools_execute(&g_tools_ctx, tc->name, tc->arguments_json); + if (!tool_result) { + tool_result = strdup("{\"success\":false,\"error\":\"tool execution failed\"}"); + } + + if (append_tool_result_message(messages, + tc->id ? tc->id : "", + tool_result ? tool_result : "{\"success\":false,\"error\":\"tool execution failed\"}") != 0) { + free(tool_result); + llm_response_free(&resp); + cJSON_Delete(messages); + free(tools_json); + (void)nostr_handler_send_dm_auto(g_cfg->admin.pubkey, "Triggered skill execution failed: tool result append failed."); + return; + } + + free(tool_result); + } + + llm_response_free(&resp); + } + + if (!final_answer_owned) { + final_answer_owned = strdup("I hit my tool-use limit for this trigger."); + } + + const char* final_answer = final_answer_owned ? final_answer_owned : "I hit my tool-use limit for this trigger."; + (void)nostr_handler_send_dm_auto(g_cfg->admin.pubkey, final_answer); + + free(final_answer_owned); + cJSON_Delete(messages); + free(tools_json); } int agent_build_admin_messages_json(const char* current_user_message, diff --git a/src/main.h b/src/main.h index f8feba7..b834fbe 100644 --- a/src/main.h +++ b/src/main.h @@ -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 58 -#define DIDACTYL_VERSION "v0.0.58" +#define DIDACTYL_VERSION_PATCH 59 +#define DIDACTYL_VERSION "v0.0.59" // Agent metadata #define DIDACTYL_NAME "Didactyl"