From 6edad064c6466383ea9296b2deddbcb1bff1e2e1 Mon Sep 17 00:00:00 2001 From: Didactyl User Date: Mon, 6 Apr 2026 07:17:07 -0400 Subject: [PATCH] v0.2.27 - Remove markdown context snapshot logging; keep only raw provider request JSON logging and simplify context debug plumbing --- README.md | 4 +- src/agent.c | 329 ++----------------------------------------------- src/agent.h | 2 - src/http_api.c | 19 --- src/llm.c | 40 ++++++ src/main.c | 24 ---- src/main.h | 4 +- 7 files changed, 57 insertions(+), 365 deletions(-) diff --git a/README.md b/README.md index 40550f5..80b7aef 100644 --- a/README.md +++ b/README.md @@ -54,11 +54,11 @@ Skills compose by adoption-list order (`10123`) and trigger tags carry runtime e 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.2.26 +## Current Status — v0.2.27 **Active build — this project is barely working. Experiment at your own risk.** -> Last release update: v0.2.26 — Add immediate adopted-skill cache invalidation on skill events plus new skill_refresh tool and /skill_refresh support +> Last release update: v0.2.27 — Remove markdown context snapshot logging; keep only raw provider request JSON logging and simplify context debug plumbing - 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 f8aacbe..20621fa 100644 --- a/src/agent.c +++ b/src/agent.c @@ -23,22 +23,11 @@ #include "../../nostr_core_lib/nostr_core/nostr_core.h" #include "context_format.h" #include "context_roles.h" -#include "json_to_markdown.h" static didactyl_config_t* g_cfg = NULL; static tools_context_t g_tools_ctx; static struct trigger_manager* g_trigger_manager = NULL; -typedef enum { - CONTEXT_DEBUG_OFF = 0, - CONTEXT_DEBUG_INIT = 1, - CONTEXT_DEBUG_FULL = 2 -} context_debug_mode_t; - -static context_debug_mode_t g_context_debug_mode = CONTEXT_DEBUG_OFF; -static char g_context_debug_run_stamp[32] = {0}; -static int g_context_debug_turn = 0; - #define AGENT_CONTEXT_PART_NAMES_MAX 512 static char* g_context_part_names[AGENT_CONTEXT_PART_NAMES_MAX]; static int g_context_part_names_count = 0; @@ -263,8 +252,6 @@ static int append_tool_result_message(cJSON* messages, const char* tool_call_id, return 0; } -static void append_context_log(const char* sender_pubkey_hex, const char* phase, const char* context_payload); - static char* local_json_error(const char* msg) { cJSON* out = cJSON_CreateObject(); if (!out) return NULL; @@ -848,7 +835,6 @@ static int handle_slash_command(const char* sender_pubkey_hex, const char* messa message, result_json ? result_json : "", dm_payload ? dm_payload : ""); - append_context_log(sender_pubkey_hex, "direct_tool_exec", log_payload); free(log_payload); } @@ -915,125 +901,6 @@ const char* agent_classify_message_part(cJSON* msg, int idx) { return detect_context_section(role_s, content_s, idx); } -static char* render_messages_json_as_markdown(const char* messages_json) { - if (!messages_json || messages_json[0] == '\0') { - return NULL; - } - - cJSON* root = cJSON_Parse(messages_json); - if (!root || !cJSON_IsArray(root)) { - cJSON_Delete(root); - return NULL; - } - - char* out = (char*)malloc(4096); - if (!out) { - cJSON_Delete(root); - return NULL; - } - size_t cap = 4096; - size_t used = 0; - out[0] = '\0'; - - int n = cJSON_GetArraySize(root); - for (int i = 0; i < n; i++) { - cJSON* msg = cJSON_GetArrayItem(root, i); - if (!msg || !cJSON_IsObject(msg)) { - continue; - } - - cJSON* role = cJSON_GetObjectItemCaseSensitive(msg, "role"); - const char* role_s = (role && cJSON_IsString(role) && role->valuestring) ? role->valuestring : "message"; - - if (i > 0) { - if (append_textf_local(&out, &cap, &used, "\n---\n\n") != 0) { - free(out); - cJSON_Delete(root); - return NULL; - } - } - - if (append_textf_local(&out, - &cap, - &used, - "**Message %d (%s)**\n\n", - i + 1, - role_s) != 0) { - free(out); - cJSON_Delete(root); - return NULL; - } - - cJSON* field = NULL; - cJSON_ArrayForEach(field, msg) { - if (!field || !field->string) { - continue; - } - - const char* key = field->string; - if (strcmp(key, "_ts") == 0) { - continue; // Internal timestamp metadata; not useful in human-readable snapshots - } - - char* raw_value = NULL; - char* rendered_md = NULL; - - if (strcmp(key, "content") == 0 && strcmp(role_s, "tool") == 0 && cJSON_IsString(field) && field->valuestring) { - cJSON* tool_payload = cJSON_Parse(field->valuestring); - if (tool_payload && cJSON_IsObject(tool_payload)) { - cJSON* inner = cJSON_GetObjectItemCaseSensitive(tool_payload, "content"); - if (inner && cJSON_IsString(inner) && inner->valuestring) { - cJSON* inner_json = cJSON_Parse(inner->valuestring); - if (inner_json) { - cJSON_ReplaceItemInObjectCaseSensitive(tool_payload, "content", inner_json); - } - } - raw_value = cJSON_PrintUnformatted(tool_payload); - cJSON_Delete(tool_payload); - } - } - - if (!raw_value) { - if (cJSON_IsString(field) && field->valuestring) { - raw_value = strdup(field->valuestring); - } else { - raw_value = cJSON_PrintUnformatted(field); - } - } - - if (raw_value) { - rendered_md = json_to_markdown(raw_value, 8); - } - - const char* display = rendered_md ? rendered_md : (raw_value ? raw_value : ""); - if (append_textf_local(&out, - &cap, - &used, - "- **%s**: %s\n", - key, - display) != 0) { - free(rendered_md); - free(raw_value); - free(out); - cJSON_Delete(root); - return NULL; - } - - free(rendered_md); - free(raw_value); - } - - if (append_textf_local(&out, &cap, &used, "\n") != 0) { - free(out); - cJSON_Delete(root); - return NULL; - } - } - - cJSON_Delete(root); - return out; -} - static void clear_context_part_names_locked(void) { for (int i = 0; i < g_context_part_names_count; i++) { free(g_context_part_names[i]); @@ -1082,147 +949,6 @@ static __attribute__((unused)) void template_emit_hook(const char* section_name, set_context_part_name(message_index, section_name ? section_name : "context_part"); } -static void append_context_log(const char* sender_pubkey_hex, const char* phase, const char* context_payload) { - (void)sender_pubkey_hex; - - if (!phase || g_context_debug_mode == CONTEXT_DEBUG_OFF) { - return; - } - - int is_initial_phase = - (strcmp(phase, "llm_chat_with_tools_messages") == 0 || - strcmp(phase, "llm_trigger") == 0); - int is_turn_phase = - (strcmp(phase, "llm_chat_with_tools_turn") == 0 || - strcmp(phase, "llm_trigger_with_tools_turn") == 0); - - if (!is_initial_phase && !is_turn_phase) { - return; - } - if (g_context_debug_mode == CONTEXT_DEBUG_INIT && !is_initial_phase) { - return; - } - - const char* safe_payload = context_payload ? context_payload : ""; - char* turn_markdown = NULL; - if (is_turn_phase && context_payload && context_payload[0] == '[') { - turn_markdown = render_messages_json_as_markdown(context_payload); - if (turn_markdown) { - safe_payload = turn_markdown; - } - } - - int entry_len = snprintf(NULL, 0, "%s\n\n", safe_payload); - if (entry_len < 0) { - return; - } - - char* entry = (char*)malloc((size_t)entry_len + 1U); - if (!entry) { - return; - } - snprintf(entry, (size_t)entry_len + 1U, "%s\n\n", safe_payload); - - char* old_data = NULL; - size_t old_len = 0; - - FILE* in = fopen("context.log.md", "rb"); - if (in) { - if (fseek(in, 0, SEEK_END) == 0) { - long sz = ftell(in); - if (sz > 0 && fseek(in, 0, SEEK_SET) == 0) { - old_len = (size_t)sz; - old_data = (char*)malloc(old_len); - if (old_data) { - size_t n = fread(old_data, 1, old_len, in); - if (n != old_len) { - free(old_data); - old_data = NULL; - old_len = 0; - } - } else { - old_len = 0; - } - } - } - fclose(in); - } - - FILE* out = fopen("context.log.md", "wb"); - if (!out) { - free(old_data); - free(entry); - return; - } - - (void)fwrite(entry, 1, (size_t)entry_len, out); - if (old_data && old_len > 0) { - (void)fwrite(old_data, 1, old_len, out); - } - - fclose(out); - free(old_data); - - if (mkdir("context.logs", 0755) != 0 && errno != EEXIST) { - free(entry); - return; - } - - time_t now = time(NULL); - struct tm tm_info; - localtime_r(&now, &tm_info); - - if (g_context_debug_run_stamp[0] == '\0') { - strftime(g_context_debug_run_stamp, sizeof(g_context_debug_run_stamp), "%Y%m%dT%H%M%S", &tm_info); - g_context_debug_turn = 0; - } - - char snapshot_path[256] = {0}; - if (is_initial_phase) { - snprintf(snapshot_path, - sizeof(snapshot_path), - "context.logs/%s_init.md", - g_context_debug_run_stamp); - } else { - g_context_debug_turn++; - snprintf(snapshot_path, - sizeof(snapshot_path), - "context.logs/%s_t%03d.md", - g_context_debug_run_stamp, - g_context_debug_turn); - } - - FILE* snapshot = fopen(snapshot_path, "wb"); - if (snapshot) { - (void)fwrite(safe_payload, 1, strlen(safe_payload), snapshot); - fclose(snapshot); - } - - free(entry); - free(turn_markdown); -} - -int agent_set_context_debug_mode(const char* mode) { - if (!mode || mode[0] == '\0' || strcmp(mode, "off") == 0) { - g_context_debug_mode = CONTEXT_DEBUG_OFF; - } else if (strcmp(mode, "init") == 0) { - g_context_debug_mode = CONTEXT_DEBUG_INIT; - } else if (strcmp(mode, "full") == 0) { - g_context_debug_mode = CONTEXT_DEBUG_FULL; - } else { - return -1; - } - - g_context_debug_run_stamp[0] = '\0'; - g_context_debug_turn = 0; - return 0; -} - -void agent_append_context_log(const char* sender_pubkey_hex, const char* phase, const char* context_payload) { - append_context_log(sender_pubkey_hex, phase, context_payload); -} - - static __attribute__((unused)) char* build_sender_verification_text(didactyl_sender_tier_t sender_tier) { if (sender_tier == DIDACTYL_SENDER_ADMIN) { return strdup("This message has been cryptographically verified as coming from your administrator."); @@ -2053,7 +1779,6 @@ void agent_on_trigger(const char* skill_d_tag, return; } - append_context_log(g_cfg->admin.pubkey, "llm_trigger", full_markdown); context_roles_free(&roles); free(full_markdown); @@ -2070,8 +1795,6 @@ void agent_on_trigger(const char* skill_d_tag, break; } - append_context_log(g_cfg->admin.pubkey, "llm_trigger_with_tools_turn", messages_json); - llm_response_t resp; int rc = llm_chat_with_tools_messages(messages_json, tools_json, "auto", &resp); free(messages_json); @@ -2242,7 +1965,6 @@ void agent_on_message(const char* sender_pubkey_hex, if (message[0] == '/') { if (!allow_tools) { const char* denied = "{\"success\":false,\"error\":\"slash commands are disabled for this sender tier\"}"; - append_context_log(sender_pubkey_hex, "direct_tool_exec", denied); (void)nostr_handler_send_dm_auto_with_role(sender_pubkey_hex, denied, DIDACTYL_DM_HISTORY_TOOL_RESPONSE); @@ -2273,19 +1995,7 @@ void agent_on_message(const char* sender_pubkey_hex, if (!dm_context) { dm_context = strdup("You are an AI agent. Respond to the message."); } - - size_t full_len = strlen(dm_context) + strlen("\n\nuser:\n") + strlen(message) + 1U; - char* full_markdown = (char*)malloc(full_len); - if (!full_markdown) { - free(dm_context); - return; - } - snprintf(full_markdown, full_len, "%s\n\nuser:\n%s", dm_context, message); - free(dm_context); - - context_roles_t roles; - if (context_roles_split(full_markdown, &roles) != 0) { - free(full_markdown); + if (!dm_context) { return; } @@ -2294,22 +2004,18 @@ void agent_on_message(const char* sender_pubkey_hex, ? "You are responding to a web-of-trust contact. Keep the response helpful and concise. Tool use is disabled for this tier." : "You are responding in chat-only mode. Tool use is disabled."; - size_t ctx_len = strlen(roles.system_content ? roles.system_content : "") + strlen("\n\n") + strlen(tier_prefix) + 1U; + size_t ctx_len = strlen(dm_context) + strlen("\n\n") + strlen(tier_prefix) + 1U; char* system_for_chat = (char*)malloc(ctx_len); if (!system_for_chat) { - context_roles_free(&roles); - free(full_markdown); + free(dm_context); return; } - snprintf(system_for_chat, ctx_len, "%s\n\n%s", roles.system_content ? roles.system_content : "", tier_prefix); + snprintf(system_for_chat, ctx_len, "%s\n\n%s", dm_context, tier_prefix); - append_context_log(sender_pubkey_hex, "llm_chat", full_markdown); - - char* response = llm_chat(system_for_chat, roles.user_content ? roles.user_content : message); + char* response = llm_chat(system_for_chat, message); free(system_for_chat); - context_roles_free(&roles); - free(full_markdown); + free(dm_context); if (!response) { const char* fallback = "I could not get a response from the LLM right now."; @@ -2328,20 +2034,18 @@ void agent_on_message(const char* sender_pubkey_hex, char* tools_json = tools_build_openai_schema_json(&g_tools_ctx); if (!tools_json) { - context_roles_free(&roles); - free(full_markdown); + free(dm_context); (void)nostr_handler_send_dm_auto(sender_pubkey_hex, "Tool schema generation failed."); return; } cJSON* messages = cJSON_CreateArray(); if (!messages || - append_simple_message(messages, "system", roles.system_content ? roles.system_content : "You are an AI agent. Respond to the message.") != 0 || - append_simple_message(messages, "user", roles.user_content ? roles.user_content : message) != 0) { + append_simple_message(messages, "system", dm_context) != 0 || + append_simple_message(messages, "user", message) != 0) { cJSON_Delete(messages); free(tools_json); - context_roles_free(&roles); - free(full_markdown); + free(dm_context); (void)nostr_handler_send_dm_auto(sender_pubkey_hex, "Failed to initialize conversation messages."); return; } @@ -2360,8 +2064,6 @@ void agent_on_message(const char* sender_pubkey_hex, char* final_answer_owned = NULL; int turns_run = 0; - append_context_log(sender_pubkey_hex, "llm_chat_with_tools_messages", full_markdown); - for (int turn = 0; turn < max_turns; turn++) { turns_run = turn + 1; char* messages_json = cJSON_PrintUnformatted(messages); @@ -2369,8 +2071,6 @@ void agent_on_message(const char* sender_pubkey_hex, break; } - append_context_log(sender_pubkey_hex, "llm_chat_with_tools_turn", messages_json); - llm_response_t resp; int rc = llm_chat_with_tools_messages(messages_json, tools_json, "auto", &resp); free(messages_json); @@ -2378,8 +2078,7 @@ void agent_on_message(const char* sender_pubkey_hex, (void)nostr_handler_send_dm_auto(sender_pubkey_hex, "LLM request failed."); cJSON_Delete(messages); free(tools_json); - context_roles_free(&roles); - free(full_markdown); + free(dm_context); return; } @@ -2432,8 +2131,7 @@ void agent_on_message(const char* sender_pubkey_hex, llm_response_free(&resp); cJSON_Delete(messages); free(tools_json); - context_roles_free(&roles); - free(full_markdown); + free(dm_context); (void)nostr_handler_send_dm_auto(sender_pubkey_hex, "Failed to append tool result."); return; } @@ -2490,8 +2188,7 @@ void agent_on_message(const char* sender_pubkey_hex, free(final_answer_owned); cJSON_Delete(messages); free(tools_json); - context_roles_free(&roles); - free(full_markdown); + free(dm_context); } void agent_cleanup(void) { diff --git a/src/agent.h b/src/agent.h index 17d6af1..fee40ac 100644 --- a/src/agent.h +++ b/src/agent.h @@ -23,8 +23,6 @@ int agent_build_admin_messages_json(const char* current_user_message, char** out_messages_json); tools_context_t* agent_tools_context(void); const char* agent_classify_message_part(cJSON* msg, int idx); -int agent_set_context_debug_mode(const char* mode); -void agent_append_context_log(const char* sender_pubkey_hex, const char* phase, const char* context_payload); void agent_invalidate_adopted_skills_cache(void); void agent_cleanup(void); diff --git a/src/http_api.c b/src/http_api.c index 479b79d..13ba084 100644 --- a/src/http_api.c +++ b/src/http_api.c @@ -882,18 +882,6 @@ static char* execute_slash_command_http(const char* message) { } char* markdown_result = format_result_markdown_http(result_json); - const char* dm_payload = markdown_result ? markdown_result : result_json; - - size_t log_cap = strlen(message) + strlen(result_json ? result_json : "") + strlen(dm_payload ? dm_payload : "") + 192U; - char* log_payload = (char*)malloc(log_cap); - if (log_payload) { - snprintf(log_payload, log_cap, "slash=%s\nresult_json=%s\nresult_markdown=%s", - message, - result_json ? result_json : "", - dm_payload ? dm_payload : ""); - agent_append_context_log("http_api_agent", "direct_tool_exec", log_payload); - free(log_payload); - } free(result_json); return markdown_result ? markdown_result : strdup("Command executed with no output."); @@ -902,7 +890,6 @@ static char* execute_slash_command_http(const char* message) { static cJSON* run_prompt_with_tools_convo(cJSON* convo, int max_turns, const char* log_sender, - const char* log_phase, const char* tool_limit_message) { if (!convo || !cJSON_IsArray(convo)) return NULL; @@ -945,10 +932,6 @@ static cJSON* run_prompt_with_tools_convo(cJSON* convo, char* messages_json = cJSON_PrintUnformatted(convo); if (!messages_json) break; - agent_append_context_log(log_sender ? log_sender : "http_api", - log_phase ? log_phase : "llm_chat_with_tools_messages_http_api", - messages_json); - llm_response_t resp; int rc = llm_chat_with_tools_messages(messages_json, tools_json, "auto", &resp); free(messages_json); @@ -1115,7 +1098,6 @@ static cJSON* run_prompt_with_tools(cJSON* body) { cJSON* root = run_prompt_with_tools_convo(convo, max_turns, "http_api", - "llm_chat_with_tools_messages_http_api", "I hit my tool-use limit for this prompt run."); cJSON_Delete(convo); return root; @@ -1379,7 +1361,6 @@ static void handle_prompt_agent(struct mg_connection* c, const struct mg_http_me result = run_prompt_with_tools_convo(convo, max_turns, "http_api_agent", - "llm_chat_with_tools_messages_agent_api", "I hit my tool-use limit for this request."); cJSON_Delete(convo); diff --git a/src/llm.c b/src/llm.c index f676b7b..79fe6ef 100644 --- a/src/llm.c +++ b/src/llm.c @@ -7,6 +7,10 @@ #include #include #include +#include +#include +#include +#include #include "cjson/cJSON.h" #include "debug.h" @@ -15,6 +19,38 @@ static llm_config_t g_cfg; static int g_initialized = 0; +static pthread_mutex_t g_llm_request_log_mutex = PTHREAD_MUTEX_INITIALIZER; +static unsigned long g_llm_request_log_seq = 0; + +static void log_provider_request_body_local(const char* body) { + if (!body || body[0] == '\0') { + return; + } + + if (mkdir("context.logs", 0755) != 0 && errno != EEXIST) { + return; + } + + char stamp[32] = {0}; + time_t now = time(NULL); + struct tm tm_info; + localtime_r(&now, &tm_info); + strftime(stamp, sizeof(stamp), "%Y%m%dT%H%M%S", &tm_info); + + pthread_mutex_lock(&g_llm_request_log_mutex); + unsigned long seq = ++g_llm_request_log_seq; + pthread_mutex_unlock(&g_llm_request_log_mutex); + + char path[256] = {0}; + snprintf(path, sizeof(path), "context.logs/%s_req%04lu.json", stamp, seq); + + FILE* out = fopen(path, "wb"); + if (!out) { + return; + } + (void)fwrite(body, 1, strlen(body), out); + fclose(out); +} static int url_looks_like_websocket(const char* url) { if (!url) return 0; @@ -82,6 +118,10 @@ static char* perform_http_request(const char* url, const char* body, int is_post DEBUG_INFO("[didactyl] llm request: method=GET url=%s", url); } + if (is_post && body) { + log_provider_request_body_local(body); + } + nostr_http_response_t resp; int rc = nostr_http_request(&req, &resp); if (rc != NOSTR_SUCCESS) { diff --git a/src/main.c b/src/main.c index aafffb7..c35b9a8 100644 --- a/src/main.c +++ b/src/main.c @@ -107,8 +107,6 @@ static void print_usage(const char* prog) { " Enable HTTP API and bind address (example: 127.0.0.1).\n" " --debug <0-5>\n" " Log level (0=fatal, 1=error, 2=warn, 3=info, 4=debug, 5=trace).\n" - " --context-debug \n" - " Context snapshot mode (off=none, init=initial only, full=initial+every turn).\n" " --dump-schemas\n" " Print tool schemas JSON and exit.\n" " --test-tool \n" @@ -117,8 +115,6 @@ static void print_usage(const char* prog) { "Environment:\n" " DIDACTYL_NSEC\n" " Fallback private key when --nsec is not provided.\n" - " DIDACTYL_CONTEXT_DEBUG\n" - " Context snapshot mode override: off, init, or full.\n" "\n" "Examples:\n" " 1) Genesis-based startup\n" @@ -914,8 +910,6 @@ int main(int argc, char** argv) { int dump_schemas = 0; const char* test_tool_name = NULL; const char* test_tool_args = "{}"; - const char* context_debug_mode = "off"; - didactyl_config_t cfg; memset(&cfg, 0, sizeof(cfg)); int config_preloaded = 0; @@ -979,8 +973,6 @@ int main(int argc, char** argv) { api_port_override = atoi(argv[++i]); } else if (strcmp(argv[i], "--api-bind") == 0 && i + 1 < argc) { api_bind_override = argv[++i]; - } else if (strcmp(argv[i], "--context-debug") == 0 && i + 1 < argc) { - context_debug_mode = argv[++i]; } else if (strcmp(argv[i], "--dump-schemas") == 0) { dump_schemas = 1; } else if (strcmp(argv[i], "--test-tool") == 0 && i + 2 < argc) { @@ -1043,22 +1035,6 @@ int main(int argc, char** argv) { DEBUG_INFO("[didactyl] startup phase: admin override applied from --admin (%.16s...)", cfg.admin.pubkey); } - { - const char* env_context_debug = getenv("DIDACTYL_CONTEXT_DEBUG"); - if (env_context_debug && env_context_debug[0] != '\0' && - strcmp(context_debug_mode, "off") == 0) { - context_debug_mode = env_context_debug; - } - - if (agent_set_context_debug_mode(context_debug_mode) != 0) { - fprintf(stderr, "Invalid context debug mode '%s' (expected off, init, or full)\n", context_debug_mode); - config_free(&cfg); - nostr_cleanup(); - return 1; - } - DEBUG_INFO("[didactyl] context debug mode: %s", context_debug_mode); - } - if (config_ensure_startup_skill_adoption(&cfg) != 0) { fprintf(stderr, "Failed to synthesize startup skill adoption events\n"); config_free(&cfg); diff --git a/src/main.h b/src/main.h index d860ba8..9b43920 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 2 -#define DIDACTYL_VERSION_PATCH 26 -#define DIDACTYL_VERSION "v0.2.26" +#define DIDACTYL_VERSION_PATCH 27 +#define DIDACTYL_VERSION "v0.2.27" // Agent metadata #define DIDACTYL_NAME "Didactyl"