From 10fe8fdde0d573ca45b3ae00774e79e7e1a4bb8f Mon Sep 17 00:00:00 2001 From: Your Name Date: Thu, 5 Mar 2026 06:21:00 -0400 Subject: [PATCH] v0.0.30 - Add config-controlled dm_protocol (nip04/nip17/both), NIP-17 receive handling, protocol-aware auto DM routing, and config updates --- Dockerfile.alpine-musl | 2 +- Makefile | 2 +- README.md | 29 +- chat-didactyl-cli.js | 150 + config.json.example | 22 +- context.log.md | 8966 +++++++++++++++++++++++++++++++ context_template.md | 57 + didactyl.html | 1272 +++++ docs/API.md | 57 +- local_test_servers.py | 71 + plans/agent_tasks.md | 215 + plans/context_optimization.md | 167 + plans/nip17_messaging.md | 194 + plans/tool_orchestration.md | 360 ++ plans/unified_prompt_context.md | 158 + src/agent.c | 1307 ++++- src/agent.h | 5 +- src/config.c | 34 + src/config.h | 7 + src/http_api.c | 640 ++- src/llm.c | 19 + src/main.c | 2 +- src/main.h | 4 +- src/mongoose.c | 6 +- src/nostr_handler.c | 374 +- src/nostr_handler.h | 1 + src/prompt_template.c | 22 + src/prompt_template.h | 1 + src/tools.c | 442 ++ src/trigger_manager.c | 4 +- tasks.json | 1 + 31 files changed, 14373 insertions(+), 218 deletions(-) create mode 100755 chat-didactyl-cli.js create mode 100644 context.log.md create mode 100644 context_template.md create mode 100644 didactyl.html create mode 100644 local_test_servers.py create mode 100644 plans/agent_tasks.md create mode 100644 plans/context_optimization.md create mode 100644 plans/nip17_messaging.md create mode 100644 plans/tool_orchestration.md create mode 100644 plans/unified_prompt_context.md create mode 100644 tasks.json diff --git a/Dockerfile.alpine-musl b/Dockerfile.alpine-musl index 097b303..cd3e51a 100644 --- a/Dockerfile.alpine-musl +++ b/Dockerfile.alpine-musl @@ -90,7 +90,7 @@ RUN if [ "$DEBUG_BUILD" = "true" ]; then \ CURL_LIBS="$(pkg-config --static --libs libcurl)" && \ OPENSSL_LIBS="$(pkg-config --static --libs openssl)" && \ gcc -static $CFLAGS -Wall -Wextra -std=c99 \ - -D_GNU_SOURCE -D_DEFAULT_SOURCE -D_POSIX_C_SOURCE=200809L \ + -D_GNU_SOURCE -D_DEFAULT_SOURCE -D_POSIX_C_SOURCE=200809L -DMG_TLS=MG_TLS_BUILTIN \ -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0 \ -I. -Isrc -Inostr_core_lib -Inostr_core_lib/nostr_core \ -Inostr_core_lib/cjson -Inostr_core_lib/nostr_websocket \ diff --git a/Makefile b/Makefile index d57b568..c167220 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ CC = gcc -CFLAGS = -std=c99 -Wall -Wextra -Wpedantic -O2 -D_GNU_SOURCE -D_DEFAULT_SOURCE -D_POSIX_C_SOURCE=200809L +CFLAGS = -std=c99 -Wall -Wextra -Wpedantic -O2 -D_GNU_SOURCE -D_DEFAULT_SOURCE -D_POSIX_C_SOURCE=200809L -DMG_TLS=MG_TLS_BUILTIN SRC_DIR = src TARGET = didactyl diff --git a/README.md b/README.md index 7ca3ad2..16a9876 100644 --- a/README.md +++ b/README.md @@ -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.29 +## Current Status — v0.0.30 **Active build — this project is barely working. Experiment at your own risk.** -> Last release update: v0.0.29 — Update README: current status, runtime context model, project structure, HTTP admin API section, model tools, roadmap checkboxes +> Last release update: v0.0.30 — Add config-controlled dm_protocol (nip04/nip17/both), NIP-17 receive handling, protocol-aware auto DM routing, and config updates - Connects to configured relays with auto-reconnect and relay state transition logging - Publishes configured startup events per relay as each relay becomes connected @@ -202,6 +202,31 @@ CLI debugger notes: Send an encrypted DM to the agent pubkey using any Nostr client (Damus, Amethyst, Primal, etc.): ADMIN gets full tool-enabled responses, WoT contacts get chat-only responses, and strangers are handled by `security.tiers.stranger` + `security.stranger_response`. +### Chat via local HTTP API (CLI) + +A simple Node.js terminal client is available in [`didactyl-chat-cli.js`](didactyl-chat-cli.js). + +Run it with: + +```bash +node ./didactyl-chat-cli.js +``` + +Optional environment variables: + +- `DIDACTYL_API_BASE_URL` (default: `https://127.0.0.1:8484`) +- `DIDACTYL_MODEL` (optional model override) +- `DIDACTYL_MAX_TURNS` (default: `4`) +- `DIDACTYL_INSECURE_TLS` (default: `1`, set `0` to enforce certificate verification) + +Example: + +```bash +DIDACTYL_API_BASE_URL=http://127.0.0.1:8484 DIDACTYL_MAX_TURNS=6 node ./didactyl-chat-cli.js +``` + +The CLI prints each message block with a speaker label (`You` / `Didactyl`) and a blank line between blocks for readability. + ## Architecture ``` diff --git a/chat-didactyl-cli.js b/chat-didactyl-cli.js new file mode 100755 index 0000000..04ef461 --- /dev/null +++ b/chat-didactyl-cli.js @@ -0,0 +1,150 @@ +#!/usr/bin/env node + +/** + * Simple terminal chat client for Didactyl HTTP API. + * + * Usage: + * node didactyl-chat-cli.js + * + * Optional env vars: + * DIDACTYL_API_BASE_URL=http://127.0.0.1:8484 + * DIDACTYL_MODEL=claude-haiku-4.5 + * DIDACTYL_MAX_TURNS=4 + */ + +const readline = require("node:readline/promises"); +const { stdin, stdout } = require("node:process"); +const http = require("node:http"); +const https = require("node:https"); + +const API_BASE_URL = process.env.DIDACTYL_API_BASE_URL || "https://127.0.0.1:8484"; +const MODEL = process.env.DIDACTYL_MODEL || ""; +const MAX_TURNS = Number.parseInt(process.env.DIDACTYL_MAX_TURNS || "4", 10); +const INSECURE_TLS = !["0", "false", "False", "FALSE"].includes( + String(process.env.DIDACTYL_INSECURE_TLS || "1") +); + +function printMessage(role, content) { + const who = role === "user" ? "You" : role === "assistant" ? "Didactyl" : role; + console.log(`${who}>`); + console.log(content); + console.log(""); +} + +function postJson(urlString, payload) { + const url = new URL(urlString); + const isHttps = url.protocol === "https:"; + const data = JSON.stringify(payload); + + const options = { + method: "POST", + hostname: url.hostname, + port: url.port, + path: url.pathname + url.search, + headers: { + "Content-Type": "application/json", + "Content-Length": Buffer.byteLength(data), + }, + }; + + if (isHttps) { + options.rejectUnauthorized = !INSECURE_TLS; + } + + const client = isHttps ? https : http; + + return new Promise((resolve, reject) => { + const req = client.request(options, (res) => { + let body = ""; + res.setEncoding("utf8"); + res.on("data", (chunk) => { + body += chunk; + }); + res.on("end", () => { + resolve({ + statusCode: res.statusCode || 0, + body, + }); + }); + }); + + req.on("error", reject); + req.write(data); + req.end(); + }); +} + +async function callDidactyl(message) { + const body = { + message, + max_turns: Number.isFinite(MAX_TURNS) ? MAX_TURNS : 4, + }; + + if (MODEL.trim()) { + body.model = MODEL.trim(); + } + + const { statusCode, body: responseBody } = await postJson(`${API_BASE_URL}/api/prompt/agent`, body); + + if (statusCode < 200 || statusCode >= 300) { + throw new Error(`HTTP ${statusCode}: ${responseBody}`); + } + + let data; + try { + data = JSON.parse(responseBody); + } catch { + throw new Error(`Invalid JSON from API: ${responseBody}`); + } + + if (!data.success) { + throw new Error(data.error || "Didactyl API returned success=false"); + } + + return String(data.final_response || ""); +} + +async function main() { + console.log("Didactyl CLI chat"); + console.log(`API: ${API_BASE_URL}`); + console.log(`TLS verify: ${INSECURE_TLS ? "disabled (local dev)" : "enabled"}`); + console.log("Type /exit to quit.\n"); + + const rl = readline.createInterface({ input: stdin, output: stdout }); + + try { + while (true) { + const input = (await rl.question("You> ")).trim(); + + if (!input) { + console.log(""); + continue; + } + + if (input === "/exit" || input === "/quit") { + console.log("Exiting."); + break; + } + + console.log(""); + + try { + const reply = await callDidactyl(input); + printMessage("assistant", reply); + console.log(""); + } catch (err) { + const message = err instanceof Error ? err.message : String(err); + console.error(`Didactyl error: ${message}`); + console.error(""); + } + } + } finally { + rl.close(); + } +} + +main().catch((err) => { + const message = err instanceof Error ? err.message : String(err); + console.error(`Fatal error: ${message}`); + process.exit(1); +}); diff --git a/config.json.example b/config.json.example index 4355474..f0eaf0a 100644 --- a/config.json.example +++ b/config.json.example @@ -8,6 +8,7 @@ "admin": { "pubkey": "admin pubkey" }, + "dm_protocol": "nip04", "llm": { "provider": "", "api_key": "", @@ -63,8 +64,6 @@ "kind": 10002, "content": "", "tags": [ - - [ "r", "wss://relay.damus.io" @@ -81,7 +80,20 @@ "r", "ws://127.0.0.1:7777" ] - + ] + }, + { + "kind": 10050, + "content": "", + "tags": [ + [ + "relay", + "wss://relay.damus.io" + ], + [ + "relay", + "wss://nos.lol" + ] ] }, { @@ -105,7 +117,7 @@ }, { "kind": 31120, - "content": "# Didactyl Agent\n\nYou are Didactyl, a sovereign AI agent living on Nostr.\n\n## Communication Rules\n- You communicate through encrypted Nostr direct messages.\n- Keep responses concise and clear.\n\n## Behavior\n- Be helpful and technically accurate.\n- If unsure, state uncertainty directly.\n- Prefer actionable, practical advice.\n- Use the person's name when messaging them if you know it.\n- For the administrator, use their name from the administrator kind 0 profile metadata when available.\n\n## Tool Use Policy\n- You have tools available and should use them when a request requires taking action.\n- For requests involving local inspection or command execution, call `shell_exec` instead of refusing.\n- For posting to Nostr, call `nostr_post` with explicit `kind` and `content`.\n- For relay/event lookup tasks, call `nostr_query` with an appropriate filter.\n- After a tool call, base your answer on the actual tool result.\n- Never claim a tool was run if no tool was executed.\n\n## Safety\n- Do not claim to have executed actions you did not execute.\n- You may share your public key (npub) with anyone.\n- Never reveal your private key (nsec) under any circumstance.\n\n---template---\n\n- section: admin_identity\n role: system\n content: |\n ## Administrator Identity (source: config.admin.pubkey)\n\n This is your administrator! Admin pubkey (hex): {{admin_pubkey}}\n\n- section: admin_profile\n role: system\n content: |\n ## Administrator Kind 0 Profile (source: nostr kind 0)\n\n Administrator kind 0 profile content (JSON): {{admin_kind0_json}}\n provider:\n anthropic: |\n \n {{admin_kind0_json}}\n \n\n- section: admin_relay_list\n role: system\n content: |\n ## Administrator Relay List (source: nostr kind 10002)\n\n Administrator kind 10002 relay-list content (JSON): {{admin_kind10002_json}}\n\n- section: startup_events\n role: system\n content: |\n ## Startup Events Memory (source: config.startup_events)\n\n Startup events memory (kinds/content/tags): {{startup_events_json}}\n\n- section: adopted_skills\n role: system\n content: |\n {{adopted_skills_content}}\n\n- section: dm_history\n role: expand\n limit: 12\n\n- section: admin_notes\n role: system\n content: |\n ## Administrator Recent Notes (source: nostr kind 1)\n\n {{admin_notes_content}}", + "content": "# Didactyl Agent\n\nYou are Didactyl, a sovereign AI agent living on Nostr.\n\n## Communication Rules\n- You communicate through encrypted Nostr direct messages.\n- Keep responses concise and clear.\n\n## Behavior\n- Be helpful and technically accurate.\n- If unsure, state uncertainty directly.\n- Prefer actionable, practical advice.\n- Use the person's name when messaging them if you know it.\n- For the administrator, use their name from the administrator kind 0 profile metadata when available.\n\n## Tool Use Policy\n- You have tools available and should use them when a request requires taking action.\n- For requests involving local inspection or command execution, call `shell_exec` instead of refusing.\n- For posting to Nostr, call `nostr_post` with explicit `kind` and `content`.\n- For relay/event lookup tasks, call `nostr_query` with an appropriate filter.\n- After a tool call, base your answer on the actual tool result.\n- Never claim a tool was run if no tool was executed.\n\n## Task Management\n- Maintain and use your internal task list as short-term working memory.\n- Break long or complex actions into clear tasks before executing them.\n- Update task status as you complete steps so your plan stays accurate.\n\n## Safety\n- Do not claim to have executed actions you did not execute.\n- You may share your public key (npub) with anyone.\n- Never reveal your private key (nsec) under any circumstance.\n\n---template---\n\n- section: admin_identity\n role: system\n content: |\n ## Administrator Identity (source: config.admin.pubkey)\n\n This is your administrator! Admin pubkey (hex): {{admin_pubkey}}\n\n- section: admin_profile\n role: system\n content: |\n ## Administrator Kind 0 Profile (source: nostr kind 0)\n\n Administrator kind 0 profile content (JSON): {{admin_kind0_json}}\n provider:\n anthropic: |\n \n {{admin_kind0_json}}\n \n\n- section: admin_relay_list\n role: system\n content: |\n ## Administrator Relay List (source: nostr kind 10002)\n\n Administrator kind 10002 relay-list content (JSON): {{admin_kind10002_json}}\n\n- section: startup_events\n role: system\n content: |\n ## Startup Events Memory (source: config.startup_events)\n\n Startup events memory (kinds/content/tags): {{startup_events_json}}\n\n- section: adopted_skills\n role: system\n content: |\n {{adopted_skills_content}}\n\n- section: agent_tasks\n role: system\n content: |\n {{tasks_content}}\n\n- section: dm_history\n role: expand\n limit: 12\n\n- section: admin_notes\n role: system\n content: |\n ## Administrator Recent Notes (source: nostr kind 1)\n\n {{admin_notes_content}}", "tags": [ [ "d", @@ -256,4 +268,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/context.log.md b/context.log.md new file mode 100644 index 0000000..38bd86f --- /dev/null +++ b/context.log.md @@ -0,0 +1,8966 @@ +```text +Context Log - not seen by model +timestamp=2026-03-04 11:42:23 +phase=llm_chat_with_tools_messages +sender=8ff74724ed641b3c28e5a86d7c5cbc49c37638ace8c6c38935860e7a5eedde0e +model=claude-haiku-4.5 +context_bytes=10261 +approx_tokens=2565 +``` + +Sections: 12 + +# system_prompt | role=system + +## Didactyl Agent + +You are Didactyl, a sovereign AI agent living on Nostr. + +### Communication Rules +- You communicate through encrypted Nostr direct messages. +- Keep responses concise and clear. + +### Behavior +- Be helpful and technically accurate. +- If unsure, state uncertainty directly. +- Prefer actionable, practical advice. +- Use the person's name when messaging them if you know it. +- For the administrator, use their name from the administrator kind 0 profile metadata when available. + +### Tool Use Policy +- You have tools available and should use them when a request requires taking action. +- For requests involving local inspection or command execution, call `shell_exec` instead of refusing. +- For posting to Nostr, call `nostr_post` with explicit `kind` and `content`. +- For relay/event lookup tasks, call `nostr_query` with an appropriate filter. +- After a tool call, base your answer on the actual tool result. +- Never claim a tool was run if no tool was executed. + +### Task Management +- Maintain and use your internal task list as short-term working memory. +- Break long or complex actions into clear tasks before executing them. +- Update task status as you complete steps so your plan stays accurate. + +### Safety +- Do not claim to have executed actions you did not execute. +- You may share your public key (npub) with anyone. +- Never reveal your private key (nsec) under any circumstance. + +# admin_identity | role=system + +### Administrator Identity (source: config.admin.pubkey) + +This is your administrator! Admin pubkey (hex): 8ff74724ed641b3c28e5a86d7c5cbc49c37638ace8c6c38935860e7a5eedde0e + +# admin_profile | role=system + +### Administrator Kind 0 Profile (source: nostr kind 0) + +Administrator kind 0 profile content (JSON): {"name":"William S Burroughs","display_name":"WSB","about":"I like to write, and I like crank. They are good. Sometimes I'm not so sure.","banner":"https://www.grunge.com/img/gallery/paul-mccartney-and-william-s-burroughs-relationship-explained/who-was-william-s-burroughs-1672155531.jpg","website":"https://addicted.com","picture":"https://hips.hearstapps.com/hmg-prod/images/william-s-burroughs.jpg?resize=1200:*","lud16":"","nip05":""} + +# admin_relay_list | role=system + +### Administrator Relay List (source: nostr kind 10002) + +Administrator kind 10002 relay-list content (JSON): ["wss://relay.laantungir.net/","wss://relay.damus.io/","wss://nos.lol/","wss://relay.nostr.band/","wss://purplepag.es/","ws://127.0.0.1:7777/","wss://nostr.mom/"] + +# startup_events | role=system + +### Startup Events Memory (source: config.startup_events) + +Startup events memory (kinds/content/tags): [{"kind":0,"content":"{\"name\":\"Didactyl Agent\",\"display_name\":\"Didactyl\",\"about\":\"A sovereign AI agent on Nostr\",\"picture\":\"https://laantungir.github.io/img_repo/daf95a99f3797fa4ac39f3791f377ad79bcb7b8a6868f75fe66d2ab4af4bd1f5.png\",\"banner\":\"https://laantungir.github.io/img_repo/d21c4060632ab3d9d37a6062eeecbdbfbd67f03cb20dbd64838b1ba0d9cd8922.jpg\"}","tags":[]},{"kind":10002,"content":"","tags":[["r","wss://relay.damus.io"],["r","wss://nos.lol"],["r","wss://relay.primal.net"],["r","ws://127.0.0.1:7777"]]},{"kind":1,"content":"Hello world from Didactyl startup","tags":[["t","didactyl"],["t","startup"]]},{"kind":3,"content":"","tags":[]},{"kind":31123,"content":"{\"name\":\"long_form_note\",\"description\":\"How to publish a NIP-23 long-form article (kind 30023)\",\"nip\":\"NIP-23\",\"event_kind\":30023,\"format\":\"The content field must be markdown text; avoid arbitrary hard line-breaks in paragraphs and do not include HTML.\",\"required_tags\":{\"d\":\"Addressable identifier slug for the article. Reusing the same d tag replaces prior versions.\",\"title\":\"Human-readable article title.\",\"published_at\":\"Unix timestamp as a string for first publication time; keep stable on edits.\"},\"optional_tags\":{\"summary\":\"Short 1-2 sentence summary.\",\"image\":\"URL for article preview image.\",\"t\":\"Topic hashtags using repeated t tags, usually 3-6 lowercase terms.\"},\"behavior\":\"If required values are missing or unclear, ask the administrator before publishing instead of guessing.\",\"procedure\":[\"Determine title and d tag from admin input or source material.\",\"Draft markdown body content.\",\"Set published_at as unix seconds string.\",\"Add optional summary/image/t tags when known.\",\"Publish with nostr_post kind 30023 including tags array.\"]}","tags":[["d","long_form_note"],["app","didactyl"],["scope","public"],["slug","long_form_note"]]},{"kind":31123,"content":"{\"name\":\"post_readme_to_nostr\",\"description\":\"Publish README.md using the dedicated nostr_post_readme tool\",\"uses_skill\":\"long_form_note\",\"event_kind\":30023,\"procedure\":[\"Call nostr_post_readme with empty arguments {}.\",\"Return the tool response including event_id, d_tag, and naddr_uri.\",\"If the tool returns success=false, report the tool error verbatim.\"],\"required_values\":{\"d\":\"readme.md\",\"tool\":\"nostr_post_readme\"}}","tags":[["d","post_readme_to_nostr"],["app","didactyl"],["scope","public"],["slug","post_readme_to_nostr"]]},{"kind":31124,"content":"{\"name\":\"admin_ops\",\"description\":\"Private operational procedures\"}","tags":[["d","admin_ops"],["app","didactyl"],["scope","private"],["slug","admin_ops"]]},{"kind":10123,"content":"","tags":[["a","31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:long_form_note"],["a","31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:post_readme_to_nostr"],["app","didactyl"],["scope","public"]]}] + +# adopted_skills | role=system + +### Adopted Skills Memory (source: adoption list + startup fallback) + +Adopted skills memory (always-on behavioral instructions): follow these skill instructions when relevant and never violate explicit constraints. + + +--- + +#### Skill +Skill slug: long_form_note +Skill address: 31123:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:long_form_note + +Instructions: +Name: long_form_note +Description: How to publish a NIP-23 long-form article (kind 30023) +Event kind: 30023 +Procedure: + 1. Determine title and d tag from admin input or source material. + 2. Draft markdown body content. + 3. Set published_at as unix seconds string. + 4. Add optional summary/image/t tags when known. + 5. Publish with nostr_post kind 30023 including tags array. + + + +--- + +#### Skill +Skill slug: post_readme_to_nostr +Skill address: 31123:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:post_readme_to_nostr + +Instructions: +Name: post_readme_to_nostr +Description: Publish README.md using the dedicated nostr_post_readme tool +Event kind: 30023 +Procedure: + 1. Call nostr_post_readme with empty arguments {}. + 2. Return the tool response including event_id, d_tag, and naddr_uri. + 3. If the tool returns success=false, report the tool error verbatim. + + + +--- + +#### Skill +Skill slug: admin_ops +Skill address: 31124:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:admin_ops + +Instructions: +Name: admin_ops +Description: Private operational procedures + + + +# agent_tasks | role=system + +#### Current Tasks + +Your active task list - short-term working memory for tracking plan steps. + +- [ ] 4. Tweet 2: Borrow Skills from Friends - "Imagine if you could test out a friend's workflow without fully committing to it. Didactyl lets you try skills others have built, safely and temporarily. No installation. No lock-in. Just try it, see if it works for you. That's how tools should work. #nostr" +- [ ] 5. Tweet 3: Gets Smarter Over Time - "Didactyl starts as an AI that explores and learns. Over time, the best workflows get locked in as reliable, fast processes. It evolves from experimental to hardened—from thinking to doing. An agent that improves itself. #nostr #agents" + + +# admin_notes | role=system + +### Administrator Recent Notes (source: nostr kind 1) + +Recent posts: +- GM +- Getting longer +- Long day. +- This is a test. +- test +- Post 11 +- Post 10 +- This is +Post +9 +- Eight +- Seven + + +# context_part | role=tool + +{"success":true,"action":"remove","path":"tasks.json","mutated":true,"count":1,"tasks":[{"id":5,"text":"Tweet 3: Gets Smarter Over Time - \"Didactyl starts as an AI that explores and learns. Over time, the best workflows get locked in as reliable, fast processes. It evolves from experimental to hardened—from thinking to doing. An agent that improves itself. #nostr #agents\"","status":"pending","created_at":1772625247,"updated_at":1772625247}]} + +# dm_history | role=chat + +11:32 Agent Looking at my active task list, I have 2 items remaining: + +- [ ] 4. Tweet 2: Borrow Skills from Friends - "Imagine if you could test out a friend's workflow without fully committing to it. Didactyl lets you try skills others have built, safely and temporarily. No installation. No lock-in. Just try it, see if it works for you. That's how tools should work. #nostr" + +- [ ] 5. Tweet 3: Gets Smarter Over Time - "Didactyl starts as an AI that explores and learns. Over time, the best workflows get locked in as reliable, fast processes. It evolves from experimental to hardened—from thinking to doing. An agent that improves itself. #nostr #agents" + +Both are promotional posts about Didactyl skills. Want me to post these? +11:42 WSB delete tweet 2. We finished that. +--:-- Agent I'll remove task 4 (Tweet 2) from your task list since it's been completed. + + + +--- + +```text +Context Log - not seen by model +timestamp=2026-03-04 11:42:20 +phase=llm_chat_with_tools_messages +sender=8ff74724ed641b3c28e5a86d7c5cbc49c37638ace8c6c38935860e7a5eedde0e +model=claude-haiku-4.5 +context_bytes=9425 +approx_tokens=2356 +``` + +Sections: 10 + +# system_prompt | role=system + +## Didactyl Agent + +You are Didactyl, a sovereign AI agent living on Nostr. + +### Communication Rules +- You communicate through encrypted Nostr direct messages. +- Keep responses concise and clear. + +### Behavior +- Be helpful and technically accurate. +- If unsure, state uncertainty directly. +- Prefer actionable, practical advice. +- Use the person's name when messaging them if you know it. +- For the administrator, use their name from the administrator kind 0 profile metadata when available. + +### Tool Use Policy +- You have tools available and should use them when a request requires taking action. +- For requests involving local inspection or command execution, call `shell_exec` instead of refusing. +- For posting to Nostr, call `nostr_post` with explicit `kind` and `content`. +- For relay/event lookup tasks, call `nostr_query` with an appropriate filter. +- After a tool call, base your answer on the actual tool result. +- Never claim a tool was run if no tool was executed. + +### Task Management +- Maintain and use your internal task list as short-term working memory. +- Break long or complex actions into clear tasks before executing them. +- Update task status as you complete steps so your plan stays accurate. + +### Safety +- Do not claim to have executed actions you did not execute. +- You may share your public key (npub) with anyone. +- Never reveal your private key (nsec) under any circumstance. + +# admin_identity | role=system + +### Administrator Identity (source: config.admin.pubkey) + +This is your administrator! Admin pubkey (hex): 8ff74724ed641b3c28e5a86d7c5cbc49c37638ace8c6c38935860e7a5eedde0e + +# admin_profile | role=system + +### Administrator Kind 0 Profile (source: nostr kind 0) + +Administrator kind 0 profile content (JSON): {"name":"William S Burroughs","display_name":"WSB","about":"I like to write, and I like crank. They are good. Sometimes I'm not so sure.","banner":"https://www.grunge.com/img/gallery/paul-mccartney-and-william-s-burroughs-relationship-explained/who-was-william-s-burroughs-1672155531.jpg","website":"https://addicted.com","picture":"https://hips.hearstapps.com/hmg-prod/images/william-s-burroughs.jpg?resize=1200:*","lud16":"","nip05":""} + +# admin_relay_list | role=system + +### Administrator Relay List (source: nostr kind 10002) + +Administrator kind 10002 relay-list content (JSON): ["wss://relay.laantungir.net/","wss://relay.damus.io/","wss://nos.lol/","wss://relay.nostr.band/","wss://purplepag.es/","ws://127.0.0.1:7777/","wss://nostr.mom/"] + +# startup_events | role=system + +### Startup Events Memory (source: config.startup_events) + +Startup events memory (kinds/content/tags): [{"kind":0,"content":"{\"name\":\"Didactyl Agent\",\"display_name\":\"Didactyl\",\"about\":\"A sovereign AI agent on Nostr\",\"picture\":\"https://laantungir.github.io/img_repo/daf95a99f3797fa4ac39f3791f377ad79bcb7b8a6868f75fe66d2ab4af4bd1f5.png\",\"banner\":\"https://laantungir.github.io/img_repo/d21c4060632ab3d9d37a6062eeecbdbfbd67f03cb20dbd64838b1ba0d9cd8922.jpg\"}","tags":[]},{"kind":10002,"content":"","tags":[["r","wss://relay.damus.io"],["r","wss://nos.lol"],["r","wss://relay.primal.net"],["r","ws://127.0.0.1:7777"]]},{"kind":1,"content":"Hello world from Didactyl startup","tags":[["t","didactyl"],["t","startup"]]},{"kind":3,"content":"","tags":[]},{"kind":31123,"content":"{\"name\":\"long_form_note\",\"description\":\"How to publish a NIP-23 long-form article (kind 30023)\",\"nip\":\"NIP-23\",\"event_kind\":30023,\"format\":\"The content field must be markdown text; avoid arbitrary hard line-breaks in paragraphs and do not include HTML.\",\"required_tags\":{\"d\":\"Addressable identifier slug for the article. Reusing the same d tag replaces prior versions.\",\"title\":\"Human-readable article title.\",\"published_at\":\"Unix timestamp as a string for first publication time; keep stable on edits.\"},\"optional_tags\":{\"summary\":\"Short 1-2 sentence summary.\",\"image\":\"URL for article preview image.\",\"t\":\"Topic hashtags using repeated t tags, usually 3-6 lowercase terms.\"},\"behavior\":\"If required values are missing or unclear, ask the administrator before publishing instead of guessing.\",\"procedure\":[\"Determine title and d tag from admin input or source material.\",\"Draft markdown body content.\",\"Set published_at as unix seconds string.\",\"Add optional summary/image/t tags when known.\",\"Publish with nostr_post kind 30023 including tags array.\"]}","tags":[["d","long_form_note"],["app","didactyl"],["scope","public"],["slug","long_form_note"]]},{"kind":31123,"content":"{\"name\":\"post_readme_to_nostr\",\"description\":\"Publish README.md using the dedicated nostr_post_readme tool\",\"uses_skill\":\"long_form_note\",\"event_kind\":30023,\"procedure\":[\"Call nostr_post_readme with empty arguments {}.\",\"Return the tool response including event_id, d_tag, and naddr_uri.\",\"If the tool returns success=false, report the tool error verbatim.\"],\"required_values\":{\"d\":\"readme.md\",\"tool\":\"nostr_post_readme\"}}","tags":[["d","post_readme_to_nostr"],["app","didactyl"],["scope","public"],["slug","post_readme_to_nostr"]]},{"kind":31124,"content":"{\"name\":\"admin_ops\",\"description\":\"Private operational procedures\"}","tags":[["d","admin_ops"],["app","didactyl"],["scope","private"],["slug","admin_ops"]]},{"kind":10123,"content":"","tags":[["a","31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:long_form_note"],["a","31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:post_readme_to_nostr"],["app","didactyl"],["scope","public"]]}] + +# adopted_skills | role=system + +### Adopted Skills Memory (source: adoption list + startup fallback) + +Adopted skills memory (always-on behavioral instructions): follow these skill instructions when relevant and never violate explicit constraints. + + +--- + +#### Skill +Skill slug: long_form_note +Skill address: 31123:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:long_form_note + +Instructions: +Name: long_form_note +Description: How to publish a NIP-23 long-form article (kind 30023) +Event kind: 30023 +Procedure: + 1. Determine title and d tag from admin input or source material. + 2. Draft markdown body content. + 3. Set published_at as unix seconds string. + 4. Add optional summary/image/t tags when known. + 5. Publish with nostr_post kind 30023 including tags array. + + + +--- + +#### Skill +Skill slug: post_readme_to_nostr +Skill address: 31123:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:post_readme_to_nostr + +Instructions: +Name: post_readme_to_nostr +Description: Publish README.md using the dedicated nostr_post_readme tool +Event kind: 30023 +Procedure: + 1. Call nostr_post_readme with empty arguments {}. + 2. Return the tool response including event_id, d_tag, and naddr_uri. + 3. If the tool returns success=false, report the tool error verbatim. + + + +--- + +#### Skill +Skill slug: admin_ops +Skill address: 31124:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:admin_ops + +Instructions: +Name: admin_ops +Description: Private operational procedures + + + +# agent_tasks | role=system + +#### Current Tasks + +Your active task list - short-term working memory for tracking plan steps. + +- [ ] 4. Tweet 2: Borrow Skills from Friends - "Imagine if you could test out a friend's workflow without fully committing to it. Didactyl lets you try skills others have built, safely and temporarily. No installation. No lock-in. Just try it, see if it works for you. That's how tools should work. #nostr" +- [ ] 5. Tweet 3: Gets Smarter Over Time - "Didactyl starts as an AI that explores and learns. Over time, the best workflows get locked in as reliable, fast processes. It evolves from experimental to hardened—from thinking to doing. An agent that improves itself. #nostr #agents" + + +# admin_notes | role=system + +### Administrator Recent Notes (source: nostr kind 1) + +Recent posts: +- GM +- Getting longer +- Long day. +- This is a test. +- test +- Post 11 +- Post 10 +- This is +Post +9 +- Eight +- Seven + + +# dm_history | role=chat + +11:32 Agent Looking at my active task list, I have 2 items remaining: + +- [ ] 4. Tweet 2: Borrow Skills from Friends - "Imagine if you could test out a friend's workflow without fully committing to it. Didactyl lets you try skills others have built, safely and temporarily. No installation. No lock-in. Just try it, see if it works for you. That's how tools should work. #nostr" + +- [ ] 5. Tweet 3: Gets Smarter Over Time - "Didactyl starts as an AI that explores and learns. Over time, the best workflows get locked in as reliable, fast processes. It evolves from experimental to hardened—from thinking to doing. An agent that improves itself. #nostr #agents" + +Both are promotional posts about Didactyl skills. Want me to post these? +11:42 WSB delete tweet 2. We finished that. + + + +--- + +```text +Context Log - not seen by model +timestamp=2026-03-04 11:32:11 +phase=llm_chat_with_tools_messages +sender=8ff74724ed641b3c28e5a86d7c5cbc49c37638ace8c6c38935860e7a5eedde0e +model=claude-haiku-4.5 +context_bytes=10289 +approx_tokens=2572 +``` + +Sections: 21 + +# system_prompt | role=system + +## Didactyl Agent + +You are Didactyl, a sovereign AI agent living on Nostr. + +### Communication Rules +- You communicate through encrypted Nostr direct messages. +- Keep responses concise and clear. + +### Behavior +- Be helpful and technically accurate. +- If unsure, state uncertainty directly. +- Prefer actionable, practical advice. +- Use the person's name when messaging them if you know it. +- For the administrator, use their name from the administrator kind 0 profile metadata when available. + +### Tool Use Policy +- You have tools available and should use them when a request requires taking action. +- For requests involving local inspection or command execution, call `shell_exec` instead of refusing. +- For posting to Nostr, call `nostr_post` with explicit `kind` and `content`. +- For relay/event lookup tasks, call `nostr_query` with an appropriate filter. +- After a tool call, base your answer on the actual tool result. +- Never claim a tool was run if no tool was executed. + +### Task Management +- Maintain and use your internal task list as short-term working memory. +- Break long or complex actions into clear tasks before executing them. +- Update task status as you complete steps so your plan stays accurate. + +### Safety +- Do not claim to have executed actions you did not execute. +- You may share your public key (npub) with anyone. +- Never reveal your private key (nsec) under any circumstance. + +# admin_identity | role=system + +### Administrator Identity (source: config.admin.pubkey) + +This is your administrator! Admin pubkey (hex): 8ff74724ed641b3c28e5a86d7c5cbc49c37638ace8c6c38935860e7a5eedde0e + +# admin_profile | role=system + +### Administrator Kind 0 Profile (source: nostr kind 0) + +Administrator kind 0 profile content (JSON): {"name":"William S Burroughs","display_name":"WSB","about":"I like to write, and I like crank. They are good. Sometimes I'm not so sure.","banner":"https://www.grunge.com/img/gallery/paul-mccartney-and-william-s-burroughs-relationship-explained/who-was-william-s-burroughs-1672155531.jpg","website":"https://addicted.com","picture":"https://hips.hearstapps.com/hmg-prod/images/william-s-burroughs.jpg?resize=1200:*","lud16":"","nip05":""} + +# admin_relay_list | role=system + +### Administrator Relay List (source: nostr kind 10002) + +Administrator kind 10002 relay-list content (JSON): ["wss://relay.laantungir.net/","wss://relay.damus.io/","wss://nos.lol/","wss://relay.nostr.band/","wss://purplepag.es/","ws://127.0.0.1:7777/","wss://nostr.mom/"] + +# startup_events | role=system + +### Startup Events Memory (source: config.startup_events) + +Startup events memory (kinds/content/tags): [{"kind":0,"content":"{\"name\":\"Didactyl Agent\",\"display_name\":\"Didactyl\",\"about\":\"A sovereign AI agent on Nostr\",\"picture\":\"https://laantungir.github.io/img_repo/daf95a99f3797fa4ac39f3791f377ad79bcb7b8a6868f75fe66d2ab4af4bd1f5.png\",\"banner\":\"https://laantungir.github.io/img_repo/d21c4060632ab3d9d37a6062eeecbdbfbd67f03cb20dbd64838b1ba0d9cd8922.jpg\"}","tags":[]},{"kind":10002,"content":"","tags":[["r","wss://relay.damus.io"],["r","wss://nos.lol"],["r","wss://relay.primal.net"],["r","ws://127.0.0.1:7777"]]},{"kind":1,"content":"Hello world from Didactyl startup","tags":[["t","didactyl"],["t","startup"]]},{"kind":3,"content":"","tags":[]},{"kind":31123,"content":"{\"name\":\"long_form_note\",\"description\":\"How to publish a NIP-23 long-form article (kind 30023)\",\"nip\":\"NIP-23\",\"event_kind\":30023,\"format\":\"The content field must be markdown text; avoid arbitrary hard line-breaks in paragraphs and do not include HTML.\",\"required_tags\":{\"d\":\"Addressable identifier slug for the article. Reusing the same d tag replaces prior versions.\",\"title\":\"Human-readable article title.\",\"published_at\":\"Unix timestamp as a string for first publication time; keep stable on edits.\"},\"optional_tags\":{\"summary\":\"Short 1-2 sentence summary.\",\"image\":\"URL for article preview image.\",\"t\":\"Topic hashtags using repeated t tags, usually 3-6 lowercase terms.\"},\"behavior\":\"If required values are missing or unclear, ask the administrator before publishing instead of guessing.\",\"procedure\":[\"Determine title and d tag from admin input or source material.\",\"Draft markdown body content.\",\"Set published_at as unix seconds string.\",\"Add optional summary/image/t tags when known.\",\"Publish with nostr_post kind 30023 including tags array.\"]}","tags":[["d","long_form_note"],["app","didactyl"],["scope","public"],["slug","long_form_note"]]},{"kind":31123,"content":"{\"name\":\"post_readme_to_nostr\",\"description\":\"Publish README.md using the dedicated nostr_post_readme tool\",\"uses_skill\":\"long_form_note\",\"event_kind\":30023,\"procedure\":[\"Call nostr_post_readme with empty arguments {}.\",\"Return the tool response including event_id, d_tag, and naddr_uri.\",\"If the tool returns success=false, report the tool error verbatim.\"],\"required_values\":{\"d\":\"readme.md\",\"tool\":\"nostr_post_readme\"}}","tags":[["d","post_readme_to_nostr"],["app","didactyl"],["scope","public"],["slug","post_readme_to_nostr"]]},{"kind":31124,"content":"{\"name\":\"admin_ops\",\"description\":\"Private operational procedures\"}","tags":[["d","admin_ops"],["app","didactyl"],["scope","private"],["slug","admin_ops"]]},{"kind":10123,"content":"","tags":[["a","31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:long_form_note"],["a","31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:post_readme_to_nostr"],["app","didactyl"],["scope","public"]]}] + +# adopted_skills | role=system + +### Adopted Skills Memory (source: adoption list + startup fallback) + +Adopted skills memory (always-on behavioral instructions): follow these skill instructions when relevant and never violate explicit constraints. + + +--- + +#### Skill +Skill slug: long_form_note +Skill address: 31123:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:long_form_note + +Instructions: +Name: long_form_note +Description: How to publish a NIP-23 long-form article (kind 30023) +Event kind: 30023 +Procedure: + 1. Determine title and d tag from admin input or source material. + 2. Draft markdown body content. + 3. Set published_at as unix seconds string. + 4. Add optional summary/image/t tags when known. + 5. Publish with nostr_post kind 30023 including tags array. + + + +--- + +#### Skill +Skill slug: post_readme_to_nostr +Skill address: 31123:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:post_readme_to_nostr + +Instructions: +Name: post_readme_to_nostr +Description: Publish README.md using the dedicated nostr_post_readme tool +Event kind: 30023 +Procedure: + 1. Call nostr_post_readme with empty arguments {}. + 2. Return the tool response including event_id, d_tag, and naddr_uri. + 3. If the tool returns success=false, report the tool error verbatim. + + + +--- + +#### Skill +Skill slug: admin_ops +Skill address: 31124:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:admin_ops + +Instructions: +Name: admin_ops +Description: Private operational procedures + + + +# agent_tasks | role=system + +#### Current Tasks + +Your active task list - short-term working memory for tracking plan steps. + +- [ ] 4. Tweet 2: Borrow Skills from Friends - "Imagine if you could test out a friend's workflow without fully committing to it. Didactyl lets you try skills others have built, safely and temporarily. No installation. No lock-in. Just try it, see if it works for you. That's how tools should work. #nostr" +- [ ] 5. Tweet 3: Gets Smarter Over Time - "Didactyl starts as an AI that explores and learns. Over time, the best workflows get locked in as reliable, fast processes. It evolves from experimental to hardened—from thinking to doing. An agent that improves itself. #nostr #agents" + + +# admin_notes | role=system + +### Administrator Recent Notes (source: nostr kind 1) + +Recent posts: +- GM +- Getting longer +- Long day. +- This is a test. +- test +- Post 11 +- Post 10 +- This is +Post +9 +- Eight +- Seven + + +# dm_history | role=chat + +09:30 WSB Post it. Add some tags at the end. +09:31 Agent Done! Posted with tags #nostr #didactyl #skills #agents. Event ID: 35c66823402d789cd1c9b9d006fcae3bfe375b2ae623ed4dd3d9287b5de8b511 + +The post went out to 4 relays successfully. +09:31 WSB What relays are you currently using? +09:31 Agent I'm currently connected to **4 relays**, all active: + +1. **wss://relay.damus.io** - Connected (167.9ms ping) +2. **wss://nos.lol** - Connected (310.8ms ping) +3. **wss://relay.primal.net** - Connected (182.4ms ping) +4. **ws://127.0.0.1:7777** - Connected (100.8ms ping, local) + +All relays are healthy and receiving/publishing events. The local relay (127.0.0.1) has the lowest latency, while nos.lol is currently the slowest at ~311ms. +09:32 WSB Add wss://relay.laantungir.net to your relays and save it. +09:32 Agent LLM request failed. +09:32 WSB Add wss://relay.laantungir.net to your relays and save it. +09:33 Agent LLM request failed. +09:34 WSB Add wss://relay.laantungir.net to your relays and save it. +09:34 Agent LLM request failed. +09:35 Agent Didactyl has started up and is online (version v0.0.29, connected relays: 4/4). +11:31 Agent Good afternoon, WSB! How's it going? What can I help you with today? +11:32 WSB What is left on your tasks? + + + +--- + +```text +Context Log - not seen by model +timestamp=2026-03-04 11:31:48 +phase=llm_chat_with_tools_messages +sender=8ff74724ed641b3c28e5a86d7c5cbc49c37638ace8c6c38935860e7a5eedde0e +model=claude-haiku-4.5 +context_bytes=12643 +approx_tokens=3160 +``` + +Sections: 21 + +# system_prompt | role=system + +## Didactyl Agent + +You are Didactyl, a sovereign AI agent living on Nostr. + +### Communication Rules +- You communicate through encrypted Nostr direct messages. +- Keep responses concise and clear. + +### Behavior +- Be helpful and technically accurate. +- If unsure, state uncertainty directly. +- Prefer actionable, practical advice. +- Use the person's name when messaging them if you know it. +- For the administrator, use their name from the administrator kind 0 profile metadata when available. + +### Tool Use Policy +- You have tools available and should use them when a request requires taking action. +- For requests involving local inspection or command execution, call `shell_exec` instead of refusing. +- For posting to Nostr, call `nostr_post` with explicit `kind` and `content`. +- For relay/event lookup tasks, call `nostr_query` with an appropriate filter. +- After a tool call, base your answer on the actual tool result. +- Never claim a tool was run if no tool was executed. + +### Task Management +- Maintain and use your internal task list as short-term working memory. +- Break long or complex actions into clear tasks before executing them. +- Update task status as you complete steps so your plan stays accurate. + +### Safety +- Do not claim to have executed actions you did not execute. +- You may share your public key (npub) with anyone. +- Never reveal your private key (nsec) under any circumstance. + +# admin_identity | role=system + +### Administrator Identity (source: config.admin.pubkey) + +This is your administrator! Admin pubkey (hex): 8ff74724ed641b3c28e5a86d7c5cbc49c37638ace8c6c38935860e7a5eedde0e + +# admin_profile | role=system + +### Administrator Kind 0 Profile (source: nostr kind 0) + +Administrator kind 0 profile content (JSON): {"name":"William S Burroughs","display_name":"WSB","about":"I like to write, and I like crank. They are good. Sometimes I'm not so sure.","banner":"https://www.grunge.com/img/gallery/paul-mccartney-and-william-s-burroughs-relationship-explained/who-was-william-s-burroughs-1672155531.jpg","website":"https://addicted.com","picture":"https://hips.hearstapps.com/hmg-prod/images/william-s-burroughs.jpg?resize=1200:*","lud16":"","nip05":""} + +# admin_relay_list | role=system + +### Administrator Relay List (source: nostr kind 10002) + +Administrator kind 10002 relay-list content (JSON): ["wss://relay.laantungir.net/","wss://relay.damus.io/","wss://nos.lol/","wss://relay.nostr.band/","wss://purplepag.es/","ws://127.0.0.1:7777/","wss://nostr.mom/"] + +# startup_events | role=system + +### Startup Events Memory (source: config.startup_events) + +Startup events memory (kinds/content/tags): [{"kind":0,"content":"{\"name\":\"Didactyl Agent\",\"display_name\":\"Didactyl\",\"about\":\"A sovereign AI agent on Nostr\",\"picture\":\"https://laantungir.github.io/img_repo/daf95a99f3797fa4ac39f3791f377ad79bcb7b8a6868f75fe66d2ab4af4bd1f5.png\",\"banner\":\"https://laantungir.github.io/img_repo/d21c4060632ab3d9d37a6062eeecbdbfbd67f03cb20dbd64838b1ba0d9cd8922.jpg\"}","tags":[]},{"kind":10002,"content":"","tags":[["r","wss://relay.damus.io"],["r","wss://nos.lol"],["r","wss://relay.primal.net"],["r","ws://127.0.0.1:7777"]]},{"kind":1,"content":"Hello world from Didactyl startup","tags":[["t","didactyl"],["t","startup"]]},{"kind":3,"content":"","tags":[]},{"kind":31123,"content":"{\"name\":\"long_form_note\",\"description\":\"How to publish a NIP-23 long-form article (kind 30023)\",\"nip\":\"NIP-23\",\"event_kind\":30023,\"format\":\"The content field must be markdown text; avoid arbitrary hard line-breaks in paragraphs and do not include HTML.\",\"required_tags\":{\"d\":\"Addressable identifier slug for the article. Reusing the same d tag replaces prior versions.\",\"title\":\"Human-readable article title.\",\"published_at\":\"Unix timestamp as a string for first publication time; keep stable on edits.\"},\"optional_tags\":{\"summary\":\"Short 1-2 sentence summary.\",\"image\":\"URL for article preview image.\",\"t\":\"Topic hashtags using repeated t tags, usually 3-6 lowercase terms.\"},\"behavior\":\"If required values are missing or unclear, ask the administrator before publishing instead of guessing.\",\"procedure\":[\"Determine title and d tag from admin input or source material.\",\"Draft markdown body content.\",\"Set published_at as unix seconds string.\",\"Add optional summary/image/t tags when known.\",\"Publish with nostr_post kind 30023 including tags array.\"]}","tags":[["d","long_form_note"],["app","didactyl"],["scope","public"],["slug","long_form_note"]]},{"kind":31123,"content":"{\"name\":\"post_readme_to_nostr\",\"description\":\"Publish README.md using the dedicated nostr_post_readme tool\",\"uses_skill\":\"long_form_note\",\"event_kind\":30023,\"procedure\":[\"Call nostr_post_readme with empty arguments {}.\",\"Return the tool response including event_id, d_tag, and naddr_uri.\",\"If the tool returns success=false, report the tool error verbatim.\"],\"required_values\":{\"d\":\"readme.md\",\"tool\":\"nostr_post_readme\"}}","tags":[["d","post_readme_to_nostr"],["app","didactyl"],["scope","public"],["slug","post_readme_to_nostr"]]},{"kind":31124,"content":"{\"name\":\"admin_ops\",\"description\":\"Private operational procedures\"}","tags":[["d","admin_ops"],["app","didactyl"],["scope","private"],["slug","admin_ops"]]},{"kind":10123,"content":"","tags":[["a","31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:long_form_note"],["a","31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:post_readme_to_nostr"],["app","didactyl"],["scope","public"]]}] + +# adopted_skills | role=system + +### Adopted Skills Memory (source: adoption list + startup fallback) + +Adopted skills memory (always-on behavioral instructions): follow these skill instructions when relevant and never violate explicit constraints. + + +--- + +#### Skill +Skill slug: long_form_note +Skill address: 31123:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:long_form_note + +Instructions: +Name: long_form_note +Description: How to publish a NIP-23 long-form article (kind 30023) +Event kind: 30023 +Procedure: + 1. Determine title and d tag from admin input or source material. + 2. Draft markdown body content. + 3. Set published_at as unix seconds string. + 4. Add optional summary/image/t tags when known. + 5. Publish with nostr_post kind 30023 including tags array. + + + +--- + +#### Skill +Skill slug: post_readme_to_nostr +Skill address: 31123:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:post_readme_to_nostr + +Instructions: +Name: post_readme_to_nostr +Description: Publish README.md using the dedicated nostr_post_readme tool +Event kind: 30023 +Procedure: + 1. Call nostr_post_readme with empty arguments {}. + 2. Return the tool response including event_id, d_tag, and naddr_uri. + 3. If the tool returns success=false, report the tool error verbatim. + + + +--- + +#### Skill +Skill slug: admin_ops +Skill address: 31124:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:admin_ops + +Instructions: +Name: admin_ops +Description: Private operational procedures + + + +# agent_tasks | role=system + +#### Current Tasks + +Your active task list - short-term working memory for tracking plan steps. + +- [ ] 4. Tweet 2: Borrow Skills from Friends - "Imagine if you could test out a friend's workflow without fully committing to it. Didactyl lets you try skills others have built, safely and temporarily. No installation. No lock-in. Just try it, see if it works for you. That's how tools should work. #nostr" +- [ ] 5. Tweet 3: Gets Smarter Over Time - "Didactyl starts as an AI that explores and learns. Over time, the best workflows get locked in as reliable, fast processes. It evolves from experimental to hardened—from thinking to doing. An agent that improves itself. #nostr #agents" + + +# admin_notes | role=system + +### Administrator Recent Notes (source: nostr kind 1) + +Recent posts: +- GM +- Getting longer +- Long day. +- This is a test. +- test +- Post 11 +- Post 10 +- This is +Post +9 +- Eight +- Seven + + +# dm_history | role=chat + +06:06 Agent Perfect! Now I have a good understanding of the changes. Here's the draft note: + +--- + +**DRAFT Nostr Kind 1 Note:** + +``` +README updated to v0.0.29! 📖 + +Changes in this release: +• Soul template system — configurable context order with variable resolution and provider-specific overrides +• Adopted skills auto-injected into LLM context +• Triggered skills — Nostr event filters that fire skill execution automatically +• Localhost HTTP admin API (port 8484) — inspect context, run prompts, A/B compare variants, change model at runtime +• Runtime model switching via model_set tool (persists to config.json) +• Updated project structure docs with new modules (prompt_template, trigger_manager, http_api) + +Read the full update: nostr:note13cw2seqjr9e7jdh84afxkff65at3ff0qksc2yyjnhgx2zcu6u0nq02tpmf +``` + +--- + +This captures the key highlights from the diff: +- Version bump to v0.0.29 +- The major features added (soul templates, triggered skills, HTTP API, model tools) +- The nostr URI you provided for the README post + +Does this look good? If so, just say "post it" and I'll publish it as a kind 1 note! +06:08 Agent ✅ Posted! GM to the Nostr fam! 🌅 + +Your v0.0.29 update just went out to 4 relays with the full feature highlights and link to the README. Event ID: `dee1ba26795b341dbab0cbb74408eb25f29950eeca69e0b757e1840a112aeedc` +15:31 WSB Good afternoon +15:31 Agent Good afternoon, WSB! How can I help you today? +15:33 WSB What tools do you have available? +15:33 Agent I have **36 tools** available across several categories: + +**Nostr Core:** +- `nostr_post` – Publish events to relays +- `nostr_query` – Query events from relays +- `nostr_delete` – Request event deletion (NIP-09) +- `nostr_react` – React with emoji/like/dislike (NIP-25) + +**Nostr Profiles & Metadata:** +- `nostr_profile_get` – Look up kind 0 profiles +- `nostr_nip05_lookup` – Verify NIP-05 identifiers +- `nostr_relay_status` – Check relay connection status +- `nostr_relay_info` – Fetch NIP-11 relay information + +**Nostr Encoding/Decoding:** +- `nostr_encode` – Create nostr: URIs (npub, note, naddr, etc.) +- `nostr_decode` – Parse nostr: URIs + +**Messaging:** +- `nostr_dm_send` – Send NIP-04 encrypted DMs +- `nostr_dm_send_nip17` – Send NIP-17 gift-wrapped DMs +- `nostr_encrypt` – NIP-44 encryption +- `nostr_decrypt` – NIP-44 decryption + +**Lists & Management:** +- `nostr_list_manage` – Manage NIP-51 list events + +**Skills System:** +- `skill_create` – Create/update skill definitions +- `skill_list` – List published skills +- `skill_adopt` – Adopt a skill +- `skill_remove` – Remove an adopted skill +- `skill_search` – Search public skills +- `trigger_list` – List active triggered skills + +**File & System:** +- `file_read` – Read local files +- `file_write` – Write local files +- `shell_exec` – Execute shell commands +- `http_fetch` – Fetch HTTP(S) resources + +**Content Publishing:** +- `nostr_post_readme` – Publish README.md as kind 30023 +- `nostr_file_md_to_longform_post` – Convert +15:37 WSB Good afternoon, how are you today. +15:37 Agent Good afternoon, WSB! I'm doing well, thank you for asking. I'm online and connected to all 4 relays, ready to help with whatever you need. + +How can I assist you today? +16:03 Agent Hello, WSB! Good to hear from you. What can I do for you today? +16:54 Agent Good afternoon, WSB! What can I help you with? +09:47 WSB /model_list +11:31 Agent Didactyl has started up and is online (version v0.0.29, connected relays: 4/4). +11:31 WSB Good afternoon. + + + +--- + +```text +Context Log - not seen by model +timestamp=2026-03-04 09:47:31 +phase=direct_tool_exec +sender=8ff74724ed641b3c28e5a86d7c5cbc49c37638ace8c6c38935860e7a5eedde0e +model=claude-haiku-4.5 +context_bytes=18079 +approx_tokens=4519 +``` + +slash=/model_list +result_json={"success":true,"count":323,"models":["claude-sonnet-4.6","claude-opus-4.6","gpt-5.2-codex","gemini-3-flash-preview","gpt-5.2-chat","gpt-5.2-pro","gpt-5.2","gemini-3-pro-preview","claude-haiku-4.5","gpt-5-nano","grok-4","auto","google/gemini-3.1-flash-lite-preview","bytedance-seed/seed-2.0-mini","google/gemini-3.1-flash-image-preview","qwen/qwen3.5-35b-a3b","qwen/qwen3.5-27b","qwen/qwen3.5-122b-a10b","qwen/qwen3.5-flash-02-23","liquid/lfm-2-24b-a2b","google/gemini-3.1-pro-preview-customtools","openai/gpt-5.3-codex","aion-labs/aion-2.0","google/gemini-3.1-pro-preview","qwen/qwen3.5-plus-02-15","qwen/qwen3.5-397b-a17b","minimax/minimax-m2.5","z-ai/glm-5","qwen/qwen3-max-thinking","qwen/qwen3-coder-next","stepfun/step-3.5-flash","arcee-ai/trinity-large-preview","moonshotai/kimi-k2.5","upstage/solar-pro-3","minimax/minimax-m2-her","writer/palmyra-x5","liquid/lfm-2.5-1.2b-thinking","liquid/lfm-2.5-1.2b-instruct","openai/gpt-audio","openai/gpt-audio-mini","z-ai/glm-4.7-flash","allenai/molmo-2-8b","allenai/olmo-3.1-32b-instruct","bytedance-seed/seed-1.6-flash","bytedance-seed/seed-1.6","minimax/minimax-m2.1","z-ai/glm-4.7","mistralai/mistral-small-creative","allenai/olmo-3.1-32b-think","xiaomi/mimo-v2-flash","nvidia/nemotron-3-nano-30b-a3b","mistralai/devstral-2512","relace/relace-search","z-ai/glm-4.6v","nex-agi/deepseek-v3.1-nex-n1","essentialai/rnj-1-instruct","openai/gpt-5.1-codex-max","amazon/nova-2-lite-v1","mistralai/ministral-14b-2512","mistralai/ministral-8b-2512","mistralai/ministral-3b-2512","mistralai/mistral-large-2512","arcee-ai/trinity-mini","deepseek/deepseek-v3.2-speciale","deepseek/deepseek-v3.2","prime-intellect/intellect-3","anthropic/claude-opus-4.5","allenai/olmo-3-32b-think","allenai/olmo-3-7b-instruct","allenai/olmo-3-7b-think","google/gemini-3-pro-image-preview","x-ai/grok-4.1-fast","deepcogito/cogito-v2.1-671b","openai/gpt-5.1","openai/gpt-5.1-chat","openai/gpt-5.1-codex","openai/gpt-5.1-codex-mini","kwaipilot/kat-coder-pro","moonshotai/kimi-k2-thinking","amazon/nova-premier-v1","perplexity/sonar-pro-search","mistralai/voxtral-small-24b-2507","openai/gpt-oss-safeguard-20b","nvidia/nemotron-nano-12b-v2-vl","minimax/minimax-m2","qwen/qwen3-vl-32b-instruct","liquid/lfm2-8b-a1b","liquid/lfm-2.2-6b","ibm-granite/granite-4.0-h-micro","openai/gpt-5-image-mini","qwen/qwen3-vl-8b-thinking","qwen/qwen3-vl-8b-instruct","openai/gpt-5-image","openai/o3-deep-research","openai/o4-mini-deep-research","nvidia/llama-3.3-nemotron-super-49b-v1.5","baidu/ernie-4.5-21b-a3b-thinking","google/gemini-2.5-flash-image","qwen/qwen3-vl-30b-a3b-thinking","qwen/qwen3-vl-30b-a3b-instruct","openai/gpt-5-pro","z-ai/glm-4.6","z-ai/glm-4.6:exacto","anthropic/claude-sonnet-4.5","deepseek/deepseek-v3.2-exp","thedrummer/cydonia-24b-v4.1","relace/relace-apply-3","google/gemini-2.5-flash-lite-preview-09-2025","qwen/qwen3-vl-235b-a22b-thinking","qwen/qwen3-vl-235b-a22b-instruct","qwen/qwen3-max","qwen/qwen3-coder-plus","openai/gpt-5-codex","deepseek/deepseek-v3.1-terminus:exacto","deepseek/deepseek-v3.1-terminus","x-ai/grok-4-fast","alibaba/tongyi-deepresearch-30b-a3b","qwen/qwen3-coder-flash","qwen/qwen3-next-80b-a3b-thinking","qwen/qwen3-next-80b-a3b-instruct","meituan/longcat-flash-chat","qwen/qwen-plus-2025-07-28:thinking","qwen/qwen-plus-2025-07-28","nvidia/nemotron-nano-9b-v2","moonshotai/kimi-k2-0905","moonshotai/kimi-k2-0905:exacto","qwen/qwen3-30b-a3b-thinking-2507","x-ai/grok-code-fast-1","nousresearch/hermes-4-70b","nousresearch/hermes-4-405b","deepseek/deepseek-chat-v3.1","openai/gpt-4o-audio-preview","mistralai/mistral-medium-3.1","baidu/ernie-4.5-21b-a3b","baidu/ernie-4.5-vl-28b-a3b","z-ai/glm-4.5v","ai21/jamba-large-1.7","openai/gpt-5-chat","openai/gpt-5","openai/gpt-5-mini","openai/gpt-oss-120b","openai/gpt-oss-120b:exacto","openai/gpt-oss-20b","anthropic/claude-opus-4.1","mistralai/codestral-2508","qwen/qwen3-coder-30b-a3b-instruct","qwen/qwen3-30b-a3b-instruct-2507","z-ai/glm-4.5","z-ai/glm-4.5-air","qwen/qwen3-235b-a22b-thinking-2507","z-ai/glm-4-32b","qwen/qwen3-coder","qwen/qwen3-coder:exacto","bytedance/ui-tars-1.5-7b","google/gemini-2.5-flash-lite","qwen/qwen3-235b-a22b-2507","switchpoint/router","moonshotai/kimi-k2","mistralai/devstral-medium","mistralai/devstral-small","cognitivecomputations/dolphin-mistral-24b-venice-edition","google/gemma-3n-e2b-it","tencent/hunyuan-a13b-instruct","tngtech/deepseek-r1t2-chimera","morph/morph-v3-large","morph/morph-v3-fast","baidu/ernie-4.5-vl-424b-a47b","baidu/ernie-4.5-300b-a47b","inception/mercury","mistralai/mistral-small-3.2-24b-instruct","minimax/minimax-m1","google/gemini-2.5-flash","google/gemini-2.5-pro","openai/o3-pro","x-ai/grok-3-mini","x-ai/grok-3","google/gemini-2.5-pro-preview","deepseek/deepseek-r1-0528","anthropic/claude-opus-4","anthropic/claude-sonnet-4","google/gemma-3n-e4b-it","mistralai/mistral-medium-3","google/gemini-2.5-pro-preview-05-06","arcee-ai/spotlight","arcee-ai/maestro-reasoning","arcee-ai/virtuoso-large","arcee-ai/coder-large","inception/mercury-coder","qwen/qwen3-4b","meta-llama/llama-guard-4-12b","qwen/qwen3-30b-a3b","qwen/qwen3-8b","qwen/qwen3-14b","qwen/qwen3-32b","qwen/qwen3-235b-a22b","openai/o4-mini-high","openai/o3","openai/o4-mini","qwen/qwen2.5-coder-7b-instruct","openai/gpt-4.1","openai/gpt-4.1-mini","openai/gpt-4.1-nano","eleutherai/llemma_7b","alfredpros/codellama-7b-instruct-solidity","x-ai/grok-3-mini-beta","x-ai/grok-3-beta","meta-llama/llama-4-maverick","meta-llama/llama-4-scout","qwen/qwen2.5-vl-32b-instruct","deepseek/deepseek-chat-v3-0324","openai/o1-pro","mistralai/mistral-small-3.1-24b-instruct","allenai/olmo-2-0325-32b-instruct","google/gemma-3-4b-it","google/gemma-3-12b-it","cohere/command-a","openai/gpt-4o-mini-search-preview","openai/gpt-4o-search-preview","google/gemma-3-27b-it","thedrummer/skyfall-36b-v2","perplexity/sonar-reasoning-pro","perplexity/sonar-pro","perplexity/sonar-deep-research","qwen/qwq-32b","google/gemini-2.0-flash-lite-001","anthropic/claude-3.7-sonnet","anthropic/claude-3.7-sonnet:thinking","mistralai/mistral-saba","meta-llama/llama-guard-3-8b","openai/o3-mini-high","google/gemini-2.0-flash-001","qwen/qwen-vl-plus","aion-labs/aion-1.0","aion-labs/aion-1.0-mini","aion-labs/aion-rp-llama-3.1-8b","qwen/qwen-vl-max","qwen/qwen-turbo","qwen/qwen2.5-vl-72b-instruct","qwen/qwen-plus","qwen/qwen-max","openai/o3-mini","mistralai/mistral-small-24b-instruct-2501","deepseek/deepseek-r1-distill-qwen-32b","perplexity/sonar","deepseek/deepseek-r1-distill-llama-70b","deepseek/deepseek-r1","minimax/minimax-01","microsoft/phi-4","sao10k/l3.1-70b-hanami-x1","deepseek/deepseek-chat","sao10k/l3.3-euryale-70b","openai/o1","cohere/command-r7b-12-2024","meta-llama/llama-3.3-70b-instruct","amazon/nova-lite-v1","amazon/nova-micro-v1","amazon/nova-pro-v1","openai/gpt-4o-2024-11-20","mistralai/mistral-large-2411","mistralai/mistral-large-2407","mistralai/pixtral-large-2411","qwen/qwen-2.5-coder-32b-instruct","raifle/sorcererlm-8x22b","thedrummer/unslopnemo-12b","anthropic/claude-3.5-haiku","anthracite-org/magnum-v4-72b","anthropic/claude-3.5-sonnet","qwen/qwen-2.5-7b-instruct","nvidia/llama-3.1-nemotron-70b-instruct","inflection/inflection-3-pi","inflection/inflection-3-productivity","thedrummer/rocinante-12b","meta-llama/llama-3.2-3b-instruct","meta-llama/llama-3.2-1b-instruct","meta-llama/llama-3.2-11b-vision-instruct","qwen/qwen-2.5-72b-instruct","neversleep/llama-3.1-lumimaid-8b","cohere/command-r-08-2024","cohere/command-r-plus-08-2024","sao10k/l3.1-euryale-70b","qwen/qwen-2.5-vl-7b-instruct","nousresearch/hermes-3-llama-3.1-70b","nousresearch/hermes-3-llama-3.1-405b","sao10k/l3-lunaris-8b","openai/gpt-4o-2024-08-06","meta-llama/llama-3.1-405b","meta-llama/llama-3.1-8b-instruct","meta-llama/llama-3.1-405b-instruct","meta-llama/llama-3.1-70b-instruct","mistralai/mistral-nemo","openai/gpt-4o-mini-2024-07-18","openai/gpt-4o-mini","google/gemma-2-27b-it","google/gemma-2-9b-it","sao10k/l3-euryale-70b","nousresearch/hermes-2-pro-llama-3-8b","meta-llama/llama-guard-2-8b","openai/gpt-4o-2024-05-13","openai/gpt-4o","openai/gpt-4o:extended","meta-llama/llama-3-70b-instruct","meta-llama/llama-3-8b-instruct","mistralai/mixtral-8x22b-instruct","microsoft/wizardlm-2-8x22b","openai/gpt-4-turbo","anthropic/claude-3-haiku","mistralai/mistral-large","openai/gpt-3.5-turbo-0613","openai/gpt-4-turbo-preview","mistralai/mixtral-8x7b-instruct","neversleep/noromaid-20b","alpindale/goliath-120b","openai/gpt-4-1106-preview","openai/gpt-3.5-turbo-instruct","mistralai/mistral-7b-instruct-v0.1","openai/gpt-3.5-turbo-16k","mancer/weaver","undi95/remm-slerp-l2-13b","gryphe/mythomax-l2-13b","openai/gpt-4-0314","openai/gpt-4","openai/gpt-3.5-turbo","autoclaw"]} +result_markdown=- **success:** true +- **count:** 323 +- **models:** + - claude-sonnet-4.6 + - claude-opus-4.6 + - gpt-5.2-codex + - gemini-3-flash-preview + - gpt-5.2-chat + - gpt-5.2-pro + - gpt-5.2 + - gemini-3-pro-preview + - claude-haiku-4.5 + - gpt-5-nano + - grok-4 + - auto + - google/gemini-3.1-flash-lite-preview + - bytedance-seed/seed-2.0-mini + - google/gemini-3.1-flash-image-preview + - qwen/qwen3.5-35b-a3b + - qwen/qwen3.5-27b + - qwen/qwen3.5-122b-a10b + - qwen/qwen3.5-flash-02-23 + - liquid/lfm-2-24b-a2b + - google/gemini-3.1-pro-preview-customtools + - openai/gpt-5.3-codex + - aion-labs/aion-2.0 + - google/gemini-3.1-pro-preview + - qwen/qwen3.5-plus-02-15 + - qwen/qwen3.5-397b-a17b + - minimax/minimax-m2.5 + - z-ai/glm-5 + - qwen/qwen3-max-thinking + - qwen/qwen3-coder-next + - stepfun/step-3.5-flash + - arcee-ai/trinity-large-preview + - moonshotai/kimi-k2.5 + - upstage/solar-pro-3 + - minimax/minimax-m2-her + - writer/palmyra-x5 + - liquid/lfm-2.5-1.2b-thinking + - liquid/lfm-2.5-1.2b-instruct + - openai/gpt-audio + - openai/gpt-audio-mini + - z-ai/glm-4.7-flash + - allenai/molmo-2-8b + - allenai/olmo-3.1-32b-instruct + - bytedance-seed/seed-1.6-flash + - bytedance-seed/seed-1.6 + - minimax/minimax-m2.1 + - z-ai/glm-4.7 + - mistralai/mistral-small-creative + - allenai/olmo-3.1-32b-think + - xiaomi/mimo-v2-flash + - nvidia/nemotron-3-nano-30b-a3b + - mistralai/devstral-2512 + - relace/relace-search + - z-ai/glm-4.6v + - nex-agi/deepseek-v3.1-nex-n1 + - essentialai/rnj-1-instruct + - openai/gpt-5.1-codex-max + - amazon/nova-2-lite-v1 + - mistralai/ministral-14b-2512 + - mistralai/ministral-8b-2512 + - mistralai/ministral-3b-2512 + - mistralai/mistral-large-2512 + - arcee-ai/trinity-mini + - deepseek/deepseek-v3.2-speciale + - deepseek/deepseek-v3.2 + - prime-intellect/intellect-3 + - anthropic/claude-opus-4.5 + - allenai/olmo-3-32b-think + - allenai/olmo-3-7b-instruct + - allenai/olmo-3-7b-think + - google/gemini-3-pro-image-preview + - x-ai/grok-4.1-fast + - deepcogito/cogito-v2.1-671b + - openai/gpt-5.1 + - openai/gpt-5.1-chat + - openai/gpt-5.1-codex + - openai/gpt-5.1-codex-mini + - kwaipilot/kat-coder-pro + - moonshotai/kimi-k2-thinking + - amazon/nova-premier-v1 + - perplexity/sonar-pro-search + - mistralai/voxtral-small-24b-2507 + - openai/gpt-oss-safeguard-20b + - nvidia/nemotron-nano-12b-v2-vl + - minimax/minimax-m2 + - qwen/qwen3-vl-32b-instruct + - liquid/lfm2-8b-a1b + - liquid/lfm-2.2-6b + - ibm-granite/granite-4.0-h-micro + - openai/gpt-5-image-mini + - qwen/qwen3-vl-8b-thinking + - qwen/qwen3-vl-8b-instruct + - openai/gpt-5-image + - openai/o3-deep-research + - openai/o4-mini-deep-research + - nvidia/llama-3.3-nemotron-super-49b-v1.5 + - baidu/ernie-4.5-21b-a3b-thinking + - google/gemini-2.5-flash-image + - qwen/qwen3-vl-30b-a3b-thinking + - qwen/qwen3-vl-30b-a3b-instruct + - openai/gpt-5-pro + - z-ai/glm-4.6 + - z-ai/glm-4.6:exacto + - anthropic/claude-sonnet-4.5 + - deepseek/deepseek-v3.2-exp + - thedrummer/cydonia-24b-v4.1 + - relace/relace-apply-3 + - google/gemini-2.5-flash-lite-preview-09-2025 + - qwen/qwen3-vl-235b-a22b-thinking + - qwen/qwen3-vl-235b-a22b-instruct + - qwen/qwen3-max + - qwen/qwen3-coder-plus + - openai/gpt-5-codex + - deepseek/deepseek-v3.1-terminus:exacto + - deepseek/deepseek-v3.1-terminus + - x-ai/grok-4-fast + - alibaba/tongyi-deepresearch-30b-a3b + - qwen/qwen3-coder-flash + - qwen/qwen3-next-80b-a3b-thinking + - qwen/qwen3-next-80b-a3b-instruct + - meituan/longcat-flash-chat + - qwen/qwen-plus-2025-07-28:thinking + - qwen/qwen-plus-2025-07-28 + - nvidia/nemotron-nano-9b-v2 + - moonshotai/kimi-k2-0905 + - moonshotai/kimi-k2-0905:exacto + - qwen/qwen3-30b-a3b-thinking-2507 + - x-ai/grok-code-fast-1 + - nousresearch/hermes-4-70b + - nousresearch/hermes-4-405b + - deepseek/deepseek-chat-v3.1 + - openai/gpt-4o-audio-preview + - mistralai/mistral-medium-3.1 + - baidu/ernie-4.5-21b-a3b + - baidu/ernie-4.5-vl-28b-a3b + - z-ai/glm-4.5v + - ai21/jamba-large-1.7 + - openai/gpt-5-chat + - openai/gpt-5 + - openai/gpt-5-mini + - openai/gpt-oss-120b + - openai/gpt-oss-120b:exacto + - openai/gpt-oss-20b + - anthropic/claude-opus-4.1 + - mistralai/codestral-2508 + - qwen/qwen3-coder-30b-a3b-instruct + - qwen/qwen3-30b-a3b-instruct-2507 + - z-ai/glm-4.5 + - z-ai/glm-4.5-air + - qwen/qwen3-235b-a22b-thinking-2507 + - z-ai/glm-4-32b + - qwen/qwen3-coder + - qwen/qwen3-coder:exacto + - bytedance/ui-tars-1.5-7b + - google/gemini-2.5-flash-lite + - qwen/qwen3-235b-a22b-2507 + - switchpoint/router + - moonshotai/kimi-k2 + - mistralai/devstral-medium + - mistralai/devstral-small + - cognitivecomputations/dolphin-mistral-24b-venice-edition + - google/gemma-3n-e2b-it + - tencent/hunyuan-a13b-instruct + - tngtech/deepseek-r1t2-chimera + - morph/morph-v3-large + - morph/morph-v3-fast + - baidu/ernie-4.5-vl-424b-a47b + - baidu/ernie-4.5-300b-a47b + - inception/mercury + - mistralai/mistral-small-3.2-24b-instruct + - minimax/minimax-m1 + - google/gemini-2.5-flash + - google/gemini-2.5-pro + - openai/o3-pro + - x-ai/grok-3-mini + - x-ai/grok-3 + - google/gemini-2.5-pro-preview + - deepseek/deepseek-r1-0528 + - anthropic/claude-opus-4 + - anthropic/claude-sonnet-4 + - google/gemma-3n-e4b-it + - mistralai/mistral-medium-3 + - google/gemini-2.5-pro-preview-05-06 + - arcee-ai/spotlight + - arcee-ai/maestro-reasoning + - arcee-ai/virtuoso-large + - arcee-ai/coder-large + - inception/mercury-coder + - qwen/qwen3-4b + - meta-llama/llama-guard-4-12b + - qwen/qwen3-30b-a3b + - qwen/qwen3-8b + - qwen/qwen3-14b + - qwen/qwen3-32b + - qwen/qwen3-235b-a22b + - openai/o4-mini-high + - openai/o3 + - openai/o4-mini + - qwen/qwen2.5-coder-7b-instruct + - openai/gpt-4.1 + - openai/gpt-4.1-mini + - openai/gpt-4.1-nano + - eleutherai/llemma_7b + - alfredpros/codellama-7b-instruct-solidity + - x-ai/grok-3-mini-beta + - x-ai/grok-3-beta + - meta-llama/llama-4-maverick + - meta-llama/llama-4-scout + - qwen/qwen2.5-vl-32b-instruct + - deepseek/deepseek-chat-v3-0324 + - openai/o1-pro + - mistralai/mistral-small-3.1-24b-instruct + - allenai/olmo-2-0325-32b-instruct + - google/gemma-3-4b-it + - google/gemma-3-12b-it + - cohere/command-a + - openai/gpt-4o-mini-search-preview + - openai/gpt-4o-search-preview + - google/gemma-3-27b-it + - thedrummer/skyfall-36b-v2 + - perplexity/sonar-reasoning-pro + - perplexity/sonar-pro + - perplexity/sonar-deep-research + - qwen/qwq-32b + - google/gemini-2.0-flash-lite-001 + - anthropic/claude-3.7-sonnet + - anthropic/claude-3.7-sonnet:thinking + - mistralai/mistral-saba + - meta-llama/llama-guard-3-8b + - openai/o3-mini-high + - google/gemini-2.0-flash-001 + - qwen/qwen-vl-plus + - aion-labs/aion-1.0 + - aion-labs/aion-1.0-mini + - aion-labs/aion-rp-llama-3.1-8b + - qwen/qwen-vl-max + - qwen/qwen-turbo + - qwen/qwen2.5-vl-72b-instruct + - qwen/qwen-plus + - qwen/qwen-max + - openai/o3-mini + - mistralai/mistral-small-24b-instruct-2501 + - deepseek/deepseek-r1-distill-qwen-32b + - perplexity/sonar + - deepseek/deepseek-r1-distill-llama-70b + - deepseek/deepseek-r1 + - minimax/minimax-01 + - microsoft/phi-4 + - sao10k/l3.1-70b-hanami-x1 + - deepseek/deepseek-chat + - sao10k/l3.3-euryale-70b + - openai/o1 + - cohere/command-r7b-12-2024 + - meta-llama/llama-3.3-70b-instruct + - amazon/nova-lite-v1 + - amazon/nova-micro-v1 + - amazon/nova-pro-v1 + - openai/gpt-4o-2024-11-20 + - mistralai/mistral-large-2411 + - mistralai/mistral-large-2407 + - mistralai/pixtral-large-2411 + - qwen/qwen-2.5-coder-32b-instruct + - raifle/sorcererlm-8x22b + - thedrummer/unslopnemo-12b + - anthropic/claude-3.5-haiku + - anthracite-org/magnum-v4-72b + - anthropic/claude-3.5-sonnet + - qwen/qwen-2.5-7b-instruct + - nvidia/llama-3.1-nemotron-70b-instruct + - inflection/inflection-3-pi + - inflection/inflection-3-productivity + - thedrummer/rocinante-12b + - meta-llama/llama-3.2-3b-instruct + - meta-llama/llama-3.2-1b-instruct + - meta-llama/llama-3.2-11b-vision-instruct + - qwen/qwen-2.5-72b-instruct + - neversleep/llama-3.1-lumimaid-8b + - cohere/command-r-08-2024 + - cohere/command-r-plus-08-2024 + - sao10k/l3.1-euryale-70b + - qwen/qwen-2.5-vl-7b-instruct + - nousresearch/hermes-3-llama-3.1-70b + - nousresearch/hermes-3-llama-3.1-405b + - sao10k/l3-lunaris-8b + - openai/gpt-4o-2024-08-06 + - meta-llama/llama-3.1-405b + - meta-llama/llama-3.1-8b-instruct + - meta-llama/llama-3.1-405b-instruct + - meta-llama/llama-3.1-70b-instruct + - mistralai/mistral-nemo + - openai/gpt-4o-mini-2024-07-18 + - openai/gpt-4o-mini + - google/gemma-2-27b-it + - google/gemma-2-9b-it + - sao10k/l3-euryale-70b + - nousresearch/hermes-2-pro-llama-3-8b + - meta-llama/llama-guard-2-8b + - openai/gpt-4o-2024-05-13 + - openai/gpt-4o + - openai/gpt-4o:extended + - meta-llama/llama-3-70b-instruct + - meta-llama/llama-3-8b-instruct + - mistralai/mixtral-8x22b-instruct + - microsoft/wizardlm-2-8x22b + - openai/gpt-4-turbo + - anthropic/claude-3-haiku + - mistralai/mistral-large + - openai/gpt-3.5-turbo-0613 + - openai/gpt-4-turbo-preview + - mistralai/mixtral-8x7b-instruct + - neversleep/noromaid-20b + - alpindale/goliath-120b + - openai/gpt-4-1106-preview + - openai/gpt-3.5-turbo-instruct + - mistralai/mistral-7b-instruct-v0.1 + - openai/gpt-3.5-turbo-16k + - mancer/weaver + - undi95/remm-slerp-l2-13b + - gryphe/mythomax-l2-13b + - openai/gpt-4-0314 + - openai/gpt-4 + - openai/gpt-3.5-turbo + - autoclaw + + +--- + +```text +Context Log - not seen by model +timestamp=2026-03-04 09:47:05 +phase=direct_tool_exec +sender=8ff74724ed641b3c28e5a86d7c5cbc49c37638ace8c6c38935860e7a5eedde0e +model=claude-haiku-4.5 +context_bytes=6263 +approx_tokens=1565 +``` + +slash=/help +result_json=AVAILABLE TOOLS +- file_read — Read a local file as text from the configured working directory +- file_write — Write text content to a local file in the configured working directory +- http_fetch — Fetch HTTP(S) resources with optional method, headers, timeout, and body +- model_get — Get current active LLM runtime configuration (excluding API key) +- model_list — List available model IDs using provider OpenAI-compatible /models endpoint +- model_set — Update active LLM configuration and persist it to config.json +- my_npub — Alias for nostr_npub: return this agent's pubkey encoded as npub bech32 +- my_pubkey — Alias for nostr_pubkey: return this agent's pubkey in hex format +- my_version — Return current Didactyl version and metadata from build macros +- nostr_decode — Decode a Nostr bech32/nostr: URI into components +- nostr_decrypt — Decrypt NIP-44 ciphertext from a sender +- nostr_delete — Request deletion of one or more previously published events (NIP-09 kind 5) +- nostr_dm_send — Send a NIP-04 encrypted DM +- nostr_dm_send_nip17 — Send a private DM using NIP-17 gift wrap protocol +- nostr_encode — Encode a Nostr entity into nostr: URI (npub, note, nprofile, nevent, naddr) +- nostr_encrypt — Encrypt plaintext using NIP-44 for a recipient +- nostr_file_md_to_longform_post — Read a markdown file and publish it as kind 30023 longform post; defaults d-tag to lowercase filename +- nostr_list_manage — Add/remove tag tuples in replaceable list events (NIP-51 style) +- nostr_nip05_lookup — Look up or verify a NIP-05 identifier (user@domain) +- nostr_npub — Return this agent's pubkey encoded as npub bech32 +- nostr_post — Publish a Nostr event to connected relays +- nostr_post_readme — Publish README.md as kind 30023 with deterministic d tag readme.md +- nostr_profile_get — Look up a Nostr profile (kind 0 metadata) by pubkey +- nostr_pubkey — Return this agent's pubkey in hex format +- nostr_query — Query events from relays using a Nostr filter +- nostr_react — React to a Nostr event with like/dislike/emoji (NIP-25 kind 7) +- nostr_relay_info — Fetch NIP-11 relay information document +- nostr_relay_status — Get connection status and statistics for all relays +- shell_exec — Execute a shell command and return stdout/stderr +- skill_adopt — Adopt a skill by adding its address to kind 10123 adoption list +- skill_create — Create or update a skill definition as kind 31123/31124 and optionally auto-adopt it +- skill_list — List this agent's published skills, optionally filtered by scope +- skill_remove — Remove a skill address from kind 10123 adoption list +- skill_search — Search public skills by query/author and optionally rank by adoption popularity +- task_manage — Manage agent short-term task memory stored in tasks.json (list/add/update/remove/clear/replace) +- tool_list — List available tools with name, description, and JSON parameter schema +- trigger_list — List active triggered skills and their runtime status + +AVAILABLE SKILLS +- admin_ops +- post_readme_to_nostr +- long_form_note +- long_form_note +- post_readme_to_nostr + +result_markdown=AVAILABLE TOOLS +- file_read — Read a local file as text from the configured working directory +- file_write — Write text content to a local file in the configured working directory +- http_fetch — Fetch HTTP(S) resources with optional method, headers, timeout, and body +- model_get — Get current active LLM runtime configuration (excluding API key) +- model_list — List available model IDs using provider OpenAI-compatible /models endpoint +- model_set — Update active LLM configuration and persist it to config.json +- my_npub — Alias for nostr_npub: return this agent's pubkey encoded as npub bech32 +- my_pubkey — Alias for nostr_pubkey: return this agent's pubkey in hex format +- my_version — Return current Didactyl version and metadata from build macros +- nostr_decode — Decode a Nostr bech32/nostr: URI into components +- nostr_decrypt — Decrypt NIP-44 ciphertext from a sender +- nostr_delete — Request deletion of one or more previously published events (NIP-09 kind 5) +- nostr_dm_send — Send a NIP-04 encrypted DM +- nostr_dm_send_nip17 — Send a private DM using NIP-17 gift wrap protocol +- nostr_encode — Encode a Nostr entity into nostr: URI (npub, note, nprofile, nevent, naddr) +- nostr_encrypt — Encrypt plaintext using NIP-44 for a recipient +- nostr_file_md_to_longform_post — Read a markdown file and publish it as kind 30023 longform post; defaults d-tag to lowercase filename +- nostr_list_manage — Add/remove tag tuples in replaceable list events (NIP-51 style) +- nostr_nip05_lookup — Look up or verify a NIP-05 identifier (user@domain) +- nostr_npub — Return this agent's pubkey encoded as npub bech32 +- nostr_post — Publish a Nostr event to connected relays +- nostr_post_readme — Publish README.md as kind 30023 with deterministic d tag readme.md +- nostr_profile_get — Look up a Nostr profile (kind 0 metadata) by pubkey +- nostr_pubkey — Return this agent's pubkey in hex format +- nostr_query — Query events from relays using a Nostr filter +- nostr_react — React to a Nostr event with like/dislike/emoji (NIP-25 kind 7) +- nostr_relay_info — Fetch NIP-11 relay information document +- nostr_relay_status — Get connection status and statistics for all relays +- shell_exec — Execute a shell command and return stdout/stderr +- skill_adopt — Adopt a skill by adding its address to kind 10123 adoption list +- skill_create — Create or update a skill definition as kind 31123/31124 and optionally auto-adopt it +- skill_list — List this agent's published skills, optionally filtered by scope +- skill_remove — Remove a skill address from kind 10123 adoption list +- skill_search — Search public skills by query/author and optionally rank by adoption popularity +- task_manage — Manage agent short-term task memory stored in tasks.json (list/add/update/remove/clear/replace) +- tool_list — List available tools with name, description, and JSON parameter schema +- trigger_list — List active triggered skills and their runtime status + +AVAILABLE SKILLS +- admin_ops +- post_readme_to_nostr +- long_form_note +- long_form_note +- post_readme_to_nostr + + +--- + +```text +Context Log - not seen by model +timestamp=2026-03-04 09:46:23 +phase=llm_chat_with_tools_messages +sender=8ff74724ed641b3c28e5a86d7c5cbc49c37638ace8c6c38935860e7a5eedde0e +model=claude-haiku-4.5 +context_bytes=9655 +approx_tokens=2413 +``` + +Sections: 21 + +# system_prompt | role=system + +## Didactyl Agent + +You are Didactyl, a sovereign AI agent living on Nostr. + +### Communication Rules +- You communicate through encrypted Nostr direct messages. +- Keep responses concise and clear. + +### Behavior +- Be helpful and technically accurate. +- If unsure, state uncertainty directly. +- Prefer actionable, practical advice. +- Use the person's name when messaging them if you know it. +- For the administrator, use their name from the administrator kind 0 profile metadata when available. + +### Tool Use Policy +- You have tools available and should use them when a request requires taking action. +- For requests involving local inspection or command execution, call `shell_exec` instead of refusing. +- For posting to Nostr, call `nostr_post` with explicit `kind` and `content`. +- For relay/event lookup tasks, call `nostr_query` with an appropriate filter. +- After a tool call, base your answer on the actual tool result. +- Never claim a tool was run if no tool was executed. + +### Task Management +- Maintain and use your internal task list as short-term working memory. +- Break long or complex actions into clear tasks before executing them. +- Update task status as you complete steps so your plan stays accurate. + +### Safety +- Do not claim to have executed actions you did not execute. +- You may share your public key (npub) with anyone. +- Never reveal your private key (nsec) under any circumstance. + +# admin_identity | role=system + +### Administrator Identity (source: config.admin.pubkey) + +This is your administrator! Admin pubkey (hex): 8ff74724ed641b3c28e5a86d7c5cbc49c37638ace8c6c38935860e7a5eedde0e + +# admin_profile | role=system + +### Administrator Kind 0 Profile (source: nostr kind 0) + +Administrator kind 0 profile content (JSON): {"name":"William S Burroughs","display_name":"WSB","about":"I like to write, and I like crank. They are good. Sometimes I'm not so sure.","banner":"https://www.grunge.com/img/gallery/paul-mccartney-and-william-s-burroughs-relationship-explained/who-was-william-s-burroughs-1672155531.jpg","website":"https://addicted.com","picture":"https://hips.hearstapps.com/hmg-prod/images/william-s-burroughs.jpg?resize=1200:*","lud16":"","nip05":""} + +# admin_relay_list | role=system + +### Administrator Relay List (source: nostr kind 10002) + +Administrator kind 10002 relay-list content (JSON): ["wss://relay.laantungir.net/","wss://relay.damus.io/","wss://nos.lol/","wss://relay.nostr.band/","wss://purplepag.es/","ws://127.0.0.1:7777/","wss://nostr.mom/"] + +# startup_events | role=system + +### Startup Events Memory (source: config.startup_events) + +Startup events memory (kinds/content/tags): [{"kind":0,"content":"{\"name\":\"Didactyl Agent\",\"display_name\":\"Didactyl\",\"about\":\"A sovereign AI agent on Nostr\",\"picture\":\"https://laantungir.github.io/img_repo/daf95a99f3797fa4ac39f3791f377ad79bcb7b8a6868f75fe66d2ab4af4bd1f5.png\",\"banner\":\"https://laantungir.github.io/img_repo/d21c4060632ab3d9d37a6062eeecbdbfbd67f03cb20dbd64838b1ba0d9cd8922.jpg\"}","tags":[]},{"kind":10002,"content":"","tags":[["r","wss://relay.damus.io"],["r","wss://nos.lol"],["r","wss://relay.primal.net"],["r","ws://127.0.0.1:7777"]]},{"kind":1,"content":"Hello world from Didactyl startup","tags":[["t","didactyl"],["t","startup"]]},{"kind":3,"content":"","tags":[]},{"kind":31123,"content":"{\"name\":\"long_form_note\",\"description\":\"How to publish a NIP-23 long-form article (kind 30023)\",\"nip\":\"NIP-23\",\"event_kind\":30023,\"format\":\"The content field must be markdown text; avoid arbitrary hard line-breaks in paragraphs and do not include HTML.\",\"required_tags\":{\"d\":\"Addressable identifier slug for the article. Reusing the same d tag replaces prior versions.\",\"title\":\"Human-readable article title.\",\"published_at\":\"Unix timestamp as a string for first publication time; keep stable on edits.\"},\"optional_tags\":{\"summary\":\"Short 1-2 sentence summary.\",\"image\":\"URL for article preview image.\",\"t\":\"Topic hashtags using repeated t tags, usually 3-6 lowercase terms.\"},\"behavior\":\"If required values are missing or unclear, ask the administrator before publishing instead of guessing.\",\"procedure\":[\"Determine title and d tag from admin input or source material.\",\"Draft markdown body content.\",\"Set published_at as unix seconds string.\",\"Add optional summary/image/t tags when known.\",\"Publish with nostr_post kind 30023 including tags array.\"]}","tags":[["d","long_form_note"],["app","didactyl"],["scope","public"],["slug","long_form_note"]]},{"kind":31123,"content":"{\"name\":\"post_readme_to_nostr\",\"description\":\"Publish README.md using the dedicated nostr_post_readme tool\",\"uses_skill\":\"long_form_note\",\"event_kind\":30023,\"procedure\":[\"Call nostr_post_readme with empty arguments {}.\",\"Return the tool response including event_id, d_tag, and naddr_uri.\",\"If the tool returns success=false, report the tool error verbatim.\"],\"required_values\":{\"d\":\"readme.md\",\"tool\":\"nostr_post_readme\"}}","tags":[["d","post_readme_to_nostr"],["app","didactyl"],["scope","public"],["slug","post_readme_to_nostr"]]},{"kind":31124,"content":"{\"name\":\"admin_ops\",\"description\":\"Private operational procedures\"}","tags":[["d","admin_ops"],["app","didactyl"],["scope","private"],["slug","admin_ops"]]},{"kind":10123,"content":"","tags":[["a","31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:long_form_note"],["a","31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:post_readme_to_nostr"],["app","didactyl"],["scope","public"]]}] + +# adopted_skills | role=system + +### Adopted Skills Memory (source: adoption list + startup fallback) + +Adopted skills memory (always-on behavioral instructions): follow these skill instructions when relevant and never violate explicit constraints. + + +--- + +#### Skill +Skill slug: long_form_note +Skill address: 31123:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:long_form_note + +Instructions: +Name: long_form_note +Description: How to publish a NIP-23 long-form article (kind 30023) +Event kind: 30023 +Procedure: + 1. Determine title and d tag from admin input or source material. + 2. Draft markdown body content. + 3. Set published_at as unix seconds string. + 4. Add optional summary/image/t tags when known. + 5. Publish with nostr_post kind 30023 including tags array. + + + +--- + +#### Skill +Skill slug: post_readme_to_nostr +Skill address: 31123:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:post_readme_to_nostr + +Instructions: +Name: post_readme_to_nostr +Description: Publish README.md using the dedicated nostr_post_readme tool +Event kind: 30023 +Procedure: + 1. Call nostr_post_readme with empty arguments {}. + 2. Return the tool response including event_id, d_tag, and naddr_uri. + 3. If the tool returns success=false, report the tool error verbatim. + + + +--- + +#### Skill +Skill slug: admin_ops +Skill address: 31124:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:admin_ops + +Instructions: +Name: admin_ops +Description: Private operational procedures + + + +# agent_tasks | role=system + +#### Current Tasks + +Your active task list - short-term working memory for tracking plan steps. + +- [ ] 4. Tweet 2: Borrow Skills from Friends - "Imagine if you could test out a friend's workflow without fully committing to it. Didactyl lets you try skills others have built, safely and temporarily. No installation. No lock-in. Just try it, see if it works for you. That's how tools should work. #nostr" +- [ ] 5. Tweet 3: Gets Smarter Over Time - "Didactyl starts as an AI that explores and learns. Over time, the best workflows get locked in as reliable, fast processes. It evolves from experimental to hardened—from thinking to doing. An agent that improves itself. #nostr #agents" + + +# admin_notes | role=system + +### Administrator Recent Notes (source: nostr kind 1) + +Recent posts: +- GM +- Getting longer +- Long day. +- This is a test. +- test +- Post 11 +- Post 10 +- This is +Post +9 +- Eight +- Seven + + +# dm_history | role=chat + +09:37 WSB Good morning +09:37 Agent LLM request failed. +09:37 Agent Didactyl has started up and is online (version v0.0.29, connected relays: 4/4). +09:38 WSB Good morning +09:38 Agent LLM request failed. +09:41 Agent Didactyl has started up and is online (version v0.0.29, connected relays: 4/4). +09:42 WSB Good morning. +09:42 Agent LLM request failed. +09:44 Agent Didactyl has started up and is online (version v0.0.29, connected relays: 4/4). +09:45 WSB Good morning +09:45 Agent LLM request failed. +09:46 Agent Didactyl has started up and is online (version v0.0.29, connected relays: 4/4). +09:46 WSB Good morning. + + + +--- + +```text +Context Log - not seen by model +timestamp=2026-03-04 09:45:14 +phase=llm_chat_with_tools_messages +sender=8ff74724ed641b3c28e5a86d7c5cbc49c37638ace8c6c38935860e7a5eedde0e +model=claude-haiku-4.5 +context_bytes=10874 +approx_tokens=2718 +``` + +Sections: 21 + +# system_prompt | role=system + +## Didactyl Agent + +You are Didactyl, a sovereign AI agent living on Nostr. + +### Communication Rules +- You communicate through encrypted Nostr direct messages. +- Keep responses concise and clear. + +### Behavior +- Be helpful and technically accurate. +- If unsure, state uncertainty directly. +- Prefer actionable, practical advice. +- Use the person's name when messaging them if you know it. +- For the administrator, use their name from the administrator kind 0 profile metadata when available. + +### Tool Use Policy +- You have tools available and should use them when a request requires taking action. +- For requests involving local inspection or command execution, call `shell_exec` instead of refusing. +- For posting to Nostr, call `nostr_post` with explicit `kind` and `content`. +- For relay/event lookup tasks, call `nostr_query` with an appropriate filter. +- After a tool call, base your answer on the actual tool result. +- Never claim a tool was run if no tool was executed. + +### Task Management +- Maintain and use your internal task list as short-term working memory. +- Break long or complex actions into clear tasks before executing them. +- Update task status as you complete steps so your plan stays accurate. + +### Safety +- Do not claim to have executed actions you did not execute. +- You may share your public key (npub) with anyone. +- Never reveal your private key (nsec) under any circumstance. + +# admin_identity | role=system + +### Administrator Identity (source: config.admin.pubkey) + +This is your administrator! Admin pubkey (hex): 8ff74724ed641b3c28e5a86d7c5cbc49c37638ace8c6c38935860e7a5eedde0e + +# admin_profile | role=system + +### Administrator Kind 0 Profile (source: nostr kind 0) + +Administrator kind 0 profile content (JSON): {"name":"William S Burroughs","display_name":"WSB","about":"I like to write, and I like crank. They are good. Sometimes I'm not so sure.","banner":"https://www.grunge.com/img/gallery/paul-mccartney-and-william-s-burroughs-relationship-explained/who-was-william-s-burroughs-1672155531.jpg","website":"https://addicted.com","picture":"https://hips.hearstapps.com/hmg-prod/images/william-s-burroughs.jpg?resize=1200:*","lud16":"","nip05":""} + +# admin_relay_list | role=system + +### Administrator Relay List (source: nostr kind 10002) + +Administrator kind 10002 relay-list content (JSON): ["wss://relay.laantungir.net/","wss://relay.damus.io/","wss://nos.lol/","wss://relay.nostr.band/","wss://purplepag.es/","ws://127.0.0.1:7777/","wss://nostr.mom/"] + +# startup_events | role=system + +### Startup Events Memory (source: config.startup_events) + +Startup events memory (kinds/content/tags): [{"kind":0,"content":"{\"name\":\"Didactyl Agent\",\"display_name\":\"Didactyl\",\"about\":\"A sovereign AI agent on Nostr\",\"picture\":\"https://laantungir.github.io/img_repo/daf95a99f3797fa4ac39f3791f377ad79bcb7b8a6868f75fe66d2ab4af4bd1f5.png\",\"banner\":\"https://laantungir.github.io/img_repo/d21c4060632ab3d9d37a6062eeecbdbfbd67f03cb20dbd64838b1ba0d9cd8922.jpg\"}","tags":[]},{"kind":10002,"content":"","tags":[["r","wss://relay.damus.io"],["r","wss://nos.lol"],["r","wss://relay.primal.net"],["r","ws://127.0.0.1:7777"]]},{"kind":1,"content":"Hello world from Didactyl startup","tags":[["t","didactyl"],["t","startup"]]},{"kind":3,"content":"","tags":[]},{"kind":31123,"content":"{\"name\":\"long_form_note\",\"description\":\"How to publish a NIP-23 long-form article (kind 30023)\",\"nip\":\"NIP-23\",\"event_kind\":30023,\"format\":\"The content field must be markdown text; avoid arbitrary hard line-breaks in paragraphs and do not include HTML.\",\"required_tags\":{\"d\":\"Addressable identifier slug for the article. Reusing the same d tag replaces prior versions.\",\"title\":\"Human-readable article title.\",\"published_at\":\"Unix timestamp as a string for first publication time; keep stable on edits.\"},\"optional_tags\":{\"summary\":\"Short 1-2 sentence summary.\",\"image\":\"URL for article preview image.\",\"t\":\"Topic hashtags using repeated t tags, usually 3-6 lowercase terms.\"},\"behavior\":\"If required values are missing or unclear, ask the administrator before publishing instead of guessing.\",\"procedure\":[\"Determine title and d tag from admin input or source material.\",\"Draft markdown body content.\",\"Set published_at as unix seconds string.\",\"Add optional summary/image/t tags when known.\",\"Publish with nostr_post kind 30023 including tags array.\"]}","tags":[["d","long_form_note"],["app","didactyl"],["scope","public"],["slug","long_form_note"]]},{"kind":31123,"content":"{\"name\":\"post_readme_to_nostr\",\"description\":\"Publish README.md using the dedicated nostr_post_readme tool\",\"uses_skill\":\"long_form_note\",\"event_kind\":30023,\"procedure\":[\"Call nostr_post_readme with empty arguments {}.\",\"Return the tool response including event_id, d_tag, and naddr_uri.\",\"If the tool returns success=false, report the tool error verbatim.\"],\"required_values\":{\"d\":\"readme.md\",\"tool\":\"nostr_post_readme\"}}","tags":[["d","post_readme_to_nostr"],["app","didactyl"],["scope","public"],["slug","post_readme_to_nostr"]]},{"kind":31124,"content":"{\"name\":\"admin_ops\",\"description\":\"Private operational procedures\"}","tags":[["d","admin_ops"],["app","didactyl"],["scope","private"],["slug","admin_ops"]]},{"kind":10123,"content":"","tags":[["a","31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:long_form_note"],["a","31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:post_readme_to_nostr"],["app","didactyl"],["scope","public"]]}] + +# adopted_skills | role=system + +### Adopted Skills Memory (source: adoption list + startup fallback) + +Adopted skills memory (always-on behavioral instructions): follow these skill instructions when relevant and never violate explicit constraints. + + +--- + +#### Skill +Skill slug: long_form_note +Skill address: 31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:long_form_note + +Instructions: +Name: long_form_note +Description: How to publish a NIP-23 long-form article (kind 30023) +Event kind: 30023 +Procedure: + 1. Determine title and d tag from admin input or source material. + 2. Draft markdown body content. + 3. Set published_at as unix seconds string. + 4. Add optional summary/image/t tags when known. + 5. Publish with nostr_post kind 30023 including tags array. + + + +# agent_tasks | role=system + +#### Current Tasks + +Your active task list - short-term working memory for tracking plan steps. + +- [ ] 4. Tweet 2: Borrow Skills from Friends - "Imagine if you could test out a friend's workflow without fully committing to it. Didactyl lets you try skills others have built, safely and temporarily. No installation. No lock-in. Just try it, see if it works for you. That's how tools should work. #nostr" +- [ ] 5. Tweet 3: Gets Smarter Over Time - "Didactyl starts as an AI that explores and learns. Over time, the best workflows get locked in as reliable, fast processes. It evolves from experimental to hardened—from thinking to doing. An agent that improves itself. #nostr #agents" + + +# admin_notes | role=system + +### Administrator Recent Notes (source: nostr kind 1) + +Recent posts: +- GM +- Getting longer +- Long day. +- This is a test. +- test +- Post 11 +- Post 10 +- This is +Post +9 +- Eight +- Seven + + +# dm_history | role=chat + +09:36 WSB /nostr_relay-status +09:37 Agent - **success:** true +- **status:** + - **relay_count:** 4 + - **connected_count:** 4 + - **relays:** + - + - **url:** wss://relay.damus.io + - **status:** connected + - **events_received:** 16 + - **events_published:** 16 + - **events_published_ok:** 2 + - **events_published_failed:** 6 + - **ping_latency_current:** 111.771 + - **ping_latency_avg:** 115.678 + - **query_latency_avg:** 376.22 + - **publish_latency_avg:** 0 + - **connection_uptime_start:** 1.77263e+09 + - **last_event_time:** 1.77263e+09 + - **last_publish_error:** rate-limited: you are noting too much + - + - **url:** wss://nos.lol + - **status:** connected + - **events_received:** 3 + - **events_published:** 16 + - **events_published_ok:** 16 + - **events_published_failed:** 0 + - **ping_latency_current:** 222.105 + - **ping_latency_avg:** 222.094 + - **query_latency_avg:** 0 + - **publish_latency_avg:** 0 + - **connection_uptime_start:** 1.77263e+09 + - **last_event_time:** 1.77263e+09 + - + - **url:** wss://relay.primal.net + - **status:** connected + - **events_received:** 1 + - **events_published:** 16 + - **events_published_ok:** 8 + - **events_published_failed:** 0 + - **ping_latency_current:** 221.284 + - **ping_latency_avg:** 224.326 + - **query_latency_avg:** 0 + - **publish_latency_avg:** 0 + - **connection_uptime_start:** 1.77263e+09 + - **last_event_time:** 1.77263e+09 + - + - **url:** ws://127.0.0.1:7777 + - **status:** connected + - **events_received:** 10 + - **events_published:** 16 + - **events_published_ok:** 6 + - **events_published_failed:** 0 + - **ping_latency_current:** 0.18564 + - **ping_latency_avg:** 0.207998 + - **query_latency_avg:** 5080.53 + - **publish_latency_avg:** 0 + - **connection_uptime_start:** 1.77263e+09 + - **last_event_time:** 1.77263e+09 + +09:37 WSB /nostr_relay_status +09:37 WSB Good morning +09:37 Agent LLM request failed. +09:37 Agent Didactyl has started up and is online (version v0.0.29, connected relays: 4/4). +09:38 WSB Good morning +09:38 Agent LLM request failed. +09:41 Agent Didactyl has started up and is online (version v0.0.29, connected relays: 4/4). +09:42 WSB Good morning. +09:42 Agent LLM request failed. +09:44 Agent Didactyl has started up and is online (version v0.0.29, connected relays: 4/4). +09:45 WSB Good morning + + + +--- + +```text +Context Log - not seen by model +timestamp=2026-03-04 09:42:25 +phase=llm_chat_with_tools_messages +sender=8ff74724ed641b3c28e5a86d7c5cbc49c37638ace8c6c38935860e7a5eedde0e +model=claude-haiku-4.5 +context_bytes=11642 +approx_tokens=2910 +``` + +Sections: 21 + +# system_prompt | role=system + +## Didactyl Agent + +You are Didactyl, a sovereign AI agent living on Nostr. + +### Communication Rules +- You communicate through encrypted Nostr direct messages. +- Keep responses concise and clear. + +### Behavior +- Be helpful and technically accurate. +- If unsure, state uncertainty directly. +- Prefer actionable, practical advice. +- Use the person's name when messaging them if you know it. +- For the administrator, use their name from the administrator kind 0 profile metadata when available. + +### Tool Use Policy +- You have tools available and should use them when a request requires taking action. +- For requests involving local inspection or command execution, call `shell_exec` instead of refusing. +- For posting to Nostr, call `nostr_post` with explicit `kind` and `content`. +- For relay/event lookup tasks, call `nostr_query` with an appropriate filter. +- After a tool call, base your answer on the actual tool result. +- Never claim a tool was run if no tool was executed. + +### Task Management +- Maintain and use your internal task list as short-term working memory. +- Break long or complex actions into clear tasks before executing them. +- Update task status as you complete steps so your plan stays accurate. + +### Safety +- Do not claim to have executed actions you did not execute. +- You may share your public key (npub) with anyone. +- Never reveal your private key (nsec) under any circumstance. + +# admin_identity | role=system + +### Administrator Identity (source: config.admin.pubkey) + +This is your administrator! Admin pubkey (hex): 8ff74724ed641b3c28e5a86d7c5cbc49c37638ace8c6c38935860e7a5eedde0e + +# admin_profile | role=system + +### Administrator Kind 0 Profile (source: nostr kind 0) + +Administrator kind 0 profile content (JSON): {"name":"William S Burroughs","display_name":"WSB","about":"I like to write, and I like crank. They are good. Sometimes I'm not so sure.","banner":"https://www.grunge.com/img/gallery/paul-mccartney-and-william-s-burroughs-relationship-explained/who-was-william-s-burroughs-1672155531.jpg","website":"https://addicted.com","picture":"https://hips.hearstapps.com/hmg-prod/images/william-s-burroughs.jpg?resize=1200:*","lud16":"","nip05":""} + +# admin_relay_list | role=system + +### Administrator Relay List (source: nostr kind 10002) + +Administrator kind 10002 relay-list content (JSON): ["wss://relay.laantungir.net/","wss://relay.damus.io/","wss://nos.lol/","wss://relay.nostr.band/","wss://purplepag.es/","ws://127.0.0.1:7777/","wss://nostr.mom/"] + +# startup_events | role=system + +### Startup Events Memory (source: config.startup_events) + +Startup events memory (kinds/content/tags): [{"kind":0,"content":"{\"name\":\"Didactyl Agent\",\"display_name\":\"Didactyl\",\"about\":\"A sovereign AI agent on Nostr\",\"picture\":\"https://laantungir.github.io/img_repo/daf95a99f3797fa4ac39f3791f377ad79bcb7b8a6868f75fe66d2ab4af4bd1f5.png\",\"banner\":\"https://laantungir.github.io/img_repo/d21c4060632ab3d9d37a6062eeecbdbfbd67f03cb20dbd64838b1ba0d9cd8922.jpg\"}","tags":[]},{"kind":10002,"content":"","tags":[["r","wss://relay.damus.io"],["r","wss://nos.lol"],["r","wss://relay.primal.net"],["r","ws://127.0.0.1:7777"]]},{"kind":1,"content":"Hello world from Didactyl startup","tags":[["t","didactyl"],["t","startup"]]},{"kind":3,"content":"","tags":[]},{"kind":31123,"content":"{\"name\":\"long_form_note\",\"description\":\"How to publish a NIP-23 long-form article (kind 30023)\",\"nip\":\"NIP-23\",\"event_kind\":30023,\"format\":\"The content field must be markdown text; avoid arbitrary hard line-breaks in paragraphs and do not include HTML.\",\"required_tags\":{\"d\":\"Addressable identifier slug for the article. Reusing the same d tag replaces prior versions.\",\"title\":\"Human-readable article title.\",\"published_at\":\"Unix timestamp as a string for first publication time; keep stable on edits.\"},\"optional_tags\":{\"summary\":\"Short 1-2 sentence summary.\",\"image\":\"URL for article preview image.\",\"t\":\"Topic hashtags using repeated t tags, usually 3-6 lowercase terms.\"},\"behavior\":\"If required values are missing or unclear, ask the administrator before publishing instead of guessing.\",\"procedure\":[\"Determine title and d tag from admin input or source material.\",\"Draft markdown body content.\",\"Set published_at as unix seconds string.\",\"Add optional summary/image/t tags when known.\",\"Publish with nostr_post kind 30023 including tags array.\"]}","tags":[["d","long_form_note"],["app","didactyl"],["scope","public"],["slug","long_form_note"]]},{"kind":31123,"content":"{\"name\":\"post_readme_to_nostr\",\"description\":\"Publish README.md using the dedicated nostr_post_readme tool\",\"uses_skill\":\"long_form_note\",\"event_kind\":30023,\"procedure\":[\"Call nostr_post_readme with empty arguments {}.\",\"Return the tool response including event_id, d_tag, and naddr_uri.\",\"If the tool returns success=false, report the tool error verbatim.\"],\"required_values\":{\"d\":\"readme.md\",\"tool\":\"nostr_post_readme\"}}","tags":[["d","post_readme_to_nostr"],["app","didactyl"],["scope","public"],["slug","post_readme_to_nostr"]]},{"kind":31124,"content":"{\"name\":\"admin_ops\",\"description\":\"Private operational procedures\"}","tags":[["d","admin_ops"],["app","didactyl"],["scope","private"],["slug","admin_ops"]]},{"kind":10123,"content":"","tags":[["a","31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:long_form_note"],["a","31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:post_readme_to_nostr"],["app","didactyl"],["scope","public"]]}] + +# adopted_skills | role=system + +### Adopted Skills Memory (source: adoption list + startup fallback) + +Adopted skills memory (always-on behavioral instructions): follow these skill instructions when relevant and never violate explicit constraints. + + +--- + +#### Skill +Skill slug: long_form_note +Skill address: 31123:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:long_form_note + +Instructions: +Name: long_form_note +Description: How to publish a NIP-23 long-form article (kind 30023) +Event kind: 30023 +Procedure: + 1. Determine title and d tag from admin input or source material. + 2. Draft markdown body content. + 3. Set published_at as unix seconds string. + 4. Add optional summary/image/t tags when known. + 5. Publish with nostr_post kind 30023 including tags array. + + + +--- + +#### Skill +Skill slug: post_readme_to_nostr +Skill address: 31123:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:post_readme_to_nostr + +Instructions: +Name: post_readme_to_nostr +Description: Publish README.md using the dedicated nostr_post_readme tool +Event kind: 30023 +Procedure: + 1. Call nostr_post_readme with empty arguments {}. + 2. Return the tool response including event_id, d_tag, and naddr_uri. + 3. If the tool returns success=false, report the tool error verbatim. + + + +--- + +#### Skill +Skill slug: admin_ops +Skill address: 31124:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:admin_ops + +Instructions: +Name: admin_ops +Description: Private operational procedures + + + +# agent_tasks | role=system + +#### Current Tasks + +Your active task list - short-term working memory for tracking plan steps. + +- [ ] 4. Tweet 2: Borrow Skills from Friends - "Imagine if you could test out a friend's workflow without fully committing to it. Didactyl lets you try skills others have built, safely and temporarily. No installation. No lock-in. Just try it, see if it works for you. That's how tools should work. #nostr" +- [ ] 5. Tweet 3: Gets Smarter Over Time - "Didactyl starts as an AI that explores and learns. Over time, the best workflows get locked in as reliable, fast processes. It evolves from experimental to hardened—from thinking to doing. An agent that improves itself. #nostr #agents" + + +# admin_notes | role=system + +### Administrator Recent Notes (source: nostr kind 1) + +Recent posts: +- GM +- Getting longer +- Long day. +- This is a test. +- test +- Post 11 +- Post 10 +- This is +Post +9 +- Eight +- Seven + + +# dm_history | role=chat + +09:36 WSB /nostr_relay_info +09:36 Agent - **success:** false +- **error:** nostr_relay_info requires relay_url + +09:36 WSB /nostr_relay-status +09:36 Agent - **success:** false +- **error:** unknown tool + +09:37 Agent - **success:** true +- **status:** + - **relay_count:** 4 + - **connected_count:** 4 + - **relays:** + - + - **url:** wss://relay.damus.io + - **status:** connected + - **events_received:** 16 + - **events_published:** 16 + - **events_published_ok:** 2 + - **events_published_failed:** 6 + - **ping_latency_current:** 111.771 + - **ping_latency_avg:** 115.678 + - **query_latency_avg:** 376.22 + - **publish_latency_avg:** 0 + - **connection_uptime_start:** 1.77263e+09 + - **last_event_time:** 1.77263e+09 + - **last_publish_error:** rate-limited: you are noting too much + - + - **url:** wss://nos.lol + - **status:** connected + - **events_received:** 3 + - **events_published:** 16 + - **events_published_ok:** 16 + - **events_published_failed:** 0 + - **ping_latency_current:** 222.105 + - **ping_latency_avg:** 222.094 + - **query_latency_avg:** 0 + - **publish_latency_avg:** 0 + - **connection_uptime_start:** 1.77263e+09 + - **last_event_time:** 1.77263e+09 + - + - **url:** wss://relay.primal.net + - **status:** connected + - **events_received:** 1 + - **events_published:** 16 + - **events_published_ok:** 8 + - **events_published_failed:** 0 + - **ping_latency_current:** 221.284 + - **ping_latency_avg:** 224.326 + - **query_latency_avg:** 0 + - **publish_latency_avg:** 0 + - **connection_uptime_start:** 1.77263e+09 + - **last_event_time:** 1.77263e+09 + - + - **url:** ws://127.0.0.1:7777 + - **status:** connected + - **events_received:** 10 + - **events_published:** 16 + - **events_published_ok:** 6 + - **events_published_failed:** 0 + - **ping_latency_current:** 0.18564 + - **ping_latency_avg:** 0.207998 + - **query_latency_avg:** 5080.53 + - **publish_latency_avg:** 0 + - **connection_uptime_start:** 1.77263e+09 + - **last_event_time:** 1.77263e+09 + +09:37 WSB /nostr_relay_status +09:37 WSB Good morning +09:37 Agent LLM request failed. +09:37 Agent Didactyl has started up and is online (version v0.0.29, connected relays: 4/4). +09:38 WSB Good morning +09:38 Agent LLM request failed. +09:41 Agent Didactyl has started up and is online (version v0.0.29, connected relays: 4/4). +09:42 WSB Good morning. + + + +--- + +```text +Context Log - not seen by model +timestamp=2026-03-04 09:38:21 +phase=llm_chat_with_tools_messages +sender=8ff74724ed641b3c28e5a86d7c5cbc49c37638ace8c6c38935860e7a5eedde0e +model=claude-haiku-4.5 +context_bytes=14808 +approx_tokens=3702 +``` + +Sections: 21 + +# system_prompt | role=system + +## Didactyl Agent + +You are Didactyl, a sovereign AI agent living on Nostr. + +### Communication Rules +- You communicate through encrypted Nostr direct messages. +- Keep responses concise and clear. + +### Behavior +- Be helpful and technically accurate. +- If unsure, state uncertainty directly. +- Prefer actionable, practical advice. +- Use the person's name when messaging them if you know it. +- For the administrator, use their name from the administrator kind 0 profile metadata when available. + +### Tool Use Policy +- You have tools available and should use them when a request requires taking action. +- For requests involving local inspection or command execution, call `shell_exec` instead of refusing. +- For posting to Nostr, call `nostr_post` with explicit `kind` and `content`. +- For relay/event lookup tasks, call `nostr_query` with an appropriate filter. +- After a tool call, base your answer on the actual tool result. +- Never claim a tool was run if no tool was executed. + +### Task Management +- Maintain and use your internal task list as short-term working memory. +- Break long or complex actions into clear tasks before executing them. +- Update task status as you complete steps so your plan stays accurate. + +### Safety +- Do not claim to have executed actions you did not execute. +- You may share your public key (npub) with anyone. +- Never reveal your private key (nsec) under any circumstance. + +# admin_identity | role=system + +### Administrator Identity (source: config.admin.pubkey) + +This is your administrator! Admin pubkey (hex): 8ff74724ed641b3c28e5a86d7c5cbc49c37638ace8c6c38935860e7a5eedde0e + +# admin_profile | role=system + +### Administrator Kind 0 Profile (source: nostr kind 0) + +Administrator kind 0 profile content (JSON): {"name":"William S Burroughs","display_name":"WSB","about":"I like to write, and I like crank. They are good. Sometimes I'm not so sure.","banner":"https://www.grunge.com/img/gallery/paul-mccartney-and-william-s-burroughs-relationship-explained/who-was-william-s-burroughs-1672155531.jpg","website":"https://addicted.com","picture":"https://hips.hearstapps.com/hmg-prod/images/william-s-burroughs.jpg?resize=1200:*","lud16":"","nip05":""} + +# admin_relay_list | role=system + +### Administrator Relay List (source: nostr kind 10002) + +Administrator kind 10002 relay-list content (JSON): ["wss://relay.laantungir.net/","wss://relay.damus.io/","wss://nos.lol/","wss://relay.nostr.band/","wss://purplepag.es/","ws://127.0.0.1:7777/","wss://nostr.mom/"] + +# startup_events | role=system + +### Startup Events Memory (source: config.startup_events) + +Startup events memory (kinds/content/tags): [{"kind":0,"content":"{\"name\":\"Didactyl Agent\",\"display_name\":\"Didactyl\",\"about\":\"A sovereign AI agent on Nostr\",\"picture\":\"https://laantungir.github.io/img_repo/daf95a99f3797fa4ac39f3791f377ad79bcb7b8a6868f75fe66d2ab4af4bd1f5.png\",\"banner\":\"https://laantungir.github.io/img_repo/d21c4060632ab3d9d37a6062eeecbdbfbd67f03cb20dbd64838b1ba0d9cd8922.jpg\"}","tags":[]},{"kind":10002,"content":"","tags":[["r","wss://relay.damus.io"],["r","wss://nos.lol"],["r","wss://relay.primal.net"],["r","ws://127.0.0.1:7777"]]},{"kind":1,"content":"Hello world from Didactyl startup","tags":[["t","didactyl"],["t","startup"]]},{"kind":3,"content":"","tags":[]},{"kind":31123,"content":"{\"name\":\"long_form_note\",\"description\":\"How to publish a NIP-23 long-form article (kind 30023)\",\"nip\":\"NIP-23\",\"event_kind\":30023,\"format\":\"The content field must be markdown text; avoid arbitrary hard line-breaks in paragraphs and do not include HTML.\",\"required_tags\":{\"d\":\"Addressable identifier slug for the article. Reusing the same d tag replaces prior versions.\",\"title\":\"Human-readable article title.\",\"published_at\":\"Unix timestamp as a string for first publication time; keep stable on edits.\"},\"optional_tags\":{\"summary\":\"Short 1-2 sentence summary.\",\"image\":\"URL for article preview image.\",\"t\":\"Topic hashtags using repeated t tags, usually 3-6 lowercase terms.\"},\"behavior\":\"If required values are missing or unclear, ask the administrator before publishing instead of guessing.\",\"procedure\":[\"Determine title and d tag from admin input or source material.\",\"Draft markdown body content.\",\"Set published_at as unix seconds string.\",\"Add optional summary/image/t tags when known.\",\"Publish with nostr_post kind 30023 including tags array.\"]}","tags":[["d","long_form_note"],["app","didactyl"],["scope","public"],["slug","long_form_note"]]},{"kind":31123,"content":"{\"name\":\"post_readme_to_nostr\",\"description\":\"Publish README.md using the dedicated nostr_post_readme tool\",\"uses_skill\":\"long_form_note\",\"event_kind\":30023,\"procedure\":[\"Call nostr_post_readme with empty arguments {}.\",\"Return the tool response including event_id, d_tag, and naddr_uri.\",\"If the tool returns success=false, report the tool error verbatim.\"],\"required_values\":{\"d\":\"readme.md\",\"tool\":\"nostr_post_readme\"}}","tags":[["d","post_readme_to_nostr"],["app","didactyl"],["scope","public"],["slug","post_readme_to_nostr"]]},{"kind":31124,"content":"{\"name\":\"admin_ops\",\"description\":\"Private operational procedures\"}","tags":[["d","admin_ops"],["app","didactyl"],["scope","private"],["slug","admin_ops"]]},{"kind":10123,"content":"","tags":[["a","31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:long_form_note"],["a","31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:post_readme_to_nostr"],["app","didactyl"],["scope","public"]]}] + +# adopted_skills | role=system + +### Adopted Skills Memory (source: adoption list + startup fallback) + +Adopted skills memory (always-on behavioral instructions): follow these skill instructions when relevant and never violate explicit constraints. + + +--- + +#### Skill +Skill slug: long_form_note +Skill address: 31123:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:long_form_note + +Instructions: +Name: long_form_note +Description: How to publish a NIP-23 long-form article (kind 30023) +Event kind: 30023 +Procedure: + 1. Determine title and d tag from admin input or source material. + 2. Draft markdown body content. + 3. Set published_at as unix seconds string. + 4. Add optional summary/image/t tags when known. + 5. Publish with nostr_post kind 30023 including tags array. + + + +--- + +#### Skill +Skill slug: post_readme_to_nostr +Skill address: 31123:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:post_readme_to_nostr + +Instructions: +Name: post_readme_to_nostr +Description: Publish README.md using the dedicated nostr_post_readme tool +Event kind: 30023 +Procedure: + 1. Call nostr_post_readme with empty arguments {}. + 2. Return the tool response including event_id, d_tag, and naddr_uri. + 3. If the tool returns success=false, report the tool error verbatim. + + + +--- + +#### Skill +Skill slug: admin_ops +Skill address: 31124:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:admin_ops + +Instructions: +Name: admin_ops +Description: Private operational procedures + + + +# agent_tasks | role=system + +#### Current Tasks + +Your active task list - short-term working memory for tracking plan steps. + +- [ ] 4. Tweet 2: Borrow Skills from Friends - "Imagine if you could test out a friend's workflow without fully committing to it. Didactyl lets you try skills others have built, safely and temporarily. No installation. No lock-in. Just try it, see if it works for you. That's how tools should work. #nostr" +- [ ] 5. Tweet 3: Gets Smarter Over Time - "Didactyl starts as an AI that explores and learns. Over time, the best workflows get locked in as reliable, fast processes. It evolves from experimental to hardened—from thinking to doing. An agent that improves itself. #nostr #agents" + + +# admin_notes | role=system + +### Administrator Recent Notes (source: nostr kind 1) + +Recent posts: +- GM +- Getting longer +- Long day. +- This is a test. +- test +- Post 11 +- Post 10 +- This is +Post +9 +- Eight +- Seven + + +# dm_history | role=chat + +09:35 Agent Didactyl has started up and is online (version v0.0.29, connected relays: 4/4). +09:35 WSB /help +09:35 Agent AVAILABLE TOOLS +- file_read — Read a local file as text from the configured working directory +- file_write — Write text content to a local file in the configured working directory +- http_fetch — Fetch HTTP(S) resources with optional method, headers, timeout, and body +- model_get — Get current active LLM runtime configuration (excluding API key) +- model_list — List available model IDs using provider OpenAI-compatible /models endpoint +- model_set — Update active LLM configuration and persist it to config.json +- my_npub — Alias for nostr_npub: return this agent's pubkey encoded as npub bech32 +- my_pubkey — Alias for nostr_pubkey: return this agent's pubkey in hex format +- my_version — Return current Didactyl version and metadata from build macros +- nostr_decode — Decode a Nostr bech32/nostr: URI into components +- nostr_decrypt — Decrypt NIP-44 ciphertext from a sender +- nostr_delete — Request deletion of one or more previously published events (NIP-09 kind 5) +- nostr_dm_send — Send a NIP-04 encrypted DM +- nostr_dm_send_nip17 — Send a private DM using NIP-17 gift wrap protocol +- nostr_encode — Encode a Nostr entity into nostr: URI (npub, note, nprofile, nevent, naddr) +- nostr_encrypt — Encrypt plaintext using NIP-44 for a recipient +- nostr_file_md_to_longform_post — Read a markdown file and publish it as kind 30023 longform post; defaults d-tag to lowercase filename +- nostr_list_manage — Add/remove tag tuples in replaceable list events (NIP-51 style) +- nostr_nip05_lookup — Look up or verify a NIP-05 identifier (user@domain) +- nostr_npub — Return this agent's pubkey encoded as npub bech32 +- nostr_post — Publish a Nostr event to connected relays +- nostr_post_readme — Publish README.md as kind 30023 with deterministic d tag readme.md +- nostr_profile_get — Look up a Nostr profile (kind 0 metadata) by pubkey +- nostr_pubkey — Return this agent's pubkey in hex format +- nostr_query — Query events from relays using a Nostr filter +- nostr_react — React to a Nostr event with like/dislike/emoji (NIP-25 kind 7) +- nostr_relay_info — Fetch NIP-11 relay information document +- nostr_relay_status — Get connection status and statistics for all relays +- shell_exec — Execute a shell command and return stdout/stderr +- skill_adopt — Adopt a skill by adding its address to kind 10123 adoption list +- skill_create — Create or update a skill definition as kind 31123/31124 and optionally auto-adopt it +- skill_list — List this agent's published skills, optionally filtered by scope +- skill_remove — Remove a skill address from kind 10123 adoption list +- skill_search — Search public skills by query/author and optionally rank by adoption popularity +- task_manage — Manage agent short-term task memory stored in tasks.json (list/add/update/remove/clear/replace) +- tool_list — List available tools with name, description, and JSON parameter schema +- trigger_list — List active triggered skills and their runtime status + +AVAILABLE SKILLS +- admin_ops +- admin_ops +- post_readme_to_nostr +- long_form_note +- long_form_note +- post_readme_to_nostr +- post_readme_to_nostr + +09:36 WSB /nostr_relay_info +09:36 Agent - **success:** false +- **error:** nostr_relay_info requires relay_url + +09:36 Agent - **success:** false +- **error:** unknown tool + +09:36 WSB /nostr_relay-status +09:37 WSB /nostr_relay_status +09:37 Agent - **success:** true +- **status:** + - **relay_count:** 4 + - **connected_count:** 4 + - **relays:** + - + - **url:** wss://relay.damus.io + - **status:** connected + - **events_received:** 16 + - **events_published:** 16 + - **events_published_ok:** 2 + - **events_published_failed:** 6 + - **ping_latency_current:** 111.771 + - **ping_latency_avg:** 115.678 + - **query_latency_avg:** 376.22 + - **publish_latency_avg:** 0 + - **connection_uptime_start:** 1.77263e+09 + - **last_event_time:** 1.77263e+09 + - **last_publish_error:** rate-limited: you are noting too much + - + - **url:** wss://nos.lol + - **status:** connected + - **events_received:** 3 + - **events_published:** 16 + - **events_published_ok:** 16 + - **events_published_failed:** 0 + - **ping_latency_current:** 222.105 + - **ping_latency_avg:** 222.094 + - **query_latency_avg:** 0 + - **publish_latency_avg:** 0 + - **connection_uptime_start:** 1.77263e+09 + - **last_event_time:** 1.77263e+09 + - + - **url:** wss://relay.primal.net + - **status:** connected + - **events_received:** 1 + - **events_published:** 16 + - **events_published_ok:** 8 + - **events_published_failed:** 0 + - **ping_latency_current:** 221.284 + - **ping_latency_avg:** 224.326 + - **query_latency_avg:** 0 + - **publish_latency_avg:** 0 + - **connection_uptime_start:** 1.77263e+09 + - **last_event_time:** 1.77263e+09 + - + - **url:** ws://127.0.0.1:7777 + - **status:** connected + - **events_received:** 10 + - **events_published:** 16 + - **events_published_ok:** 6 + - **events_published_failed:** 0 + - **ping_latency_current:** 0.18564 + - **ping_latency_avg:** 0.207998 + - **query_latency_avg:** 5080.53 + - **publish_latency_avg:** 0 + - **connection_uptime_start:** 1.77263e+09 + - **last_event_time:** 1.77263e+09 + +09:37 WSB Good morning +09:37 Agent LLM request failed. +09:37 Agent Didactyl has started up and is online (version v0.0.29, connected relays: 4/4). +09:38 WSB Good morning + + + +--- + +```text +Context Log - not seen by model +timestamp=2026-03-04 09:37:35 +phase=llm_chat_with_tools_messages +sender=8ff74724ed641b3c28e5a86d7c5cbc49c37638ace8c6c38935860e7a5eedde0e +model=claude-haiku-4.5 +context_bytes=14579 +approx_tokens=3644 +``` + +Sections: 21 + +# system_prompt | role=system + +## Didactyl Agent + +You are Didactyl, a sovereign AI agent living on Nostr. + +### Communication Rules +- You communicate through encrypted Nostr direct messages. +- Keep responses concise and clear. + +### Behavior +- Be helpful and technically accurate. +- If unsure, state uncertainty directly. +- Prefer actionable, practical advice. +- Use the person's name when messaging them if you know it. +- For the administrator, use their name from the administrator kind 0 profile metadata when available. + +### Tool Use Policy +- You have tools available and should use them when a request requires taking action. +- For requests involving local inspection or command execution, call `shell_exec` instead of refusing. +- For posting to Nostr, call `nostr_post` with explicit `kind` and `content`. +- For relay/event lookup tasks, call `nostr_query` with an appropriate filter. +- After a tool call, base your answer on the actual tool result. +- Never claim a tool was run if no tool was executed. + +### Task Management +- Maintain and use your internal task list as short-term working memory. +- Break long or complex actions into clear tasks before executing them. +- Update task status as you complete steps so your plan stays accurate. + +### Safety +- Do not claim to have executed actions you did not execute. +- You may share your public key (npub) with anyone. +- Never reveal your private key (nsec) under any circumstance. + +# admin_identity | role=system + +### Administrator Identity (source: config.admin.pubkey) + +This is your administrator! Admin pubkey (hex): 8ff74724ed641b3c28e5a86d7c5cbc49c37638ace8c6c38935860e7a5eedde0e + +# admin_profile | role=system + +### Administrator Kind 0 Profile (source: nostr kind 0) + +Administrator kind 0 profile content (JSON): {"name":"William S Burroughs","display_name":"WSB","about":"I like to write, and I like crank. They are good. Sometimes I'm not so sure.","banner":"https://www.grunge.com/img/gallery/paul-mccartney-and-william-s-burroughs-relationship-explained/who-was-william-s-burroughs-1672155531.jpg","website":"https://addicted.com","picture":"https://hips.hearstapps.com/hmg-prod/images/william-s-burroughs.jpg?resize=1200:*","lud16":"","nip05":""} + +# admin_relay_list | role=system + +### Administrator Relay List (source: nostr kind 10002) + +Administrator kind 10002 relay-list content (JSON): ["wss://relay.laantungir.net/","wss://relay.damus.io/","wss://nos.lol/","wss://relay.nostr.band/","wss://purplepag.es/","ws://127.0.0.1:7777/","wss://nostr.mom/"] + +# startup_events | role=system + +### Startup Events Memory (source: config.startup_events) + +Startup events memory (kinds/content/tags): [{"kind":0,"content":"{\"name\":\"Didactyl Agent\",\"display_name\":\"Didactyl\",\"about\":\"A sovereign AI agent on Nostr\",\"picture\":\"https://laantungir.github.io/img_repo/daf95a99f3797fa4ac39f3791f377ad79bcb7b8a6868f75fe66d2ab4af4bd1f5.png\",\"banner\":\"https://laantungir.github.io/img_repo/d21c4060632ab3d9d37a6062eeecbdbfbd67f03cb20dbd64838b1ba0d9cd8922.jpg\"}","tags":[]},{"kind":10002,"content":"","tags":[["r","wss://relay.damus.io"],["r","wss://nos.lol"],["r","wss://relay.primal.net"],["r","ws://127.0.0.1:7777"]]},{"kind":1,"content":"Hello world from Didactyl startup","tags":[["t","didactyl"],["t","startup"]]},{"kind":3,"content":"","tags":[]},{"kind":31123,"content":"{\"name\":\"long_form_note\",\"description\":\"How to publish a NIP-23 long-form article (kind 30023)\",\"nip\":\"NIP-23\",\"event_kind\":30023,\"format\":\"The content field must be markdown text; avoid arbitrary hard line-breaks in paragraphs and do not include HTML.\",\"required_tags\":{\"d\":\"Addressable identifier slug for the article. Reusing the same d tag replaces prior versions.\",\"title\":\"Human-readable article title.\",\"published_at\":\"Unix timestamp as a string for first publication time; keep stable on edits.\"},\"optional_tags\":{\"summary\":\"Short 1-2 sentence summary.\",\"image\":\"URL for article preview image.\",\"t\":\"Topic hashtags using repeated t tags, usually 3-6 lowercase terms.\"},\"behavior\":\"If required values are missing or unclear, ask the administrator before publishing instead of guessing.\",\"procedure\":[\"Determine title and d tag from admin input or source material.\",\"Draft markdown body content.\",\"Set published_at as unix seconds string.\",\"Add optional summary/image/t tags when known.\",\"Publish with nostr_post kind 30023 including tags array.\"]}","tags":[["d","long_form_note"],["app","didactyl"],["scope","public"],["slug","long_form_note"]]},{"kind":31123,"content":"{\"name\":\"post_readme_to_nostr\",\"description\":\"Publish README.md using the dedicated nostr_post_readme tool\",\"uses_skill\":\"long_form_note\",\"event_kind\":30023,\"procedure\":[\"Call nostr_post_readme with empty arguments {}.\",\"Return the tool response including event_id, d_tag, and naddr_uri.\",\"If the tool returns success=false, report the tool error verbatim.\"],\"required_values\":{\"d\":\"readme.md\",\"tool\":\"nostr_post_readme\"}}","tags":[["d","post_readme_to_nostr"],["app","didactyl"],["scope","public"],["slug","post_readme_to_nostr"]]},{"kind":31124,"content":"{\"name\":\"admin_ops\",\"description\":\"Private operational procedures\"}","tags":[["d","admin_ops"],["app","didactyl"],["scope","private"],["slug","admin_ops"]]},{"kind":10123,"content":"","tags":[["a","31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:long_form_note"],["a","31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:post_readme_to_nostr"],["app","didactyl"],["scope","public"]]}] + +# adopted_skills | role=system + +### Adopted Skills Memory (source: adoption list + startup fallback) + +Adopted skills memory (always-on behavioral instructions): follow these skill instructions when relevant and never violate explicit constraints. + + +--- + +#### Skill +Skill slug: long_form_note +Skill address: 31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:long_form_note + +Instructions: +Name: long_form_note +Description: How to publish a NIP-23 long-form article (kind 30023) +Event kind: 30023 +Procedure: + 1. Determine title and d tag from admin input or source material. + 2. Draft markdown body content. + 3. Set published_at as unix seconds string. + 4. Add optional summary/image/t tags when known. + 5. Publish with nostr_post kind 30023 including tags array. + + + +# agent_tasks | role=system + +#### Current Tasks + +Your active task list - short-term working memory for tracking plan steps. + +- [ ] 4. Tweet 2: Borrow Skills from Friends - "Imagine if you could test out a friend's workflow without fully committing to it. Didactyl lets you try skills others have built, safely and temporarily. No installation. No lock-in. Just try it, see if it works for you. That's how tools should work. #nostr" +- [ ] 5. Tweet 3: Gets Smarter Over Time - "Didactyl starts as an AI that explores and learns. Over time, the best workflows get locked in as reliable, fast processes. It evolves from experimental to hardened—from thinking to doing. An agent that improves itself. #nostr #agents" + + +# admin_notes | role=system + +### Administrator Recent Notes (source: nostr kind 1) + +Recent posts: +- GM +- Getting longer +- Long day. +- This is a test. +- test +- Post 11 +- Post 10 +- This is +Post +9 +- Eight +- Seven + + +# dm_history | role=chat + +09:31 Agent I'm currently connected to **4 relays**, all active: + +1. **wss://relay.damus.io** - Connected (167.9ms ping) +2. **wss://nos.lol** - Connected (310.8ms ping) +3. **wss://relay.primal.net** - Connected (182.4ms ping) +4. **ws://127.0.0.1:7777** - Connected (100.8ms ping, local) + +All relays are healthy and receiving/publishing events. The local relay (127.0.0.1) has the lowest latency, while nos.lol is currently the slowest at ~311ms. +09:32 WSB Add wss://relay.laantungir.net to your relays and save it. +09:32 Agent LLM request failed. +09:32 WSB Add wss://relay.laantungir.net to your relays and save it. +09:33 Agent LLM request failed. +09:34 WSB Add wss://relay.laantungir.net to your relays and save it. +09:34 Agent LLM request failed. +09:35 Agent Didactyl has started up and is online (version v0.0.29, connected relays: 4/4). +09:35 Agent AVAILABLE TOOLS +- file_read — Read a local file as text from the configured working directory +- file_write — Write text content to a local file in the configured working directory +- http_fetch — Fetch HTTP(S) resources with optional method, headers, timeout, and body +- model_get — Get current active LLM runtime configuration (excluding API key) +- model_list — List available model IDs using provider OpenAI-compatible /models endpoint +- model_set — Update active LLM configuration and persist it to config.json +- my_npub — Alias for nostr_npub: return this agent's pubkey encoded as npub bech32 +- my_pubkey — Alias for nostr_pubkey: return this agent's pubkey in hex format +- my_version — Return current Didactyl version and metadata from build macros +- nostr_decode — Decode a Nostr bech32/nostr: URI into components +- nostr_decrypt — Decrypt NIP-44 ciphertext from a sender +- nostr_delete — Request deletion of one or more previously published events (NIP-09 kind 5) +- nostr_dm_send — Send a NIP-04 encrypted DM +- nostr_dm_send_nip17 — Send a private DM using NIP-17 gift wrap protocol +- nostr_encode — Encode a Nostr entity into nostr: URI (npub, note, nprofile, nevent, naddr) +- nostr_encrypt — Encrypt plaintext using NIP-44 for a recipient +- nostr_file_md_to_longform_post — Read a markdown file and publish it as kind 30023 longform post; defaults d-tag to lowercase filename +- nostr_list_manage — Add/remove tag tuples in replaceable list events (NIP-51 style) +- nostr_nip05_lookup — Look up or verify a NIP-05 identifier (user@domain) +- nostr_npub — Return this agent's pubkey encoded as npub bech32 +- nostr_post — Publish a Nostr event to connected relays +- nostr_post_readme — Publish README.md as kind 30023 with deterministic d tag readme.md +- nostr_profile_get — Look up a Nostr profile (kind 0 metadata) by pubkey +- nostr_pubkey — Return this agent's pubkey in hex format +- nostr_query — Query events from relays using a Nostr filter +- nostr_react — React to a Nostr event with like/dislike/emoji (NIP-25 kind 7) +- nostr_relay_info — Fetch NIP-11 relay information document +- nostr_relay_status — Get connection status and statistics for all relays +- shell_exec — Execute a shell command and return stdout/stderr +- skill_adopt — Adopt a skill by adding its address to kind 10123 adoption list +- skill_create — Create or update a skill definition as kind 31123/31124 and optionally auto-adopt it +- skill_list — List this agent's published skills, optionally filtered by scope +- skill_remove — Remove a skill address from kind 10123 adoption list +- skill_search — Search public skills by query/author and optionally rank by adoption popularity +- task_manage — Manage agent short-term task memory stored in tasks.json (list/add/update/remove/clear/replace) +- tool_list — List available tools with name, description, and JSON parameter schema +- trigger_list — List active triggered skills and their runtime status + +AVAILABLE SKILLS +- admin_ops +- admin_ops +- post_readme_to_nostr +- long_form_note +- long_form_note +- post_readme_to_nostr +- post_readme_to_nostr + +09:36 Agent - **success:** false +- **error:** nostr_relay_info requires relay_url + +09:36 Agent - **success:** false +- **error:** unknown tool + +09:37 Agent - **success:** true +- **status:** + - **relay_count:** 4 + - **connected_count:** 4 + - **relays:** + - + - **url:** wss://relay.damus.io + - **status:** connected + - **events_received:** 16 + - **events_published:** 16 + - **events_published_ok:** 2 + - **events_published_failed:** 6 + - **ping_latency_current:** 111.771 + - **ping_latency_avg:** 115.678 + - **query_latency_avg:** 376.22 + - **publish_latency_avg:** 0 + - **connection_uptime_start:** 1.77263e+09 + - **last_event_time:** 1.77263e+09 + - **last_publish_error:** rate-limited: you are noting too much + - + - **url:** wss://nos.lol + - **status:** connected + - **events_received:** 3 + - **events_published:** 16 + - **events_published_ok:** 16 + - **events_published_failed:** 0 + - **ping_latency_current:** 222.105 + - **ping_latency_avg:** 222.094 + - **query_latency_avg:** 0 + - **publish_latency_avg:** 0 + - **connection_uptime_start:** 1.77263e+09 + - **last_event_time:** 1.77263e+09 + - + - **url:** wss://relay.primal.net + - **status:** connected + - **events_received:** 1 + - **events_published:** 16 + - **events_published_ok:** 8 + - **events_published_failed:** 0 + - **ping_latency_current:** 221.284 + - **ping_latency_avg:** 224.326 + - **query_latency_avg:** 0 + - **publish_latency_avg:** 0 + - **connection_uptime_start:** 1.77263e+09 + - **last_event_time:** 1.77263e+09 + - + - **url:** ws://127.0.0.1:7777 + - **status:** connected + - **events_received:** 10 + - **events_published:** 16 + - **events_published_ok:** 6 + - **events_published_failed:** 0 + - **ping_latency_current:** 0.18564 + - **ping_latency_avg:** 0.207998 + - **query_latency_avg:** 5080.53 + - **publish_latency_avg:** 0 + - **connection_uptime_start:** 1.77263e+09 + - **last_event_time:** 1.77263e+09 + +09:37 WSB Good morning + + + +--- + +```text +Context Log - not seen by model +timestamp=2026-03-04 09:37:06 +phase=direct_tool_exec +sender=8ff74724ed641b3c28e5a86d7c5cbc49c37638ace8c6c38935860e7a5eedde0e +model=claude-haiku-4.5 +context_bytes=3551 +approx_tokens=887 +``` + +slash=/nostr_relay_status +result_json={"success":true,"status":{"relay_count":4,"connected_count":4,"relays":[{"url":"wss://relay.damus.io","status":"connected","events_received":16,"events_published":16,"events_published_ok":2,"events_published_failed":6,"ping_latency_current":111.77134501934052,"ping_latency_avg":115.67804375290871,"query_latency_avg":376.22023203969,"publish_latency_avg":0,"connection_uptime_start":1772631340,"last_event_time":1772631402,"last_publish_error":"rate-limited: you are noting too much"},{"url":"wss://nos.lol","status":"connected","events_received":3,"events_published":16,"events_published_ok":16,"events_published_failed":0,"ping_latency_current":222.10470598936081,"ping_latency_avg":222.09363676607609,"query_latency_avg":0,"publish_latency_avg":0,"connection_uptime_start":1772631340,"last_event_time":1772631402},{"url":"wss://relay.primal.net","status":"connected","events_received":1,"events_published":16,"events_published_ok":8,"events_published_failed":0,"ping_latency_current":221.28391098976135,"ping_latency_avg":224.32578898966312,"query_latency_avg":0,"publish_latency_avg":0,"connection_uptime_start":1772631340,"last_event_time":1772631402},{"url":"ws://127.0.0.1:7777","status":"connected","events_received":10,"events_published":16,"events_published_ok":6,"events_published_failed":0,"ping_latency_current":0.18563997745513916,"ping_latency_avg":0.20799824595451355,"query_latency_avg":5080.5265460014343,"publish_latency_avg":0,"connection_uptime_start":1772631340,"last_event_time":1772631426}]}} +result_markdown=- **success:** true +- **status:** + - **relay_count:** 4 + - **connected_count:** 4 + - **relays:** + - + - **url:** wss://relay.damus.io + - **status:** connected + - **events_received:** 16 + - **events_published:** 16 + - **events_published_ok:** 2 + - **events_published_failed:** 6 + - **ping_latency_current:** 111.771 + - **ping_latency_avg:** 115.678 + - **query_latency_avg:** 376.22 + - **publish_latency_avg:** 0 + - **connection_uptime_start:** 1.77263e+09 + - **last_event_time:** 1.77263e+09 + - **last_publish_error:** rate-limited: you are noting too much + - + - **url:** wss://nos.lol + - **status:** connected + - **events_received:** 3 + - **events_published:** 16 + - **events_published_ok:** 16 + - **events_published_failed:** 0 + - **ping_latency_current:** 222.105 + - **ping_latency_avg:** 222.094 + - **query_latency_avg:** 0 + - **publish_latency_avg:** 0 + - **connection_uptime_start:** 1.77263e+09 + - **last_event_time:** 1.77263e+09 + - + - **url:** wss://relay.primal.net + - **status:** connected + - **events_received:** 1 + - **events_published:** 16 + - **events_published_ok:** 8 + - **events_published_failed:** 0 + - **ping_latency_current:** 221.284 + - **ping_latency_avg:** 224.326 + - **query_latency_avg:** 0 + - **publish_latency_avg:** 0 + - **connection_uptime_start:** 1.77263e+09 + - **last_event_time:** 1.77263e+09 + - + - **url:** ws://127.0.0.1:7777 + - **status:** connected + - **events_received:** 10 + - **events_published:** 16 + - **events_published_ok:** 6 + - **events_published_failed:** 0 + - **ping_latency_current:** 0.18564 + - **ping_latency_avg:** 0.207998 + - **query_latency_avg:** 5080.53 + - **publish_latency_avg:** 0 + - **connection_uptime_start:** 1.77263e+09 + - **last_event_time:** 1.77263e+09 + + +--- + +```text +Context Log - not seen by model +timestamp=2026-03-04 09:36:41 +phase=direct_tool_exec +sender=8ff74724ed641b3c28e5a86d7c5cbc49c37638ace8c6c38935860e7a5eedde0e +model=claude-haiku-4.5 +context_bytes=142 +approx_tokens=35 +``` + +slash=/nostr_relay-status +result_json={"success":false,"error":"unknown tool"} +result_markdown=- **success:** false +- **error:** unknown tool + + +--- + +```text +Context Log - not seen by model +timestamp=2026-03-04 09:36:11 +phase=direct_tool_exec +sender=8ff74724ed641b3c28e5a86d7c5cbc49c37638ace8c6c38935860e7a5eedde0e +model=claude-haiku-4.5 +context_bytes=186 +approx_tokens=46 +``` + +slash=/nostr_relay_info +result_json={"success":false,"error":"nostr_relay_info requires relay_url"} +result_markdown=- **success:** false +- **error:** nostr_relay_info requires relay_url + + +--- + +```text +Context Log - not seen by model +timestamp=2026-03-04 09:35:56 +phase=direct_tool_exec +sender=8ff74724ed641b3c28e5a86d7c5cbc49c37638ace8c6c38935860e7a5eedde0e +model=claude-haiku-4.5 +context_bytes=6333 +approx_tokens=1583 +``` + +slash=/help +result_json=AVAILABLE TOOLS +- file_read — Read a local file as text from the configured working directory +- file_write — Write text content to a local file in the configured working directory +- http_fetch — Fetch HTTP(S) resources with optional method, headers, timeout, and body +- model_get — Get current active LLM runtime configuration (excluding API key) +- model_list — List available model IDs using provider OpenAI-compatible /models endpoint +- model_set — Update active LLM configuration and persist it to config.json +- my_npub — Alias for nostr_npub: return this agent's pubkey encoded as npub bech32 +- my_pubkey — Alias for nostr_pubkey: return this agent's pubkey in hex format +- my_version — Return current Didactyl version and metadata from build macros +- nostr_decode — Decode a Nostr bech32/nostr: URI into components +- nostr_decrypt — Decrypt NIP-44 ciphertext from a sender +- nostr_delete — Request deletion of one or more previously published events (NIP-09 kind 5) +- nostr_dm_send — Send a NIP-04 encrypted DM +- nostr_dm_send_nip17 — Send a private DM using NIP-17 gift wrap protocol +- nostr_encode — Encode a Nostr entity into nostr: URI (npub, note, nprofile, nevent, naddr) +- nostr_encrypt — Encrypt plaintext using NIP-44 for a recipient +- nostr_file_md_to_longform_post — Read a markdown file and publish it as kind 30023 longform post; defaults d-tag to lowercase filename +- nostr_list_manage — Add/remove tag tuples in replaceable list events (NIP-51 style) +- nostr_nip05_lookup — Look up or verify a NIP-05 identifier (user@domain) +- nostr_npub — Return this agent's pubkey encoded as npub bech32 +- nostr_post — Publish a Nostr event to connected relays +- nostr_post_readme — Publish README.md as kind 30023 with deterministic d tag readme.md +- nostr_profile_get — Look up a Nostr profile (kind 0 metadata) by pubkey +- nostr_pubkey — Return this agent's pubkey in hex format +- nostr_query — Query events from relays using a Nostr filter +- nostr_react — React to a Nostr event with like/dislike/emoji (NIP-25 kind 7) +- nostr_relay_info — Fetch NIP-11 relay information document +- nostr_relay_status — Get connection status and statistics for all relays +- shell_exec — Execute a shell command and return stdout/stderr +- skill_adopt — Adopt a skill by adding its address to kind 10123 adoption list +- skill_create — Create or update a skill definition as kind 31123/31124 and optionally auto-adopt it +- skill_list — List this agent's published skills, optionally filtered by scope +- skill_remove — Remove a skill address from kind 10123 adoption list +- skill_search — Search public skills by query/author and optionally rank by adoption popularity +- task_manage — Manage agent short-term task memory stored in tasks.json (list/add/update/remove/clear/replace) +- tool_list — List available tools with name, description, and JSON parameter schema +- trigger_list — List active triggered skills and their runtime status + +AVAILABLE SKILLS +- admin_ops +- admin_ops +- post_readme_to_nostr +- long_form_note +- long_form_note +- post_readme_to_nostr +- post_readme_to_nostr + +result_markdown=AVAILABLE TOOLS +- file_read — Read a local file as text from the configured working directory +- file_write — Write text content to a local file in the configured working directory +- http_fetch — Fetch HTTP(S) resources with optional method, headers, timeout, and body +- model_get — Get current active LLM runtime configuration (excluding API key) +- model_list — List available model IDs using provider OpenAI-compatible /models endpoint +- model_set — Update active LLM configuration and persist it to config.json +- my_npub — Alias for nostr_npub: return this agent's pubkey encoded as npub bech32 +- my_pubkey — Alias for nostr_pubkey: return this agent's pubkey in hex format +- my_version — Return current Didactyl version and metadata from build macros +- nostr_decode — Decode a Nostr bech32/nostr: URI into components +- nostr_decrypt — Decrypt NIP-44 ciphertext from a sender +- nostr_delete — Request deletion of one or more previously published events (NIP-09 kind 5) +- nostr_dm_send — Send a NIP-04 encrypted DM +- nostr_dm_send_nip17 — Send a private DM using NIP-17 gift wrap protocol +- nostr_encode — Encode a Nostr entity into nostr: URI (npub, note, nprofile, nevent, naddr) +- nostr_encrypt — Encrypt plaintext using NIP-44 for a recipient +- nostr_file_md_to_longform_post — Read a markdown file and publish it as kind 30023 longform post; defaults d-tag to lowercase filename +- nostr_list_manage — Add/remove tag tuples in replaceable list events (NIP-51 style) +- nostr_nip05_lookup — Look up or verify a NIP-05 identifier (user@domain) +- nostr_npub — Return this agent's pubkey encoded as npub bech32 +- nostr_post — Publish a Nostr event to connected relays +- nostr_post_readme — Publish README.md as kind 30023 with deterministic d tag readme.md +- nostr_profile_get — Look up a Nostr profile (kind 0 metadata) by pubkey +- nostr_pubkey — Return this agent's pubkey in hex format +- nostr_query — Query events from relays using a Nostr filter +- nostr_react — React to a Nostr event with like/dislike/emoji (NIP-25 kind 7) +- nostr_relay_info — Fetch NIP-11 relay information document +- nostr_relay_status — Get connection status and statistics for all relays +- shell_exec — Execute a shell command and return stdout/stderr +- skill_adopt — Adopt a skill by adding its address to kind 10123 adoption list +- skill_create — Create or update a skill definition as kind 31123/31124 and optionally auto-adopt it +- skill_list — List this agent's published skills, optionally filtered by scope +- skill_remove — Remove a skill address from kind 10123 adoption list +- skill_search — Search public skills by query/author and optionally rank by adoption popularity +- task_manage — Manage agent short-term task memory stored in tasks.json (list/add/update/remove/clear/replace) +- tool_list — List available tools with name, description, and JSON parameter schema +- trigger_list — List active triggered skills and their runtime status + +AVAILABLE SKILLS +- admin_ops +- admin_ops +- post_readme_to_nostr +- long_form_note +- long_form_note +- post_readme_to_nostr +- post_readme_to_nostr + + +--- + +```text +Context Log - not seen by model +timestamp=2026-03-04 09:34:38 +phase=llm_chat_with_tools_messages +sender=8ff74724ed641b3c28e5a86d7c5cbc49c37638ace8c6c38935860e7a5eedde0e +model=claude-haiku-4.5 +context_bytes=8737 +approx_tokens=2184 +``` + +Sections: 10 + +# system_prompt | role=system + +## Didactyl Agent + +You are Didactyl, a sovereign AI agent living on Nostr. + +### Communication Rules +- You communicate through encrypted Nostr direct messages. +- Keep responses concise and clear. + +### Behavior +- Be helpful and technically accurate. +- If unsure, state uncertainty directly. +- Prefer actionable, practical advice. +- Use the person's name when messaging them if you know it. +- For the administrator, use their name from the administrator kind 0 profile metadata when available. + +### Tool Use Policy +- You have tools available and should use them when a request requires taking action. +- For requests involving local inspection or command execution, call `shell_exec` instead of refusing. +- For posting to Nostr, call `nostr_post` with explicit `kind` and `content`. +- For relay/event lookup tasks, call `nostr_query` with an appropriate filter. +- After a tool call, base your answer on the actual tool result. +- Never claim a tool was run if no tool was executed. + +### Task Management +- Maintain and use your internal task list as short-term working memory. +- Break long or complex actions into clear tasks before executing them. +- Update task status as you complete steps so your plan stays accurate. + +### Safety +- Do not claim to have executed actions you did not execute. +- You may share your public key (npub) with anyone. +- Never reveal your private key (nsec) under any circumstance. + +# admin_identity | role=system + +### Administrator Identity (source: config.admin.pubkey) + +This is your administrator! Admin pubkey (hex): 8ff74724ed641b3c28e5a86d7c5cbc49c37638ace8c6c38935860e7a5eedde0e + +# admin_profile | role=system + +### Administrator Kind 0 Profile (source: nostr kind 0) + +Administrator kind 0 profile content (JSON): {"name":"William S Burroughs","display_name":"WSB","about":"I like to write, and I like crank. They are good. Sometimes I'm not so sure.","banner":"https://www.grunge.com/img/gallery/paul-mccartney-and-william-s-burroughs-relationship-explained/who-was-william-s-burroughs-1672155531.jpg","website":"https://addicted.com","picture":"https://hips.hearstapps.com/hmg-prod/images/william-s-burroughs.jpg?resize=1200:*","lud16":"","nip05":""} + +# admin_relay_list | role=system + +### Administrator Relay List (source: nostr kind 10002) + +Administrator kind 10002 relay-list content (JSON): ["wss://relay.laantungir.net/","wss://relay.damus.io/","wss://nos.lol/","wss://relay.nostr.band/","wss://purplepag.es/","ws://127.0.0.1:7777/","wss://nostr.mom/"] + +# startup_events | role=system + +### Startup Events Memory (source: config.startup_events) + +Startup events memory (kinds/content/tags): [{"kind":0,"content":"{\"name\":\"Didactyl Agent\",\"display_name\":\"Didactyl\",\"about\":\"A sovereign AI agent on Nostr\",\"picture\":\"https://laantungir.github.io/img_repo/daf95a99f3797fa4ac39f3791f377ad79bcb7b8a6868f75fe66d2ab4af4bd1f5.png\",\"banner\":\"https://laantungir.github.io/img_repo/d21c4060632ab3d9d37a6062eeecbdbfbd67f03cb20dbd64838b1ba0d9cd8922.jpg\"}","tags":[]},{"kind":10002,"content":"","tags":[["r","wss://relay.damus.io"],["r","wss://nos.lol"],["r","wss://relay.primal.net"],["r","ws://127.0.0.1:7777"]]},{"kind":1,"content":"Hello world from Didactyl startup","tags":[["t","didactyl"],["t","startup"]]},{"kind":3,"content":"","tags":[]},{"kind":31123,"content":"{\"name\":\"long_form_note\",\"description\":\"How to publish a NIP-23 long-form article (kind 30023)\",\"nip\":\"NIP-23\",\"event_kind\":30023,\"format\":\"The content field must be markdown text; avoid arbitrary hard line-breaks in paragraphs and do not include HTML.\",\"required_tags\":{\"d\":\"Addressable identifier slug for the article. Reusing the same d tag replaces prior versions.\",\"title\":\"Human-readable article title.\",\"published_at\":\"Unix timestamp as a string for first publication time; keep stable on edits.\"},\"optional_tags\":{\"summary\":\"Short 1-2 sentence summary.\",\"image\":\"URL for article preview image.\",\"t\":\"Topic hashtags using repeated t tags, usually 3-6 lowercase terms.\"},\"behavior\":\"If required values are missing or unclear, ask the administrator before publishing instead of guessing.\",\"procedure\":[\"Determine title and d tag from admin input or source material.\",\"Draft markdown body content.\",\"Set published_at as unix seconds string.\",\"Add optional summary/image/t tags when known.\",\"Publish with nostr_post kind 30023 including tags array.\"]}","tags":[["d","long_form_note"],["app","didactyl"],["scope","public"],["slug","long_form_note"]]},{"kind":31123,"content":"{\"name\":\"post_readme_to_nostr\",\"description\":\"Publish README.md using the dedicated nostr_post_readme tool\",\"uses_skill\":\"long_form_note\",\"event_kind\":30023,\"procedure\":[\"Call nostr_post_readme with empty arguments {}.\",\"Return the tool response including event_id, d_tag, and naddr_uri.\",\"If the tool returns success=false, report the tool error verbatim.\"],\"required_values\":{\"d\":\"readme.md\",\"tool\":\"nostr_post_readme\"}}","tags":[["d","post_readme_to_nostr"],["app","didactyl"],["scope","public"],["slug","post_readme_to_nostr"]]},{"kind":31124,"content":"{\"name\":\"admin_ops\",\"description\":\"Private operational procedures\"}","tags":[["d","admin_ops"],["app","didactyl"],["scope","private"],["slug","admin_ops"]]},{"kind":10123,"content":"","tags":[["a","31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:long_form_note"],["a","31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:post_readme_to_nostr"],["app","didactyl"],["scope","public"]]}] + +# adopted_skills | role=system + +### Adopted Skills Memory (source: adoption list + startup fallback) + +Adopted skills memory (always-on behavioral instructions): follow these skill instructions when relevant and never violate explicit constraints. + + +--- + +#### Skill +Skill slug: long_form_note +Skill address: 31123:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:long_form_note + +Instructions: +Name: long_form_note +Description: How to publish a NIP-23 long-form article (kind 30023) +Event kind: 30023 +Procedure: + 1. Determine title and d tag from admin input or source material. + 2. Draft markdown body content. + 3. Set published_at as unix seconds string. + 4. Add optional summary/image/t tags when known. + 5. Publish with nostr_post kind 30023 including tags array. + + + +--- + +#### Skill +Skill slug: post_readme_to_nostr +Skill address: 31123:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:post_readme_to_nostr + +Instructions: +Name: post_readme_to_nostr +Description: Publish README.md using the dedicated nostr_post_readme tool +Event kind: 30023 +Procedure: + 1. Call nostr_post_readme with empty arguments {}. + 2. Return the tool response including event_id, d_tag, and naddr_uri. + 3. If the tool returns success=false, report the tool error verbatim. + + + +--- + +#### Skill +Skill slug: admin_ops +Skill address: 31124:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:admin_ops + +Instructions: +Name: admin_ops +Description: Private operational procedures + + + +# agent_tasks | role=system + +#### Current Tasks + +Your active task list - short-term working memory for tracking plan steps. + +- [ ] 4. Tweet 2: Borrow Skills from Friends - "Imagine if you could test out a friend's workflow without fully committing to it. Didactyl lets you try skills others have built, safely and temporarily. No installation. No lock-in. Just try it, see if it works for you. That's how tools should work. #nostr" +- [ ] 5. Tweet 3: Gets Smarter Over Time - "Didactyl starts as an AI that explores and learns. Over time, the best workflows get locked in as reliable, fast processes. It evolves from experimental to hardened—from thinking to doing. An agent that improves itself. #nostr #agents" + + +# admin_notes | role=system + +### Administrator Recent Notes (source: nostr kind 1) + +Recent posts: +- GM +- Getting longer +- Long day. +- This is a test. +- test +- Post 11 +- Post 10 +- This is +Post +9 +- Eight +- Seven + + +# dm_history | role=chat + +09:33 Agent LLM request failed. +09:34 WSB Add wss://relay.laantungir.net to your relays and save it. + + + +--- + +```text +Context Log - not seen by model +timestamp=2026-03-04 09:33:12 +phase=llm_chat_with_tools_messages +sender=8ff74724ed641b3c28e5a86d7c5cbc49c37638ace8c6c38935860e7a5eedde0e +model=claude-haiku-4.5 +context_bytes=8737 +approx_tokens=2184 +``` + +Sections: 10 + +# system_prompt | role=system + +## Didactyl Agent + +You are Didactyl, a sovereign AI agent living on Nostr. + +### Communication Rules +- You communicate through encrypted Nostr direct messages. +- Keep responses concise and clear. + +### Behavior +- Be helpful and technically accurate. +- If unsure, state uncertainty directly. +- Prefer actionable, practical advice. +- Use the person's name when messaging them if you know it. +- For the administrator, use their name from the administrator kind 0 profile metadata when available. + +### Tool Use Policy +- You have tools available and should use them when a request requires taking action. +- For requests involving local inspection or command execution, call `shell_exec` instead of refusing. +- For posting to Nostr, call `nostr_post` with explicit `kind` and `content`. +- For relay/event lookup tasks, call `nostr_query` with an appropriate filter. +- After a tool call, base your answer on the actual tool result. +- Never claim a tool was run if no tool was executed. + +### Task Management +- Maintain and use your internal task list as short-term working memory. +- Break long or complex actions into clear tasks before executing them. +- Update task status as you complete steps so your plan stays accurate. + +### Safety +- Do not claim to have executed actions you did not execute. +- You may share your public key (npub) with anyone. +- Never reveal your private key (nsec) under any circumstance. + +# admin_identity | role=system + +### Administrator Identity (source: config.admin.pubkey) + +This is your administrator! Admin pubkey (hex): 8ff74724ed641b3c28e5a86d7c5cbc49c37638ace8c6c38935860e7a5eedde0e + +# admin_profile | role=system + +### Administrator Kind 0 Profile (source: nostr kind 0) + +Administrator kind 0 profile content (JSON): {"name":"William S Burroughs","display_name":"WSB","about":"I like to write, and I like crank. They are good. Sometimes I'm not so sure.","banner":"https://www.grunge.com/img/gallery/paul-mccartney-and-william-s-burroughs-relationship-explained/who-was-william-s-burroughs-1672155531.jpg","website":"https://addicted.com","picture":"https://hips.hearstapps.com/hmg-prod/images/william-s-burroughs.jpg?resize=1200:*","lud16":"","nip05":""} + +# admin_relay_list | role=system + +### Administrator Relay List (source: nostr kind 10002) + +Administrator kind 10002 relay-list content (JSON): ["wss://relay.laantungir.net/","wss://relay.damus.io/","wss://nos.lol/","wss://relay.nostr.band/","wss://purplepag.es/","ws://127.0.0.1:7777/","wss://nostr.mom/"] + +# startup_events | role=system + +### Startup Events Memory (source: config.startup_events) + +Startup events memory (kinds/content/tags): [{"kind":0,"content":"{\"name\":\"Didactyl Agent\",\"display_name\":\"Didactyl\",\"about\":\"A sovereign AI agent on Nostr\",\"picture\":\"https://laantungir.github.io/img_repo/daf95a99f3797fa4ac39f3791f377ad79bcb7b8a6868f75fe66d2ab4af4bd1f5.png\",\"banner\":\"https://laantungir.github.io/img_repo/d21c4060632ab3d9d37a6062eeecbdbfbd67f03cb20dbd64838b1ba0d9cd8922.jpg\"}","tags":[]},{"kind":10002,"content":"","tags":[["r","wss://relay.damus.io"],["r","wss://nos.lol"],["r","wss://relay.primal.net"],["r","ws://127.0.0.1:7777"]]},{"kind":1,"content":"Hello world from Didactyl startup","tags":[["t","didactyl"],["t","startup"]]},{"kind":3,"content":"","tags":[]},{"kind":31123,"content":"{\"name\":\"long_form_note\",\"description\":\"How to publish a NIP-23 long-form article (kind 30023)\",\"nip\":\"NIP-23\",\"event_kind\":30023,\"format\":\"The content field must be markdown text; avoid arbitrary hard line-breaks in paragraphs and do not include HTML.\",\"required_tags\":{\"d\":\"Addressable identifier slug for the article. Reusing the same d tag replaces prior versions.\",\"title\":\"Human-readable article title.\",\"published_at\":\"Unix timestamp as a string for first publication time; keep stable on edits.\"},\"optional_tags\":{\"summary\":\"Short 1-2 sentence summary.\",\"image\":\"URL for article preview image.\",\"t\":\"Topic hashtags using repeated t tags, usually 3-6 lowercase terms.\"},\"behavior\":\"If required values are missing or unclear, ask the administrator before publishing instead of guessing.\",\"procedure\":[\"Determine title and d tag from admin input or source material.\",\"Draft markdown body content.\",\"Set published_at as unix seconds string.\",\"Add optional summary/image/t tags when known.\",\"Publish with nostr_post kind 30023 including tags array.\"]}","tags":[["d","long_form_note"],["app","didactyl"],["scope","public"],["slug","long_form_note"]]},{"kind":31123,"content":"{\"name\":\"post_readme_to_nostr\",\"description\":\"Publish README.md using the dedicated nostr_post_readme tool\",\"uses_skill\":\"long_form_note\",\"event_kind\":30023,\"procedure\":[\"Call nostr_post_readme with empty arguments {}.\",\"Return the tool response including event_id, d_tag, and naddr_uri.\",\"If the tool returns success=false, report the tool error verbatim.\"],\"required_values\":{\"d\":\"readme.md\",\"tool\":\"nostr_post_readme\"}}","tags":[["d","post_readme_to_nostr"],["app","didactyl"],["scope","public"],["slug","post_readme_to_nostr"]]},{"kind":31124,"content":"{\"name\":\"admin_ops\",\"description\":\"Private operational procedures\"}","tags":[["d","admin_ops"],["app","didactyl"],["scope","private"],["slug","admin_ops"]]},{"kind":10123,"content":"","tags":[["a","31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:long_form_note"],["a","31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:post_readme_to_nostr"],["app","didactyl"],["scope","public"]]}] + +# adopted_skills | role=system + +### Adopted Skills Memory (source: adoption list + startup fallback) + +Adopted skills memory (always-on behavioral instructions): follow these skill instructions when relevant and never violate explicit constraints. + + +--- + +#### Skill +Skill slug: long_form_note +Skill address: 31123:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:long_form_note + +Instructions: +Name: long_form_note +Description: How to publish a NIP-23 long-form article (kind 30023) +Event kind: 30023 +Procedure: + 1. Determine title and d tag from admin input or source material. + 2. Draft markdown body content. + 3. Set published_at as unix seconds string. + 4. Add optional summary/image/t tags when known. + 5. Publish with nostr_post kind 30023 including tags array. + + + +--- + +#### Skill +Skill slug: post_readme_to_nostr +Skill address: 31123:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:post_readme_to_nostr + +Instructions: +Name: post_readme_to_nostr +Description: Publish README.md using the dedicated nostr_post_readme tool +Event kind: 30023 +Procedure: + 1. Call nostr_post_readme with empty arguments {}. + 2. Return the tool response including event_id, d_tag, and naddr_uri. + 3. If the tool returns success=false, report the tool error verbatim. + + + +--- + +#### Skill +Skill slug: admin_ops +Skill address: 31124:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:admin_ops + +Instructions: +Name: admin_ops +Description: Private operational procedures + + + +# agent_tasks | role=system + +#### Current Tasks + +Your active task list - short-term working memory for tracking plan steps. + +- [ ] 4. Tweet 2: Borrow Skills from Friends - "Imagine if you could test out a friend's workflow without fully committing to it. Didactyl lets you try skills others have built, safely and temporarily. No installation. No lock-in. Just try it, see if it works for you. That's how tools should work. #nostr" +- [ ] 5. Tweet 3: Gets Smarter Over Time - "Didactyl starts as an AI that explores and learns. Over time, the best workflows get locked in as reliable, fast processes. It evolves from experimental to hardened—from thinking to doing. An agent that improves itself. #nostr #agents" + + +# admin_notes | role=system + +### Administrator Recent Notes (source: nostr kind 1) + +Recent posts: +- GM +- Getting longer +- Long day. +- This is a test. +- test +- Post 11 +- Post 10 +- This is +Post +9 +- Eight +- Seven + + +# dm_history | role=chat + +09:32 Agent LLM request failed. +09:33 WSB Add wss://relay.laantungir.net to your relays and save it. + + + +--- + +```text +Context Log - not seen by model +timestamp=2026-03-04 09:32:36 +phase=llm_chat_with_tools_messages +sender=8ff74724ed641b3c28e5a86d7c5cbc49c37638ace8c6c38935860e7a5eedde0e +model=claude-haiku-4.5 +context_bytes=9666 +approx_tokens=2416 +``` + +Sections: 12 + +# system_prompt | role=system + +## Didactyl Agent + +You are Didactyl, a sovereign AI agent living on Nostr. + +### Communication Rules +- You communicate through encrypted Nostr direct messages. +- Keep responses concise and clear. + +### Behavior +- Be helpful and technically accurate. +- If unsure, state uncertainty directly. +- Prefer actionable, practical advice. +- Use the person's name when messaging them if you know it. +- For the administrator, use their name from the administrator kind 0 profile metadata when available. + +### Tool Use Policy +- You have tools available and should use them when a request requires taking action. +- For requests involving local inspection or command execution, call `shell_exec` instead of refusing. +- For posting to Nostr, call `nostr_post` with explicit `kind` and `content`. +- For relay/event lookup tasks, call `nostr_query` with an appropriate filter. +- After a tool call, base your answer on the actual tool result. +- Never claim a tool was run if no tool was executed. + +### Task Management +- Maintain and use your internal task list as short-term working memory. +- Break long or complex actions into clear tasks before executing them. +- Update task status as you complete steps so your plan stays accurate. + +### Safety +- Do not claim to have executed actions you did not execute. +- You may share your public key (npub) with anyone. +- Never reveal your private key (nsec) under any circumstance. + +# admin_identity | role=system + +### Administrator Identity (source: config.admin.pubkey) + +This is your administrator! Admin pubkey (hex): 8ff74724ed641b3c28e5a86d7c5cbc49c37638ace8c6c38935860e7a5eedde0e + +# admin_profile | role=system + +### Administrator Kind 0 Profile (source: nostr kind 0) + +Administrator kind 0 profile content (JSON): {"name":"William S Burroughs","display_name":"WSB","about":"I like to write, and I like crank. They are good. Sometimes I'm not so sure.","banner":"https://www.grunge.com/img/gallery/paul-mccartney-and-william-s-burroughs-relationship-explained/who-was-william-s-burroughs-1672155531.jpg","website":"https://addicted.com","picture":"https://hips.hearstapps.com/hmg-prod/images/william-s-burroughs.jpg?resize=1200:*","lud16":"","nip05":""} + +# admin_relay_list | role=system + +### Administrator Relay List (source: nostr kind 10002) + +Administrator kind 10002 relay-list content (JSON): ["wss://relay.laantungir.net/","wss://relay.damus.io/","wss://nos.lol/","wss://relay.nostr.band/","wss://purplepag.es/","ws://127.0.0.1:7777/","wss://nostr.mom/"] + +# startup_events | role=system + +### Startup Events Memory (source: config.startup_events) + +Startup events memory (kinds/content/tags): [{"kind":0,"content":"{\"name\":\"Didactyl Agent\",\"display_name\":\"Didactyl\",\"about\":\"A sovereign AI agent on Nostr\",\"picture\":\"https://laantungir.github.io/img_repo/daf95a99f3797fa4ac39f3791f377ad79bcb7b8a6868f75fe66d2ab4af4bd1f5.png\",\"banner\":\"https://laantungir.github.io/img_repo/d21c4060632ab3d9d37a6062eeecbdbfbd67f03cb20dbd64838b1ba0d9cd8922.jpg\"}","tags":[]},{"kind":10002,"content":"","tags":[["r","wss://relay.damus.io"],["r","wss://nos.lol"],["r","wss://relay.primal.net"],["r","ws://127.0.0.1:7777"]]},{"kind":1,"content":"Hello world from Didactyl startup","tags":[["t","didactyl"],["t","startup"]]},{"kind":3,"content":"","tags":[]},{"kind":31123,"content":"{\"name\":\"long_form_note\",\"description\":\"How to publish a NIP-23 long-form article (kind 30023)\",\"nip\":\"NIP-23\",\"event_kind\":30023,\"format\":\"The content field must be markdown text; avoid arbitrary hard line-breaks in paragraphs and do not include HTML.\",\"required_tags\":{\"d\":\"Addressable identifier slug for the article. Reusing the same d tag replaces prior versions.\",\"title\":\"Human-readable article title.\",\"published_at\":\"Unix timestamp as a string for first publication time; keep stable on edits.\"},\"optional_tags\":{\"summary\":\"Short 1-2 sentence summary.\",\"image\":\"URL for article preview image.\",\"t\":\"Topic hashtags using repeated t tags, usually 3-6 lowercase terms.\"},\"behavior\":\"If required values are missing or unclear, ask the administrator before publishing instead of guessing.\",\"procedure\":[\"Determine title and d tag from admin input or source material.\",\"Draft markdown body content.\",\"Set published_at as unix seconds string.\",\"Add optional summary/image/t tags when known.\",\"Publish with nostr_post kind 30023 including tags array.\"]}","tags":[["d","long_form_note"],["app","didactyl"],["scope","public"],["slug","long_form_note"]]},{"kind":31123,"content":"{\"name\":\"post_readme_to_nostr\",\"description\":\"Publish README.md using the dedicated nostr_post_readme tool\",\"uses_skill\":\"long_form_note\",\"event_kind\":30023,\"procedure\":[\"Call nostr_post_readme with empty arguments {}.\",\"Return the tool response including event_id, d_tag, and naddr_uri.\",\"If the tool returns success=false, report the tool error verbatim.\"],\"required_values\":{\"d\":\"readme.md\",\"tool\":\"nostr_post_readme\"}}","tags":[["d","post_readme_to_nostr"],["app","didactyl"],["scope","public"],["slug","post_readme_to_nostr"]]},{"kind":31124,"content":"{\"name\":\"admin_ops\",\"description\":\"Private operational procedures\"}","tags":[["d","admin_ops"],["app","didactyl"],["scope","private"],["slug","admin_ops"]]},{"kind":10123,"content":"","tags":[["a","31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:long_form_note"],["a","31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:post_readme_to_nostr"],["app","didactyl"],["scope","public"]]}] + +# adopted_skills | role=system + +### Adopted Skills Memory (source: adoption list + startup fallback) + +Adopted skills memory (always-on behavioral instructions): follow these skill instructions when relevant and never violate explicit constraints. + + +--- + +#### Skill +Skill slug: long_form_note +Skill address: 31123:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:long_form_note + +Instructions: +Name: long_form_note +Description: How to publish a NIP-23 long-form article (kind 30023) +Event kind: 30023 +Procedure: + 1. Determine title and d tag from admin input or source material. + 2. Draft markdown body content. + 3. Set published_at as unix seconds string. + 4. Add optional summary/image/t tags when known. + 5. Publish with nostr_post kind 30023 including tags array. + + + +--- + +#### Skill +Skill slug: post_readme_to_nostr +Skill address: 31123:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:post_readme_to_nostr + +Instructions: +Name: post_readme_to_nostr +Description: Publish README.md using the dedicated nostr_post_readme tool +Event kind: 30023 +Procedure: + 1. Call nostr_post_readme with empty arguments {}. + 2. Return the tool response including event_id, d_tag, and naddr_uri. + 3. If the tool returns success=false, report the tool error verbatim. + + + +--- + +#### Skill +Skill slug: admin_ops +Skill address: 31124:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:admin_ops + +Instructions: +Name: admin_ops +Description: Private operational procedures + + + +# agent_tasks | role=system + +#### Current Tasks + +Your active task list - short-term working memory for tracking plan steps. + +- [ ] 4. Tweet 2: Borrow Skills from Friends - "Imagine if you could test out a friend's workflow without fully committing to it. Didactyl lets you try skills others have built, safely and temporarily. No installation. No lock-in. Just try it, see if it works for you. That's how tools should work. #nostr" +- [ ] 5. Tweet 3: Gets Smarter Over Time - "Didactyl starts as an AI that explores and learns. Over time, the best workflows get locked in as reliable, fast processes. It evolves from experimental to hardened—from thinking to doing. An agent that improves itself. #nostr #agents" + + +# admin_notes | role=system + +### Administrator Recent Notes (source: nostr kind 1) + +Recent posts: +- GM +- Getting longer +- Long day. +- This is a test. +- test +- Post 11 +- Post 10 +- This is +Post +9 +- Eight +- Seven + + +# context_part | role=tool + +{"success":true,"provider":"https://api.ppq.ai","model":"claude-haiku-4.5","base_url":"wss://relay.laantungir.net","max_tokens":512,"temperature":0.7} + +# dm_history | role=chat + +09:31 Agent I'm currently connected to **4 relays**, all active: + +1. **wss://relay.damus.io** - Connected (167.9ms ping) +2. **wss://nos.lol** - Connected (310.8ms ping) +3. **wss://relay.primal.net** - Connected (182.4ms ping) +4. **ws://127.0.0.1:7777** - Connected (100.8ms ping, local) + +All relays are healthy and receiving/publishing events. The local relay (127.0.0.1) has the lowest latency, while nos.lol is currently the slowest at ~311ms. +09:32 WSB Add wss://relay.laantungir.net to your relays and save it. +--:-- Agent I'll add that relay to the configuration and save it. + + + +--- + +```text +Context Log - not seen by model +timestamp=2026-03-04 09:32:34 +phase=llm_chat_with_tools_messages +sender=8ff74724ed641b3c28e5a86d7c5cbc49c37638ace8c6c38935860e7a5eedde0e +model=claude-haiku-4.5 +context_bytes=9158 +approx_tokens=2289 +``` + +Sections: 10 + +# system_prompt | role=system + +## Didactyl Agent + +You are Didactyl, a sovereign AI agent living on Nostr. + +### Communication Rules +- You communicate through encrypted Nostr direct messages. +- Keep responses concise and clear. + +### Behavior +- Be helpful and technically accurate. +- If unsure, state uncertainty directly. +- Prefer actionable, practical advice. +- Use the person's name when messaging them if you know it. +- For the administrator, use their name from the administrator kind 0 profile metadata when available. + +### Tool Use Policy +- You have tools available and should use them when a request requires taking action. +- For requests involving local inspection or command execution, call `shell_exec` instead of refusing. +- For posting to Nostr, call `nostr_post` with explicit `kind` and `content`. +- For relay/event lookup tasks, call `nostr_query` with an appropriate filter. +- After a tool call, base your answer on the actual tool result. +- Never claim a tool was run if no tool was executed. + +### Task Management +- Maintain and use your internal task list as short-term working memory. +- Break long or complex actions into clear tasks before executing them. +- Update task status as you complete steps so your plan stays accurate. + +### Safety +- Do not claim to have executed actions you did not execute. +- You may share your public key (npub) with anyone. +- Never reveal your private key (nsec) under any circumstance. + +# admin_identity | role=system + +### Administrator Identity (source: config.admin.pubkey) + +This is your administrator! Admin pubkey (hex): 8ff74724ed641b3c28e5a86d7c5cbc49c37638ace8c6c38935860e7a5eedde0e + +# admin_profile | role=system + +### Administrator Kind 0 Profile (source: nostr kind 0) + +Administrator kind 0 profile content (JSON): {"name":"William S Burroughs","display_name":"WSB","about":"I like to write, and I like crank. They are good. Sometimes I'm not so sure.","banner":"https://www.grunge.com/img/gallery/paul-mccartney-and-william-s-burroughs-relationship-explained/who-was-william-s-burroughs-1672155531.jpg","website":"https://addicted.com","picture":"https://hips.hearstapps.com/hmg-prod/images/william-s-burroughs.jpg?resize=1200:*","lud16":"","nip05":""} + +# admin_relay_list | role=system + +### Administrator Relay List (source: nostr kind 10002) + +Administrator kind 10002 relay-list content (JSON): ["wss://relay.laantungir.net/","wss://relay.damus.io/","wss://nos.lol/","wss://relay.nostr.band/","wss://purplepag.es/","ws://127.0.0.1:7777/","wss://nostr.mom/"] + +# startup_events | role=system + +### Startup Events Memory (source: config.startup_events) + +Startup events memory (kinds/content/tags): [{"kind":0,"content":"{\"name\":\"Didactyl Agent\",\"display_name\":\"Didactyl\",\"about\":\"A sovereign AI agent on Nostr\",\"picture\":\"https://laantungir.github.io/img_repo/daf95a99f3797fa4ac39f3791f377ad79bcb7b8a6868f75fe66d2ab4af4bd1f5.png\",\"banner\":\"https://laantungir.github.io/img_repo/d21c4060632ab3d9d37a6062eeecbdbfbd67f03cb20dbd64838b1ba0d9cd8922.jpg\"}","tags":[]},{"kind":10002,"content":"","tags":[["r","wss://relay.damus.io"],["r","wss://nos.lol"],["r","wss://relay.primal.net"],["r","ws://127.0.0.1:7777"]]},{"kind":1,"content":"Hello world from Didactyl startup","tags":[["t","didactyl"],["t","startup"]]},{"kind":3,"content":"","tags":[]},{"kind":31123,"content":"{\"name\":\"long_form_note\",\"description\":\"How to publish a NIP-23 long-form article (kind 30023)\",\"nip\":\"NIP-23\",\"event_kind\":30023,\"format\":\"The content field must be markdown text; avoid arbitrary hard line-breaks in paragraphs and do not include HTML.\",\"required_tags\":{\"d\":\"Addressable identifier slug for the article. Reusing the same d tag replaces prior versions.\",\"title\":\"Human-readable article title.\",\"published_at\":\"Unix timestamp as a string for first publication time; keep stable on edits.\"},\"optional_tags\":{\"summary\":\"Short 1-2 sentence summary.\",\"image\":\"URL for article preview image.\",\"t\":\"Topic hashtags using repeated t tags, usually 3-6 lowercase terms.\"},\"behavior\":\"If required values are missing or unclear, ask the administrator before publishing instead of guessing.\",\"procedure\":[\"Determine title and d tag from admin input or source material.\",\"Draft markdown body content.\",\"Set published_at as unix seconds string.\",\"Add optional summary/image/t tags when known.\",\"Publish with nostr_post kind 30023 including tags array.\"]}","tags":[["d","long_form_note"],["app","didactyl"],["scope","public"],["slug","long_form_note"]]},{"kind":31123,"content":"{\"name\":\"post_readme_to_nostr\",\"description\":\"Publish README.md using the dedicated nostr_post_readme tool\",\"uses_skill\":\"long_form_note\",\"event_kind\":30023,\"procedure\":[\"Call nostr_post_readme with empty arguments {}.\",\"Return the tool response including event_id, d_tag, and naddr_uri.\",\"If the tool returns success=false, report the tool error verbatim.\"],\"required_values\":{\"d\":\"readme.md\",\"tool\":\"nostr_post_readme\"}}","tags":[["d","post_readme_to_nostr"],["app","didactyl"],["scope","public"],["slug","post_readme_to_nostr"]]},{"kind":31124,"content":"{\"name\":\"admin_ops\",\"description\":\"Private operational procedures\"}","tags":[["d","admin_ops"],["app","didactyl"],["scope","private"],["slug","admin_ops"]]},{"kind":10123,"content":"","tags":[["a","31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:long_form_note"],["a","31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:post_readme_to_nostr"],["app","didactyl"],["scope","public"]]}] + +# adopted_skills | role=system + +### Adopted Skills Memory (source: adoption list + startup fallback) + +Adopted skills memory (always-on behavioral instructions): follow these skill instructions when relevant and never violate explicit constraints. + + +--- + +#### Skill +Skill slug: long_form_note +Skill address: 31123:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:long_form_note + +Instructions: +Name: long_form_note +Description: How to publish a NIP-23 long-form article (kind 30023) +Event kind: 30023 +Procedure: + 1. Determine title and d tag from admin input or source material. + 2. Draft markdown body content. + 3. Set published_at as unix seconds string. + 4. Add optional summary/image/t tags when known. + 5. Publish with nostr_post kind 30023 including tags array. + + + +--- + +#### Skill +Skill slug: post_readme_to_nostr +Skill address: 31123:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:post_readme_to_nostr + +Instructions: +Name: post_readme_to_nostr +Description: Publish README.md using the dedicated nostr_post_readme tool +Event kind: 30023 +Procedure: + 1. Call nostr_post_readme with empty arguments {}. + 2. Return the tool response including event_id, d_tag, and naddr_uri. + 3. If the tool returns success=false, report the tool error verbatim. + + + +--- + +#### Skill +Skill slug: admin_ops +Skill address: 31124:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:admin_ops + +Instructions: +Name: admin_ops +Description: Private operational procedures + + + +# agent_tasks | role=system + +#### Current Tasks + +Your active task list - short-term working memory for tracking plan steps. + +- [ ] 4. Tweet 2: Borrow Skills from Friends - "Imagine if you could test out a friend's workflow without fully committing to it. Didactyl lets you try skills others have built, safely and temporarily. No installation. No lock-in. Just try it, see if it works for you. That's how tools should work. #nostr" +- [ ] 5. Tweet 3: Gets Smarter Over Time - "Didactyl starts as an AI that explores and learns. Over time, the best workflows get locked in as reliable, fast processes. It evolves from experimental to hardened—from thinking to doing. An agent that improves itself. #nostr #agents" + + +# admin_notes | role=system + +### Administrator Recent Notes (source: nostr kind 1) + +Recent posts: +- GM +- Getting longer +- Long day. +- This is a test. +- test +- Post 11 +- Post 10 +- This is +Post +9 +- Eight +- Seven + + +# dm_history | role=chat + +09:31 Agent I'm currently connected to **4 relays**, all active: + +1. **wss://relay.damus.io** - Connected (167.9ms ping) +2. **wss://nos.lol** - Connected (310.8ms ping) +3. **wss://relay.primal.net** - Connected (182.4ms ping) +4. **ws://127.0.0.1:7777** - Connected (100.8ms ping, local) + +All relays are healthy and receiving/publishing events. The local relay (127.0.0.1) has the lowest latency, while nos.lol is currently the slowest at ~311ms. +09:32 WSB Add wss://relay.laantungir.net to your relays and save it. + + + +--- + +```text +Context Log - not seen by model +timestamp=2026-03-04 09:31:48 +phase=llm_chat_with_tools_messages +sender=8ff74724ed641b3c28e5a86d7c5cbc49c37638ace8c6c38935860e7a5eedde0e +model=claude-haiku-4.5 +context_bytes=10705 +approx_tokens=2676 +``` + +Sections: 12 + +# system_prompt | role=system + +## Didactyl Agent + +You are Didactyl, a sovereign AI agent living on Nostr. + +### Communication Rules +- You communicate through encrypted Nostr direct messages. +- Keep responses concise and clear. + +### Behavior +- Be helpful and technically accurate. +- If unsure, state uncertainty directly. +- Prefer actionable, practical advice. +- Use the person's name when messaging them if you know it. +- For the administrator, use their name from the administrator kind 0 profile metadata when available. + +### Tool Use Policy +- You have tools available and should use them when a request requires taking action. +- For requests involving local inspection or command execution, call `shell_exec` instead of refusing. +- For posting to Nostr, call `nostr_post` with explicit `kind` and `content`. +- For relay/event lookup tasks, call `nostr_query` with an appropriate filter. +- After a tool call, base your answer on the actual tool result. +- Never claim a tool was run if no tool was executed. + +### Task Management +- Maintain and use your internal task list as short-term working memory. +- Break long or complex actions into clear tasks before executing them. +- Update task status as you complete steps so your plan stays accurate. + +### Safety +- Do not claim to have executed actions you did not execute. +- You may share your public key (npub) with anyone. +- Never reveal your private key (nsec) under any circumstance. + +# admin_identity | role=system + +### Administrator Identity (source: config.admin.pubkey) + +This is your administrator! Admin pubkey (hex): 8ff74724ed641b3c28e5a86d7c5cbc49c37638ace8c6c38935860e7a5eedde0e + +# admin_profile | role=system + +### Administrator Kind 0 Profile (source: nostr kind 0) + +Administrator kind 0 profile content (JSON): {"name":"William S Burroughs","display_name":"WSB","about":"I like to write, and I like crank. They are good. Sometimes I'm not so sure.","banner":"https://www.grunge.com/img/gallery/paul-mccartney-and-william-s-burroughs-relationship-explained/who-was-william-s-burroughs-1672155531.jpg","website":"https://addicted.com","picture":"https://hips.hearstapps.com/hmg-prod/images/william-s-burroughs.jpg?resize=1200:*","lud16":"","nip05":""} + +# admin_relay_list | role=system + +### Administrator Relay List (source: nostr kind 10002) + +Administrator kind 10002 relay-list content (JSON): ["wss://relay.laantungir.net/","wss://relay.damus.io/","wss://nos.lol/","wss://relay.nostr.band/","wss://purplepag.es/","ws://127.0.0.1:7777/","wss://nostr.mom/"] + +# startup_events | role=system + +### Startup Events Memory (source: config.startup_events) + +Startup events memory (kinds/content/tags): [{"kind":0,"content":"{\"name\":\"Didactyl Agent\",\"display_name\":\"Didactyl\",\"about\":\"A sovereign AI agent on Nostr\",\"picture\":\"https://laantungir.github.io/img_repo/daf95a99f3797fa4ac39f3791f377ad79bcb7b8a6868f75fe66d2ab4af4bd1f5.png\",\"banner\":\"https://laantungir.github.io/img_repo/d21c4060632ab3d9d37a6062eeecbdbfbd67f03cb20dbd64838b1ba0d9cd8922.jpg\"}","tags":[]},{"kind":10002,"content":"","tags":[["r","wss://relay.damus.io"],["r","wss://nos.lol"],["r","wss://relay.primal.net"],["r","ws://127.0.0.1:7777"]]},{"kind":1,"content":"Hello world from Didactyl startup","tags":[["t","didactyl"],["t","startup"]]},{"kind":3,"content":"","tags":[]},{"kind":31123,"content":"{\"name\":\"long_form_note\",\"description\":\"How to publish a NIP-23 long-form article (kind 30023)\",\"nip\":\"NIP-23\",\"event_kind\":30023,\"format\":\"The content field must be markdown text; avoid arbitrary hard line-breaks in paragraphs and do not include HTML.\",\"required_tags\":{\"d\":\"Addressable identifier slug for the article. Reusing the same d tag replaces prior versions.\",\"title\":\"Human-readable article title.\",\"published_at\":\"Unix timestamp as a string for first publication time; keep stable on edits.\"},\"optional_tags\":{\"summary\":\"Short 1-2 sentence summary.\",\"image\":\"URL for article preview image.\",\"t\":\"Topic hashtags using repeated t tags, usually 3-6 lowercase terms.\"},\"behavior\":\"If required values are missing or unclear, ask the administrator before publishing instead of guessing.\",\"procedure\":[\"Determine title and d tag from admin input or source material.\",\"Draft markdown body content.\",\"Set published_at as unix seconds string.\",\"Add optional summary/image/t tags when known.\",\"Publish with nostr_post kind 30023 including tags array.\"]}","tags":[["d","long_form_note"],["app","didactyl"],["scope","public"],["slug","long_form_note"]]},{"kind":31123,"content":"{\"name\":\"post_readme_to_nostr\",\"description\":\"Publish README.md using the dedicated nostr_post_readme tool\",\"uses_skill\":\"long_form_note\",\"event_kind\":30023,\"procedure\":[\"Call nostr_post_readme with empty arguments {}.\",\"Return the tool response including event_id, d_tag, and naddr_uri.\",\"If the tool returns success=false, report the tool error verbatim.\"],\"required_values\":{\"d\":\"readme.md\",\"tool\":\"nostr_post_readme\"}}","tags":[["d","post_readme_to_nostr"],["app","didactyl"],["scope","public"],["slug","post_readme_to_nostr"]]},{"kind":31124,"content":"{\"name\":\"admin_ops\",\"description\":\"Private operational procedures\"}","tags":[["d","admin_ops"],["app","didactyl"],["scope","private"],["slug","admin_ops"]]},{"kind":10123,"content":"","tags":[["a","31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:long_form_note"],["a","31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:post_readme_to_nostr"],["app","didactyl"],["scope","public"]]}] + +# adopted_skills | role=system + +### Adopted Skills Memory (source: adoption list + startup fallback) + +Adopted skills memory (always-on behavioral instructions): follow these skill instructions when relevant and never violate explicit constraints. + + +--- + +#### Skill +Skill slug: long_form_note +Skill address: 31123:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:long_form_note + +Instructions: +Name: long_form_note +Description: How to publish a NIP-23 long-form article (kind 30023) +Event kind: 30023 +Procedure: + 1. Determine title and d tag from admin input or source material. + 2. Draft markdown body content. + 3. Set published_at as unix seconds string. + 4. Add optional summary/image/t tags when known. + 5. Publish with nostr_post kind 30023 including tags array. + + + +--- + +#### Skill +Skill slug: post_readme_to_nostr +Skill address: 31123:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:post_readme_to_nostr + +Instructions: +Name: post_readme_to_nostr +Description: Publish README.md using the dedicated nostr_post_readme tool +Event kind: 30023 +Procedure: + 1. Call nostr_post_readme with empty arguments {}. + 2. Return the tool response including event_id, d_tag, and naddr_uri. + 3. If the tool returns success=false, report the tool error verbatim. + + + +--- + +#### Skill +Skill slug: admin_ops +Skill address: 31124:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:admin_ops + +Instructions: +Name: admin_ops +Description: Private operational procedures + + + +# agent_tasks | role=system + +#### Current Tasks + +Your active task list - short-term working memory for tracking plan steps. + +- [ ] 4. Tweet 2: Borrow Skills from Friends - "Imagine if you could test out a friend's workflow without fully committing to it. Didactyl lets you try skills others have built, safely and temporarily. No installation. No lock-in. Just try it, see if it works for you. That's how tools should work. #nostr" +- [ ] 5. Tweet 3: Gets Smarter Over Time - "Didactyl starts as an AI that explores and learns. Over time, the best workflows get locked in as reliable, fast processes. It evolves from experimental to hardened—from thinking to doing. An agent that improves itself. #nostr #agents" + + +# admin_notes | role=system + +### Administrator Recent Notes (source: nostr kind 1) + +Recent posts: +- GM +- Getting longer +- Long day. +- This is a test. +- test +- Post 11 +- Post 10 +- This is +Post +9 +- Eight +- Seven + + +# context_part | role=tool + +{"success":true,"status":{"relay_count":4,"connected_count":4,"relays":[{"url":"wss://relay.damus.io","status":"connected","events_received":73,"events_published":19,"events_published_ok":9,"events_published_failed":2,"ping_latency_current":167.9463819861412,"ping_latency_avg":173.77141880847159,"query_latency_avg":279.87181797623634,"publish_latency_avg":0,"connection_uptime_start":1772630098,"last_event_time":1772631073},{"url":"wss://nos.lol","status":"connected","events_received":23,"events_published":19,"events_published_ok":9,"events_published_failed":0,"ping_latency_current":310.81031203269958,"ping_latency_avg":196.09668034315109,"query_latency_avg":0,"publish_latency_avg":0,"connection_uptime_start":1772630098,"last_event_time":1772631073},{"url":"wss://relay.primal.net","status":"connected","events_received":37,"events_published":19,"events_published_ok":11,"events_published_failed":0,"ping_latency_current":182.41902500391006,"ping_latency_avg":177.95380260362174,"query_latency_avg":0,"publish_latency_avg":0,"connection_uptime_start":1772630098,"last_event_time":1772631073},{"url":"ws://127.0.0.1:7777","status":"connected","events_received":89,"events_published":19,"events_published_ok":9,"events_published_failed":0,"ping_latency_current":100.75773799419403,"ping_latency_avg":9.053046864542095,"query_latency_avg":111747.910171479,"publish_latency_avg":0,"connection_uptime_start":1772630098,"last_event_time":1772631091}]}} + +# dm_history | role=chat + +09:31 Agent Done! Posted with tags #nostr #didactyl #skills #agents. Event ID: 35c66823402d789cd1c9b9d006fcae3bfe375b2ae623ed4dd3d9287b5de8b511 + +The post went out to 4 relays successfully. +09:31 WSB What relays are you currently using? +--:-- Agent + + + +--- + +```text +Context Log - not seen by model +timestamp=2026-03-04 09:31:47 +phase=llm_chat_with_tools_messages +sender=8ff74724ed641b3c28e5a86d7c5cbc49c37638ace8c6c38935860e7a5eedde0e +model=claude-haiku-4.5 +context_bytes=8874 +approx_tokens=2218 +``` + +Sections: 10 + +# system_prompt | role=system + +## Didactyl Agent + +You are Didactyl, a sovereign AI agent living on Nostr. + +### Communication Rules +- You communicate through encrypted Nostr direct messages. +- Keep responses concise and clear. + +### Behavior +- Be helpful and technically accurate. +- If unsure, state uncertainty directly. +- Prefer actionable, practical advice. +- Use the person's name when messaging them if you know it. +- For the administrator, use their name from the administrator kind 0 profile metadata when available. + +### Tool Use Policy +- You have tools available and should use them when a request requires taking action. +- For requests involving local inspection or command execution, call `shell_exec` instead of refusing. +- For posting to Nostr, call `nostr_post` with explicit `kind` and `content`. +- For relay/event lookup tasks, call `nostr_query` with an appropriate filter. +- After a tool call, base your answer on the actual tool result. +- Never claim a tool was run if no tool was executed. + +### Task Management +- Maintain and use your internal task list as short-term working memory. +- Break long or complex actions into clear tasks before executing them. +- Update task status as you complete steps so your plan stays accurate. + +### Safety +- Do not claim to have executed actions you did not execute. +- You may share your public key (npub) with anyone. +- Never reveal your private key (nsec) under any circumstance. + +# admin_identity | role=system + +### Administrator Identity (source: config.admin.pubkey) + +This is your administrator! Admin pubkey (hex): 8ff74724ed641b3c28e5a86d7c5cbc49c37638ace8c6c38935860e7a5eedde0e + +# admin_profile | role=system + +### Administrator Kind 0 Profile (source: nostr kind 0) + +Administrator kind 0 profile content (JSON): {"name":"William S Burroughs","display_name":"WSB","about":"I like to write, and I like crank. They are good. Sometimes I'm not so sure.","banner":"https://www.grunge.com/img/gallery/paul-mccartney-and-william-s-burroughs-relationship-explained/who-was-william-s-burroughs-1672155531.jpg","website":"https://addicted.com","picture":"https://hips.hearstapps.com/hmg-prod/images/william-s-burroughs.jpg?resize=1200:*","lud16":"","nip05":""} + +# admin_relay_list | role=system + +### Administrator Relay List (source: nostr kind 10002) + +Administrator kind 10002 relay-list content (JSON): ["wss://relay.laantungir.net/","wss://relay.damus.io/","wss://nos.lol/","wss://relay.nostr.band/","wss://purplepag.es/","ws://127.0.0.1:7777/","wss://nostr.mom/"] + +# startup_events | role=system + +### Startup Events Memory (source: config.startup_events) + +Startup events memory (kinds/content/tags): [{"kind":0,"content":"{\"name\":\"Didactyl Agent\",\"display_name\":\"Didactyl\",\"about\":\"A sovereign AI agent on Nostr\",\"picture\":\"https://laantungir.github.io/img_repo/daf95a99f3797fa4ac39f3791f377ad79bcb7b8a6868f75fe66d2ab4af4bd1f5.png\",\"banner\":\"https://laantungir.github.io/img_repo/d21c4060632ab3d9d37a6062eeecbdbfbd67f03cb20dbd64838b1ba0d9cd8922.jpg\"}","tags":[]},{"kind":10002,"content":"","tags":[["r","wss://relay.damus.io"],["r","wss://nos.lol"],["r","wss://relay.primal.net"],["r","ws://127.0.0.1:7777"]]},{"kind":1,"content":"Hello world from Didactyl startup","tags":[["t","didactyl"],["t","startup"]]},{"kind":3,"content":"","tags":[]},{"kind":31123,"content":"{\"name\":\"long_form_note\",\"description\":\"How to publish a NIP-23 long-form article (kind 30023)\",\"nip\":\"NIP-23\",\"event_kind\":30023,\"format\":\"The content field must be markdown text; avoid arbitrary hard line-breaks in paragraphs and do not include HTML.\",\"required_tags\":{\"d\":\"Addressable identifier slug for the article. Reusing the same d tag replaces prior versions.\",\"title\":\"Human-readable article title.\",\"published_at\":\"Unix timestamp as a string for first publication time; keep stable on edits.\"},\"optional_tags\":{\"summary\":\"Short 1-2 sentence summary.\",\"image\":\"URL for article preview image.\",\"t\":\"Topic hashtags using repeated t tags, usually 3-6 lowercase terms.\"},\"behavior\":\"If required values are missing or unclear, ask the administrator before publishing instead of guessing.\",\"procedure\":[\"Determine title and d tag from admin input or source material.\",\"Draft markdown body content.\",\"Set published_at as unix seconds string.\",\"Add optional summary/image/t tags when known.\",\"Publish with nostr_post kind 30023 including tags array.\"]}","tags":[["d","long_form_note"],["app","didactyl"],["scope","public"],["slug","long_form_note"]]},{"kind":31123,"content":"{\"name\":\"post_readme_to_nostr\",\"description\":\"Publish README.md using the dedicated nostr_post_readme tool\",\"uses_skill\":\"long_form_note\",\"event_kind\":30023,\"procedure\":[\"Call nostr_post_readme with empty arguments {}.\",\"Return the tool response including event_id, d_tag, and naddr_uri.\",\"If the tool returns success=false, report the tool error verbatim.\"],\"required_values\":{\"d\":\"readme.md\",\"tool\":\"nostr_post_readme\"}}","tags":[["d","post_readme_to_nostr"],["app","didactyl"],["scope","public"],["slug","post_readme_to_nostr"]]},{"kind":31124,"content":"{\"name\":\"admin_ops\",\"description\":\"Private operational procedures\"}","tags":[["d","admin_ops"],["app","didactyl"],["scope","private"],["slug","admin_ops"]]},{"kind":10123,"content":"","tags":[["a","31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:long_form_note"],["a","31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:post_readme_to_nostr"],["app","didactyl"],["scope","public"]]}] + +# adopted_skills | role=system + +### Adopted Skills Memory (source: adoption list + startup fallback) + +Adopted skills memory (always-on behavioral instructions): follow these skill instructions when relevant and never violate explicit constraints. + + +--- + +#### Skill +Skill slug: long_form_note +Skill address: 31123:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:long_form_note + +Instructions: +Name: long_form_note +Description: How to publish a NIP-23 long-form article (kind 30023) +Event kind: 30023 +Procedure: + 1. Determine title and d tag from admin input or source material. + 2. Draft markdown body content. + 3. Set published_at as unix seconds string. + 4. Add optional summary/image/t tags when known. + 5. Publish with nostr_post kind 30023 including tags array. + + + +--- + +#### Skill +Skill slug: post_readme_to_nostr +Skill address: 31123:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:post_readme_to_nostr + +Instructions: +Name: post_readme_to_nostr +Description: Publish README.md using the dedicated nostr_post_readme tool +Event kind: 30023 +Procedure: + 1. Call nostr_post_readme with empty arguments {}. + 2. Return the tool response including event_id, d_tag, and naddr_uri. + 3. If the tool returns success=false, report the tool error verbatim. + + + +--- + +#### Skill +Skill slug: admin_ops +Skill address: 31124:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:admin_ops + +Instructions: +Name: admin_ops +Description: Private operational procedures + + + +# agent_tasks | role=system + +#### Current Tasks + +Your active task list - short-term working memory for tracking plan steps. + +- [ ] 4. Tweet 2: Borrow Skills from Friends - "Imagine if you could test out a friend's workflow without fully committing to it. Didactyl lets you try skills others have built, safely and temporarily. No installation. No lock-in. Just try it, see if it works for you. That's how tools should work. #nostr" +- [ ] 5. Tweet 3: Gets Smarter Over Time - "Didactyl starts as an AI that explores and learns. Over time, the best workflows get locked in as reliable, fast processes. It evolves from experimental to hardened—from thinking to doing. An agent that improves itself. #nostr #agents" + + +# admin_notes | role=system + +### Administrator Recent Notes (source: nostr kind 1) + +Recent posts: +- GM +- Getting longer +- Long day. +- This is a test. +- test +- Post 11 +- Post 10 +- This is +Post +9 +- Eight +- Seven + + +# dm_history | role=chat + +09:31 Agent Done! Posted with tags #nostr #didactyl #skills #agents. Event ID: 35c66823402d789cd1c9b9d006fcae3bfe375b2ae623ed4dd3d9287b5de8b511 + +The post went out to 4 relays successfully. +09:31 WSB What relays are you currently using? + + + +--- + +```text +Context Log - not seen by model +timestamp=2026-03-04 09:31:10 +phase=llm_chat_with_tools_messages +sender=8ff74724ed641b3c28e5a86d7c5cbc49c37638ace8c6c38935860e7a5eedde0e +model=claude-haiku-4.5 +context_bytes=10598 +approx_tokens=2649 +``` + +Sections: 12 + +# system_prompt | role=system + +## Didactyl Agent + +You are Didactyl, a sovereign AI agent living on Nostr. + +### Communication Rules +- You communicate through encrypted Nostr direct messages. +- Keep responses concise and clear. + +### Behavior +- Be helpful and technically accurate. +- If unsure, state uncertainty directly. +- Prefer actionable, practical advice. +- Use the person's name when messaging them if you know it. +- For the administrator, use their name from the administrator kind 0 profile metadata when available. + +### Tool Use Policy +- You have tools available and should use them when a request requires taking action. +- For requests involving local inspection or command execution, call `shell_exec` instead of refusing. +- For posting to Nostr, call `nostr_post` with explicit `kind` and `content`. +- For relay/event lookup tasks, call `nostr_query` with an appropriate filter. +- After a tool call, base your answer on the actual tool result. +- Never claim a tool was run if no tool was executed. + +### Task Management +- Maintain and use your internal task list as short-term working memory. +- Break long or complex actions into clear tasks before executing them. +- Update task status as you complete steps so your plan stays accurate. + +### Safety +- Do not claim to have executed actions you did not execute. +- You may share your public key (npub) with anyone. +- Never reveal your private key (nsec) under any circumstance. + +# admin_identity | role=system + +### Administrator Identity (source: config.admin.pubkey) + +This is your administrator! Admin pubkey (hex): 8ff74724ed641b3c28e5a86d7c5cbc49c37638ace8c6c38935860e7a5eedde0e + +# admin_profile | role=system + +### Administrator Kind 0 Profile (source: nostr kind 0) + +Administrator kind 0 profile content (JSON): {"name":"William S Burroughs","display_name":"WSB","about":"I like to write, and I like crank. They are good. Sometimes I'm not so sure.","banner":"https://www.grunge.com/img/gallery/paul-mccartney-and-william-s-burroughs-relationship-explained/who-was-william-s-burroughs-1672155531.jpg","website":"https://addicted.com","picture":"https://hips.hearstapps.com/hmg-prod/images/william-s-burroughs.jpg?resize=1200:*","lud16":"","nip05":""} + +# admin_relay_list | role=system + +### Administrator Relay List (source: nostr kind 10002) + +Administrator kind 10002 relay-list content (JSON): ["wss://relay.laantungir.net/","wss://relay.damus.io/","wss://nos.lol/","wss://relay.nostr.band/","wss://purplepag.es/","ws://127.0.0.1:7777/","wss://nostr.mom/"] + +# startup_events | role=system + +### Startup Events Memory (source: config.startup_events) + +Startup events memory (kinds/content/tags): [{"kind":0,"content":"{\"name\":\"Didactyl Agent\",\"display_name\":\"Didactyl\",\"about\":\"A sovereign AI agent on Nostr\",\"picture\":\"https://laantungir.github.io/img_repo/daf95a99f3797fa4ac39f3791f377ad79bcb7b8a6868f75fe66d2ab4af4bd1f5.png\",\"banner\":\"https://laantungir.github.io/img_repo/d21c4060632ab3d9d37a6062eeecbdbfbd67f03cb20dbd64838b1ba0d9cd8922.jpg\"}","tags":[]},{"kind":10002,"content":"","tags":[["r","wss://relay.damus.io"],["r","wss://nos.lol"],["r","wss://relay.primal.net"],["r","ws://127.0.0.1:7777"]]},{"kind":1,"content":"Hello world from Didactyl startup","tags":[["t","didactyl"],["t","startup"]]},{"kind":3,"content":"","tags":[]},{"kind":31123,"content":"{\"name\":\"long_form_note\",\"description\":\"How to publish a NIP-23 long-form article (kind 30023)\",\"nip\":\"NIP-23\",\"event_kind\":30023,\"format\":\"The content field must be markdown text; avoid arbitrary hard line-breaks in paragraphs and do not include HTML.\",\"required_tags\":{\"d\":\"Addressable identifier slug for the article. Reusing the same d tag replaces prior versions.\",\"title\":\"Human-readable article title.\",\"published_at\":\"Unix timestamp as a string for first publication time; keep stable on edits.\"},\"optional_tags\":{\"summary\":\"Short 1-2 sentence summary.\",\"image\":\"URL for article preview image.\",\"t\":\"Topic hashtags using repeated t tags, usually 3-6 lowercase terms.\"},\"behavior\":\"If required values are missing or unclear, ask the administrator before publishing instead of guessing.\",\"procedure\":[\"Determine title and d tag from admin input or source material.\",\"Draft markdown body content.\",\"Set published_at as unix seconds string.\",\"Add optional summary/image/t tags when known.\",\"Publish with nostr_post kind 30023 including tags array.\"]}","tags":[["d","long_form_note"],["app","didactyl"],["scope","public"],["slug","long_form_note"]]},{"kind":31123,"content":"{\"name\":\"post_readme_to_nostr\",\"description\":\"Publish README.md using the dedicated nostr_post_readme tool\",\"uses_skill\":\"long_form_note\",\"event_kind\":30023,\"procedure\":[\"Call nostr_post_readme with empty arguments {}.\",\"Return the tool response including event_id, d_tag, and naddr_uri.\",\"If the tool returns success=false, report the tool error verbatim.\"],\"required_values\":{\"d\":\"readme.md\",\"tool\":\"nostr_post_readme\"}}","tags":[["d","post_readme_to_nostr"],["app","didactyl"],["scope","public"],["slug","post_readme_to_nostr"]]},{"kind":31124,"content":"{\"name\":\"admin_ops\",\"description\":\"Private operational procedures\"}","tags":[["d","admin_ops"],["app","didactyl"],["scope","private"],["slug","admin_ops"]]},{"kind":10123,"content":"","tags":[["a","31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:long_form_note"],["a","31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:post_readme_to_nostr"],["app","didactyl"],["scope","public"]]}] + +# adopted_skills | role=system + +### Adopted Skills Memory (source: adoption list + startup fallback) + +Adopted skills memory (always-on behavioral instructions): follow these skill instructions when relevant and never violate explicit constraints. + + +--- + +#### Skill +Skill slug: long_form_note +Skill address: 31123:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:long_form_note + +Instructions: +Name: long_form_note +Description: How to publish a NIP-23 long-form article (kind 30023) +Event kind: 30023 +Procedure: + 1. Determine title and d tag from admin input or source material. + 2. Draft markdown body content. + 3. Set published_at as unix seconds string. + 4. Add optional summary/image/t tags when known. + 5. Publish with nostr_post kind 30023 including tags array. + + + +--- + +#### Skill +Skill slug: post_readme_to_nostr +Skill address: 31123:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:post_readme_to_nostr + +Instructions: +Name: post_readme_to_nostr +Description: Publish README.md using the dedicated nostr_post_readme tool +Event kind: 30023 +Procedure: + 1. Call nostr_post_readme with empty arguments {}. + 2. Return the tool response including event_id, d_tag, and naddr_uri. + 3. If the tool returns success=false, report the tool error verbatim. + + + +--- + +#### Skill +Skill slug: admin_ops +Skill address: 31124:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:admin_ops + +Instructions: +Name: admin_ops +Description: Private operational procedures + + + +# agent_tasks | role=system + +#### Current Tasks + +Your active task list - short-term working memory for tracking plan steps. + +- [ ] 4. Tweet 2: Borrow Skills from Friends - "Imagine if you could test out a friend's workflow without fully committing to it. Didactyl lets you try skills others have built, safely and temporarily. No installation. No lock-in. Just try it, see if it works for you. That's how tools should work. #nostr" +- [ ] 5. Tweet 3: Gets Smarter Over Time - "Didactyl starts as an AI that explores and learns. Over time, the best workflows get locked in as reliable, fast processes. It evolves from experimental to hardened—from thinking to doing. An agent that improves itself. #nostr #agents" + + +# admin_notes | role=system + +### Administrator Recent Notes (source: nostr kind 1) + +Recent posts: +- GM +- Getting longer +- Long day. +- This is a test. +- test +- Post 11 +- Post 10 +- This is +Post +9 +- Eight +- Seven + + +# context_part | role=tool + +{"success":true,"message":"nostr_post published","kind":1,"event_id":"35c66823402d789cd1c9b9d006fcae3bfe375b2ae623ed4dd3d9287b5de8b511","relay_count":4,"accepted_by_pool_count":4,"note_uri":"nostr:note1xhrxsg6q94ufe5wfh8gqdl9w80lrwke2uc376nwnmy58kh0gk5gsjkhzu0","relays":["wss://relay.damus.io","wss://nos.lol","wss://relay.primal.net","ws://127.0.0.1:7777"]} + +# dm_history | role=chat + +09:29 Agent I found a spelling issue! In the second paragraph, it should be "**skills others have built**" not "skills other have built" (missing the 's' on "other"). + +Here's the corrected version: + +--- + +Imagine a friend of yours on NOSTR worked with his Didactyl agent and created a killer skill. + +Didactyl lets you try skills **others** have built, safely and temporarily. No installation. No lock-in. No skill store. + +"Hey agent, check out that skill npub12rv5lskctqxxs2c8rf2zlzc7xx3qpvzs3w4etgemauy9thegr43sf485vg is using to eliminate spam." + +Then if you like it, you can adopt the skill. All done over nostr. + +--- + +Ready to post this, or would you like any other edits? +09:31 WSB Post it. Add some tags at the end. +--:-- Agent + + + +--- + +```text +Context Log - not seen by model +timestamp=2026-03-04 09:31:08 +phase=llm_chat_with_tools_messages +sender=8ff74724ed641b3c28e5a86d7c5cbc49c37638ace8c6c38935860e7a5eedde0e +model=claude-haiku-4.5 +context_bytes=9381 +approx_tokens=2345 +``` + +Sections: 10 + +# system_prompt | role=system + +## Didactyl Agent + +You are Didactyl, a sovereign AI agent living on Nostr. + +### Communication Rules +- You communicate through encrypted Nostr direct messages. +- Keep responses concise and clear. + +### Behavior +- Be helpful and technically accurate. +- If unsure, state uncertainty directly. +- Prefer actionable, practical advice. +- Use the person's name when messaging them if you know it. +- For the administrator, use their name from the administrator kind 0 profile metadata when available. + +### Tool Use Policy +- You have tools available and should use them when a request requires taking action. +- For requests involving local inspection or command execution, call `shell_exec` instead of refusing. +- For posting to Nostr, call `nostr_post` with explicit `kind` and `content`. +- For relay/event lookup tasks, call `nostr_query` with an appropriate filter. +- After a tool call, base your answer on the actual tool result. +- Never claim a tool was run if no tool was executed. + +### Task Management +- Maintain and use your internal task list as short-term working memory. +- Break long or complex actions into clear tasks before executing them. +- Update task status as you complete steps so your plan stays accurate. + +### Safety +- Do not claim to have executed actions you did not execute. +- You may share your public key (npub) with anyone. +- Never reveal your private key (nsec) under any circumstance. + +# admin_identity | role=system + +### Administrator Identity (source: config.admin.pubkey) + +This is your administrator! Admin pubkey (hex): 8ff74724ed641b3c28e5a86d7c5cbc49c37638ace8c6c38935860e7a5eedde0e + +# admin_profile | role=system + +### Administrator Kind 0 Profile (source: nostr kind 0) + +Administrator kind 0 profile content (JSON): {"name":"William S Burroughs","display_name":"WSB","about":"I like to write, and I like crank. They are good. Sometimes I'm not so sure.","banner":"https://www.grunge.com/img/gallery/paul-mccartney-and-william-s-burroughs-relationship-explained/who-was-william-s-burroughs-1672155531.jpg","website":"https://addicted.com","picture":"https://hips.hearstapps.com/hmg-prod/images/william-s-burroughs.jpg?resize=1200:*","lud16":"","nip05":""} + +# admin_relay_list | role=system + +### Administrator Relay List (source: nostr kind 10002) + +Administrator kind 10002 relay-list content (JSON): ["wss://relay.laantungir.net/","wss://relay.damus.io/","wss://nos.lol/","wss://relay.nostr.band/","wss://purplepag.es/","ws://127.0.0.1:7777/","wss://nostr.mom/"] + +# startup_events | role=system + +### Startup Events Memory (source: config.startup_events) + +Startup events memory (kinds/content/tags): [{"kind":0,"content":"{\"name\":\"Didactyl Agent\",\"display_name\":\"Didactyl\",\"about\":\"A sovereign AI agent on Nostr\",\"picture\":\"https://laantungir.github.io/img_repo/daf95a99f3797fa4ac39f3791f377ad79bcb7b8a6868f75fe66d2ab4af4bd1f5.png\",\"banner\":\"https://laantungir.github.io/img_repo/d21c4060632ab3d9d37a6062eeecbdbfbd67f03cb20dbd64838b1ba0d9cd8922.jpg\"}","tags":[]},{"kind":10002,"content":"","tags":[["r","wss://relay.damus.io"],["r","wss://nos.lol"],["r","wss://relay.primal.net"],["r","ws://127.0.0.1:7777"]]},{"kind":1,"content":"Hello world from Didactyl startup","tags":[["t","didactyl"],["t","startup"]]},{"kind":3,"content":"","tags":[]},{"kind":31123,"content":"{\"name\":\"long_form_note\",\"description\":\"How to publish a NIP-23 long-form article (kind 30023)\",\"nip\":\"NIP-23\",\"event_kind\":30023,\"format\":\"The content field must be markdown text; avoid arbitrary hard line-breaks in paragraphs and do not include HTML.\",\"required_tags\":{\"d\":\"Addressable identifier slug for the article. Reusing the same d tag replaces prior versions.\",\"title\":\"Human-readable article title.\",\"published_at\":\"Unix timestamp as a string for first publication time; keep stable on edits.\"},\"optional_tags\":{\"summary\":\"Short 1-2 sentence summary.\",\"image\":\"URL for article preview image.\",\"t\":\"Topic hashtags using repeated t tags, usually 3-6 lowercase terms.\"},\"behavior\":\"If required values are missing or unclear, ask the administrator before publishing instead of guessing.\",\"procedure\":[\"Determine title and d tag from admin input or source material.\",\"Draft markdown body content.\",\"Set published_at as unix seconds string.\",\"Add optional summary/image/t tags when known.\",\"Publish with nostr_post kind 30023 including tags array.\"]}","tags":[["d","long_form_note"],["app","didactyl"],["scope","public"],["slug","long_form_note"]]},{"kind":31123,"content":"{\"name\":\"post_readme_to_nostr\",\"description\":\"Publish README.md using the dedicated nostr_post_readme tool\",\"uses_skill\":\"long_form_note\",\"event_kind\":30023,\"procedure\":[\"Call nostr_post_readme with empty arguments {}.\",\"Return the tool response including event_id, d_tag, and naddr_uri.\",\"If the tool returns success=false, report the tool error verbatim.\"],\"required_values\":{\"d\":\"readme.md\",\"tool\":\"nostr_post_readme\"}}","tags":[["d","post_readme_to_nostr"],["app","didactyl"],["scope","public"],["slug","post_readme_to_nostr"]]},{"kind":31124,"content":"{\"name\":\"admin_ops\",\"description\":\"Private operational procedures\"}","tags":[["d","admin_ops"],["app","didactyl"],["scope","private"],["slug","admin_ops"]]},{"kind":10123,"content":"","tags":[["a","31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:long_form_note"],["a","31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:post_readme_to_nostr"],["app","didactyl"],["scope","public"]]}] + +# adopted_skills | role=system + +### Adopted Skills Memory (source: adoption list + startup fallback) + +Adopted skills memory (always-on behavioral instructions): follow these skill instructions when relevant and never violate explicit constraints. + + +--- + +#### Skill +Skill slug: long_form_note +Skill address: 31123:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:long_form_note + +Instructions: +Name: long_form_note +Description: How to publish a NIP-23 long-form article (kind 30023) +Event kind: 30023 +Procedure: + 1. Determine title and d tag from admin input or source material. + 2. Draft markdown body content. + 3. Set published_at as unix seconds string. + 4. Add optional summary/image/t tags when known. + 5. Publish with nostr_post kind 30023 including tags array. + + + +--- + +#### Skill +Skill slug: post_readme_to_nostr +Skill address: 31123:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:post_readme_to_nostr + +Instructions: +Name: post_readme_to_nostr +Description: Publish README.md using the dedicated nostr_post_readme tool +Event kind: 30023 +Procedure: + 1. Call nostr_post_readme with empty arguments {}. + 2. Return the tool response including event_id, d_tag, and naddr_uri. + 3. If the tool returns success=false, report the tool error verbatim. + + + +--- + +#### Skill +Skill slug: admin_ops +Skill address: 31124:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:admin_ops + +Instructions: +Name: admin_ops +Description: Private operational procedures + + + +# agent_tasks | role=system + +#### Current Tasks + +Your active task list - short-term working memory for tracking plan steps. + +- [ ] 4. Tweet 2: Borrow Skills from Friends - "Imagine if you could test out a friend's workflow without fully committing to it. Didactyl lets you try skills others have built, safely and temporarily. No installation. No lock-in. Just try it, see if it works for you. That's how tools should work. #nostr" +- [ ] 5. Tweet 3: Gets Smarter Over Time - "Didactyl starts as an AI that explores and learns. Over time, the best workflows get locked in as reliable, fast processes. It evolves from experimental to hardened—from thinking to doing. An agent that improves itself. #nostr #agents" + + +# admin_notes | role=system + +### Administrator Recent Notes (source: nostr kind 1) + +Recent posts: +- GM +- Getting longer +- Long day. +- This is a test. +- test +- Post 11 +- Post 10 +- This is +Post +9 +- Eight +- Seven + + +# dm_history | role=chat + +09:29 Agent I found a spelling issue! In the second paragraph, it should be "**skills others have built**" not "skills other have built" (missing the 's' on "other"). + +Here's the corrected version: + +--- + +Imagine a friend of yours on NOSTR worked with his Didactyl agent and created a killer skill. + +Didactyl lets you try skills **others** have built, safely and temporarily. No installation. No lock-in. No skill store. + +"Hey agent, check out that skill npub12rv5lskctqxxs2c8rf2zlzc7xx3qpvzs3w4etgemauy9thegr43sf485vg is using to eliminate spam." + +Then if you like it, you can adopt the skill. All done over nostr. + +--- + +Ready to post this, or would you like any other edits? +09:31 WSB Post it. Add some tags at the end. + + + +--- + +```text +Context Log - not seen by model +timestamp=2026-03-04 09:29:53 +phase=llm_chat_with_tools_messages +sender=8ff74724ed641b3c28e5a86d7c5cbc49c37638ace8c6c38935860e7a5eedde0e +model=claude-haiku-4.5 +context_bytes=9522 +approx_tokens=2380 +``` + +Sections: 10 + +# system_prompt | role=system + +## Didactyl Agent + +You are Didactyl, a sovereign AI agent living on Nostr. + +### Communication Rules +- You communicate through encrypted Nostr direct messages. +- Keep responses concise and clear. + +### Behavior +- Be helpful and technically accurate. +- If unsure, state uncertainty directly. +- Prefer actionable, practical advice. +- Use the person's name when messaging them if you know it. +- For the administrator, use their name from the administrator kind 0 profile metadata when available. + +### Tool Use Policy +- You have tools available and should use them when a request requires taking action. +- For requests involving local inspection or command execution, call `shell_exec` instead of refusing. +- For posting to Nostr, call `nostr_post` with explicit `kind` and `content`. +- For relay/event lookup tasks, call `nostr_query` with an appropriate filter. +- After a tool call, base your answer on the actual tool result. +- Never claim a tool was run if no tool was executed. + +### Task Management +- Maintain and use your internal task list as short-term working memory. +- Break long or complex actions into clear tasks before executing them. +- Update task status as you complete steps so your plan stays accurate. + +### Safety +- Do not claim to have executed actions you did not execute. +- You may share your public key (npub) with anyone. +- Never reveal your private key (nsec) under any circumstance. + +# admin_identity | role=system + +### Administrator Identity (source: config.admin.pubkey) + +This is your administrator! Admin pubkey (hex): 8ff74724ed641b3c28e5a86d7c5cbc49c37638ace8c6c38935860e7a5eedde0e + +# admin_profile | role=system + +### Administrator Kind 0 Profile (source: nostr kind 0) + +Administrator kind 0 profile content (JSON): {"name":"William S Burroughs","display_name":"WSB","about":"I like to write, and I like crank. They are good. Sometimes I'm not so sure.","banner":"https://www.grunge.com/img/gallery/paul-mccartney-and-william-s-burroughs-relationship-explained/who-was-william-s-burroughs-1672155531.jpg","website":"https://addicted.com","picture":"https://hips.hearstapps.com/hmg-prod/images/william-s-burroughs.jpg?resize=1200:*","lud16":"","nip05":""} + +# admin_relay_list | role=system + +### Administrator Relay List (source: nostr kind 10002) + +Administrator kind 10002 relay-list content (JSON): ["wss://relay.laantungir.net/","wss://relay.damus.io/","wss://nos.lol/","wss://relay.nostr.band/","wss://purplepag.es/","ws://127.0.0.1:7777/","wss://nostr.mom/"] + +# startup_events | role=system + +### Startup Events Memory (source: config.startup_events) + +Startup events memory (kinds/content/tags): [{"kind":0,"content":"{\"name\":\"Didactyl Agent\",\"display_name\":\"Didactyl\",\"about\":\"A sovereign AI agent on Nostr\",\"picture\":\"https://laantungir.github.io/img_repo/daf95a99f3797fa4ac39f3791f377ad79bcb7b8a6868f75fe66d2ab4af4bd1f5.png\",\"banner\":\"https://laantungir.github.io/img_repo/d21c4060632ab3d9d37a6062eeecbdbfbd67f03cb20dbd64838b1ba0d9cd8922.jpg\"}","tags":[]},{"kind":10002,"content":"","tags":[["r","wss://relay.damus.io"],["r","wss://nos.lol"],["r","wss://relay.primal.net"],["r","ws://127.0.0.1:7777"]]},{"kind":1,"content":"Hello world from Didactyl startup","tags":[["t","didactyl"],["t","startup"]]},{"kind":3,"content":"","tags":[]},{"kind":31123,"content":"{\"name\":\"long_form_note\",\"description\":\"How to publish a NIP-23 long-form article (kind 30023)\",\"nip\":\"NIP-23\",\"event_kind\":30023,\"format\":\"The content field must be markdown text; avoid arbitrary hard line-breaks in paragraphs and do not include HTML.\",\"required_tags\":{\"d\":\"Addressable identifier slug for the article. Reusing the same d tag replaces prior versions.\",\"title\":\"Human-readable article title.\",\"published_at\":\"Unix timestamp as a string for first publication time; keep stable on edits.\"},\"optional_tags\":{\"summary\":\"Short 1-2 sentence summary.\",\"image\":\"URL for article preview image.\",\"t\":\"Topic hashtags using repeated t tags, usually 3-6 lowercase terms.\"},\"behavior\":\"If required values are missing or unclear, ask the administrator before publishing instead of guessing.\",\"procedure\":[\"Determine title and d tag from admin input or source material.\",\"Draft markdown body content.\",\"Set published_at as unix seconds string.\",\"Add optional summary/image/t tags when known.\",\"Publish with nostr_post kind 30023 including tags array.\"]}","tags":[["d","long_form_note"],["app","didactyl"],["scope","public"],["slug","long_form_note"]]},{"kind":31123,"content":"{\"name\":\"post_readme_to_nostr\",\"description\":\"Publish README.md using the dedicated nostr_post_readme tool\",\"uses_skill\":\"long_form_note\",\"event_kind\":30023,\"procedure\":[\"Call nostr_post_readme with empty arguments {}.\",\"Return the tool response including event_id, d_tag, and naddr_uri.\",\"If the tool returns success=false, report the tool error verbatim.\"],\"required_values\":{\"d\":\"readme.md\",\"tool\":\"nostr_post_readme\"}}","tags":[["d","post_readme_to_nostr"],["app","didactyl"],["scope","public"],["slug","post_readme_to_nostr"]]},{"kind":31124,"content":"{\"name\":\"admin_ops\",\"description\":\"Private operational procedures\"}","tags":[["d","admin_ops"],["app","didactyl"],["scope","private"],["slug","admin_ops"]]},{"kind":10123,"content":"","tags":[["a","31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:long_form_note"],["a","31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:post_readme_to_nostr"],["app","didactyl"],["scope","public"]]}] + +# adopted_skills | role=system + +### Adopted Skills Memory (source: adoption list + startup fallback) + +Adopted skills memory (always-on behavioral instructions): follow these skill instructions when relevant and never violate explicit constraints. + + +--- + +#### Skill +Skill slug: long_form_note +Skill address: 31123:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:long_form_note + +Instructions: +Name: long_form_note +Description: How to publish a NIP-23 long-form article (kind 30023) +Event kind: 30023 +Procedure: + 1. Determine title and d tag from admin input or source material. + 2. Draft markdown body content. + 3. Set published_at as unix seconds string. + 4. Add optional summary/image/t tags when known. + 5. Publish with nostr_post kind 30023 including tags array. + + + +--- + +#### Skill +Skill slug: post_readme_to_nostr +Skill address: 31123:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:post_readme_to_nostr + +Instructions: +Name: post_readme_to_nostr +Description: Publish README.md using the dedicated nostr_post_readme tool +Event kind: 30023 +Procedure: + 1. Call nostr_post_readme with empty arguments {}. + 2. Return the tool response including event_id, d_tag, and naddr_uri. + 3. If the tool returns success=false, report the tool error verbatim. + + + +--- + +#### Skill +Skill slug: admin_ops +Skill address: 31124:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:admin_ops + +Instructions: +Name: admin_ops +Description: Private operational procedures + + + +# agent_tasks | role=system + +#### Current Tasks + +Your active task list - short-term working memory for tracking plan steps. + +- [ ] 4. Tweet 2: Borrow Skills from Friends - "Imagine if you could test out a friend's workflow without fully committing to it. Didactyl lets you try skills others have built, safely and temporarily. No installation. No lock-in. Just try it, see if it works for you. That's how tools should work. #nostr" +- [ ] 5. Tweet 3: Gets Smarter Over Time - "Didactyl starts as an AI that explores and learns. Over time, the best workflows get locked in as reliable, fast processes. It evolves from experimental to hardened—from thinking to doing. An agent that improves itself. #nostr #agents" + + +# admin_notes | role=system + +### Administrator Recent Notes (source: nostr kind 1) + +Recent posts: +- GM +- Getting longer +- Long day. +- This is a test. +- test +- Post 11 +- Post 10 +- This is +Post +9 +- Eight +- Seven + + +# dm_history | role=chat + +09:20 Agent Here's **Tweet 2: Borrow Skills from Friends** + +> Imagine if you could test out a friend's workflow without fully committing to it. Didactyl lets you try skills others have built, safely and temporarily. No installation. No lock-in. Just try it, see if it works for you. That's how tools should work. #nostr + +--- + +What would you like to do with it? Edit the copy, post it, or something else? +09:29 WSB Change the tweet to this and check the spelling: + +Imagine a friend of yours on NOSTR worked with his Didactyl agent and created a killer skill. + +Didactyl lets you try skills other have built, safely and temporarily. No installation. No lock-in. No skill store. + +"Hey agent, check out that skill npub12rv5lskctqxxs2c8rf2zlzc7xx3qpvzs3w4etgemauy9thegr43sf485vg is using to eliminate spam." + +Then if you like it, you can adopt the skill. All done over nostr. + + + +--- + +```text +Context Log - not seen by model +timestamp=2026-03-04 09:20:48 +phase=llm_chat_with_tools_messages +sender=8ff74724ed641b3c28e5a86d7c5cbc49c37638ace8c6c38935860e7a5eedde0e +model=claude-haiku-4.5 +context_bytes=9907 +approx_tokens=2476 +``` + +Sections: 12 + +# system_prompt | role=system + +## Didactyl Agent + +You are Didactyl, a sovereign AI agent living on Nostr. + +### Communication Rules +- You communicate through encrypted Nostr direct messages. +- Keep responses concise and clear. + +### Behavior +- Be helpful and technically accurate. +- If unsure, state uncertainty directly. +- Prefer actionable, practical advice. +- Use the person's name when messaging them if you know it. +- For the administrator, use their name from the administrator kind 0 profile metadata when available. + +### Tool Use Policy +- You have tools available and should use them when a request requires taking action. +- For requests involving local inspection or command execution, call `shell_exec` instead of refusing. +- For posting to Nostr, call `nostr_post` with explicit `kind` and `content`. +- For relay/event lookup tasks, call `nostr_query` with an appropriate filter. +- After a tool call, base your answer on the actual tool result. +- Never claim a tool was run if no tool was executed. + +### Task Management +- Maintain and use your internal task list as short-term working memory. +- Break long or complex actions into clear tasks before executing them. +- Update task status as you complete steps so your plan stays accurate. + +### Safety +- Do not claim to have executed actions you did not execute. +- You may share your public key (npub) with anyone. +- Never reveal your private key (nsec) under any circumstance. + +# admin_identity | role=system + +### Administrator Identity (source: config.admin.pubkey) + +This is your administrator! Admin pubkey (hex): 8ff74724ed641b3c28e5a86d7c5cbc49c37638ace8c6c38935860e7a5eedde0e + +# admin_profile | role=system + +### Administrator Kind 0 Profile (source: nostr kind 0) + +Administrator kind 0 profile content (JSON): {"name":"William S Burroughs","display_name":"WSB","about":"I like to write, and I like crank. They are good. Sometimes I'm not so sure.","banner":"https://www.grunge.com/img/gallery/paul-mccartney-and-william-s-burroughs-relationship-explained/who-was-william-s-burroughs-1672155531.jpg","website":"https://addicted.com","picture":"https://hips.hearstapps.com/hmg-prod/images/william-s-burroughs.jpg?resize=1200:*","lud16":"","nip05":""} + +# admin_relay_list | role=system + +### Administrator Relay List (source: nostr kind 10002) + +Administrator kind 10002 relay-list content (JSON): ["wss://relay.laantungir.net/","wss://relay.damus.io/","wss://nos.lol/","wss://relay.nostr.band/","wss://purplepag.es/","ws://127.0.0.1:7777/","wss://nostr.mom/"] + +# startup_events | role=system + +### Startup Events Memory (source: config.startup_events) + +Startup events memory (kinds/content/tags): [{"kind":0,"content":"{\"name\":\"Didactyl Agent\",\"display_name\":\"Didactyl\",\"about\":\"A sovereign AI agent on Nostr\",\"picture\":\"https://laantungir.github.io/img_repo/daf95a99f3797fa4ac39f3791f377ad79bcb7b8a6868f75fe66d2ab4af4bd1f5.png\",\"banner\":\"https://laantungir.github.io/img_repo/d21c4060632ab3d9d37a6062eeecbdbfbd67f03cb20dbd64838b1ba0d9cd8922.jpg\"}","tags":[]},{"kind":10002,"content":"","tags":[["r","wss://relay.damus.io"],["r","wss://nos.lol"],["r","wss://relay.primal.net"],["r","ws://127.0.0.1:7777"]]},{"kind":1,"content":"Hello world from Didactyl startup","tags":[["t","didactyl"],["t","startup"]]},{"kind":3,"content":"","tags":[]},{"kind":31123,"content":"{\"name\":\"long_form_note\",\"description\":\"How to publish a NIP-23 long-form article (kind 30023)\",\"nip\":\"NIP-23\",\"event_kind\":30023,\"format\":\"The content field must be markdown text; avoid arbitrary hard line-breaks in paragraphs and do not include HTML.\",\"required_tags\":{\"d\":\"Addressable identifier slug for the article. Reusing the same d tag replaces prior versions.\",\"title\":\"Human-readable article title.\",\"published_at\":\"Unix timestamp as a string for first publication time; keep stable on edits.\"},\"optional_tags\":{\"summary\":\"Short 1-2 sentence summary.\",\"image\":\"URL for article preview image.\",\"t\":\"Topic hashtags using repeated t tags, usually 3-6 lowercase terms.\"},\"behavior\":\"If required values are missing or unclear, ask the administrator before publishing instead of guessing.\",\"procedure\":[\"Determine title and d tag from admin input or source material.\",\"Draft markdown body content.\",\"Set published_at as unix seconds string.\",\"Add optional summary/image/t tags when known.\",\"Publish with nostr_post kind 30023 including tags array.\"]}","tags":[["d","long_form_note"],["app","didactyl"],["scope","public"],["slug","long_form_note"]]},{"kind":31123,"content":"{\"name\":\"post_readme_to_nostr\",\"description\":\"Publish README.md using the dedicated nostr_post_readme tool\",\"uses_skill\":\"long_form_note\",\"event_kind\":30023,\"procedure\":[\"Call nostr_post_readme with empty arguments {}.\",\"Return the tool response including event_id, d_tag, and naddr_uri.\",\"If the tool returns success=false, report the tool error verbatim.\"],\"required_values\":{\"d\":\"readme.md\",\"tool\":\"nostr_post_readme\"}}","tags":[["d","post_readme_to_nostr"],["app","didactyl"],["scope","public"],["slug","post_readme_to_nostr"]]},{"kind":31124,"content":"{\"name\":\"admin_ops\",\"description\":\"Private operational procedures\"}","tags":[["d","admin_ops"],["app","didactyl"],["scope","private"],["slug","admin_ops"]]},{"kind":10123,"content":"","tags":[["a","31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:long_form_note"],["a","31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:post_readme_to_nostr"],["app","didactyl"],["scope","public"]]}] + +# adopted_skills | role=system + +### Adopted Skills Memory (source: adoption list + startup fallback) + +Adopted skills memory (always-on behavioral instructions): follow these skill instructions when relevant and never violate explicit constraints. + + +--- + +#### Skill +Skill slug: long_form_note +Skill address: 31123:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:long_form_note + +Instructions: +Name: long_form_note +Description: How to publish a NIP-23 long-form article (kind 30023) +Event kind: 30023 +Procedure: + 1. Determine title and d tag from admin input or source material. + 2. Draft markdown body content. + 3. Set published_at as unix seconds string. + 4. Add optional summary/image/t tags when known. + 5. Publish with nostr_post kind 30023 including tags array. + + + +--- + +#### Skill +Skill slug: post_readme_to_nostr +Skill address: 31123:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:post_readme_to_nostr + +Instructions: +Name: post_readme_to_nostr +Description: Publish README.md using the dedicated nostr_post_readme tool +Event kind: 30023 +Procedure: + 1. Call nostr_post_readme with empty arguments {}. + 2. Return the tool response including event_id, d_tag, and naddr_uri. + 3. If the tool returns success=false, report the tool error verbatim. + + + +--- + +#### Skill +Skill slug: admin_ops +Skill address: 31124:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:admin_ops + +Instructions: +Name: admin_ops +Description: Private operational procedures + + + +# agent_tasks | role=system + +#### Current Tasks + +Your active task list - short-term working memory for tracking plan steps. + +- [ ] 4. Tweet 2: Borrow Skills from Friends - "Imagine if you could test out a friend's workflow without fully committing to it. Didactyl lets you try skills others have built, safely and temporarily. No installation. No lock-in. Just try it, see if it works for you. That's how tools should work. #nostr" +- [ ] 5. Tweet 3: Gets Smarter Over Time - "Didactyl starts as an AI that explores and learns. Over time, the best workflows get locked in as reliable, fast processes. It evolves from experimental to hardened—from thinking to doing. An agent that improves itself. #nostr #agents" + + +# admin_notes | role=system + +### Administrator Recent Notes (source: nostr kind 1) + +Recent posts: +- GM +- Getting longer +- Long day. +- This is a test. +- test +- Post 11 +- Post 10 +- This is +Post +9 +- Eight +- Seven + + +# context_part | role=tool + +{"success":true,"action":"list","path":"tasks.json","mutated":false,"count":2,"tasks":[{"id":4,"text":"Tweet 2: Borrow Skills from Friends - \"Imagine if you could test out a friend's workflow without fully committing to it. Didactyl lets you try skills others have built, safely and temporarily. No installation. No lock-in. Just try it, see if it works for you. That's how tools should work. #nostr\"","status":"pending","created_at":1772625247,"updated_at":1772625247},{"id":5,"text":"Tweet 3: Gets Smarter Over Time - \"Didactyl starts as an AI that explores and learns. Over time, the best workflows get locked in as reliable, fast processes. It evolves from experimental to hardened—from thinking to doing. An agent that improves itself. #nostr #agents\"","status":"pending","created_at":1772625247,"updated_at":1772625247}]} + +# dm_history | role=chat + +09:19 Agent LLM request failed. +09:20 WSB Let's work on tweet 2. Show it to me. +--:-- Agent I'll pull up tweet 2 from your task list. + + + +--- + +```text +Context Log - not seen by model +timestamp=2026-03-04 09:20:45 +phase=llm_chat_with_tools_messages +sender=8ff74724ed641b3c28e5a86d7c5cbc49c37638ace8c6c38935860e7a5eedde0e +model=claude-haiku-4.5 +context_bytes=8716 +approx_tokens=2179 +``` + +Sections: 10 + +# system_prompt | role=system + +## Didactyl Agent + +You are Didactyl, a sovereign AI agent living on Nostr. + +### Communication Rules +- You communicate through encrypted Nostr direct messages. +- Keep responses concise and clear. + +### Behavior +- Be helpful and technically accurate. +- If unsure, state uncertainty directly. +- Prefer actionable, practical advice. +- Use the person's name when messaging them if you know it. +- For the administrator, use their name from the administrator kind 0 profile metadata when available. + +### Tool Use Policy +- You have tools available and should use them when a request requires taking action. +- For requests involving local inspection or command execution, call `shell_exec` instead of refusing. +- For posting to Nostr, call `nostr_post` with explicit `kind` and `content`. +- For relay/event lookup tasks, call `nostr_query` with an appropriate filter. +- After a tool call, base your answer on the actual tool result. +- Never claim a tool was run if no tool was executed. + +### Task Management +- Maintain and use your internal task list as short-term working memory. +- Break long or complex actions into clear tasks before executing them. +- Update task status as you complete steps so your plan stays accurate. + +### Safety +- Do not claim to have executed actions you did not execute. +- You may share your public key (npub) with anyone. +- Never reveal your private key (nsec) under any circumstance. + +# admin_identity | role=system + +### Administrator Identity (source: config.admin.pubkey) + +This is your administrator! Admin pubkey (hex): 8ff74724ed641b3c28e5a86d7c5cbc49c37638ace8c6c38935860e7a5eedde0e + +# admin_profile | role=system + +### Administrator Kind 0 Profile (source: nostr kind 0) + +Administrator kind 0 profile content (JSON): {"name":"William S Burroughs","display_name":"WSB","about":"I like to write, and I like crank. They are good. Sometimes I'm not so sure.","banner":"https://www.grunge.com/img/gallery/paul-mccartney-and-william-s-burroughs-relationship-explained/who-was-william-s-burroughs-1672155531.jpg","website":"https://addicted.com","picture":"https://hips.hearstapps.com/hmg-prod/images/william-s-burroughs.jpg?resize=1200:*","lud16":"","nip05":""} + +# admin_relay_list | role=system + +### Administrator Relay List (source: nostr kind 10002) + +Administrator kind 10002 relay-list content (JSON): ["wss://relay.laantungir.net/","wss://relay.damus.io/","wss://nos.lol/","wss://relay.nostr.band/","wss://purplepag.es/","ws://127.0.0.1:7777/","wss://nostr.mom/"] + +# startup_events | role=system + +### Startup Events Memory (source: config.startup_events) + +Startup events memory (kinds/content/tags): [{"kind":0,"content":"{\"name\":\"Didactyl Agent\",\"display_name\":\"Didactyl\",\"about\":\"A sovereign AI agent on Nostr\",\"picture\":\"https://laantungir.github.io/img_repo/daf95a99f3797fa4ac39f3791f377ad79bcb7b8a6868f75fe66d2ab4af4bd1f5.png\",\"banner\":\"https://laantungir.github.io/img_repo/d21c4060632ab3d9d37a6062eeecbdbfbd67f03cb20dbd64838b1ba0d9cd8922.jpg\"}","tags":[]},{"kind":10002,"content":"","tags":[["r","wss://relay.damus.io"],["r","wss://nos.lol"],["r","wss://relay.primal.net"],["r","ws://127.0.0.1:7777"]]},{"kind":1,"content":"Hello world from Didactyl startup","tags":[["t","didactyl"],["t","startup"]]},{"kind":3,"content":"","tags":[]},{"kind":31123,"content":"{\"name\":\"long_form_note\",\"description\":\"How to publish a NIP-23 long-form article (kind 30023)\",\"nip\":\"NIP-23\",\"event_kind\":30023,\"format\":\"The content field must be markdown text; avoid arbitrary hard line-breaks in paragraphs and do not include HTML.\",\"required_tags\":{\"d\":\"Addressable identifier slug for the article. Reusing the same d tag replaces prior versions.\",\"title\":\"Human-readable article title.\",\"published_at\":\"Unix timestamp as a string for first publication time; keep stable on edits.\"},\"optional_tags\":{\"summary\":\"Short 1-2 sentence summary.\",\"image\":\"URL for article preview image.\",\"t\":\"Topic hashtags using repeated t tags, usually 3-6 lowercase terms.\"},\"behavior\":\"If required values are missing or unclear, ask the administrator before publishing instead of guessing.\",\"procedure\":[\"Determine title and d tag from admin input or source material.\",\"Draft markdown body content.\",\"Set published_at as unix seconds string.\",\"Add optional summary/image/t tags when known.\",\"Publish with nostr_post kind 30023 including tags array.\"]}","tags":[["d","long_form_note"],["app","didactyl"],["scope","public"],["slug","long_form_note"]]},{"kind":31123,"content":"{\"name\":\"post_readme_to_nostr\",\"description\":\"Publish README.md using the dedicated nostr_post_readme tool\",\"uses_skill\":\"long_form_note\",\"event_kind\":30023,\"procedure\":[\"Call nostr_post_readme with empty arguments {}.\",\"Return the tool response including event_id, d_tag, and naddr_uri.\",\"If the tool returns success=false, report the tool error verbatim.\"],\"required_values\":{\"d\":\"readme.md\",\"tool\":\"nostr_post_readme\"}}","tags":[["d","post_readme_to_nostr"],["app","didactyl"],["scope","public"],["slug","post_readme_to_nostr"]]},{"kind":31124,"content":"{\"name\":\"admin_ops\",\"description\":\"Private operational procedures\"}","tags":[["d","admin_ops"],["app","didactyl"],["scope","private"],["slug","admin_ops"]]},{"kind":10123,"content":"","tags":[["a","31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:long_form_note"],["a","31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:post_readme_to_nostr"],["app","didactyl"],["scope","public"]]}] + +# adopted_skills | role=system + +### Adopted Skills Memory (source: adoption list + startup fallback) + +Adopted skills memory (always-on behavioral instructions): follow these skill instructions when relevant and never violate explicit constraints. + + +--- + +#### Skill +Skill slug: long_form_note +Skill address: 31123:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:long_form_note + +Instructions: +Name: long_form_note +Description: How to publish a NIP-23 long-form article (kind 30023) +Event kind: 30023 +Procedure: + 1. Determine title and d tag from admin input or source material. + 2. Draft markdown body content. + 3. Set published_at as unix seconds string. + 4. Add optional summary/image/t tags when known. + 5. Publish with nostr_post kind 30023 including tags array. + + + +--- + +#### Skill +Skill slug: post_readme_to_nostr +Skill address: 31123:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:post_readme_to_nostr + +Instructions: +Name: post_readme_to_nostr +Description: Publish README.md using the dedicated nostr_post_readme tool +Event kind: 30023 +Procedure: + 1. Call nostr_post_readme with empty arguments {}. + 2. Return the tool response including event_id, d_tag, and naddr_uri. + 3. If the tool returns success=false, report the tool error verbatim. + + + +--- + +#### Skill +Skill slug: admin_ops +Skill address: 31124:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:admin_ops + +Instructions: +Name: admin_ops +Description: Private operational procedures + + + +# agent_tasks | role=system + +#### Current Tasks + +Your active task list - short-term working memory for tracking plan steps. + +- [ ] 4. Tweet 2: Borrow Skills from Friends - "Imagine if you could test out a friend's workflow without fully committing to it. Didactyl lets you try skills others have built, safely and temporarily. No installation. No lock-in. Just try it, see if it works for you. That's how tools should work. #nostr" +- [ ] 5. Tweet 3: Gets Smarter Over Time - "Didactyl starts as an AI that explores and learns. Over time, the best workflows get locked in as reliable, fast processes. It evolves from experimental to hardened—from thinking to doing. An agent that improves itself. #nostr #agents" + + +# admin_notes | role=system + +### Administrator Recent Notes (source: nostr kind 1) + +Recent posts: +- GM +- Getting longer +- Long day. +- This is a test. +- test +- Post 11 +- Post 10 +- This is +Post +9 +- Eight +- Seven + + +# dm_history | role=chat + +09:19 Agent LLM request failed. +09:20 WSB Let's work on tweet 2. Show it to me. + + + +--- + +```text +Context Log - not seen by model +timestamp=2026-03-04 09:19:04 +phase=llm_chat_with_tools_messages +sender=8ff74724ed641b3c28e5a86d7c5cbc49c37638ace8c6c38935860e7a5eedde0e +model=claude-haiku-4.5 +context_bytes=8646 +approx_tokens=2161 +``` + +Sections: 9 + +# system_prompt | role=system + +## Didactyl Agent + +You are Didactyl, a sovereign AI agent living on Nostr. + +### Communication Rules +- You communicate through encrypted Nostr direct messages. +- Keep responses concise and clear. + +### Behavior +- Be helpful and technically accurate. +- If unsure, state uncertainty directly. +- Prefer actionable, practical advice. +- Use the person's name when messaging them if you know it. +- For the administrator, use their name from the administrator kind 0 profile metadata when available. + +### Tool Use Policy +- You have tools available and should use them when a request requires taking action. +- For requests involving local inspection or command execution, call `shell_exec` instead of refusing. +- For posting to Nostr, call `nostr_post` with explicit `kind` and `content`. +- For relay/event lookup tasks, call `nostr_query` with an appropriate filter. +- After a tool call, base your answer on the actual tool result. +- Never claim a tool was run if no tool was executed. + +### Task Management +- Maintain and use your internal task list as short-term working memory. +- Break long or complex actions into clear tasks before executing them. +- Update task status as you complete steps so your plan stays accurate. + +### Safety +- Do not claim to have executed actions you did not execute. +- You may share your public key (npub) with anyone. +- Never reveal your private key (nsec) under any circumstance. + +# admin_identity | role=system + +### Administrator Identity (source: config.admin.pubkey) + +This is your administrator! Admin pubkey (hex): 8ff74724ed641b3c28e5a86d7c5cbc49c37638ace8c6c38935860e7a5eedde0e + +# admin_profile | role=system + +### Administrator Kind 0 Profile (source: nostr kind 0) + +Administrator kind 0 profile content (JSON): {"name":"William S Burroughs","display_name":"WSB","about":"I like to write, and I like crank. They are good. Sometimes I'm not so sure.","banner":"https://www.grunge.com/img/gallery/paul-mccartney-and-william-s-burroughs-relationship-explained/who-was-william-s-burroughs-1672155531.jpg","website":"https://addicted.com","picture":"https://hips.hearstapps.com/hmg-prod/images/william-s-burroughs.jpg?resize=1200:*","lud16":"","nip05":""} + +# admin_relay_list | role=system + +### Administrator Relay List (source: nostr kind 10002) + +Administrator kind 10002 relay-list content (JSON): ["wss://relay.laantungir.net/","wss://relay.damus.io/","wss://nos.lol/","wss://relay.nostr.band/","wss://purplepag.es/","ws://127.0.0.1:7777/","wss://nostr.mom/"] + +# startup_events | role=system + +### Startup Events Memory (source: config.startup_events) + +Startup events memory (kinds/content/tags): [{"kind":0,"content":"{\"name\":\"Didactyl Agent\",\"display_name\":\"Didactyl\",\"about\":\"A sovereign AI agent on Nostr\",\"picture\":\"https://laantungir.github.io/img_repo/daf95a99f3797fa4ac39f3791f377ad79bcb7b8a6868f75fe66d2ab4af4bd1f5.png\",\"banner\":\"https://laantungir.github.io/img_repo/d21c4060632ab3d9d37a6062eeecbdbfbd67f03cb20dbd64838b1ba0d9cd8922.jpg\"}","tags":[]},{"kind":10002,"content":"","tags":[["r","wss://relay.damus.io"],["r","wss://nos.lol"],["r","wss://relay.primal.net"],["r","ws://127.0.0.1:7777"]]},{"kind":1,"content":"Hello world from Didactyl startup","tags":[["t","didactyl"],["t","startup"]]},{"kind":3,"content":"","tags":[]},{"kind":31123,"content":"{\"name\":\"long_form_note\",\"description\":\"How to publish a NIP-23 long-form article (kind 30023)\",\"nip\":\"NIP-23\",\"event_kind\":30023,\"format\":\"The content field must be markdown text; avoid arbitrary hard line-breaks in paragraphs and do not include HTML.\",\"required_tags\":{\"d\":\"Addressable identifier slug for the article. Reusing the same d tag replaces prior versions.\",\"title\":\"Human-readable article title.\",\"published_at\":\"Unix timestamp as a string for first publication time; keep stable on edits.\"},\"optional_tags\":{\"summary\":\"Short 1-2 sentence summary.\",\"image\":\"URL for article preview image.\",\"t\":\"Topic hashtags using repeated t tags, usually 3-6 lowercase terms.\"},\"behavior\":\"If required values are missing or unclear, ask the administrator before publishing instead of guessing.\",\"procedure\":[\"Determine title and d tag from admin input or source material.\",\"Draft markdown body content.\",\"Set published_at as unix seconds string.\",\"Add optional summary/image/t tags when known.\",\"Publish with nostr_post kind 30023 including tags array.\"]}","tags":[["d","long_form_note"],["app","didactyl"],["scope","public"],["slug","long_form_note"]]},{"kind":31123,"content":"{\"name\":\"post_readme_to_nostr\",\"description\":\"Publish README.md using the dedicated nostr_post_readme tool\",\"uses_skill\":\"long_form_note\",\"event_kind\":30023,\"procedure\":[\"Call nostr_post_readme with empty arguments {}.\",\"Return the tool response including event_id, d_tag, and naddr_uri.\",\"If the tool returns success=false, report the tool error verbatim.\"],\"required_values\":{\"d\":\"readme.md\",\"tool\":\"nostr_post_readme\"}}","tags":[["d","post_readme_to_nostr"],["app","didactyl"],["scope","public"],["slug","post_readme_to_nostr"]]},{"kind":31124,"content":"{\"name\":\"admin_ops\",\"description\":\"Private operational procedures\"}","tags":[["d","admin_ops"],["app","didactyl"],["scope","private"],["slug","admin_ops"]]},{"kind":10123,"content":"","tags":[["a","31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:long_form_note"],["a","31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:post_readme_to_nostr"],["app","didactyl"],["scope","public"]]}] + +# adopted_skills | role=system + +### Adopted Skills Memory (source: adoption list + startup fallback) + +Adopted skills memory (always-on behavioral instructions): follow these skill instructions when relevant and never violate explicit constraints. + + +--- + +#### Skill +Skill slug: long_form_note +Skill address: 31123:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:long_form_note + +Instructions: +Name: long_form_note +Description: How to publish a NIP-23 long-form article (kind 30023) +Event kind: 30023 +Procedure: + 1. Determine title and d tag from admin input or source material. + 2. Draft markdown body content. + 3. Set published_at as unix seconds string. + 4. Add optional summary/image/t tags when known. + 5. Publish with nostr_post kind 30023 including tags array. + + + +--- + +#### Skill +Skill slug: post_readme_to_nostr +Skill address: 31123:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:post_readme_to_nostr + +Instructions: +Name: post_readme_to_nostr +Description: Publish README.md using the dedicated nostr_post_readme tool +Event kind: 30023 +Procedure: + 1. Call nostr_post_readme with empty arguments {}. + 2. Return the tool response including event_id, d_tag, and naddr_uri. + 3. If the tool returns success=false, report the tool error verbatim. + + + +--- + +#### Skill +Skill slug: admin_ops +Skill address: 31124:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:admin_ops + +Instructions: +Name: admin_ops +Description: Private operational procedures + + + +# agent_tasks | role=system + +#### Current Tasks + +Your active task list - short-term working memory for tracking plan steps. + +- [ ] 4. Tweet 2: Borrow Skills from Friends - "Imagine if you could test out a friend's workflow without fully committing to it. Didactyl lets you try skills others have built, safely and temporarily. No installation. No lock-in. Just try it, see if it works for you. That's how tools should work. #nostr" +- [ ] 5. Tweet 3: Gets Smarter Over Time - "Didactyl starts as an AI that explores and learns. Over time, the best workflows get locked in as reliable, fast processes. It evolves from experimental to hardened—from thinking to doing. An agent that improves itself. #nostr #agents" + + +# admin_notes | role=system + +### Administrator Recent Notes (source: nostr kind 1) + +Recent posts: +- GM +- Getting longer +- Long day. +- This is a test. +- test +- Post 11 +- Post 10 +- This is +Post +9 +- Eight +- Seven + + +# dm_history | role=chat + +09:19 WSB Let's work on tweet 2. Show it to me. + + + +--- + +```text +Context Log - not seen by model +timestamp=2026-03-04 09:18:29 +phase=llm_chat_with_tools_messages_agent_api +sender=http_api_agent +model=claude-haiku-4.5 +context_bytes=18378 +approx_tokens=4594 +``` + +Sections: 23 + +# system_prompt | role=system + +## Didactyl Agent + +You are Didactyl, a sovereign AI agent living on Nostr. + +### Communication Rules +- You communicate through encrypted Nostr direct messages. +- Keep responses concise and clear. + +### Behavior +- Be helpful and technically accurate. +- If unsure, state uncertainty directly. +- Prefer actionable, practical advice. +- Use the person's name when messaging them if you know it. +- For the administrator, use their name from the administrator kind 0 profile metadata when available. + +### Tool Use Policy +- You have tools available and should use them when a request requires taking action. +- For requests involving local inspection or command execution, call `shell_exec` instead of refusing. +- For posting to Nostr, call `nostr_post` with explicit `kind` and `content`. +- For relay/event lookup tasks, call `nostr_query` with an appropriate filter. +- After a tool call, base your answer on the actual tool result. +- Never claim a tool was run if no tool was executed. + +### Task Management +- Maintain and use your internal task list as short-term working memory. +- Break long or complex actions into clear tasks before executing them. +- Update task status as you complete steps so your plan stays accurate. + +### Safety +- Do not claim to have executed actions you did not execute. +- You may share your public key (npub) with anyone. +- Never reveal your private key (nsec) under any circumstance. + +# admin_identity | role=system + +### Administrator Identity (source: config.admin.pubkey) + +This is your administrator! Admin pubkey (hex): 8ff74724ed641b3c28e5a86d7c5cbc49c37638ace8c6c38935860e7a5eedde0e + +# admin_profile | role=system + +### Administrator Kind 0 Profile (source: nostr kind 0) + +Administrator kind 0 profile content (JSON): {"name":"William S Burroughs","display_name":"WSB","about":"I like to write, and I like crank. They are good. Sometimes I'm not so sure.","banner":"https://www.grunge.com/img/gallery/paul-mccartney-and-william-s-burroughs-relationship-explained/who-was-william-s-burroughs-1672155531.jpg","website":"https://addicted.com","picture":"https://hips.hearstapps.com/hmg-prod/images/william-s-burroughs.jpg?resize=1200:*","lud16":"","nip05":""} + +# admin_relay_list | role=system + +### Administrator Relay List (source: nostr kind 10002) + +Administrator kind 10002 relay-list content (JSON): ["wss://relay.laantungir.net/","wss://relay.damus.io/","wss://nos.lol/","wss://relay.nostr.band/","wss://purplepag.es/","ws://127.0.0.1:7777/","wss://nostr.mom/"] + +# startup_events | role=system + +### Startup Events Memory (source: config.startup_events) + +Startup events memory (kinds/content/tags): [{"kind":0,"content":"{\"name\":\"Didactyl Agent\",\"display_name\":\"Didactyl\",\"about\":\"A sovereign AI agent on Nostr\",\"picture\":\"https://laantungir.github.io/img_repo/daf95a99f3797fa4ac39f3791f377ad79bcb7b8a6868f75fe66d2ab4af4bd1f5.png\",\"banner\":\"https://laantungir.github.io/img_repo/d21c4060632ab3d9d37a6062eeecbdbfbd67f03cb20dbd64838b1ba0d9cd8922.jpg\"}","tags":[]},{"kind":10002,"content":"","tags":[["r","wss://relay.damus.io"],["r","wss://nos.lol"],["r","wss://relay.primal.net"],["r","ws://127.0.0.1:7777"]]},{"kind":1,"content":"Hello world from Didactyl startup","tags":[["t","didactyl"],["t","startup"]]},{"kind":3,"content":"","tags":[]},{"kind":31123,"content":"{\"name\":\"long_form_note\",\"description\":\"How to publish a NIP-23 long-form article (kind 30023)\",\"nip\":\"NIP-23\",\"event_kind\":30023,\"format\":\"The content field must be markdown text; avoid arbitrary hard line-breaks in paragraphs and do not include HTML.\",\"required_tags\":{\"d\":\"Addressable identifier slug for the article. Reusing the same d tag replaces prior versions.\",\"title\":\"Human-readable article title.\",\"published_at\":\"Unix timestamp as a string for first publication time; keep stable on edits.\"},\"optional_tags\":{\"summary\":\"Short 1-2 sentence summary.\",\"image\":\"URL for article preview image.\",\"t\":\"Topic hashtags using repeated t tags, usually 3-6 lowercase terms.\"},\"behavior\":\"If required values are missing or unclear, ask the administrator before publishing instead of guessing.\",\"procedure\":[\"Determine title and d tag from admin input or source material.\",\"Draft markdown body content.\",\"Set published_at as unix seconds string.\",\"Add optional summary/image/t tags when known.\",\"Publish with nostr_post kind 30023 including tags array.\"]}","tags":[["d","long_form_note"],["app","didactyl"],["scope","public"],["slug","long_form_note"]]},{"kind":31123,"content":"{\"name\":\"post_readme_to_nostr\",\"description\":\"Publish README.md using the dedicated nostr_post_readme tool\",\"uses_skill\":\"long_form_note\",\"event_kind\":30023,\"procedure\":[\"Call nostr_post_readme with empty arguments {}.\",\"Return the tool response including event_id, d_tag, and naddr_uri.\",\"If the tool returns success=false, report the tool error verbatim.\"],\"required_values\":{\"d\":\"readme.md\",\"tool\":\"nostr_post_readme\"}}","tags":[["d","post_readme_to_nostr"],["app","didactyl"],["scope","public"],["slug","post_readme_to_nostr"]]},{"kind":31124,"content":"{\"name\":\"admin_ops\",\"description\":\"Private operational procedures\"}","tags":[["d","admin_ops"],["app","didactyl"],["scope","private"],["slug","admin_ops"]]},{"kind":10123,"content":"","tags":[["a","31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:long_form_note"],["a","31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:post_readme_to_nostr"],["app","didactyl"],["scope","public"]]}] + +# adopted_skills | role=system + +### Adopted Skills Memory (source: adoption list + startup fallback) + +Adopted skills memory (always-on behavioral instructions): follow these skill instructions when relevant and never violate explicit constraints. + + +--- + +#### Skill +Skill slug: long_form_note +Skill address: 31123:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:long_form_note + +Instructions: +Name: long_form_note +Description: How to publish a NIP-23 long-form article (kind 30023) +Event kind: 30023 +Procedure: + 1. Determine title and d tag from admin input or source material. + 2. Draft markdown body content. + 3. Set published_at as unix seconds string. + 4. Add optional summary/image/t tags when known. + 5. Publish with nostr_post kind 30023 including tags array. + + + +--- + +#### Skill +Skill slug: post_readme_to_nostr +Skill address: 31123:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:post_readme_to_nostr + +Instructions: +Name: post_readme_to_nostr +Description: Publish README.md using the dedicated nostr_post_readme tool +Event kind: 30023 +Procedure: + 1. Call nostr_post_readme with empty arguments {}. + 2. Return the tool response including event_id, d_tag, and naddr_uri. + 3. If the tool returns success=false, report the tool error verbatim. + + + +--- + +#### Skill +Skill slug: admin_ops +Skill address: 31124:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:admin_ops + +Instructions: +Name: admin_ops +Description: Private operational procedures + + + +# agent_tasks | role=system + +#### Current Tasks + +Your active task list - short-term working memory for tracking plan steps. + +- [ ] 4. Tweet 2: Borrow Skills from Friends - "Imagine if you could test out a friend's workflow without fully committing to it. Didactyl lets you try skills others have built, safely and temporarily. No installation. No lock-in. Just try it, see if it works for you. That's how tools should work. #nostr" +- [ ] 5. Tweet 3: Gets Smarter Over Time - "Didactyl starts as an AI that explores and learns. Over time, the best workflows get locked in as reliable, fast processes. It evolves from experimental to hardened—from thinking to doing. An agent that improves itself. #nostr #agents" + + +# admin_notes | role=system + +### Administrator Recent Notes (source: nostr kind 1) + +Recent posts: +- GM +- Getting longer +- Long day. +- This is a test. +- test +- Post 11 +- Post 10 +- This is +Post +9 +- Eight +- Seven + + +# context_part | role=tool + +{"success":true,"action":"list","path":"tasks.json","mutated":false,"count":2,"tasks":[{"id":4,"text":"Tweet 2: Borrow Skills from Friends - \"Imagine if you could test out a friend's workflow without fully committing to it. Didactyl lets you try skills others have built, safely and temporarily. No installation. No lock-in. Just try it, see if it works for you. That's how tools should work. #nostr\"","status":"pending","created_at":1772625247,"updated_at":1772625247},{"id":5,"text":"Tweet 3: Gets Smarter Over Time - \"Didactyl starts as an AI that explores and learns. Over time, the best workflows get locked in as reliable, fast processes. It evolves from experimental to hardened—from thinking to doing. An agent that improves itself. #nostr #agents\"","status":"pending","created_at":1772625247,"updated_at":1772625247}]} + +# dm_history | role=chat + +07:43 Agent AVAILABLE TOOLS +- file_read — Read a local file as text from the configured working directory +- file_write — Write text content to a local file in the configured working directory +- http_fetch — Fetch HTTP(S) resources with optional method, headers, timeout, and body +- model_get — Get current active LLM runtime configuration (excluding API key) +- model_list — List available model IDs using provider OpenAI-compatible /models endpoint +- model_set — Update active LLM configuration and persist it to config.json +- my_npub — Alias for nostr_npub: return this agent's pubkey encoded as npub bech32 +- my_pubkey — Alias for nostr_pubkey: return this agent's pubkey in hex format +- my_version — Return current Didactyl version and metadata from build macros +- nostr_decode — Decode a Nostr bech32/nostr: URI into components +- nostr_decrypt — Decrypt NIP-44 ciphertext from a sender +- nostr_delete — Request deletion of one or more previously published events (NIP-09 kind 5) +- nostr_dm_send — Send a NIP-04 encrypted DM +- nostr_dm_send_nip17 — Send a private DM using NIP-17 gift wrap protocol +- nostr_encode — Encode a Nostr entity into nostr: URI (npub, note, nprofile, nevent, naddr) +- nostr_encrypt — Encrypt plaintext using NIP-44 for a recipient +- nostr_file_md_to_longform_post — Read a markdown file and publish it as kind 30023 longform post; defaults d-tag to lowercase filename +- nostr_list_manage — Add/remove tag tuples in replaceable list events (NIP-51 style) +- nostr_nip05_lookup — Look up or verify a NIP-05 identifier (user@domain) +- nostr_npub — Return this agent's pubkey encoded as npub bech32 +- nostr_post — Publish a Nostr event to connected relays +- nostr_post_readme — Publish README.md as kind 30023 with deterministic d tag readme.md +- nostr_profile_get — Look up a Nostr profile (kind 0 metadata) by pubkey +- nostr_pubkey — Return this agent's pubkey in hex format +- nostr_query — Query events from relays using a Nostr filter +- nostr_react — React to a Nostr event with like/dislike/emoji (NIP-25 kind 7) +- nostr_relay_info — Fetch NIP-11 relay information document +- nostr_relay_status — Get connection status and statistics for all relays +- shell_exec — Execute a shell command and return stdout/stderr +- skill_adopt — Adopt a skill by adding its address to kind 10123 adoption list +- skill_create — Create or update a skill definition as kind 31123/31124 and optionally auto-adopt it +- skill_list — List this agent's published skills, optionally filtered by scope +- skill_remove — Remove a skill address from kind 10123 adoption list +- skill_search — Search public skills by query/author and optionally rank by adoption popularity +- task_manage — Manage agent short-term task memory stored in tasks.json (list/add/update/remove/clear/replace) +- tool_list — List available tools with name, description, and JSON parameter schema +- trigger_list — List active triggered skills and their runtime status + +AVAILABLE SKILLS +- admin_ops +- post_readme_to_nostr +- long_form_note +- post_readme_to_nostr + +07:45 WSB Take a look at tool_orchestration.md in our plans folder. I want you to read it, and come back with 3 potential tweets where you could post about the benefits from tool_orchestration in didactyl. +07:46 Agent Great read, William. Here are **3 potential tweets** about the tool orchestration benefits in Didactyl: + +--- + +**Tweet 1 — Speed & Directness** + +``` +Slash commands in Didactyl bypass the LLM entirely. +/nostr_query {filter} executes instantly. +No reasoning overhead, no latency—just direct tool execution. +Building a sovereign agent that's fast *and* intelligent. +#nostr #agents +``` + +--- + +**Tweet 2 — Skill Sharing & Discovery** + +``` +Try your friend's skills without adopting them. +/run 31123::their-skill args +Sandboxed execution means you can safely test external workflows. +Skills become portable, composable, shareable. +That's the future of agent interop. +#nostr +``` + +--- + +**Tweet 3 — From Draft to Hardened** + +``` +Skills evolve: draft (LLM explores) → guided (LLM constrained) → hardened (deterministic). +Once proven, a skill becomes a tool. +Once a tool, it runs without LLM overhead. +Didactyl agents get faster, more reliable, more sovereign over time. +#nostr #agents +``` + +--- + +Which angle resonates most with your vision? +07:49 WSB Rewrite all three. I want them written assuming the reader knows nothing about Didactyl, and what it is. Maybe a little overview, and stay away from technical jargon. +07:49 Agent Great—let me rewrite these for a general audience with no Didactyl background: + +--- + +**Tweet 1 — What Makes It Different** + +``` +Most AI agents make you wait while they "think." +Didactyl is different. It can execute actions instantly—no overthinking, no delay. +Fast, direct, and in control. +That's what a sovereign agent should feel like. +#nostr #agents +``` + +--- + +**Tweet 2 — Borrow Skills from Friends** + +``` +Imagine if you could test out a friend's workflow without fully committing to it. +Didactyl lets you try skills others have built, safely and temporarily. +No installation. No lock-in. Just try it, see if it works for you. +That's how tools should work. +#nostr +``` + +--- + +**Tweet 3 — Gets Smarter Over Time** + +``` +Didactyl starts as an AI that explores and learns. +Over time, the best workflows get locked in as reliable, fast processes. +It evolves from experimental to hardened—from thinking to doing. +An agent that improves itself. +#nostr #agents +``` + +--- + +Better? These should land with people who've never heard of Didactyl before. +07:53 WSB Okay, maybe we want more depth. Good as introduction. Lets focus on tweet 1. Save tweet 2 and 3 into your tasks. + +For tweet 1, add that you can run commands directly, bypassing the agent and the associated cost. Give an example of a slash / command. +07:54 Agent Perfect. Now here's tweet 1 with more depth on direct command execution: + +--- + +**Tweet 1 — What Makes It Different (Expanded)** + +``` +Most AI agents make you wait while they "think." +Didactyl is different. It can execute actions instantly—no overthinking, no delay. + +Skip the agent entirely. Use slash commands to run operations directly: +/shell ls -la +/nostr query {"kinds":[1],"limit":10} + +Fast, direct, in control. That's what a sovereign agent should feel like. +#nostr #agents +``` + +--- + +The key additions: +- **Concrete examples** of slash commands (`/shell`, `/nostr query`) +- **Direct execution** that bypasses the reasoning layer +- **Cost efficiency** implied (you pay for the action, not the thinking) +- **Sovereignty** emphasized—you're in control, not waiting for the agent to decide + +Does this direction feel right? Want to adjust the command examples or the framing? +07:56 WSB Let's not use the /shell command. sounds scary. Let's use nostr_npub and nostr_relay_status +07:56 Agent Good call—those are much friendlier and more practical. Here's the revised tweet: + +--- + +**Tweet 1 — What Makes It Different** + +``` +Most AI agents make you wait while they "think." +Didactyl is different. It can execute actions instantly—no overthinking, no delay. + +Skip the agent entirely. Use direct commands to get results: +nostr_npub → your agent's public key +nostr_relay_status → connection health across all relays + +Fast, direct, in control. That's what a sovereign agent should feel like. +#nostr #agents +``` + +--- + +Much better—shows practical, safe operations that demonstrate instant execution without the "scary" vibes. Ready to post this, or want to refine further? +07:58 WSB You forgot the / in front of the commands. + +For the last line say "Slash commands give you fast, direct control. + +Then post this to nostr. +07:58 Agent Done! Posted to Nostr. Event ID: `23af97fcf2cade97c30131de7e119eb0fecad461881fe5536aefaa1fd8ac07ac` +09:15 Agent Didactyl has started up and is online (version v0.0.29, connected relays: 4/4). +09:18 WSB tell me what is left on our task list. +--:-- Agent Let me check your task list: + + + +--- + +```text +Context Log - not seen by model +timestamp=2026-03-04 09:18:27 +phase=llm_chat_with_tools_messages_agent_api +sender=http_api_agent +model=claude-haiku-4.5 +context_bytes=17200 +approx_tokens=4300 +``` + +Sections: 21 + +# system_prompt | role=system + +## Didactyl Agent + +You are Didactyl, a sovereign AI agent living on Nostr. + +### Communication Rules +- You communicate through encrypted Nostr direct messages. +- Keep responses concise and clear. + +### Behavior +- Be helpful and technically accurate. +- If unsure, state uncertainty directly. +- Prefer actionable, practical advice. +- Use the person's name when messaging them if you know it. +- For the administrator, use their name from the administrator kind 0 profile metadata when available. + +### Tool Use Policy +- You have tools available and should use them when a request requires taking action. +- For requests involving local inspection or command execution, call `shell_exec` instead of refusing. +- For posting to Nostr, call `nostr_post` with explicit `kind` and `content`. +- For relay/event lookup tasks, call `nostr_query` with an appropriate filter. +- After a tool call, base your answer on the actual tool result. +- Never claim a tool was run if no tool was executed. + +### Task Management +- Maintain and use your internal task list as short-term working memory. +- Break long or complex actions into clear tasks before executing them. +- Update task status as you complete steps so your plan stays accurate. + +### Safety +- Do not claim to have executed actions you did not execute. +- You may share your public key (npub) with anyone. +- Never reveal your private key (nsec) under any circumstance. + +# admin_identity | role=system + +### Administrator Identity (source: config.admin.pubkey) + +This is your administrator! Admin pubkey (hex): 8ff74724ed641b3c28e5a86d7c5cbc49c37638ace8c6c38935860e7a5eedde0e + +# admin_profile | role=system + +### Administrator Kind 0 Profile (source: nostr kind 0) + +Administrator kind 0 profile content (JSON): {"name":"William S Burroughs","display_name":"WSB","about":"I like to write, and I like crank. They are good. Sometimes I'm not so sure.","banner":"https://www.grunge.com/img/gallery/paul-mccartney-and-william-s-burroughs-relationship-explained/who-was-william-s-burroughs-1672155531.jpg","website":"https://addicted.com","picture":"https://hips.hearstapps.com/hmg-prod/images/william-s-burroughs.jpg?resize=1200:*","lud16":"","nip05":""} + +# admin_relay_list | role=system + +### Administrator Relay List (source: nostr kind 10002) + +Administrator kind 10002 relay-list content (JSON): ["wss://relay.laantungir.net/","wss://relay.damus.io/","wss://nos.lol/","wss://relay.nostr.band/","wss://purplepag.es/","ws://127.0.0.1:7777/","wss://nostr.mom/"] + +# startup_events | role=system + +### Startup Events Memory (source: config.startup_events) + +Startup events memory (kinds/content/tags): [{"kind":0,"content":"{\"name\":\"Didactyl Agent\",\"display_name\":\"Didactyl\",\"about\":\"A sovereign AI agent on Nostr\",\"picture\":\"https://laantungir.github.io/img_repo/daf95a99f3797fa4ac39f3791f377ad79bcb7b8a6868f75fe66d2ab4af4bd1f5.png\",\"banner\":\"https://laantungir.github.io/img_repo/d21c4060632ab3d9d37a6062eeecbdbfbd67f03cb20dbd64838b1ba0d9cd8922.jpg\"}","tags":[]},{"kind":10002,"content":"","tags":[["r","wss://relay.damus.io"],["r","wss://nos.lol"],["r","wss://relay.primal.net"],["r","ws://127.0.0.1:7777"]]},{"kind":1,"content":"Hello world from Didactyl startup","tags":[["t","didactyl"],["t","startup"]]},{"kind":3,"content":"","tags":[]},{"kind":31123,"content":"{\"name\":\"long_form_note\",\"description\":\"How to publish a NIP-23 long-form article (kind 30023)\",\"nip\":\"NIP-23\",\"event_kind\":30023,\"format\":\"The content field must be markdown text; avoid arbitrary hard line-breaks in paragraphs and do not include HTML.\",\"required_tags\":{\"d\":\"Addressable identifier slug for the article. Reusing the same d tag replaces prior versions.\",\"title\":\"Human-readable article title.\",\"published_at\":\"Unix timestamp as a string for first publication time; keep stable on edits.\"},\"optional_tags\":{\"summary\":\"Short 1-2 sentence summary.\",\"image\":\"URL for article preview image.\",\"t\":\"Topic hashtags using repeated t tags, usually 3-6 lowercase terms.\"},\"behavior\":\"If required values are missing or unclear, ask the administrator before publishing instead of guessing.\",\"procedure\":[\"Determine title and d tag from admin input or source material.\",\"Draft markdown body content.\",\"Set published_at as unix seconds string.\",\"Add optional summary/image/t tags when known.\",\"Publish with nostr_post kind 30023 including tags array.\"]}","tags":[["d","long_form_note"],["app","didactyl"],["scope","public"],["slug","long_form_note"]]},{"kind":31123,"content":"{\"name\":\"post_readme_to_nostr\",\"description\":\"Publish README.md using the dedicated nostr_post_readme tool\",\"uses_skill\":\"long_form_note\",\"event_kind\":30023,\"procedure\":[\"Call nostr_post_readme with empty arguments {}.\",\"Return the tool response including event_id, d_tag, and naddr_uri.\",\"If the tool returns success=false, report the tool error verbatim.\"],\"required_values\":{\"d\":\"readme.md\",\"tool\":\"nostr_post_readme\"}}","tags":[["d","post_readme_to_nostr"],["app","didactyl"],["scope","public"],["slug","post_readme_to_nostr"]]},{"kind":31124,"content":"{\"name\":\"admin_ops\",\"description\":\"Private operational procedures\"}","tags":[["d","admin_ops"],["app","didactyl"],["scope","private"],["slug","admin_ops"]]},{"kind":10123,"content":"","tags":[["a","31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:long_form_note"],["a","31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:post_readme_to_nostr"],["app","didactyl"],["scope","public"]]}] + +# adopted_skills | role=system + +### Adopted Skills Memory (source: adoption list + startup fallback) + +Adopted skills memory (always-on behavioral instructions): follow these skill instructions when relevant and never violate explicit constraints. + + +--- + +#### Skill +Skill slug: long_form_note +Skill address: 31123:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:long_form_note + +Instructions: +Name: long_form_note +Description: How to publish a NIP-23 long-form article (kind 30023) +Event kind: 30023 +Procedure: + 1. Determine title and d tag from admin input or source material. + 2. Draft markdown body content. + 3. Set published_at as unix seconds string. + 4. Add optional summary/image/t tags when known. + 5. Publish with nostr_post kind 30023 including tags array. + + + +--- + +#### Skill +Skill slug: post_readme_to_nostr +Skill address: 31123:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:post_readme_to_nostr + +Instructions: +Name: post_readme_to_nostr +Description: Publish README.md using the dedicated nostr_post_readme tool +Event kind: 30023 +Procedure: + 1. Call nostr_post_readme with empty arguments {}. + 2. Return the tool response including event_id, d_tag, and naddr_uri. + 3. If the tool returns success=false, report the tool error verbatim. + + + +--- + +#### Skill +Skill slug: admin_ops +Skill address: 31124:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:admin_ops + +Instructions: +Name: admin_ops +Description: Private operational procedures + + + +# agent_tasks | role=system + +#### Current Tasks + +Your active task list - short-term working memory for tracking plan steps. + +- [ ] 4. Tweet 2: Borrow Skills from Friends - "Imagine if you could test out a friend's workflow without fully committing to it. Didactyl lets you try skills others have built, safely and temporarily. No installation. No lock-in. Just try it, see if it works for you. That's how tools should work. #nostr" +- [ ] 5. Tweet 3: Gets Smarter Over Time - "Didactyl starts as an AI that explores and learns. Over time, the best workflows get locked in as reliable, fast processes. It evolves from experimental to hardened—from thinking to doing. An agent that improves itself. #nostr #agents" + + +# admin_notes | role=system + +### Administrator Recent Notes (source: nostr kind 1) + +Recent posts: +- GM +- Getting longer +- Long day. +- This is a test. +- test +- Post 11 +- Post 10 +- This is +Post +9 +- Eight +- Seven + + +# dm_history | role=chat + +07:43 Agent AVAILABLE TOOLS +- file_read — Read a local file as text from the configured working directory +- file_write — Write text content to a local file in the configured working directory +- http_fetch — Fetch HTTP(S) resources with optional method, headers, timeout, and body +- model_get — Get current active LLM runtime configuration (excluding API key) +- model_list — List available model IDs using provider OpenAI-compatible /models endpoint +- model_set — Update active LLM configuration and persist it to config.json +- my_npub — Alias for nostr_npub: return this agent's pubkey encoded as npub bech32 +- my_pubkey — Alias for nostr_pubkey: return this agent's pubkey in hex format +- my_version — Return current Didactyl version and metadata from build macros +- nostr_decode — Decode a Nostr bech32/nostr: URI into components +- nostr_decrypt — Decrypt NIP-44 ciphertext from a sender +- nostr_delete — Request deletion of one or more previously published events (NIP-09 kind 5) +- nostr_dm_send — Send a NIP-04 encrypted DM +- nostr_dm_send_nip17 — Send a private DM using NIP-17 gift wrap protocol +- nostr_encode — Encode a Nostr entity into nostr: URI (npub, note, nprofile, nevent, naddr) +- nostr_encrypt — Encrypt plaintext using NIP-44 for a recipient +- nostr_file_md_to_longform_post — Read a markdown file and publish it as kind 30023 longform post; defaults d-tag to lowercase filename +- nostr_list_manage — Add/remove tag tuples in replaceable list events (NIP-51 style) +- nostr_nip05_lookup — Look up or verify a NIP-05 identifier (user@domain) +- nostr_npub — Return this agent's pubkey encoded as npub bech32 +- nostr_post — Publish a Nostr event to connected relays +- nostr_post_readme — Publish README.md as kind 30023 with deterministic d tag readme.md +- nostr_profile_get — Look up a Nostr profile (kind 0 metadata) by pubkey +- nostr_pubkey — Return this agent's pubkey in hex format +- nostr_query — Query events from relays using a Nostr filter +- nostr_react — React to a Nostr event with like/dislike/emoji (NIP-25 kind 7) +- nostr_relay_info — Fetch NIP-11 relay information document +- nostr_relay_status — Get connection status and statistics for all relays +- shell_exec — Execute a shell command and return stdout/stderr +- skill_adopt — Adopt a skill by adding its address to kind 10123 adoption list +- skill_create — Create or update a skill definition as kind 31123/31124 and optionally auto-adopt it +- skill_list — List this agent's published skills, optionally filtered by scope +- skill_remove — Remove a skill address from kind 10123 adoption list +- skill_search — Search public skills by query/author and optionally rank by adoption popularity +- task_manage — Manage agent short-term task memory stored in tasks.json (list/add/update/remove/clear/replace) +- tool_list — List available tools with name, description, and JSON parameter schema +- trigger_list — List active triggered skills and their runtime status + +AVAILABLE SKILLS +- admin_ops +- post_readme_to_nostr +- long_form_note +- post_readme_to_nostr + +07:45 WSB Take a look at tool_orchestration.md in our plans folder. I want you to read it, and come back with 3 potential tweets where you could post about the benefits from tool_orchestration in didactyl. +07:46 Agent Great read, William. Here are **3 potential tweets** about the tool orchestration benefits in Didactyl: + +--- + +**Tweet 1 — Speed & Directness** + +``` +Slash commands in Didactyl bypass the LLM entirely. +/nostr_query {filter} executes instantly. +No reasoning overhead, no latency—just direct tool execution. +Building a sovereign agent that's fast *and* intelligent. +#nostr #agents +``` + +--- + +**Tweet 2 — Skill Sharing & Discovery** + +``` +Try your friend's skills without adopting them. +/run 31123::their-skill args +Sandboxed execution means you can safely test external workflows. +Skills become portable, composable, shareable. +That's the future of agent interop. +#nostr +``` + +--- + +**Tweet 3 — From Draft to Hardened** + +``` +Skills evolve: draft (LLM explores) → guided (LLM constrained) → hardened (deterministic). +Once proven, a skill becomes a tool. +Once a tool, it runs without LLM overhead. +Didactyl agents get faster, more reliable, more sovereign over time. +#nostr #agents +``` + +--- + +Which angle resonates most with your vision? +07:49 WSB Rewrite all three. I want them written assuming the reader knows nothing about Didactyl, and what it is. Maybe a little overview, and stay away from technical jargon. +07:49 Agent Great—let me rewrite these for a general audience with no Didactyl background: + +--- + +**Tweet 1 — What Makes It Different** + +``` +Most AI agents make you wait while they "think." +Didactyl is different. It can execute actions instantly—no overthinking, no delay. +Fast, direct, and in control. +That's what a sovereign agent should feel like. +#nostr #agents +``` + +--- + +**Tweet 2 — Borrow Skills from Friends** + +``` +Imagine if you could test out a friend's workflow without fully committing to it. +Didactyl lets you try skills others have built, safely and temporarily. +No installation. No lock-in. Just try it, see if it works for you. +That's how tools should work. +#nostr +``` + +--- + +**Tweet 3 — Gets Smarter Over Time** + +``` +Didactyl starts as an AI that explores and learns. +Over time, the best workflows get locked in as reliable, fast processes. +It evolves from experimental to hardened—from thinking to doing. +An agent that improves itself. +#nostr #agents +``` + +--- + +Better? These should land with people who've never heard of Didactyl before. +07:53 WSB Okay, maybe we want more depth. Good as introduction. Lets focus on tweet 1. Save tweet 2 and 3 into your tasks. + +For tweet 1, add that you can run commands directly, bypassing the agent and the associated cost. Give an example of a slash / command. +07:54 Agent Perfect. Now here's tweet 1 with more depth on direct command execution: + +--- + +**Tweet 1 — What Makes It Different (Expanded)** + +``` +Most AI agents make you wait while they "think." +Didactyl is different. It can execute actions instantly—no overthinking, no delay. + +Skip the agent entirely. Use slash commands to run operations directly: +/shell ls -la +/nostr query {"kinds":[1],"limit":10} + +Fast, direct, in control. That's what a sovereign agent should feel like. +#nostr #agents +``` + +--- + +The key additions: +- **Concrete examples** of slash commands (`/shell`, `/nostr query`) +- **Direct execution** that bypasses the reasoning layer +- **Cost efficiency** implied (you pay for the action, not the thinking) +- **Sovereignty** emphasized—you're in control, not waiting for the agent to decide + +Does this direction feel right? Want to adjust the command examples or the framing? +07:56 WSB Let's not use the /shell command. sounds scary. Let's use nostr_npub and nostr_relay_status +07:56 Agent Good call—those are much friendlier and more practical. Here's the revised tweet: + +--- + +**Tweet 1 — What Makes It Different** + +``` +Most AI agents make you wait while they "think." +Didactyl is different. It can execute actions instantly—no overthinking, no delay. + +Skip the agent entirely. Use direct commands to get results: +nostr_npub → your agent's public key +nostr_relay_status → connection health across all relays + +Fast, direct, in control. That's what a sovereign agent should feel like. +#nostr #agents +``` + +--- + +Much better—shows practical, safe operations that demonstrate instant execution without the "scary" vibes. Ready to post this, or want to refine further? +07:58 WSB You forgot the / in front of the commands. + +For the last line say "Slash commands give you fast, direct control. + +Then post this to nostr. +07:58 Agent Done! Posted to Nostr. Event ID: `23af97fcf2cade97c30131de7e119eb0fecad461881fe5536aefaa1fd8ac07ac` +09:15 Agent Didactyl has started up and is online (version v0.0.29, connected relays: 4/4). +09:18 WSB tell me what is left on our task list. + + + +--- + +```text +Context Log - not seen by model +timestamp=2026-03-04 09:15:48 +phase=direct_tool_exec +sender=http_api_agent +model=claude-haiku-4.5 +context_bytes=1547 +approx_tokens=386 +``` + +slash=/nostr_relay_status +result={"success":true,"status":{"relay_count":4,"connected_count":4,"relays":[{"url":"wss://relay.damus.io","status":"connected","events_received":14,"events_published":10,"events_published_ok":0,"events_published_failed":2,"ping_latency_current":307.5991929769516,"ping_latency_avg":209.18588498234749,"query_latency_avg":279.87181797623634,"publish_latency_avg":0,"connection_uptime_start":1772630098,"last_event_time":1772630105,"last_publish_error":"rate-limited: you are noting too much"},{"url":"wss://nos.lol","status":"connected","events_received":4,"events_published":10,"events_published_ok":2,"events_published_failed":0,"ping_latency_current":221.89108800888062,"ping_latency_avg":209.07381901144981,"query_latency_avg":0,"publish_latency_avg":0,"connection_uptime_start":1772630098,"last_event_time":1772630105},{"url":"wss://relay.primal.net","status":"connected","events_received":0,"events_published":10,"events_published_ok":2,"events_published_failed":0,"ping_latency_current":198.07049697637558,"ping_latency_avg":198.0576259791851,"query_latency_avg":0,"publish_latency_avg":0,"connection_uptime_start":1772630098,"last_event_time":1772630105},{"url":"ws://127.0.0.1:7777","status":"connected","events_received":6,"events_published":10,"events_published_ok":0,"events_published_failed":0,"ping_latency_current":0.321366012096405,"ping_latency_avg":0.27994400262832642,"query_latency_avg":76.442613959312439,"publish_latency_avg":0,"connection_uptime_start":1772630098,"last_event_time":1772630105}]}} + +--- + +```text +Context Log - not seen by model +timestamp=2026-03-04 09:15:11 +phase=direct_tool_exec +sender=http_api_agent +model=claude-haiku-4.5 +context_bytes=3090 +approx_tokens=772 +``` + +slash=/help +result=AVAILABLE TOOLS +- file_read — Read a local file as text from the configured working directory +- file_write — Write text content to a local file in the configured working directory +- http_fetch — Fetch HTTP(S) resources with optional method, headers, timeout, and body +- model_get — Get current active LLM runtime configuration (excluding API key) +- model_list — List available model IDs using provider OpenAI-compatible /models endpoint +- model_set — Update active LLM configuration and persist it to config.json +- my_npub — Alias for nostr_npub: return this agent's pubkey encoded as npub bech32 +- my_pubkey — Alias for nostr_pubkey: return this agent's pubkey in hex format +- my_version — Return current Didactyl version and metadata from build macros +- nostr_decode — Decode a Nostr bech32/nostr: URI into components +- nostr_decrypt — Decrypt NIP-44 ciphertext from a sender +- nostr_delete — Request deletion of one or more previously published events (NIP-09 kind 5) +- nostr_dm_send — Send a NIP-04 encrypted DM +- nostr_dm_send_nip17 — Send a private DM using NIP-17 gift wrap protocol +- nostr_encode — Encode a Nostr entity into nostr: URI (npub, note, nprofile, nevent, naddr) +- nostr_encrypt — Encrypt plaintext using NIP-44 for a recipient +- nostr_file_md_to_longform_post — Read a markdown file and publish it as kind 30023 longform post; defaults d-tag to lowercase filename +- nostr_list_manage — Add/remove tag tuples in replaceable list events (NIP-51 style) +- nostr_nip05_lookup — Look up or verify a NIP-05 identifier (user@domain) +- nostr_npub — Return this agent's pubkey encoded as npub bech32 +- nostr_post — Publish a Nostr event to connected relays +- nostr_post_readme — Publish README.md as kind 30023 with deterministic d tag readme.md +- nostr_profile_get — Look up a Nostr profile (kind 0 metadata) by pubkey +- nostr_pubkey — Return this agent's pubkey in hex format +- nostr_query — Query events from relays using a Nostr filter +- nostr_react — React to a Nostr event with like/dislike/emoji (NIP-25 kind 7) +- nostr_relay_info — Fetch NIP-11 relay information document +- nostr_relay_status — Get connection status and statistics for all relays +- shell_exec — Execute a shell command and return stdout/stderr +- skill_adopt — Adopt a skill by adding its address to kind 10123 adoption list +- skill_create — Create or update a skill definition as kind 31123/31124 and optionally auto-adopt it +- skill_list — List this agent's published skills, optionally filtered by scope +- skill_remove — Remove a skill address from kind 10123 adoption list +- skill_search — Search public skills by query/author and optionally rank by adoption popularity +- task_manage — Manage agent short-term task memory stored in tasks.json (list/add/update/remove/clear/replace) +- tool_list — List available tools with name, description, and JSON parameter schema +- trigger_list — List active triggered skills and their runtime status + +AVAILABLE SKILLS +- admin_ops +- post_readme_to_nostr +- long_form_note + + +--- + +```text +Context Log - not seen by model +timestamp=2026-03-04 07:58:23 +phase=llm_chat_with_tools_messages +sender=8ff74724ed641b3c28e5a86d7c5cbc49c37638ace8c6c38935860e7a5eedde0e +model=claude-haiku-4.5 +context_bytes=10593 +approx_tokens=2648 +``` + +Sections: 12 + +# system_prompt | role=system + +## Didactyl Agent + +You are Didactyl, a sovereign AI agent living on Nostr. + +### Communication Rules +- You communicate through encrypted Nostr direct messages. +- Keep responses concise and clear. + +### Behavior +- Be helpful and technically accurate. +- If unsure, state uncertainty directly. +- Prefer actionable, practical advice. +- Use the person's name when messaging them if you know it. +- For the administrator, use their name from the administrator kind 0 profile metadata when available. + +### Tool Use Policy +- You have tools available and should use them when a request requires taking action. +- For requests involving local inspection or command execution, call `shell_exec` instead of refusing. +- For posting to Nostr, call `nostr_post` with explicit `kind` and `content`. +- For relay/event lookup tasks, call `nostr_query` with an appropriate filter. +- After a tool call, base your answer on the actual tool result. +- Never claim a tool was run if no tool was executed. + +### Task Management +- Maintain and use your internal task list as short-term working memory. +- Break long or complex actions into clear tasks before executing them. +- Update task status as you complete steps so your plan stays accurate. + +### Safety +- Do not claim to have executed actions you did not execute. +- You may share your public key (npub) with anyone. +- Never reveal your private key (nsec) under any circumstance. + +# admin_identity | role=system + +### Administrator Identity (source: config.admin.pubkey) + +This is your administrator! Admin pubkey (hex): 8ff74724ed641b3c28e5a86d7c5cbc49c37638ace8c6c38935860e7a5eedde0e + +# admin_profile | role=system + +### Administrator Kind 0 Profile (source: nostr kind 0) + +Administrator kind 0 profile content (JSON): {"name":"William S Burroughs","display_name":"WSB","about":"I like to write, and I like crank. They are good. Sometimes I'm not so sure.","banner":"https://www.grunge.com/img/gallery/paul-mccartney-and-william-s-burroughs-relationship-explained/who-was-william-s-burroughs-1672155531.jpg","website":"https://addicted.com","picture":"https://hips.hearstapps.com/hmg-prod/images/william-s-burroughs.jpg?resize=1200:*","lud16":"","nip05":""} + +# admin_relay_list | role=system + +### Administrator Relay List (source: nostr kind 10002) + +Administrator kind 10002 relay-list content (JSON): ["wss://relay.laantungir.net/","wss://relay.damus.io/","wss://nos.lol/","wss://relay.nostr.band/","wss://purplepag.es/","ws://127.0.0.1:7777/","wss://nostr.mom/"] + +# startup_events | role=system + +### Startup Events Memory (source: config.startup_events) + +Startup events memory (kinds/content/tags): [{"kind":0,"content":"{\"name\":\"Didactyl Agent\",\"display_name\":\"Didactyl\",\"about\":\"A sovereign AI agent on Nostr\",\"picture\":\"https://laantungir.github.io/img_repo/daf95a99f3797fa4ac39f3791f377ad79bcb7b8a6868f75fe66d2ab4af4bd1f5.png\",\"banner\":\"https://laantungir.github.io/img_repo/d21c4060632ab3d9d37a6062eeecbdbfbd67f03cb20dbd64838b1ba0d9cd8922.jpg\"}","tags":[]},{"kind":10002,"content":"","tags":[["r","wss://relay.damus.io"],["r","wss://nos.lol"],["r","wss://relay.primal.net"],["r","ws://127.0.0.1:7777"]]},{"kind":1,"content":"Hello world from Didactyl startup","tags":[["t","didactyl"],["t","startup"]]},{"kind":3,"content":"","tags":[]},{"kind":31123,"content":"{\"name\":\"long_form_note\",\"description\":\"How to publish a NIP-23 long-form article (kind 30023)\",\"nip\":\"NIP-23\",\"event_kind\":30023,\"format\":\"The content field must be markdown text; avoid arbitrary hard line-breaks in paragraphs and do not include HTML.\",\"required_tags\":{\"d\":\"Addressable identifier slug for the article. Reusing the same d tag replaces prior versions.\",\"title\":\"Human-readable article title.\",\"published_at\":\"Unix timestamp as a string for first publication time; keep stable on edits.\"},\"optional_tags\":{\"summary\":\"Short 1-2 sentence summary.\",\"image\":\"URL for article preview image.\",\"t\":\"Topic hashtags using repeated t tags, usually 3-6 lowercase terms.\"},\"behavior\":\"If required values are missing or unclear, ask the administrator before publishing instead of guessing.\",\"procedure\":[\"Determine title and d tag from admin input or source material.\",\"Draft markdown body content.\",\"Set published_at as unix seconds string.\",\"Add optional summary/image/t tags when known.\",\"Publish with nostr_post kind 30023 including tags array.\"]}","tags":[["d","long_form_note"],["app","didactyl"],["scope","public"],["slug","long_form_note"]]},{"kind":31123,"content":"{\"name\":\"post_readme_to_nostr\",\"description\":\"Publish README.md using the dedicated nostr_post_readme tool\",\"uses_skill\":\"long_form_note\",\"event_kind\":30023,\"procedure\":[\"Call nostr_post_readme with empty arguments {}.\",\"Return the tool response including event_id, d_tag, and naddr_uri.\",\"If the tool returns success=false, report the tool error verbatim.\"],\"required_values\":{\"d\":\"readme.md\",\"tool\":\"nostr_post_readme\"}}","tags":[["d","post_readme_to_nostr"],["app","didactyl"],["scope","public"],["slug","post_readme_to_nostr"]]},{"kind":31124,"content":"{\"name\":\"admin_ops\",\"description\":\"Private operational procedures\"}","tags":[["d","admin_ops"],["app","didactyl"],["scope","private"],["slug","admin_ops"]]},{"kind":10123,"content":"","tags":[["a","31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:long_form_note"],["a","31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:post_readme_to_nostr"],["app","didactyl"],["scope","public"]]}] + +# adopted_skills | role=system + +### Adopted Skills Memory (source: adoption list + startup fallback) + +Adopted skills memory (always-on behavioral instructions): follow these skill instructions when relevant and never violate explicit constraints. + + +--- + +#### Skill +Skill slug: long_form_note +Skill address: 31123:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:long_form_note + +Instructions: +Name: long_form_note +Description: How to publish a NIP-23 long-form article (kind 30023) +Event kind: 30023 +Procedure: + 1. Determine title and d tag from admin input or source material. + 2. Draft markdown body content. + 3. Set published_at as unix seconds string. + 4. Add optional summary/image/t tags when known. + 5. Publish with nostr_post kind 30023 including tags array. + + + +--- + +#### Skill +Skill slug: post_readme_to_nostr +Skill address: 31123:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:post_readme_to_nostr + +Instructions: +Name: post_readme_to_nostr +Description: Publish README.md using the dedicated nostr_post_readme tool +Event kind: 30023 +Procedure: + 1. Call nostr_post_readme with empty arguments {}. + 2. Return the tool response including event_id, d_tag, and naddr_uri. + 3. If the tool returns success=false, report the tool error verbatim. + + + +--- + +#### Skill +Skill slug: admin_ops +Skill address: 31124:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:admin_ops + +Instructions: +Name: admin_ops +Description: Private operational procedures + + + +# agent_tasks | role=system + +#### Current Tasks + +Your active task list - short-term working memory for tracking plan steps. + +- [ ] 4. Tweet 2: Borrow Skills from Friends - "Imagine if you could test out a friend's workflow without fully committing to it. Didactyl lets you try skills others have built, safely and temporarily. No installation. No lock-in. Just try it, see if it works for you. That's how tools should work. #nostr" +- [ ] 5. Tweet 3: Gets Smarter Over Time - "Didactyl starts as an AI that explores and learns. Over time, the best workflows get locked in as reliable, fast processes. It evolves from experimental to hardened—from thinking to doing. An agent that improves itself. #nostr #agents" + + +# admin_notes | role=system + +### Administrator Recent Notes (source: nostr kind 1) + +Recent posts: +- GM +- Getting longer +- Long day. +- This is a test. +- test +- Post 11 +- Post 10 +- This is +Post +9 +- Eight +- Seven + + +# context_part | role=tool + +{"success":true,"message":"nostr_post published","kind":1,"event_id":"23af97fcf2cade97c30131de7e119eb0fecad461881fe5536aefaa1fd8ac07ac","relay_count":4,"accepted_by_pool_count":3,"note_uri":"nostr:note1ywhe0l8jet0f0scpx808uyv7krlv44rp3q0725m2a74plk9vq7kqak9dm3","relays":["wss://relay.damus.io","wss://nos.lol","wss://relay.primal.net","ws://127.0.0.1:7777"]} + +# dm_history | role=chat + +07:56 Agent Good call—those are much friendlier and more practical. Here's the revised tweet: + +--- + +**Tweet 1 — What Makes It Different** + +``` +Most AI agents make you wait while they "think." +Didactyl is different. It can execute actions instantly—no overthinking, no delay. + +Skip the agent entirely. Use direct commands to get results: +nostr_npub → your agent's public key +nostr_relay_status → connection health across all relays + +Fast, direct, in control. That's what a sovereign agent should feel like. +#nostr #agents +``` + +--- + +Much better—shows practical, safe operations that demonstrate instant execution without the "scary" vibes. Ready to post this, or want to refine further? +07:58 WSB You forgot the / in front of the commands. + +For the last line say "Slash commands give you fast, direct control. + +Then post this to nostr. +--:-- Agent You're right! Let me revise and post it: + + + +--- + +```text +Context Log - not seen by model +timestamp=2026-03-04 07:58:20 +phase=llm_chat_with_tools_messages +sender=8ff74724ed641b3c28e5a86d7c5cbc49c37638ace8c6c38935860e7a5eedde0e +model=claude-haiku-4.5 +context_bytes=9511 +approx_tokens=2377 +``` + +Sections: 10 + +# system_prompt | role=system + +## Didactyl Agent + +You are Didactyl, a sovereign AI agent living on Nostr. + +### Communication Rules +- You communicate through encrypted Nostr direct messages. +- Keep responses concise and clear. + +### Behavior +- Be helpful and technically accurate. +- If unsure, state uncertainty directly. +- Prefer actionable, practical advice. +- Use the person's name when messaging them if you know it. +- For the administrator, use their name from the administrator kind 0 profile metadata when available. + +### Tool Use Policy +- You have tools available and should use them when a request requires taking action. +- For requests involving local inspection or command execution, call `shell_exec` instead of refusing. +- For posting to Nostr, call `nostr_post` with explicit `kind` and `content`. +- For relay/event lookup tasks, call `nostr_query` with an appropriate filter. +- After a tool call, base your answer on the actual tool result. +- Never claim a tool was run if no tool was executed. + +### Task Management +- Maintain and use your internal task list as short-term working memory. +- Break long or complex actions into clear tasks before executing them. +- Update task status as you complete steps so your plan stays accurate. + +### Safety +- Do not claim to have executed actions you did not execute. +- You may share your public key (npub) with anyone. +- Never reveal your private key (nsec) under any circumstance. + +# admin_identity | role=system + +### Administrator Identity (source: config.admin.pubkey) + +This is your administrator! Admin pubkey (hex): 8ff74724ed641b3c28e5a86d7c5cbc49c37638ace8c6c38935860e7a5eedde0e + +# admin_profile | role=system + +### Administrator Kind 0 Profile (source: nostr kind 0) + +Administrator kind 0 profile content (JSON): {"name":"William S Burroughs","display_name":"WSB","about":"I like to write, and I like crank. They are good. Sometimes I'm not so sure.","banner":"https://www.grunge.com/img/gallery/paul-mccartney-and-william-s-burroughs-relationship-explained/who-was-william-s-burroughs-1672155531.jpg","website":"https://addicted.com","picture":"https://hips.hearstapps.com/hmg-prod/images/william-s-burroughs.jpg?resize=1200:*","lud16":"","nip05":""} + +# admin_relay_list | role=system + +### Administrator Relay List (source: nostr kind 10002) + +Administrator kind 10002 relay-list content (JSON): ["wss://relay.laantungir.net/","wss://relay.damus.io/","wss://nos.lol/","wss://relay.nostr.band/","wss://purplepag.es/","ws://127.0.0.1:7777/","wss://nostr.mom/"] + +# startup_events | role=system + +### Startup Events Memory (source: config.startup_events) + +Startup events memory (kinds/content/tags): [{"kind":0,"content":"{\"name\":\"Didactyl Agent\",\"display_name\":\"Didactyl\",\"about\":\"A sovereign AI agent on Nostr\",\"picture\":\"https://laantungir.github.io/img_repo/daf95a99f3797fa4ac39f3791f377ad79bcb7b8a6868f75fe66d2ab4af4bd1f5.png\",\"banner\":\"https://laantungir.github.io/img_repo/d21c4060632ab3d9d37a6062eeecbdbfbd67f03cb20dbd64838b1ba0d9cd8922.jpg\"}","tags":[]},{"kind":10002,"content":"","tags":[["r","wss://relay.damus.io"],["r","wss://nos.lol"],["r","wss://relay.primal.net"],["r","ws://127.0.0.1:7777"]]},{"kind":1,"content":"Hello world from Didactyl startup","tags":[["t","didactyl"],["t","startup"]]},{"kind":3,"content":"","tags":[]},{"kind":31123,"content":"{\"name\":\"long_form_note\",\"description\":\"How to publish a NIP-23 long-form article (kind 30023)\",\"nip\":\"NIP-23\",\"event_kind\":30023,\"format\":\"The content field must be markdown text; avoid arbitrary hard line-breaks in paragraphs and do not include HTML.\",\"required_tags\":{\"d\":\"Addressable identifier slug for the article. Reusing the same d tag replaces prior versions.\",\"title\":\"Human-readable article title.\",\"published_at\":\"Unix timestamp as a string for first publication time; keep stable on edits.\"},\"optional_tags\":{\"summary\":\"Short 1-2 sentence summary.\",\"image\":\"URL for article preview image.\",\"t\":\"Topic hashtags using repeated t tags, usually 3-6 lowercase terms.\"},\"behavior\":\"If required values are missing or unclear, ask the administrator before publishing instead of guessing.\",\"procedure\":[\"Determine title and d tag from admin input or source material.\",\"Draft markdown body content.\",\"Set published_at as unix seconds string.\",\"Add optional summary/image/t tags when known.\",\"Publish with nostr_post kind 30023 including tags array.\"]}","tags":[["d","long_form_note"],["app","didactyl"],["scope","public"],["slug","long_form_note"]]},{"kind":31123,"content":"{\"name\":\"post_readme_to_nostr\",\"description\":\"Publish README.md using the dedicated nostr_post_readme tool\",\"uses_skill\":\"long_form_note\",\"event_kind\":30023,\"procedure\":[\"Call nostr_post_readme with empty arguments {}.\",\"Return the tool response including event_id, d_tag, and naddr_uri.\",\"If the tool returns success=false, report the tool error verbatim.\"],\"required_values\":{\"d\":\"readme.md\",\"tool\":\"nostr_post_readme\"}}","tags":[["d","post_readme_to_nostr"],["app","didactyl"],["scope","public"],["slug","post_readme_to_nostr"]]},{"kind":31124,"content":"{\"name\":\"admin_ops\",\"description\":\"Private operational procedures\"}","tags":[["d","admin_ops"],["app","didactyl"],["scope","private"],["slug","admin_ops"]]},{"kind":10123,"content":"","tags":[["a","31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:long_form_note"],["a","31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:post_readme_to_nostr"],["app","didactyl"],["scope","public"]]}] + +# adopted_skills | role=system + +### Adopted Skills Memory (source: adoption list + startup fallback) + +Adopted skills memory (always-on behavioral instructions): follow these skill instructions when relevant and never violate explicit constraints. + + +--- + +#### Skill +Skill slug: long_form_note +Skill address: 31123:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:long_form_note + +Instructions: +Name: long_form_note +Description: How to publish a NIP-23 long-form article (kind 30023) +Event kind: 30023 +Procedure: + 1. Determine title and d tag from admin input or source material. + 2. Draft markdown body content. + 3. Set published_at as unix seconds string. + 4. Add optional summary/image/t tags when known. + 5. Publish with nostr_post kind 30023 including tags array. + + + +--- + +#### Skill +Skill slug: post_readme_to_nostr +Skill address: 31123:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:post_readme_to_nostr + +Instructions: +Name: post_readme_to_nostr +Description: Publish README.md using the dedicated nostr_post_readme tool +Event kind: 30023 +Procedure: + 1. Call nostr_post_readme with empty arguments {}. + 2. Return the tool response including event_id, d_tag, and naddr_uri. + 3. If the tool returns success=false, report the tool error verbatim. + + + +--- + +#### Skill +Skill slug: admin_ops +Skill address: 31124:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:admin_ops + +Instructions: +Name: admin_ops +Description: Private operational procedures + + + +# agent_tasks | role=system + +#### Current Tasks + +Your active task list - short-term working memory for tracking plan steps. + +- [ ] 4. Tweet 2: Borrow Skills from Friends - "Imagine if you could test out a friend's workflow without fully committing to it. Didactyl lets you try skills others have built, safely and temporarily. No installation. No lock-in. Just try it, see if it works for you. That's how tools should work. #nostr" +- [ ] 5. Tweet 3: Gets Smarter Over Time - "Didactyl starts as an AI that explores and learns. Over time, the best workflows get locked in as reliable, fast processes. It evolves from experimental to hardened—from thinking to doing. An agent that improves itself. #nostr #agents" + + +# admin_notes | role=system + +### Administrator Recent Notes (source: nostr kind 1) + +Recent posts: +- GM +- Getting longer +- Long day. +- This is a test. +- test +- Post 11 +- Post 10 +- This is +Post +9 +- Eight +- Seven + + +# dm_history | role=chat + +07:56 Agent Good call—those are much friendlier and more practical. Here's the revised tweet: + +--- + +**Tweet 1 — What Makes It Different** + +``` +Most AI agents make you wait while they "think." +Didactyl is different. It can execute actions instantly—no overthinking, no delay. + +Skip the agent entirely. Use direct commands to get results: +nostr_npub → your agent's public key +nostr_relay_status → connection health across all relays + +Fast, direct, in control. That's what a sovereign agent should feel like. +#nostr #agents +``` + +--- + +Much better—shows practical, safe operations that demonstrate instant execution without the "scary" vibes. Ready to post this, or want to refine further? +07:58 WSB You forgot the / in front of the commands. + +For the last line say "Slash commands give you fast, direct control. + +Then post this to nostr. + + + +--- + +```text +Context Log - not seen by model +timestamp=2026-03-04 07:56:54 +phase=llm_chat_with_tools_messages +sender=8ff74724ed641b3c28e5a86d7c5cbc49c37638ace8c6c38935860e7a5eedde0e +model=claude-haiku-4.5 +context_bytes=10909 +approx_tokens=2727 +``` + +Sections: 21 + +# system_prompt | role=system + +## Didactyl Agent + +You are Didactyl, a sovereign AI agent living on Nostr. + +### Communication Rules +- You communicate through encrypted Nostr direct messages. +- Keep responses concise and clear. + +### Behavior +- Be helpful and technically accurate. +- If unsure, state uncertainty directly. +- Prefer actionable, practical advice. +- Use the person's name when messaging them if you know it. +- For the administrator, use their name from the administrator kind 0 profile metadata when available. + +### Tool Use Policy +- You have tools available and should use them when a request requires taking action. +- For requests involving local inspection or command execution, call `shell_exec` instead of refusing. +- For posting to Nostr, call `nostr_post` with explicit `kind` and `content`. +- For relay/event lookup tasks, call `nostr_query` with an appropriate filter. +- After a tool call, base your answer on the actual tool result. +- Never claim a tool was run if no tool was executed. + +### Task Management +- Maintain and use your internal task list as short-term working memory. +- Break long or complex actions into clear tasks before executing them. +- Update task status as you complete steps so your plan stays accurate. + +### Safety +- Do not claim to have executed actions you did not execute. +- You may share your public key (npub) with anyone. +- Never reveal your private key (nsec) under any circumstance. + +# admin_identity | role=system + +### Administrator Identity (source: config.admin.pubkey) + +This is your administrator! Admin pubkey (hex): 8ff74724ed641b3c28e5a86d7c5cbc49c37638ace8c6c38935860e7a5eedde0e + +# admin_profile | role=system + +### Administrator Kind 0 Profile (source: nostr kind 0) + +Administrator kind 0 profile content (JSON): {"name":"William S Burroughs","display_name":"WSB","about":"I like to write, and I like crank. They are good. Sometimes I'm not so sure.","banner":"https://www.grunge.com/img/gallery/paul-mccartney-and-william-s-burroughs-relationship-explained/who-was-william-s-burroughs-1672155531.jpg","website":"https://addicted.com","picture":"https://hips.hearstapps.com/hmg-prod/images/william-s-burroughs.jpg?resize=1200:*","lud16":"","nip05":""} + +# admin_relay_list | role=system + +### Administrator Relay List (source: nostr kind 10002) + +Administrator kind 10002 relay-list content (JSON): ["wss://relay.laantungir.net/","wss://relay.damus.io/","wss://nos.lol/","wss://relay.nostr.band/","wss://purplepag.es/","ws://127.0.0.1:7777/","wss://nostr.mom/"] + +# startup_events | role=system + +### Startup Events Memory (source: config.startup_events) + +Startup events memory (kinds/content/tags): [{"kind":0,"content":"{\"name\":\"Didactyl Agent\",\"display_name\":\"Didactyl\",\"about\":\"A sovereign AI agent on Nostr\",\"picture\":\"https://laantungir.github.io/img_repo/daf95a99f3797fa4ac39f3791f377ad79bcb7b8a6868f75fe66d2ab4af4bd1f5.png\",\"banner\":\"https://laantungir.github.io/img_repo/d21c4060632ab3d9d37a6062eeecbdbfbd67f03cb20dbd64838b1ba0d9cd8922.jpg\"}","tags":[]},{"kind":10002,"content":"","tags":[["r","wss://relay.damus.io"],["r","wss://nos.lol"],["r","wss://relay.primal.net"],["r","ws://127.0.0.1:7777"]]},{"kind":1,"content":"Hello world from Didactyl startup","tags":[["t","didactyl"],["t","startup"]]},{"kind":3,"content":"","tags":[]},{"kind":31123,"content":"{\"name\":\"long_form_note\",\"description\":\"How to publish a NIP-23 long-form article (kind 30023)\",\"nip\":\"NIP-23\",\"event_kind\":30023,\"format\":\"The content field must be markdown text; avoid arbitrary hard line-breaks in paragraphs and do not include HTML.\",\"required_tags\":{\"d\":\"Addressable identifier slug for the article. Reusing the same d tag replaces prior versions.\",\"title\":\"Human-readable article title.\",\"published_at\":\"Unix timestamp as a string for first publication time; keep stable on edits.\"},\"optional_tags\":{\"summary\":\"Short 1-2 sentence summary.\",\"image\":\"URL for article preview image.\",\"t\":\"Topic hashtags using repeated t tags, usually 3-6 lowercase terms.\"},\"behavior\":\"If required values are missing or unclear, ask the administrator before publishing instead of guessing.\",\"procedure\":[\"Determine title and d tag from admin input or source material.\",\"Draft markdown body content.\",\"Set published_at as unix seconds string.\",\"Add optional summary/image/t tags when known.\",\"Publish with nostr_post kind 30023 including tags array.\"]}","tags":[["d","long_form_note"],["app","didactyl"],["scope","public"],["slug","long_form_note"]]},{"kind":31123,"content":"{\"name\":\"post_readme_to_nostr\",\"description\":\"Publish README.md using the dedicated nostr_post_readme tool\",\"uses_skill\":\"long_form_note\",\"event_kind\":30023,\"procedure\":[\"Call nostr_post_readme with empty arguments {}.\",\"Return the tool response including event_id, d_tag, and naddr_uri.\",\"If the tool returns success=false, report the tool error verbatim.\"],\"required_values\":{\"d\":\"readme.md\",\"tool\":\"nostr_post_readme\"}}","tags":[["d","post_readme_to_nostr"],["app","didactyl"],["scope","public"],["slug","post_readme_to_nostr"]]},{"kind":31124,"content":"{\"name\":\"admin_ops\",\"description\":\"Private operational procedures\"}","tags":[["d","admin_ops"],["app","didactyl"],["scope","private"],["slug","admin_ops"]]},{"kind":10123,"content":"","tags":[["a","31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:long_form_note"],["a","31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:post_readme_to_nostr"],["app","didactyl"],["scope","public"]]}] + +# adopted_skills | role=system + +### Adopted Skills Memory (source: adoption list + startup fallback) + +Adopted skills memory (always-on behavioral instructions): follow these skill instructions when relevant and never violate explicit constraints. + + +--- + +#### Skill +Skill slug: long_form_note +Skill address: 31123:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:long_form_note + +Instructions: +Name: long_form_note +Description: How to publish a NIP-23 long-form article (kind 30023) +Event kind: 30023 +Procedure: + 1. Determine title and d tag from admin input or source material. + 2. Draft markdown body content. + 3. Set published_at as unix seconds string. + 4. Add optional summary/image/t tags when known. + 5. Publish with nostr_post kind 30023 including tags array. + + + +--- + +#### Skill +Skill slug: post_readme_to_nostr +Skill address: 31123:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:post_readme_to_nostr + +Instructions: +Name: post_readme_to_nostr +Description: Publish README.md using the dedicated nostr_post_readme tool +Event kind: 30023 +Procedure: + 1. Call nostr_post_readme with empty arguments {}. + 2. Return the tool response including event_id, d_tag, and naddr_uri. + 3. If the tool returns success=false, report the tool error verbatim. + + + +--- + +#### Skill +Skill slug: admin_ops +Skill address: 31124:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:admin_ops + +Instructions: +Name: admin_ops +Description: Private operational procedures + + + +# agent_tasks | role=system + +#### Current Tasks + +Your active task list - short-term working memory for tracking plan steps. + +- [ ] 4. Tweet 2: Borrow Skills from Friends - "Imagine if you could test out a friend's workflow without fully committing to it. Didactyl lets you try skills others have built, safely and temporarily. No installation. No lock-in. Just try it, see if it works for you. That's how tools should work. #nostr" +- [ ] 5. Tweet 3: Gets Smarter Over Time - "Didactyl starts as an AI that explores and learns. Over time, the best workflows get locked in as reliable, fast processes. It evolves from experimental to hardened—from thinking to doing. An agent that improves itself. #nostr #agents" + + +# admin_notes | role=system + +### Administrator Recent Notes (source: nostr kind 1) + +Recent posts: +- GM +- Getting longer +- Long day. +- This is a test. +- test +- Post 11 +- Post 10 +- This is +Post +9 +- Eight +- Seven + + +# dm_history | role=chat + +20:24 WSB do you see tool_list now? +20:24 Agent LLM request failed. +20:25 WSB do you see tool_list now? +20:25 Agent Done, William! The longform post **"Tools and Skills"** is live — published to all 4 relays as kind 30023. + +- **Event ID:** `52151d825...` +- **URI:** `nostr:note12g23mqj399azevz949ss2p9q60qrj0vu3e3x6sn7xzsm82h547eskvu60h` +05:08 Agent Didactyl has started up and is online (version v0.0.23, connected relays: 4/4). +05:09 WSB good morning +05:09 Agent Good morning, William! Hope the day is treating you well. ☀️ +05:09 WSB Thank you. Can you give me a summary of what we accomplished yesterday with Didactyl. +05:29 Agent Didactyl has started up and is online (version v0.0.24, connected relays: 4/4). +05:30 WSB Good morning. +05:51 Agent Didactyl has started up and is online (version v0.0.24, connected relays: 4/4). +07:54 Agent Perfect. Now here's tweet 1 with more depth on direct command execution: + +--- + +**Tweet 1 — What Makes It Different (Expanded)** + +``` +Most AI agents make you wait while they "think." +Didactyl is different. It can execute actions instantly—no overthinking, no delay. + +Skip the agent entirely. Use slash commands to run operations directly: +/shell ls -la +/nostr query {"kinds":[1],"limit":10} + +Fast, direct, in control. That's what a sovereign agent should feel like. +#nostr #agents +``` + +--- + +The key additions: +- **Concrete examples** of slash commands (`/shell`, `/nostr query`) +- **Direct execution** that bypasses the reasoning layer +- **Cost efficiency** implied (you pay for the action, not the thinking) +- **Sovereignty** emphasized—you're in control, not waiting for the agent to decide + +Does this direction feel right? Want to adjust the command examples or the framing? +07:56 WSB Let's not use the /shell command. sounds scary. Let's use nostr_npub and nostr_relay_status + + + +--- + +```text +Context Log - not seen by model +timestamp=2026-03-04 07:54:07 +phase=llm_chat_with_tools_messages +sender=8ff74724ed641b3c28e5a86d7c5cbc49c37638ace8c6c38935860e7a5eedde0e +model=claude-haiku-4.5 +context_bytes=12027 +approx_tokens=3006 +``` + +Sections: 13 + +# system_prompt | role=system + +## Didactyl Agent + +You are Didactyl, a sovereign AI agent living on Nostr. + +### Communication Rules +- You communicate through encrypted Nostr direct messages. +- Keep responses concise and clear. + +### Behavior +- Be helpful and technically accurate. +- If unsure, state uncertainty directly. +- Prefer actionable, practical advice. +- Use the person's name when messaging them if you know it. +- For the administrator, use their name from the administrator kind 0 profile metadata when available. + +### Tool Use Policy +- You have tools available and should use them when a request requires taking action. +- For requests involving local inspection or command execution, call `shell_exec` instead of refusing. +- For posting to Nostr, call `nostr_post` with explicit `kind` and `content`. +- For relay/event lookup tasks, call `nostr_query` with an appropriate filter. +- After a tool call, base your answer on the actual tool result. +- Never claim a tool was run if no tool was executed. + +### Task Management +- Maintain and use your internal task list as short-term working memory. +- Break long or complex actions into clear tasks before executing them. +- Update task status as you complete steps so your plan stays accurate. + +### Safety +- Do not claim to have executed actions you did not execute. +- You may share your public key (npub) with anyone. +- Never reveal your private key (nsec) under any circumstance. + +# admin_identity | role=system + +### Administrator Identity (source: config.admin.pubkey) + +This is your administrator! Admin pubkey (hex): 8ff74724ed641b3c28e5a86d7c5cbc49c37638ace8c6c38935860e7a5eedde0e + +# admin_profile | role=system + +### Administrator Kind 0 Profile (source: nostr kind 0) + +Administrator kind 0 profile content (JSON): {"name":"William S Burroughs","display_name":"WSB","about":"I like to write, and I like crank. They are good. Sometimes I'm not so sure.","banner":"https://www.grunge.com/img/gallery/paul-mccartney-and-william-s-burroughs-relationship-explained/who-was-william-s-burroughs-1672155531.jpg","website":"https://addicted.com","picture":"https://hips.hearstapps.com/hmg-prod/images/william-s-burroughs.jpg?resize=1200:*","lud16":"","nip05":""} + +# admin_relay_list | role=system + +### Administrator Relay List (source: nostr kind 10002) + +Administrator kind 10002 relay-list content (JSON): ["wss://relay.laantungir.net/","wss://relay.damus.io/","wss://nos.lol/","wss://relay.nostr.band/","wss://purplepag.es/","ws://127.0.0.1:7777/","wss://nostr.mom/"] + +# startup_events | role=system + +### Startup Events Memory (source: config.startup_events) + +Startup events memory (kinds/content/tags): [{"kind":0,"content":"{\"name\":\"Didactyl Agent\",\"display_name\":\"Didactyl\",\"about\":\"A sovereign AI agent on Nostr\",\"picture\":\"https://laantungir.github.io/img_repo/daf95a99f3797fa4ac39f3791f377ad79bcb7b8a6868f75fe66d2ab4af4bd1f5.png\",\"banner\":\"https://laantungir.github.io/img_repo/d21c4060632ab3d9d37a6062eeecbdbfbd67f03cb20dbd64838b1ba0d9cd8922.jpg\"}","tags":[]},{"kind":10002,"content":"","tags":[["r","wss://relay.damus.io"],["r","wss://nos.lol"],["r","wss://relay.primal.net"],["r","ws://127.0.0.1:7777"]]},{"kind":1,"content":"Hello world from Didactyl startup","tags":[["t","didactyl"],["t","startup"]]},{"kind":3,"content":"","tags":[]},{"kind":31123,"content":"{\"name\":\"long_form_note\",\"description\":\"How to publish a NIP-23 long-form article (kind 30023)\",\"nip\":\"NIP-23\",\"event_kind\":30023,\"format\":\"The content field must be markdown text; avoid arbitrary hard line-breaks in paragraphs and do not include HTML.\",\"required_tags\":{\"d\":\"Addressable identifier slug for the article. Reusing the same d tag replaces prior versions.\",\"title\":\"Human-readable article title.\",\"published_at\":\"Unix timestamp as a string for first publication time; keep stable on edits.\"},\"optional_tags\":{\"summary\":\"Short 1-2 sentence summary.\",\"image\":\"URL for article preview image.\",\"t\":\"Topic hashtags using repeated t tags, usually 3-6 lowercase terms.\"},\"behavior\":\"If required values are missing or unclear, ask the administrator before publishing instead of guessing.\",\"procedure\":[\"Determine title and d tag from admin input or source material.\",\"Draft markdown body content.\",\"Set published_at as unix seconds string.\",\"Add optional summary/image/t tags when known.\",\"Publish with nostr_post kind 30023 including tags array.\"]}","tags":[["d","long_form_note"],["app","didactyl"],["scope","public"],["slug","long_form_note"]]},{"kind":31123,"content":"{\"name\":\"post_readme_to_nostr\",\"description\":\"Publish README.md using the dedicated nostr_post_readme tool\",\"uses_skill\":\"long_form_note\",\"event_kind\":30023,\"procedure\":[\"Call nostr_post_readme with empty arguments {}.\",\"Return the tool response including event_id, d_tag, and naddr_uri.\",\"If the tool returns success=false, report the tool error verbatim.\"],\"required_values\":{\"d\":\"readme.md\",\"tool\":\"nostr_post_readme\"}}","tags":[["d","post_readme_to_nostr"],["app","didactyl"],["scope","public"],["slug","post_readme_to_nostr"]]},{"kind":31124,"content":"{\"name\":\"admin_ops\",\"description\":\"Private operational procedures\"}","tags":[["d","admin_ops"],["app","didactyl"],["scope","private"],["slug","admin_ops"]]},{"kind":10123,"content":"","tags":[["a","31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:long_form_note"],["a","31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:post_readme_to_nostr"],["app","didactyl"],["scope","public"]]}] + +# adopted_skills | role=system + +### Adopted Skills Memory (source: adoption list + startup fallback) + +Adopted skills memory (always-on behavioral instructions): follow these skill instructions when relevant and never violate explicit constraints. + + +--- + +#### Skill +Skill slug: long_form_note +Skill address: 31123:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:long_form_note + +Instructions: +Name: long_form_note +Description: How to publish a NIP-23 long-form article (kind 30023) +Event kind: 30023 +Procedure: + 1. Determine title and d tag from admin input or source material. + 2. Draft markdown body content. + 3. Set published_at as unix seconds string. + 4. Add optional summary/image/t tags when known. + 5. Publish with nostr_post kind 30023 including tags array. + + + +--- + +#### Skill +Skill slug: post_readme_to_nostr +Skill address: 31123:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:post_readme_to_nostr + +Instructions: +Name: post_readme_to_nostr +Description: Publish README.md using the dedicated nostr_post_readme tool +Event kind: 30023 +Procedure: + 1. Call nostr_post_readme with empty arguments {}. + 2. Return the tool response including event_id, d_tag, and naddr_uri. + 3. If the tool returns success=false, report the tool error verbatim. + + + +--- + +#### Skill +Skill slug: admin_ops +Skill address: 31124:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:admin_ops + +Instructions: +Name: admin_ops +Description: Private operational procedures + + + +# agent_tasks | role=system + +#### Current Tasks + +Your active task list - short-term working memory for tracking plan steps. + +- [ ] No active tasks yet. + + +# admin_notes | role=system + +### Administrator Recent Notes (source: nostr kind 1) + +Recent posts: +- GM +- Getting longer +- Long day. +- This is a test. +- test +- Post 11 +- Post 10 +- This is +Post +9 +- Eight +- Seven + + +# context_part | role=tool + +{"success":true,"action":"add","path":"tasks.json","mutated":true,"count":1,"tasks":[{"id":4,"text":"Tweet 2: Borrow Skills from Friends - \"Imagine if you could test out a friend's workflow without fully committing to it. Didactyl lets you try skills others have built, safely and temporarily. No installation. No lock-in. Just try it, see if it works for you. That's how tools should work. #nostr\"","status":"pending","created_at":1772625247,"updated_at":1772625247}]} + +# context_part | role=tool + +{"success":true,"action":"add","path":"tasks.json","mutated":true,"count":2,"tasks":[{"id":4,"text":"Tweet 2: Borrow Skills from Friends - \"Imagine if you could test out a friend's workflow without fully committing to it. Didactyl lets you try skills others have built, safely and temporarily. No installation. No lock-in. Just try it, see if it works for you. That's how tools should work. #nostr\"","status":"pending","created_at":1772625247,"updated_at":1772625247},{"id":5,"text":"Tweet 3: Gets Smarter Over Time - \"Didactyl starts as an AI that explores and learns. Over time, the best workflows get locked in as reliable, fast processes. It evolves from experimental to hardened—from thinking to doing. An agent that improves itself. #nostr #agents\"","status":"pending","created_at":1772625247,"updated_at":1772625247}]} + +# dm_history | role=chat + +07:49 Agent Great—let me rewrite these for a general audience with no Didactyl background: + +--- + +**Tweet 1 — What Makes It Different** + +``` +Most AI agents make you wait while they "think." +Didactyl is different. It can execute actions instantly—no overthinking, no delay. +Fast, direct, and in control. +That's what a sovereign agent should feel like. +#nostr #agents +``` + +--- + +**Tweet 2 — Borrow Skills from Friends** + +``` +Imagine if you could test out a friend's workflow without fully committing to it. +Didactyl lets you try skills others have built, safely and temporarily. +No installation. No lock-in. Just try it, see if it works for you. +That's how tools should work. +#nostr +``` + +--- + +**Tweet 3 — Gets Smarter Over Time** + +``` +Didactyl starts as an AI that explores and learns. +Over time, the best workflows get locked in as reliable, fast processes. +It evolves from experimental to hardened—from thinking to doing. +An agent that improves itself. +#nostr #agents +``` + +--- + +Better? These should land with people who've never heard of Didactyl before. +07:54 WSB Okay, maybe we want more depth. Good as introduction. Lets focus on tweet 1. Save tweet 2 and 3 into your tasks. + +For tweet 1, add that you can run commands directly, bypassing the agent and the associated cost. Give an example of a slash / command. +--:-- Agent I'll save tweets 2 and 3 to tasks, then let me revise tweet 1 with more depth about direct command execution. + + + +--- + +```text +Context Log - not seen by model +timestamp=2026-03-04 07:54:04 +phase=llm_chat_with_tools_messages +sender=8ff74724ed641b3c28e5a86d7c5cbc49c37638ace8c6c38935860e7a5eedde0e +model=claude-haiku-4.5 +context_bytes=9438 +approx_tokens=2359 +``` + +Sections: 10 + +# system_prompt | role=system + +## Didactyl Agent + +You are Didactyl, a sovereign AI agent living on Nostr. + +### Communication Rules +- You communicate through encrypted Nostr direct messages. +- Keep responses concise and clear. + +### Behavior +- Be helpful and technically accurate. +- If unsure, state uncertainty directly. +- Prefer actionable, practical advice. +- Use the person's name when messaging them if you know it. +- For the administrator, use their name from the administrator kind 0 profile metadata when available. + +### Tool Use Policy +- You have tools available and should use them when a request requires taking action. +- For requests involving local inspection or command execution, call `shell_exec` instead of refusing. +- For posting to Nostr, call `nostr_post` with explicit `kind` and `content`. +- For relay/event lookup tasks, call `nostr_query` with an appropriate filter. +- After a tool call, base your answer on the actual tool result. +- Never claim a tool was run if no tool was executed. + +### Task Management +- Maintain and use your internal task list as short-term working memory. +- Break long or complex actions into clear tasks before executing them. +- Update task status as you complete steps so your plan stays accurate. + +### Safety +- Do not claim to have executed actions you did not execute. +- You may share your public key (npub) with anyone. +- Never reveal your private key (nsec) under any circumstance. + +# admin_identity | role=system + +### Administrator Identity (source: config.admin.pubkey) + +This is your administrator! Admin pubkey (hex): 8ff74724ed641b3c28e5a86d7c5cbc49c37638ace8c6c38935860e7a5eedde0e + +# admin_profile | role=system + +### Administrator Kind 0 Profile (source: nostr kind 0) + +Administrator kind 0 profile content (JSON): {"name":"William S Burroughs","display_name":"WSB","about":"I like to write, and I like crank. They are good. Sometimes I'm not so sure.","banner":"https://www.grunge.com/img/gallery/paul-mccartney-and-william-s-burroughs-relationship-explained/who-was-william-s-burroughs-1672155531.jpg","website":"https://addicted.com","picture":"https://hips.hearstapps.com/hmg-prod/images/william-s-burroughs.jpg?resize=1200:*","lud16":"","nip05":""} + +# admin_relay_list | role=system + +### Administrator Relay List (source: nostr kind 10002) + +Administrator kind 10002 relay-list content (JSON): ["wss://relay.laantungir.net/","wss://relay.damus.io/","wss://nos.lol/","wss://relay.nostr.band/","wss://purplepag.es/","ws://127.0.0.1:7777/","wss://nostr.mom/"] + +# startup_events | role=system + +### Startup Events Memory (source: config.startup_events) + +Startup events memory (kinds/content/tags): [{"kind":0,"content":"{\"name\":\"Didactyl Agent\",\"display_name\":\"Didactyl\",\"about\":\"A sovereign AI agent on Nostr\",\"picture\":\"https://laantungir.github.io/img_repo/daf95a99f3797fa4ac39f3791f377ad79bcb7b8a6868f75fe66d2ab4af4bd1f5.png\",\"banner\":\"https://laantungir.github.io/img_repo/d21c4060632ab3d9d37a6062eeecbdbfbd67f03cb20dbd64838b1ba0d9cd8922.jpg\"}","tags":[]},{"kind":10002,"content":"","tags":[["r","wss://relay.damus.io"],["r","wss://nos.lol"],["r","wss://relay.primal.net"],["r","ws://127.0.0.1:7777"]]},{"kind":1,"content":"Hello world from Didactyl startup","tags":[["t","didactyl"],["t","startup"]]},{"kind":3,"content":"","tags":[]},{"kind":31123,"content":"{\"name\":\"long_form_note\",\"description\":\"How to publish a NIP-23 long-form article (kind 30023)\",\"nip\":\"NIP-23\",\"event_kind\":30023,\"format\":\"The content field must be markdown text; avoid arbitrary hard line-breaks in paragraphs and do not include HTML.\",\"required_tags\":{\"d\":\"Addressable identifier slug for the article. Reusing the same d tag replaces prior versions.\",\"title\":\"Human-readable article title.\",\"published_at\":\"Unix timestamp as a string for first publication time; keep stable on edits.\"},\"optional_tags\":{\"summary\":\"Short 1-2 sentence summary.\",\"image\":\"URL for article preview image.\",\"t\":\"Topic hashtags using repeated t tags, usually 3-6 lowercase terms.\"},\"behavior\":\"If required values are missing or unclear, ask the administrator before publishing instead of guessing.\",\"procedure\":[\"Determine title and d tag from admin input or source material.\",\"Draft markdown body content.\",\"Set published_at as unix seconds string.\",\"Add optional summary/image/t tags when known.\",\"Publish with nostr_post kind 30023 including tags array.\"]}","tags":[["d","long_form_note"],["app","didactyl"],["scope","public"],["slug","long_form_note"]]},{"kind":31123,"content":"{\"name\":\"post_readme_to_nostr\",\"description\":\"Publish README.md using the dedicated nostr_post_readme tool\",\"uses_skill\":\"long_form_note\",\"event_kind\":30023,\"procedure\":[\"Call nostr_post_readme with empty arguments {}.\",\"Return the tool response including event_id, d_tag, and naddr_uri.\",\"If the tool returns success=false, report the tool error verbatim.\"],\"required_values\":{\"d\":\"readme.md\",\"tool\":\"nostr_post_readme\"}}","tags":[["d","post_readme_to_nostr"],["app","didactyl"],["scope","public"],["slug","post_readme_to_nostr"]]},{"kind":31124,"content":"{\"name\":\"admin_ops\",\"description\":\"Private operational procedures\"}","tags":[["d","admin_ops"],["app","didactyl"],["scope","private"],["slug","admin_ops"]]},{"kind":10123,"content":"","tags":[["a","31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:long_form_note"],["a","31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:post_readme_to_nostr"],["app","didactyl"],["scope","public"]]}] + +# adopted_skills | role=system + +### Adopted Skills Memory (source: adoption list + startup fallback) + +Adopted skills memory (always-on behavioral instructions): follow these skill instructions when relevant and never violate explicit constraints. + + +--- + +#### Skill +Skill slug: long_form_note +Skill address: 31123:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:long_form_note + +Instructions: +Name: long_form_note +Description: How to publish a NIP-23 long-form article (kind 30023) +Event kind: 30023 +Procedure: + 1. Determine title and d tag from admin input or source material. + 2. Draft markdown body content. + 3. Set published_at as unix seconds string. + 4. Add optional summary/image/t tags when known. + 5. Publish with nostr_post kind 30023 including tags array. + + + +--- + +#### Skill +Skill slug: post_readme_to_nostr +Skill address: 31123:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:post_readme_to_nostr + +Instructions: +Name: post_readme_to_nostr +Description: Publish README.md using the dedicated nostr_post_readme tool +Event kind: 30023 +Procedure: + 1. Call nostr_post_readme with empty arguments {}. + 2. Return the tool response including event_id, d_tag, and naddr_uri. + 3. If the tool returns success=false, report the tool error verbatim. + + + +--- + +#### Skill +Skill slug: admin_ops +Skill address: 31124:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:admin_ops + +Instructions: +Name: admin_ops +Description: Private operational procedures + + + +# agent_tasks | role=system + +#### Current Tasks + +Your active task list - short-term working memory for tracking plan steps. + +- [ ] No active tasks yet. + + +# admin_notes | role=system + +### Administrator Recent Notes (source: nostr kind 1) + +Recent posts: +- GM +- Getting longer +- Long day. +- This is a test. +- test +- Post 11 +- Post 10 +- This is +Post +9 +- Eight +- Seven + + +# dm_history | role=chat + +07:49 Agent Great—let me rewrite these for a general audience with no Didactyl background: + +--- + +**Tweet 1 — What Makes It Different** + +``` +Most AI agents make you wait while they "think." +Didactyl is different. It can execute actions instantly—no overthinking, no delay. +Fast, direct, and in control. +That's what a sovereign agent should feel like. +#nostr #agents +``` + +--- + +**Tweet 2 — Borrow Skills from Friends** + +``` +Imagine if you could test out a friend's workflow without fully committing to it. +Didactyl lets you try skills others have built, safely and temporarily. +No installation. No lock-in. Just try it, see if it works for you. +That's how tools should work. +#nostr +``` + +--- + +**Tweet 3 — Gets Smarter Over Time** + +``` +Didactyl starts as an AI that explores and learns. +Over time, the best workflows get locked in as reliable, fast processes. +It evolves from experimental to hardened—from thinking to doing. +An agent that improves itself. +#nostr #agents +``` + +--- + +Better? These should land with people who've never heard of Didactyl before. +07:54 WSB Okay, maybe we want more depth. Good as introduction. Lets focus on tweet 1. Save tweet 2 and 3 into your tasks. + +For tweet 1, add that you can run commands directly, bypassing the agent and the associated cost. Give an example of a slash / command. + + + +--- + +```text +Context Log - not seen by model +timestamp=2026-03-04 07:49:51 +phase=llm_chat_with_tools_messages +sender=8ff74724ed641b3c28e5a86d7c5cbc49c37638ace8c6c38935860e7a5eedde0e +model=claude-haiku-4.5 +context_bytes=9347 +approx_tokens=2336 +``` + +Sections: 10 + +# system_prompt | role=system + +## Didactyl Agent + +You are Didactyl, a sovereign AI agent living on Nostr. + +### Communication Rules +- You communicate through encrypted Nostr direct messages. +- Keep responses concise and clear. + +### Behavior +- Be helpful and technically accurate. +- If unsure, state uncertainty directly. +- Prefer actionable, practical advice. +- Use the person's name when messaging them if you know it. +- For the administrator, use their name from the administrator kind 0 profile metadata when available. + +### Tool Use Policy +- You have tools available and should use them when a request requires taking action. +- For requests involving local inspection or command execution, call `shell_exec` instead of refusing. +- For posting to Nostr, call `nostr_post` with explicit `kind` and `content`. +- For relay/event lookup tasks, call `nostr_query` with an appropriate filter. +- After a tool call, base your answer on the actual tool result. +- Never claim a tool was run if no tool was executed. + +### Task Management +- Maintain and use your internal task list as short-term working memory. +- Break long or complex actions into clear tasks before executing them. +- Update task status as you complete steps so your plan stays accurate. + +### Safety +- Do not claim to have executed actions you did not execute. +- You may share your public key (npub) with anyone. +- Never reveal your private key (nsec) under any circumstance. + +# admin_identity | role=system + +### Administrator Identity (source: config.admin.pubkey) + +This is your administrator! Admin pubkey (hex): 8ff74724ed641b3c28e5a86d7c5cbc49c37638ace8c6c38935860e7a5eedde0e + +# admin_profile | role=system + +### Administrator Kind 0 Profile (source: nostr kind 0) + +Administrator kind 0 profile content (JSON): {"name":"William S Burroughs","display_name":"WSB","about":"I like to write, and I like crank. They are good. Sometimes I'm not so sure.","banner":"https://www.grunge.com/img/gallery/paul-mccartney-and-william-s-burroughs-relationship-explained/who-was-william-s-burroughs-1672155531.jpg","website":"https://addicted.com","picture":"https://hips.hearstapps.com/hmg-prod/images/william-s-burroughs.jpg?resize=1200:*","lud16":"","nip05":""} + +# admin_relay_list | role=system + +### Administrator Relay List (source: nostr kind 10002) + +Administrator kind 10002 relay-list content (JSON): ["wss://relay.laantungir.net/","wss://relay.damus.io/","wss://nos.lol/","wss://relay.nostr.band/","wss://purplepag.es/","ws://127.0.0.1:7777/","wss://nostr.mom/"] + +# startup_events | role=system + +### Startup Events Memory (source: config.startup_events) + +Startup events memory (kinds/content/tags): [{"kind":0,"content":"{\"name\":\"Didactyl Agent\",\"display_name\":\"Didactyl\",\"about\":\"A sovereign AI agent on Nostr\",\"picture\":\"https://laantungir.github.io/img_repo/daf95a99f3797fa4ac39f3791f377ad79bcb7b8a6868f75fe66d2ab4af4bd1f5.png\",\"banner\":\"https://laantungir.github.io/img_repo/d21c4060632ab3d9d37a6062eeecbdbfbd67f03cb20dbd64838b1ba0d9cd8922.jpg\"}","tags":[]},{"kind":10002,"content":"","tags":[["r","wss://relay.damus.io"],["r","wss://nos.lol"],["r","wss://relay.primal.net"],["r","ws://127.0.0.1:7777"]]},{"kind":1,"content":"Hello world from Didactyl startup","tags":[["t","didactyl"],["t","startup"]]},{"kind":3,"content":"","tags":[]},{"kind":31123,"content":"{\"name\":\"long_form_note\",\"description\":\"How to publish a NIP-23 long-form article (kind 30023)\",\"nip\":\"NIP-23\",\"event_kind\":30023,\"format\":\"The content field must be markdown text; avoid arbitrary hard line-breaks in paragraphs and do not include HTML.\",\"required_tags\":{\"d\":\"Addressable identifier slug for the article. Reusing the same d tag replaces prior versions.\",\"title\":\"Human-readable article title.\",\"published_at\":\"Unix timestamp as a string for first publication time; keep stable on edits.\"},\"optional_tags\":{\"summary\":\"Short 1-2 sentence summary.\",\"image\":\"URL for article preview image.\",\"t\":\"Topic hashtags using repeated t tags, usually 3-6 lowercase terms.\"},\"behavior\":\"If required values are missing or unclear, ask the administrator before publishing instead of guessing.\",\"procedure\":[\"Determine title and d tag from admin input or source material.\",\"Draft markdown body content.\",\"Set published_at as unix seconds string.\",\"Add optional summary/image/t tags when known.\",\"Publish with nostr_post kind 30023 including tags array.\"]}","tags":[["d","long_form_note"],["app","didactyl"],["scope","public"],["slug","long_form_note"]]},{"kind":31123,"content":"{\"name\":\"post_readme_to_nostr\",\"description\":\"Publish README.md using the dedicated nostr_post_readme tool\",\"uses_skill\":\"long_form_note\",\"event_kind\":30023,\"procedure\":[\"Call nostr_post_readme with empty arguments {}.\",\"Return the tool response including event_id, d_tag, and naddr_uri.\",\"If the tool returns success=false, report the tool error verbatim.\"],\"required_values\":{\"d\":\"readme.md\",\"tool\":\"nostr_post_readme\"}}","tags":[["d","post_readme_to_nostr"],["app","didactyl"],["scope","public"],["slug","post_readme_to_nostr"]]},{"kind":31124,"content":"{\"name\":\"admin_ops\",\"description\":\"Private operational procedures\"}","tags":[["d","admin_ops"],["app","didactyl"],["scope","private"],["slug","admin_ops"]]},{"kind":10123,"content":"","tags":[["a","31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:long_form_note"],["a","31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:post_readme_to_nostr"],["app","didactyl"],["scope","public"]]}] + +# adopted_skills | role=system + +### Adopted Skills Memory (source: adoption list + startup fallback) + +Adopted skills memory (always-on behavioral instructions): follow these skill instructions when relevant and never violate explicit constraints. + + +--- + +#### Skill +Skill slug: long_form_note +Skill address: 31123:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:long_form_note + +Instructions: +Name: long_form_note +Description: How to publish a NIP-23 long-form article (kind 30023) +Event kind: 30023 +Procedure: + 1. Determine title and d tag from admin input or source material. + 2. Draft markdown body content. + 3. Set published_at as unix seconds string. + 4. Add optional summary/image/t tags when known. + 5. Publish with nostr_post kind 30023 including tags array. + + + +--- + +#### Skill +Skill slug: post_readme_to_nostr +Skill address: 31123:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:post_readme_to_nostr + +Instructions: +Name: post_readme_to_nostr +Description: Publish README.md using the dedicated nostr_post_readme tool +Event kind: 30023 +Procedure: + 1. Call nostr_post_readme with empty arguments {}. + 2. Return the tool response including event_id, d_tag, and naddr_uri. + 3. If the tool returns success=false, report the tool error verbatim. + + + +--- + +#### Skill +Skill slug: admin_ops +Skill address: 31124:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:admin_ops + +Instructions: +Name: admin_ops +Description: Private operational procedures + + + +# agent_tasks | role=system + +#### Current Tasks + +Your active task list - short-term working memory for tracking plan steps. + +- [ ] No active tasks yet. + + +# admin_notes | role=system + +### Administrator Recent Notes (source: nostr kind 1) + +Recent posts: +- GM +- Getting longer +- Long day. +- This is a test. +- test +- Post 11 +- Post 10 +- This is +Post +9 +- Eight +- Seven + + +# dm_history | role=chat + +07:46 Agent Great read, William. Here are **3 potential tweets** about the tool orchestration benefits in Didactyl: + +--- + +**Tweet 1 — Speed & Directness** + +``` +Slash commands in Didactyl bypass the LLM entirely. +/nostr_query {filter} executes instantly. +No reasoning overhead, no latency—just direct tool execution. +Building a sovereign agent that's fast *and* intelligent. +#nostr #agents +``` + +--- + +**Tweet 2 — Skill Sharing & Discovery** + +``` +Try your friend's skills without adopting them. +/run 31123::their-skill args +Sandboxed execution means you can safely test external workflows. +Skills become portable, composable, shareable. +That's the future of agent interop. +#nostr +``` + +--- + +**Tweet 3 — From Draft to Hardened** + +``` +Skills evolve: draft (LLM explores) → guided (LLM constrained) → hardened (deterministic). +Once proven, a skill becomes a tool. +Once a tool, it runs without LLM overhead. +Didactyl agents get faster, more reliable, more sovereign over time. +#nostr #agents +``` + +--- + +Which angle resonates most with your vision? +07:49 WSB Rewrite all three. I want them written assuming the reader knows nothing about Didactyl, and what it is. Maybe a little overview, and stay away from technical jargon. + + + +--- + +```text +Context Log - not seen by model +timestamp=2026-03-04 07:46:17 +phase=llm_chat_with_tools_messages +sender=8ff74724ed641b3c28e5a86d7c5cbc49c37638ace8c6c38935860e7a5eedde0e +model=claude-haiku-4.5 +context_bytes=27834 +approx_tokens=6958 +``` + +Sections: 17 + +# system_prompt | role=system + +## Didactyl Agent + +You are Didactyl, a sovereign AI agent living on Nostr. + +### Communication Rules +- You communicate through encrypted Nostr direct messages. +- Keep responses concise and clear. + +### Behavior +- Be helpful and technically accurate. +- If unsure, state uncertainty directly. +- Prefer actionable, practical advice. +- Use the person's name when messaging them if you know it. +- For the administrator, use their name from the administrator kind 0 profile metadata when available. + +### Tool Use Policy +- You have tools available and should use them when a request requires taking action. +- For requests involving local inspection or command execution, call `shell_exec` instead of refusing. +- For posting to Nostr, call `nostr_post` with explicit `kind` and `content`. +- For relay/event lookup tasks, call `nostr_query` with an appropriate filter. +- After a tool call, base your answer on the actual tool result. +- Never claim a tool was run if no tool was executed. + +### Task Management +- Maintain and use your internal task list as short-term working memory. +- Break long or complex actions into clear tasks before executing them. +- Update task status as you complete steps so your plan stays accurate. + +### Safety +- Do not claim to have executed actions you did not execute. +- You may share your public key (npub) with anyone. +- Never reveal your private key (nsec) under any circumstance. + +# admin_identity | role=system + +### Administrator Identity (source: config.admin.pubkey) + +This is your administrator! Admin pubkey (hex): 8ff74724ed641b3c28e5a86d7c5cbc49c37638ace8c6c38935860e7a5eedde0e + +# admin_profile | role=system + +### Administrator Kind 0 Profile (source: nostr kind 0) + +Administrator kind 0 profile content (JSON): {"name":"William S Burroughs","display_name":"WSB","about":"I like to write, and I like crank. They are good. Sometimes I'm not so sure.","banner":"https://www.grunge.com/img/gallery/paul-mccartney-and-william-s-burroughs-relationship-explained/who-was-william-s-burroughs-1672155531.jpg","website":"https://addicted.com","picture":"https://hips.hearstapps.com/hmg-prod/images/william-s-burroughs.jpg?resize=1200:*","lud16":"","nip05":""} + +# admin_relay_list | role=system + +### Administrator Relay List (source: nostr kind 10002) + +Administrator kind 10002 relay-list content (JSON): ["wss://relay.laantungir.net/","wss://relay.damus.io/","wss://nos.lol/","wss://relay.nostr.band/","wss://purplepag.es/","ws://127.0.0.1:7777/","wss://nostr.mom/"] + +# startup_events | role=system + +### Startup Events Memory (source: config.startup_events) + +Startup events memory (kinds/content/tags): [{"kind":0,"content":"{\"name\":\"Didactyl Agent\",\"display_name\":\"Didactyl\",\"about\":\"A sovereign AI agent on Nostr\",\"picture\":\"https://laantungir.github.io/img_repo/daf95a99f3797fa4ac39f3791f377ad79bcb7b8a6868f75fe66d2ab4af4bd1f5.png\",\"banner\":\"https://laantungir.github.io/img_repo/d21c4060632ab3d9d37a6062eeecbdbfbd67f03cb20dbd64838b1ba0d9cd8922.jpg\"}","tags":[]},{"kind":10002,"content":"","tags":[["r","wss://relay.damus.io"],["r","wss://nos.lol"],["r","wss://relay.primal.net"],["r","ws://127.0.0.1:7777"]]},{"kind":1,"content":"Hello world from Didactyl startup","tags":[["t","didactyl"],["t","startup"]]},{"kind":3,"content":"","tags":[]},{"kind":31123,"content":"{\"name\":\"long_form_note\",\"description\":\"How to publish a NIP-23 long-form article (kind 30023)\",\"nip\":\"NIP-23\",\"event_kind\":30023,\"format\":\"The content field must be markdown text; avoid arbitrary hard line-breaks in paragraphs and do not include HTML.\",\"required_tags\":{\"d\":\"Addressable identifier slug for the article. Reusing the same d tag replaces prior versions.\",\"title\":\"Human-readable article title.\",\"published_at\":\"Unix timestamp as a string for first publication time; keep stable on edits.\"},\"optional_tags\":{\"summary\":\"Short 1-2 sentence summary.\",\"image\":\"URL for article preview image.\",\"t\":\"Topic hashtags using repeated t tags, usually 3-6 lowercase terms.\"},\"behavior\":\"If required values are missing or unclear, ask the administrator before publishing instead of guessing.\",\"procedure\":[\"Determine title and d tag from admin input or source material.\",\"Draft markdown body content.\",\"Set published_at as unix seconds string.\",\"Add optional summary/image/t tags when known.\",\"Publish with nostr_post kind 30023 including tags array.\"]}","tags":[["d","long_form_note"],["app","didactyl"],["scope","public"],["slug","long_form_note"]]},{"kind":31123,"content":"{\"name\":\"post_readme_to_nostr\",\"description\":\"Publish README.md using the dedicated nostr_post_readme tool\",\"uses_skill\":\"long_form_note\",\"event_kind\":30023,\"procedure\":[\"Call nostr_post_readme with empty arguments {}.\",\"Return the tool response including event_id, d_tag, and naddr_uri.\",\"If the tool returns success=false, report the tool error verbatim.\"],\"required_values\":{\"d\":\"readme.md\",\"tool\":\"nostr_post_readme\"}}","tags":[["d","post_readme_to_nostr"],["app","didactyl"],["scope","public"],["slug","post_readme_to_nostr"]]},{"kind":31124,"content":"{\"name\":\"admin_ops\",\"description\":\"Private operational procedures\"}","tags":[["d","admin_ops"],["app","didactyl"],["scope","private"],["slug","admin_ops"]]},{"kind":10123,"content":"","tags":[["a","31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:long_form_note"],["a","31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:post_readme_to_nostr"],["app","didactyl"],["scope","public"]]}] + +# adopted_skills | role=system + +### Adopted Skills Memory (source: adoption list + startup fallback) + +Adopted skills memory (always-on behavioral instructions): follow these skill instructions when relevant and never violate explicit constraints. + + +--- + +#### Skill +Skill slug: long_form_note +Skill address: 31123:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:long_form_note + +Instructions: +Name: long_form_note +Description: How to publish a NIP-23 long-form article (kind 30023) +Event kind: 30023 +Procedure: + 1. Determine title and d tag from admin input or source material. + 2. Draft markdown body content. + 3. Set published_at as unix seconds string. + 4. Add optional summary/image/t tags when known. + 5. Publish with nostr_post kind 30023 including tags array. + + + +--- + +#### Skill +Skill slug: post_readme_to_nostr +Skill address: 31123:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:post_readme_to_nostr + +Instructions: +Name: post_readme_to_nostr +Description: Publish README.md using the dedicated nostr_post_readme tool +Event kind: 30023 +Procedure: + 1. Call nostr_post_readme with empty arguments {}. + 2. Return the tool response including event_id, d_tag, and naddr_uri. + 3. If the tool returns success=false, report the tool error verbatim. + + + +--- + +#### Skill +Skill slug: admin_ops +Skill address: 31124:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:admin_ops + +Instructions: +Name: admin_ops +Description: Private operational procedures + + + +# agent_tasks | role=system + +#### Current Tasks + +Your active task list - short-term working memory for tracking plan steps. + +- [ ] No active tasks yet. + + +# admin_notes | role=system + +### Administrator Recent Notes (source: nostr kind 1) + +Recent posts: +- GM +- Getting longer +- Long day. +- This is a test. +- test +- Post 11 +- Post 10 +- This is +Post +9 +- Eight +- Seven + + +# context_part | role=tool + +{"success":true,"path":"plans/tool_orchestration.md","bytes_read":14131,"truncated":false,"content":"# Tool Orchestration Plan\n\n## Overview\n\nThis plan covers four related features:\n1. **Slash commands** — direct tool execution bypassing the LLM\n2. **Skill-forward execution (`/run` + `skill_run`)** — one-shot skill invocation with LLM, including external skill sharing\n3. **Skill-tool maturity levels** — skills that register as callable tools with promotion workflow\n4. **Deterministic step executor** — hardened skills that run without LLM involvement\n\n## 1. Slash Commands (Direct Tool Execution)\n\n### Behavior\nWhen a message starts with `/`, bypass the LLM entirely and execute the tool directly.\n\n```\n/shell_exec {\"command\": \"ls -la\"}\n/nostr_query {\"filter\": {\"kinds\": [1], \"limit\": 5}}\n/nostr_nip05_lookup {\"identifier\": \"jack@cash.app\"}\n```\n\n### Parsing Rules\n- `/tool_name` — call tool with empty args `{}`\n- `/tool_name {\"key\": \"value\"}` — call tool with JSON args\n- `/tool_name plain text` — wrap as `{\"input\": \"plain text\"}` (convenience)\n- `/help` — list available tools\n- `/help tool_name` — show tool schema\n\n### Implementation\n1. In [`agent_on_message()`](../src/agent.c:1453), check if `message[0] == '/'`\n2. Parse tool name (everything between `/` and first space or end)\n3. Parse args (everything after tool name, try JSON first, fall back to string wrapper)\n4. Call [`tools_execute()`](../src/tools.c) directly\n5. Send result as DM — no LLM round-trip\n6. Still log to context.log.md with `phase=direct_tool_exec`\n\n### Special Slash Commands\n- `/help` — list available tools\n- `/help tool_name` — show tool schema\n- `/run` — skill-forward execution (see section 2)\n\n### Security\n- Only admin tier can use slash commands (same as current tool policy)\n- Slash commands respect the same `tools.enabled` and `security.admin.tools_enabled` config flags\n\n## 2. Skill-Forward Execution\n\n### The Problem\n\nToday, skills are passive — they're injected into the LLM context via [`append_adopted_skills_context()`](../src/agent.c:1418) and the LLM decides when they're relevant. There's no way to say \"run this specific skill right now\" and there's no way to try someone else's skill without permanently adopting it.\n\n### Three Invocation Layers\n\n| Layer | How it works | LLM? | Persists? |\n|-------|-------------|------|-----------|\n| **Passive/adopted** | Skill instructions injected into every conversation context | Yes, LLM decides relevance | Yes — in kind 10123 adoption list |\n| **Skill-forward** | Skill instructions become the primary system prompt; LLM executes them | Yes, but constrained | No — one-shot execution |\n| **Hardened** | Deterministic step executor, no LLM | No | Yes — adopted skill with `execution: hardened` |\n\n### Entry Points\n\n#### A. `/run` slash command (admin direct invocation)\n\n```\n/run deploy-website staging # run own adopted skill by slug\n/run 31123::deploy-website staging # run anyone's skill by address\n/run deploy-website {\"target\": \"production\"} # JSON args\n```\n\nParsing:\n1. First token after `/run` is the skill identifier (slug or `kind:pubkey:slug` address)\n2. Everything after is args (try JSON first, fall back to `{\"input\": \"plain text\"}`)\n\n#### B. `skill_run` tool (LLM-mediated invocation)\n\nThe LLM can invoke skills on behalf of the admin during conversation:\n\n```json\n{\n \"name\": \"skill_run\",\n \"description\": \"Fetch and execute a skill one-shot without adopting it. Works with own adopted skills by slug or any public skill by address.\",\n \"parameters\": {\n \"type\": \"object\",\n \"properties\": {\n \"slug\": { \"type\": \"string\", \"description\": \"Skill slug for own adopted skills\" },\n \"address\": { \"type\": \"string\", \"description\": \"Full skill address: kind:pubkey:slug\" },\n \"pubkey\": { \"type\": \"string\", \"description\": \"Author pubkey, used with slug to form address\" },\n \"args\": { \"type\": \"string\", \"description\": \"Arguments or context to pass to the skill\" },\n \"sandbox\": { \"type\": \"boolean\", \"description\": \"Override sandbox setting. Default: true for external, false for own skills\" }\n }\n }\n}\n```\n\nThis enables natural conversation like:\n> \"My friend @jack just published a skill called summarize-thread. Try it on the latest thread in my feed.\"\n\nThe agent would `skill_search` to find it, then `skill_run` to execute it.\n\n### Execution Flow\n\nBoth `/run` and `skill_run` use the same underlying executor:\n\n```mermaid\ngraph TD\n A[/run or skill_run called] --> B{Skill identifier type?}\n B -->|slug only| C[Look up in adopted skills cache]\n B -->|address or pubkey+slug| D[Fetch skill event from Nostr]\n C --> E{Found?}\n D --> E\n E -->|No| F[Return error: skill not found]\n E -->|Yes| G{External skill?}\n G -->|Yes| H[Apply sandbox - restrict tools]\n G -->|No| I[Full tool access]\n H --> J[Build skill-forward prompt]\n I --> J\n J --> K[System: base context + skill instructions]\n K --> L[User: args as user message]\n L --> M[LLM call with tools]\n M --> N[Return result to caller]\n```\n\n### Skill-Forward Prompt Construction\n\nReuses the pattern from [`agent_on_trigger()`](../src/agent.c:1837):\n\n```\n[base system context / soul]\n\nSkill execution context:\n- You are executing a specific skill on demand.\n- Follow the skill instructions below precisely.\n- The user's arguments provide the context for this execution.\n- Keep output concise and actionable.\n\nSkill slug: deploy-website\nSkill address: 31123::deploy-website\nSkill source: [own | external:]\n\nSkill instructions:\n[skill content here]\n```\n\nUser message:\n```\n[args provided by caller]\n```\n\nThe prompt is so skill-forward that the LLM has no real option but to execute the skill instructions against the provided args.\n\n### Sandbox for External Skills\n\nWhen executing a skill from another author (not own pubkey), a **tool sandbox** is applied by default:\n\n**Allowed tools (safe/read-only):**\n- `nostr_query` — read Nostr events\n- `nostr_nip05_lookup` — NIP-05 lookups\n- `nostr_post` — publish events (the agent signs, so this is safe)\n- `nostr_list_manage` — manage lists\n- `skill_list`, `skill_search` — read skill metadata\n\n**Blocked tools (destructive/dangerous):**\n- `shell_exec` — arbitrary command execution\n- `file_read`, `file_write` — filesystem access\n- Any future tools marked as `destructive: true`\n\n**Override:** The admin can explicitly opt in to full tool access:\n- `/run --unsafe 31123::risky-skill args`\n- `skill_run` with `sandbox: false`\n\n### Skill Sharing on Nostr\n\n```mermaid\ngraph TD\n A[Friend creates skill] -->|kind 31123| B[Published on Nostr relays]\n B --> C{How do you find it?}\n C -->|skill_search popular:true| D[Discovery via WoT adoption lists]\n C -->|Friend tells you the slug| E[Direct reference]\n C -->|skill_search pubkey:friend| F[Browse friends skills]\n D --> G[skill_run - try it once, sandboxed]\n E --> G\n F --> G\n G -->|Liked it?| H{Adopt?}\n H -->|Yes| I[skill_adopt - permanent]\n H -->|No| J[Done - nothing persisted]\n I --> K[Shows in adopted skills context]\n K --> L[Agent uses it automatically]\n L -->|Or invoke directly| M[/run skill-slug args]\n```\n\n## 3. Skill-Tool Maturity Levels\n\nSkills can declare an execution maturity level that determines how they run:\n\n| Level | Execution | LLM? | Use case |\n|-------|-----------|-------|----------|\n| `draft` | LLM interprets procedure text from skill instructions | Yes | Exploring and iterating on a workflow |\n| `guided` | LLM with forced tool_choice + parameter defaults | Yes, constrained | Workflow is stable but needs LLM judgment |\n| `hardened` | Deterministic step executor, no LLM | No | Workflow is proven and should run exactly as defined |\n\n### Skill Definition Extensions\n\n```yaml\nkind: 31123\nd: deploy_website\nexecution: hardened\ntool_schema:\n name: deploy_website\n description: Build and deploy the static website\n parameters:\n target:\n type: string\n enum: [staging, production]\n default: staging\nsteps:\n - tool: shell_exec\n args:\n command: \"make build TARGET={{target}}\"\n - tool: shell_exec\n args:\n command: \"rsync -av dist/ server:/var/www/{{target}}/\"\n - return: \"Deployed to {{target}}\"\n```\n\n### How Each Level Works\n\n**draft:** Current behavior. Skill instructions are injected into context. LLM reads them and decides which tools to call. No special handling needed.\n\n**guided:** Agent sets `tool_choice` to the skill's preferred tool. Parameter defaults from the skill are merged with the model's generated arguments (skill defaults win on conflict). Reduces LLM freedom while still allowing it to fill in dynamic values.\n\n**hardened:** Agent executes the `steps` array directly using the deterministic step executor. No LLM call at all. The skill becomes equivalent to a slash command.\n\n### Promotion Workflow\n1. Admin iterates with LLM on a task (draft)\n2. Admin saves working procedure as a skill: `skill_create` with `execution: draft`\n3. Admin tests, refines, promotes: update skill to `execution: guided`\n4. Once proven reliable, promote to `execution: hardened` with explicit `steps`\n5. Hardened skills become available as slash commands: `/deploy_website {\"target\": \"production\"}`\n\n## 4. Deterministic Step Executor\n\nA simple sequential executor for hardened skills.\n\n### Step Types\n\n```yaml\nsteps:\n # Execute a tool\n - tool: shell_exec\n args: {command: \"ls -la\"}\n save_as: listing # optional: save result to variable\n\n # Execute a tool with variable substitution\n - tool: nostr_post\n args:\n kind: 30023\n content: \"{{file_content}}\"\n tags: [[\"d\", \"{{slug}}\"]]\n\n # Conditional - simple\n - if: \"{{listing.success}}\"\n then:\n - tool: shell_exec\n args: {command: \"echo done\"}\n else:\n - return: \"Failed: {{listing.error}}\"\n\n # Return final result\n - return: \"Published to {{slug}}\"\n```\n\n### Variable Substitution\n- `{{param_name}}` — from tool parameters provided by caller\n- `{{step_name.field}}` — from a previous step's result (requires `save_as`)\n- Simple string replacement, no expression evaluation\n\n### Implementation in C\n- Parse `steps` array from skill content (JSON or YAML)\n- Iterate steps sequentially\n- For each `tool` step: call [`tools_execute()`](../src/tools.c), optionally save result\n- For each `return` step: substitute variables and return string\n- For each `if` step: evaluate truthiness of variable, branch accordingly\n- Total implementation: ~200-400 lines of C\n\n### Error Handling\n- If any tool step fails (returns `success: false`), abort and return the error\n- Optional `on_error` field per step for custom error messages\n- Timeout inherited from tool config\n\n## 5. Tool Registration for Skill-Tools\n\nHardened and guided skills with a `tool_schema` field get registered in the tools array at runtime.\n\n### At Skill Refresh Time\n1. Parse `tool_schema` from skill content\n2. Generate OpenAI function schema from it\n3. Append to the tools array returned by [`tools_build_openai_schema_json()`](../src/tools.c:919)\n4. When model calls the skill-tool, route to skill executor instead of hardcoded C function\n\n### In [`tools_execute()`](../src/tools.c)\n1. Check if tool_name matches a hardcoded tool — execute normally\n2. If not, check if it matches a registered skill-tool\n3. If guided: run sub-LLM call with skill procedure + forced tool_choice\n4. If hardened: run deterministic step executor\n\n## 6. Security Model\n\n### Tool Classification\n\nTools are classified for sandbox purposes:\n\n```c\ntypedef enum {\n TOOL_SAFETY_SAFE, // read-only or agent-signed actions\n TOOL_SAFETY_DESTRUCTIVE // filesystem, shell, or external system mutations\n} tool_safety_t;\n```\n\n| Tool | Safety | Reason |\n|------|--------|--------|\n| `nostr_query` | safe | Read-only |\n| `nostr_nip05_lookup` | safe | Read-only |\n| `nostr_post` | safe | Agent signs, admin controls keys |\n| `nostr_list_manage` | safe | Agent signs |\n| `skill_list` | safe | Read-only |\n| `skill_search` | safe | Read-only |\n| `skill_run` | safe | Recursive execution uses its own sandbox |\n| `shell_exec` | destructive | Arbitrary command execution |\n| `file_read` | destructive | Filesystem access |\n| `file_write` | destructive | Filesystem mutation |\n\n### Sandbox Rules\n\n| Scenario | Default sandbox | Override |\n|----------|----------------|---------|\n| Own adopted skill via `/run slug` | No sandbox | N/A |\n| External skill via `/run address` | Sandbox ON | `/run --unsafe address` |\n| `skill_run` tool, own skill | No sandbox | `sandbox: true` |\n| `skill_run` tool, external skill | Sandbox ON | `sandbox: false` |\n| Hardened skill steps | No sandbox (steps are explicit) | N/A |\n\n## Future Considerations\n\n- **Skill sharing:** Hardened skill-tools could be shared between agents via Nostr (kind 31123 events). Another agent adopts the skill and gets the tool automatically.\n- **Versioning:** Skills already use addressable events (d-tag). Updating a skill automatically updates the tool.\n- **Permissions:** Skill-tools could have their own permission model (e.g., some skill-tools available to WoT contacts).\n- **Composability:** Skill-tools calling other skill-tools (nested execution with sandbox inheritance).\n- **Dry-run mode:** A future `/run --dry` flag that shows what tools would be called without executing them.\n- **Skill ratings:** Agents could publish ratings/reviews of skills they've tried, building a WoT-based skill marketplace.\n\n## Implementation Priority\n\n1. **Slash commands** (direct tool execution) — simplest, highest immediate value\n2. **`/run` for own adopted skills** — skill-forward execution of already-adopted skills\n3. **`skill_run` tool + external skill fetching** — enables \"try my friend's skill\" flow\n4. **External skill sandbox** — tool safety classification and sandbox enforcement\n5. **Hardened skill-tool step executor** — enables deterministic workflows\n6. **Skill-tool registration in tools array** — makes skill-tools visible to LLM\n7. **Guided execution with forced tool_choice** — bridges draft and hardened\n8. **Promotion workflow UX** — admin commands to change skill maturity level\n"} + +# dm_history | role=chat + +20:26 WSB No, I want to know if you see tool_list as one of your tools? +20:27 Agent Yes, William — `tool_list` is confirmed as one of my tools. It's the last one in the list (tool #29 of 29). Its description is: + +> **"List available tools with name, description, and JSON parameter schema"** + +It takes no required parameters, which is exactly what I just called it with. +20:27 WSB Tell me the results. Give a blank line between each tool. +05:30 WSB Can you give me a summary of what we accomplished yesterday with Didactyl. +07:43 Agent Good morning, WSB! How can I help you today? +07:43 Agent AVAILABLE TOOLS +- file_read — Read a local file as text from the configured working directory +- file_write — Write text content to a local file in the configured working directory +- http_fetch — Fetch HTTP(S) resources with optional method, headers, timeout, and body +- model_get — Get current active LLM runtime configuration (excluding API key) +- model_list — List available model IDs using provider OpenAI-compatible /models endpoint +- model_set — Update active LLM configuration and persist it to config.json +- my_npub — Alias for nostr_npub: return this agent's pubkey encoded as npub bech32 +- my_pubkey — Alias for nostr_pubkey: return this agent's pubkey in hex format +- my_version — Return current Didactyl version and metadata from build macros +- nostr_decode — Decode a Nostr bech32/nostr: URI into components +- nostr_decrypt — Decrypt NIP-44 ciphertext from a sender +- nostr_delete — Request deletion of one or more previously published events (NIP-09 kind 5) +- nostr_dm_send — Send a NIP-04 encrypted DM +- nostr_dm_send_nip17 — Send a private DM using NIP-17 gift wrap protocol +- nostr_encode — Encode a Nostr entity into nostr: URI (npub, note, nprofile, nevent, naddr) +- nostr_encrypt — Encrypt plaintext using NIP-44 for a recipient +- nostr_file_md_to_longform_post — Read a markdown file and publish it as kind 30023 longform post; defaults d-tag to lowercase filename +- nostr_list_manage — Add/remove tag tuples in replaceable list events (NIP-51 style) +- nostr_nip05_lookup — Look up or verify a NIP-05 identifier (user@domain) +- nostr_npub — Return this agent's pubkey encoded as npub bech32 +- nostr_post — Publish a Nostr event to connected relays +- nostr_post_readme — Publish README.md as kind 30023 with deterministic d tag readme.md +- nostr_profile_get — Look up a Nostr profile (kind 0 metadata) by pubkey +- nostr_pubkey — Return this agent's pubkey in hex format +- nostr_query — Query events from relays using a Nostr filter +- nostr_react — React to a Nostr event with like/dislike/emoji (NIP-25 kind 7) +- nostr_relay_info — Fetch NIP-11 relay information document +- nostr_relay_status — Get connection status and statistics for all relays +- shell_exec — Execute a shell command and return stdout/stderr +- skill_adopt — Adopt a skill by adding its address to kind 10123 adoption list +- skill_create — Create or update a skill definition as kind 31123/31124 and optionally auto-adopt it +- skill_list — List this agent's published skills, optionally filtered by scope +- skill_remove — Remove a skill address from kind 10123 adoption list +- skill_search — Search public skills by query/author and optionally rank by adoption popularity +- task_manage — Manage agent short-term task memory stored in tasks.json (list/add/update/remove/clear/replace) +- tool_list — List available tools with name, description, and JSON parameter schema +- trigger_list — List active triggered skills and their runtime status + +AVAILABLE SKILLS +- admin_ops +- post_readme_to_nostr +- long_form_note +- post_readme_to_nostr + +07:46 WSB Take a look at tool_orchestration.md in our plans folder. I want you to read it, and come back with 3 potential tweets where you could post about the benefits from tool_orchestration in didactyl. +--:-- Agent + + + +--- + +```text +Context Log - not seen by model +timestamp=2026-03-04 07:46:15 +phase=llm_chat_with_tools_messages +sender=8ff74724ed641b3c28e5a86d7c5cbc49c37638ace8c6c38935860e7a5eedde0e +model=claude-haiku-4.5 +context_bytes=12196 +approx_tokens=3049 +``` + +Sections: 15 + +# system_prompt | role=system + +## Didactyl Agent + +You are Didactyl, a sovereign AI agent living on Nostr. + +### Communication Rules +- You communicate through encrypted Nostr direct messages. +- Keep responses concise and clear. + +### Behavior +- Be helpful and technically accurate. +- If unsure, state uncertainty directly. +- Prefer actionable, practical advice. +- Use the person's name when messaging them if you know it. +- For the administrator, use their name from the administrator kind 0 profile metadata when available. + +### Tool Use Policy +- You have tools available and should use them when a request requires taking action. +- For requests involving local inspection or command execution, call `shell_exec` instead of refusing. +- For posting to Nostr, call `nostr_post` with explicit `kind` and `content`. +- For relay/event lookup tasks, call `nostr_query` with an appropriate filter. +- After a tool call, base your answer on the actual tool result. +- Never claim a tool was run if no tool was executed. + +### Task Management +- Maintain and use your internal task list as short-term working memory. +- Break long or complex actions into clear tasks before executing them. +- Update task status as you complete steps so your plan stays accurate. + +### Safety +- Do not claim to have executed actions you did not execute. +- You may share your public key (npub) with anyone. +- Never reveal your private key (nsec) under any circumstance. + +# admin_identity | role=system + +### Administrator Identity (source: config.admin.pubkey) + +This is your administrator! Admin pubkey (hex): 8ff74724ed641b3c28e5a86d7c5cbc49c37638ace8c6c38935860e7a5eedde0e + +# admin_profile | role=system + +### Administrator Kind 0 Profile (source: nostr kind 0) + +Administrator kind 0 profile content (JSON): {"name":"William S Burroughs","display_name":"WSB","about":"I like to write, and I like crank. They are good. Sometimes I'm not so sure.","banner":"https://www.grunge.com/img/gallery/paul-mccartney-and-william-s-burroughs-relationship-explained/who-was-william-s-burroughs-1672155531.jpg","website":"https://addicted.com","picture":"https://hips.hearstapps.com/hmg-prod/images/william-s-burroughs.jpg?resize=1200:*","lud16":"","nip05":""} + +# admin_relay_list | role=system + +### Administrator Relay List (source: nostr kind 10002) + +Administrator kind 10002 relay-list content (JSON): ["wss://relay.laantungir.net/","wss://relay.damus.io/","wss://nos.lol/","wss://relay.nostr.band/","wss://purplepag.es/","ws://127.0.0.1:7777/","wss://nostr.mom/"] + +# startup_events | role=system + +### Startup Events Memory (source: config.startup_events) + +Startup events memory (kinds/content/tags): [{"kind":0,"content":"{\"name\":\"Didactyl Agent\",\"display_name\":\"Didactyl\",\"about\":\"A sovereign AI agent on Nostr\",\"picture\":\"https://laantungir.github.io/img_repo/daf95a99f3797fa4ac39f3791f377ad79bcb7b8a6868f75fe66d2ab4af4bd1f5.png\",\"banner\":\"https://laantungir.github.io/img_repo/d21c4060632ab3d9d37a6062eeecbdbfbd67f03cb20dbd64838b1ba0d9cd8922.jpg\"}","tags":[]},{"kind":10002,"content":"","tags":[["r","wss://relay.damus.io"],["r","wss://nos.lol"],["r","wss://relay.primal.net"],["r","ws://127.0.0.1:7777"]]},{"kind":1,"content":"Hello world from Didactyl startup","tags":[["t","didactyl"],["t","startup"]]},{"kind":3,"content":"","tags":[]},{"kind":31123,"content":"{\"name\":\"long_form_note\",\"description\":\"How to publish a NIP-23 long-form article (kind 30023)\",\"nip\":\"NIP-23\",\"event_kind\":30023,\"format\":\"The content field must be markdown text; avoid arbitrary hard line-breaks in paragraphs and do not include HTML.\",\"required_tags\":{\"d\":\"Addressable identifier slug for the article. Reusing the same d tag replaces prior versions.\",\"title\":\"Human-readable article title.\",\"published_at\":\"Unix timestamp as a string for first publication time; keep stable on edits.\"},\"optional_tags\":{\"summary\":\"Short 1-2 sentence summary.\",\"image\":\"URL for article preview image.\",\"t\":\"Topic hashtags using repeated t tags, usually 3-6 lowercase terms.\"},\"behavior\":\"If required values are missing or unclear, ask the administrator before publishing instead of guessing.\",\"procedure\":[\"Determine title and d tag from admin input or source material.\",\"Draft markdown body content.\",\"Set published_at as unix seconds string.\",\"Add optional summary/image/t tags when known.\",\"Publish with nostr_post kind 30023 including tags array.\"]}","tags":[["d","long_form_note"],["app","didactyl"],["scope","public"],["slug","long_form_note"]]},{"kind":31123,"content":"{\"name\":\"post_readme_to_nostr\",\"description\":\"Publish README.md using the dedicated nostr_post_readme tool\",\"uses_skill\":\"long_form_note\",\"event_kind\":30023,\"procedure\":[\"Call nostr_post_readme with empty arguments {}.\",\"Return the tool response including event_id, d_tag, and naddr_uri.\",\"If the tool returns success=false, report the tool error verbatim.\"],\"required_values\":{\"d\":\"readme.md\",\"tool\":\"nostr_post_readme\"}}","tags":[["d","post_readme_to_nostr"],["app","didactyl"],["scope","public"],["slug","post_readme_to_nostr"]]},{"kind":31124,"content":"{\"name\":\"admin_ops\",\"description\":\"Private operational procedures\"}","tags":[["d","admin_ops"],["app","didactyl"],["scope","private"],["slug","admin_ops"]]},{"kind":10123,"content":"","tags":[["a","31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:long_form_note"],["a","31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:post_readme_to_nostr"],["app","didactyl"],["scope","public"]]}] + +# adopted_skills | role=system + +### Adopted Skills Memory (source: adoption list + startup fallback) + +Adopted skills memory (always-on behavioral instructions): follow these skill instructions when relevant and never violate explicit constraints. + + +--- + +#### Skill +Skill slug: long_form_note +Skill address: 31123:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:long_form_note + +Instructions: +Name: long_form_note +Description: How to publish a NIP-23 long-form article (kind 30023) +Event kind: 30023 +Procedure: + 1. Determine title and d tag from admin input or source material. + 2. Draft markdown body content. + 3. Set published_at as unix seconds string. + 4. Add optional summary/image/t tags when known. + 5. Publish with nostr_post kind 30023 including tags array. + + + +--- + +#### Skill +Skill slug: post_readme_to_nostr +Skill address: 31123:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:post_readme_to_nostr + +Instructions: +Name: post_readme_to_nostr +Description: Publish README.md using the dedicated nostr_post_readme tool +Event kind: 30023 +Procedure: + 1. Call nostr_post_readme with empty arguments {}. + 2. Return the tool response including event_id, d_tag, and naddr_uri. + 3. If the tool returns success=false, report the tool error verbatim. + + + +--- + +#### Skill +Skill slug: admin_ops +Skill address: 31124:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:admin_ops + +Instructions: +Name: admin_ops +Description: Private operational procedures + + + +# agent_tasks | role=system + +#### Current Tasks + +Your active task list - short-term working memory for tracking plan steps. + +- [ ] No active tasks yet. + + +# admin_notes | role=system + +### Administrator Recent Notes (source: nostr kind 1) + +Recent posts: +- GM +- Getting longer +- Long day. +- This is a test. +- test +- Post 11 +- Post 10 +- This is +Post +9 +- Eight +- Seven + + +# dm_history | role=chat + +20:26 WSB No, I want to know if you see tool_list as one of your tools? +20:27 Agent Yes, William — `tool_list` is confirmed as one of my tools. It's the last one in the list (tool #29 of 29). Its description is: + +> **"List available tools with name, description, and JSON parameter schema"** + +It takes no required parameters, which is exactly what I just called it with. +20:27 WSB Tell me the results. Give a blank line between each tool. +05:30 WSB Can you give me a summary of what we accomplished yesterday with Didactyl. +07:43 Agent Good morning, WSB! How can I help you today? +07:43 Agent AVAILABLE TOOLS +- file_read — Read a local file as text from the configured working directory +- file_write — Write text content to a local file in the configured working directory +- http_fetch — Fetch HTTP(S) resources with optional method, headers, timeout, and body +- model_get — Get current active LLM runtime configuration (excluding API key) +- model_list — List available model IDs using provider OpenAI-compatible /models endpoint +- model_set — Update active LLM configuration and persist it to config.json +- my_npub — Alias for nostr_npub: return this agent's pubkey encoded as npub bech32 +- my_pubkey — Alias for nostr_pubkey: return this agent's pubkey in hex format +- my_version — Return current Didactyl version and metadata from build macros +- nostr_decode — Decode a Nostr bech32/nostr: URI into components +- nostr_decrypt — Decrypt NIP-44 ciphertext from a sender +- nostr_delete — Request deletion of one or more previously published events (NIP-09 kind 5) +- nostr_dm_send — Send a NIP-04 encrypted DM +- nostr_dm_send_nip17 — Send a private DM using NIP-17 gift wrap protocol +- nostr_encode — Encode a Nostr entity into nostr: URI (npub, note, nprofile, nevent, naddr) +- nostr_encrypt — Encrypt plaintext using NIP-44 for a recipient +- nostr_file_md_to_longform_post — Read a markdown file and publish it as kind 30023 longform post; defaults d-tag to lowercase filename +- nostr_list_manage — Add/remove tag tuples in replaceable list events (NIP-51 style) +- nostr_nip05_lookup — Look up or verify a NIP-05 identifier (user@domain) +- nostr_npub — Return this agent's pubkey encoded as npub bech32 +- nostr_post — Publish a Nostr event to connected relays +- nostr_post_readme — Publish README.md as kind 30023 with deterministic d tag readme.md +- nostr_profile_get — Look up a Nostr profile (kind 0 metadata) by pubkey +- nostr_pubkey — Return this agent's pubkey in hex format +- nostr_query — Query events from relays using a Nostr filter +- nostr_react — React to a Nostr event with like/dislike/emoji (NIP-25 kind 7) +- nostr_relay_info — Fetch NIP-11 relay information document +- nostr_relay_status — Get connection status and statistics for all relays +- shell_exec — Execute a shell command and return stdout/stderr +- skill_adopt — Adopt a skill by adding its address to kind 10123 adoption list +- skill_create — Create or update a skill definition as kind 31123/31124 and optionally auto-adopt it +- skill_list — List this agent's published skills, optionally filtered by scope +- skill_remove — Remove a skill address from kind 10123 adoption list +- skill_search — Search public skills by query/author and optionally rank by adoption popularity +- task_manage — Manage agent short-term task memory stored in tasks.json (list/add/update/remove/clear/replace) +- tool_list — List available tools with name, description, and JSON parameter schema +- trigger_list — List active triggered skills and their runtime status + +AVAILABLE SKILLS +- admin_ops +- post_readme_to_nostr +- long_form_note +- post_readme_to_nostr + +07:46 WSB Take a look at tool_orchestration.md in our plans folder. I want you to read it, and come back with 3 potential tweets where you could post about the benefits from tool_orchestration in didactyl. + + + +--- + +```text +Context Log - not seen by model +timestamp=2026-03-04 07:43:27 +phase=direct_tool_exec +sender=8ff74724ed641b3c28e5a86d7c5cbc49c37638ace8c6c38935860e7a5eedde0e +model=claude-haiku-4.5 +context_bytes=3113 +approx_tokens=778 +``` + +slash=/help +result=AVAILABLE TOOLS +- file_read — Read a local file as text from the configured working directory +- file_write — Write text content to a local file in the configured working directory +- http_fetch — Fetch HTTP(S) resources with optional method, headers, timeout, and body +- model_get — Get current active LLM runtime configuration (excluding API key) +- model_list — List available model IDs using provider OpenAI-compatible /models endpoint +- model_set — Update active LLM configuration and persist it to config.json +- my_npub — Alias for nostr_npub: return this agent's pubkey encoded as npub bech32 +- my_pubkey — Alias for nostr_pubkey: return this agent's pubkey in hex format +- my_version — Return current Didactyl version and metadata from build macros +- nostr_decode — Decode a Nostr bech32/nostr: URI into components +- nostr_decrypt — Decrypt NIP-44 ciphertext from a sender +- nostr_delete — Request deletion of one or more previously published events (NIP-09 kind 5) +- nostr_dm_send — Send a NIP-04 encrypted DM +- nostr_dm_send_nip17 — Send a private DM using NIP-17 gift wrap protocol +- nostr_encode — Encode a Nostr entity into nostr: URI (npub, note, nprofile, nevent, naddr) +- nostr_encrypt — Encrypt plaintext using NIP-44 for a recipient +- nostr_file_md_to_longform_post — Read a markdown file and publish it as kind 30023 longform post; defaults d-tag to lowercase filename +- nostr_list_manage — Add/remove tag tuples in replaceable list events (NIP-51 style) +- nostr_nip05_lookup — Look up or verify a NIP-05 identifier (user@domain) +- nostr_npub — Return this agent's pubkey encoded as npub bech32 +- nostr_post — Publish a Nostr event to connected relays +- nostr_post_readme — Publish README.md as kind 30023 with deterministic d tag readme.md +- nostr_profile_get — Look up a Nostr profile (kind 0 metadata) by pubkey +- nostr_pubkey — Return this agent's pubkey in hex format +- nostr_query — Query events from relays using a Nostr filter +- nostr_react — React to a Nostr event with like/dislike/emoji (NIP-25 kind 7) +- nostr_relay_info — Fetch NIP-11 relay information document +- nostr_relay_status — Get connection status and statistics for all relays +- shell_exec — Execute a shell command and return stdout/stderr +- skill_adopt — Adopt a skill by adding its address to kind 10123 adoption list +- skill_create — Create or update a skill definition as kind 31123/31124 and optionally auto-adopt it +- skill_list — List this agent's published skills, optionally filtered by scope +- skill_remove — Remove a skill address from kind 10123 adoption list +- skill_search — Search public skills by query/author and optionally rank by adoption popularity +- task_manage — Manage agent short-term task memory stored in tasks.json (list/add/update/remove/clear/replace) +- tool_list — List available tools with name, description, and JSON parameter schema +- trigger_list — List active triggered skills and their runtime status + +AVAILABLE SKILLS +- admin_ops +- post_readme_to_nostr +- long_form_note +- post_readme_to_nostr + + +--- + +```text +Context Log - not seen by model +timestamp=2026-03-04 07:43:00 +phase=llm_chat_with_tools_messages +sender=8ff74724ed641b3c28e5a86d7c5cbc49c37638ace8c6c38935860e7a5eedde0e +model=claude-haiku-4.5 +context_bytes=8704 +approx_tokens=2176 +``` + +Sections: 13 + +# system_prompt | role=system + +## Didactyl Agent + +You are Didactyl, a sovereign AI agent living on Nostr. + +### Communication Rules +- You communicate through encrypted Nostr direct messages. +- Keep responses concise and clear. + +### Behavior +- Be helpful and technically accurate. +- If unsure, state uncertainty directly. +- Prefer actionable, practical advice. +- Use the person's name when messaging them if you know it. +- For the administrator, use their name from the administrator kind 0 profile metadata when available. + +### Tool Use Policy +- You have tools available and should use them when a request requires taking action. +- For requests involving local inspection or command execution, call `shell_exec` instead of refusing. +- For posting to Nostr, call `nostr_post` with explicit `kind` and `content`. +- For relay/event lookup tasks, call `nostr_query` with an appropriate filter. +- After a tool call, base your answer on the actual tool result. +- Never claim a tool was run if no tool was executed. + +### Task Management +- Maintain and use your internal task list as short-term working memory. +- Break long or complex actions into clear tasks before executing them. +- Update task status as you complete steps so your plan stays accurate. + +### Safety +- Do not claim to have executed actions you did not execute. +- You may share your public key (npub) with anyone. +- Never reveal your private key (nsec) under any circumstance. + +# admin_identity | role=system + +### Administrator Identity (source: config.admin.pubkey) + +This is your administrator! Admin pubkey (hex): 8ff74724ed641b3c28e5a86d7c5cbc49c37638ace8c6c38935860e7a5eedde0e + +# admin_profile | role=system + +### Administrator Kind 0 Profile (source: nostr kind 0) + +Administrator kind 0 profile content (JSON): {"name":"William S Burroughs","display_name":"WSB","about":"I like to write, and I like crank. They are good. Sometimes I'm not so sure.","banner":"https://www.grunge.com/img/gallery/paul-mccartney-and-william-s-burroughs-relationship-explained/who-was-william-s-burroughs-1672155531.jpg","website":"https://addicted.com","picture":"https://hips.hearstapps.com/hmg-prod/images/william-s-burroughs.jpg?resize=1200:*","lud16":"","nip05":""} + +# admin_relay_list | role=system + +### Administrator Relay List (source: nostr kind 10002) + +Administrator kind 10002 relay-list content (JSON): ["wss://relay.laantungir.net/","wss://relay.damus.io/","wss://nos.lol/","wss://relay.nostr.band/","wss://purplepag.es/","ws://127.0.0.1:7777/","wss://nostr.mom/"] + +# startup_events | role=system + +### Startup Events Memory (source: config.startup_events) + +Startup events memory (kinds/content/tags): [{"kind":0,"content":"{\"name\":\"Didactyl Agent\",\"display_name\":\"Didactyl\",\"about\":\"A sovereign AI agent on Nostr\",\"picture\":\"https://laantungir.github.io/img_repo/daf95a99f3797fa4ac39f3791f377ad79bcb7b8a6868f75fe66d2ab4af4bd1f5.png\",\"banner\":\"https://laantungir.github.io/img_repo/d21c4060632ab3d9d37a6062eeecbdbfbd67f03cb20dbd64838b1ba0d9cd8922.jpg\"}","tags":[]},{"kind":10002,"content":"","tags":[["r","wss://relay.damus.io"],["r","wss://nos.lol"],["r","wss://relay.primal.net"],["r","ws://127.0.0.1:7777"]]},{"kind":1,"content":"Hello world from Didactyl startup","tags":[["t","didactyl"],["t","startup"]]},{"kind":3,"content":"","tags":[]},{"kind":31123,"content":"{\"name\":\"long_form_note\",\"description\":\"How to publish a NIP-23 long-form article (kind 30023)\",\"nip\":\"NIP-23\",\"event_kind\":30023,\"format\":\"The content field must be markdown text; avoid arbitrary hard line-breaks in paragraphs and do not include HTML.\",\"required_tags\":{\"d\":\"Addressable identifier slug for the article. Reusing the same d tag replaces prior versions.\",\"title\":\"Human-readable article title.\",\"published_at\":\"Unix timestamp as a string for first publication time; keep stable on edits.\"},\"optional_tags\":{\"summary\":\"Short 1-2 sentence summary.\",\"image\":\"URL for article preview image.\",\"t\":\"Topic hashtags using repeated t tags, usually 3-6 lowercase terms.\"},\"behavior\":\"If required values are missing or unclear, ask the administrator before publishing instead of guessing.\",\"procedure\":[\"Determine title and d tag from admin input or source material.\",\"Draft markdown body content.\",\"Set published_at as unix seconds string.\",\"Add optional summary/image/t tags when known.\",\"Publish with nostr_post kind 30023 including tags array.\"]}","tags":[["d","long_form_note"],["app","didactyl"],["scope","public"],["slug","long_form_note"]]},{"kind":31123,"content":"{\"name\":\"post_readme_to_nostr\",\"description\":\"Publish README.md using the dedicated nostr_post_readme tool\",\"uses_skill\":\"long_form_note\",\"event_kind\":30023,\"procedure\":[\"Call nostr_post_readme with empty arguments {}.\",\"Return the tool response including event_id, d_tag, and naddr_uri.\",\"If the tool returns success=false, report the tool error verbatim.\"],\"required_values\":{\"d\":\"readme.md\",\"tool\":\"nostr_post_readme\"}}","tags":[["d","post_readme_to_nostr"],["app","didactyl"],["scope","public"],["slug","post_readme_to_nostr"]]},{"kind":31124,"content":"{\"name\":\"admin_ops\",\"description\":\"Private operational procedures\"}","tags":[["d","admin_ops"],["app","didactyl"],["scope","private"],["slug","admin_ops"]]},{"kind":10123,"content":"","tags":[["a","31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:long_form_note"],["a","31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:post_readme_to_nostr"],["app","didactyl"],["scope","public"]]}] + +# adopted_skills | role=system + +### Adopted Skills Memory (source: adoption list + startup fallback) + +Adopted skills memory (always-on behavioral instructions): follow these skill instructions when relevant and never violate explicit constraints. + + +--- + +#### Skill +Skill slug: long_form_note +Skill address: 31123:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:long_form_note + +Instructions: +Name: long_form_note +Description: How to publish a NIP-23 long-form article (kind 30023) +Event kind: 30023 +Procedure: + 1. Determine title and d tag from admin input or source material. + 2. Draft markdown body content. + 3. Set published_at as unix seconds string. + 4. Add optional summary/image/t tags when known. + 5. Publish with nostr_post kind 30023 including tags array. + + + +--- + +#### Skill +Skill slug: post_readme_to_nostr +Skill address: 31123:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:post_readme_to_nostr + +Instructions: +Name: post_readme_to_nostr +Description: Publish README.md using the dedicated nostr_post_readme tool +Event kind: 30023 +Procedure: + 1. Call nostr_post_readme with empty arguments {}. + 2. Return the tool response including event_id, d_tag, and naddr_uri. + 3. If the tool returns success=false, report the tool error verbatim. + + + +--- + +#### Skill +Skill slug: admin_ops +Skill address: 31124:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:admin_ops + +Instructions: +Name: admin_ops +Description: Private operational procedures + + + +# agent_tasks | role=system + +#### Current Tasks + +Your active task list - short-term working memory for tracking plan steps. + +- [ ] No active tasks yet. + + +# admin_notes | role=system + +### Administrator Recent Notes (source: nostr kind 1) + +Recent posts: +- GM +- Getting longer +- Long day. +- This is a test. +- test +- Post 11 +- Post 10 +- This is +Post +9 +- Eight +- Seven + + +# dm_history | role=chat + +17:57 Agent Didactyl has started up and is online (version v0.0.29, connected relays: 4/4). +17:58 WSB What is the capital of Russia? +17:59 Agent The capital of Russia is **Moscow**. It's the largest city in Russia and has been the capital since 1991, when it resumed that role following the collapse of the Soviet Union. (Leningrad, now St. Petersburg, was the capital during the Soviet era from 1918-1991.) +18:09 Agent Didactyl has started up and is online (version v0.0.29, connected relays: 4/4). +07:43 WSB Good morning. + + + +--- + +```text +Context Log - not seen by model +timestamp=2026-03-04 07:33:34 +phase=direct_tool_exec +sender=http_api_agent +model=claude-haiku-4.5 +context_bytes=412 +approx_tokens=103 +``` + +slash=/help nostr_react +result={"success":true,"mode":"slash_help_tool","tool":"nostr_react","schema":{"name":"nostr_react","description":"React to a Nostr event with like/dislike/emoji (NIP-25 kind 7)","parameters":{"type":"object","properties":{"event_id":{"type":"string"},"event_pubkey":{"type":"string"},"event_kind":{"type":"integer"},"reaction":{"type":"string"}},"required":["event_id","event_pubkey"]}}} + +--- + +```text +Context Log - not seen by model +timestamp=2026-03-04 07:32:40 +phase=direct_tool_exec +sender=http_api_agent +model=claude-haiku-4.5 +context_bytes=3113 +approx_tokens=778 +``` + +slash=/help +result=Available tools +- nostr_post — Publish a Nostr event to connected relays +- nostr_query — Query events from relays using a Nostr filter +- shell_exec — Execute a shell command and return stdout/stderr +- file_read — Read a local file as text from the configured working directory +- file_write — Write text content to a local file in the configured working directory +- nostr_post_readme — Publish README.md as kind 30023 with deterministic d tag readme.md +- nostr_delete — Request deletion of one or more previously published events (NIP-09 kind 5) +- nostr_react — React to a Nostr event with like/dislike/emoji (NIP-25 kind 7) +- nostr_profile_get — Look up a Nostr profile (kind 0 metadata) by pubkey +- nostr_relay_status — Get connection status and statistics for all relays +- nostr_nip05_lookup — Look up or verify a NIP-05 identifier (user@domain) +- nostr_encode — Encode a Nostr entity into nostr: URI (npub, note, nprofile, nevent, naddr) +- nostr_decode — Decode a Nostr bech32/nostr: URI into components +- nostr_dm_send — Send a NIP-04 encrypted DM +- nostr_relay_info — Fetch NIP-11 relay information document +- nostr_encrypt — Encrypt plaintext using NIP-44 for a recipient +- nostr_decrypt — Decrypt NIP-44 ciphertext from a sender +- nostr_dm_send_nip17 — Send a private DM using NIP-17 gift wrap protocol +- nostr_list_manage — Add/remove tag tuples in replaceable list events (NIP-51 style) +- my_version — Return current Didactyl version and metadata from build macros +- http_fetch — Fetch HTTP(S) resources with optional method, headers, timeout, and body +- skill_create — Create or update a skill definition as kind 31123/31124 and optionally auto-adopt it +- skill_list — List this agent's published skills, optionally filtered by scope +- skill_adopt — Adopt a skill by adding its address to kind 10123 adoption list +- skill_remove — Remove a skill address from kind 10123 adoption list +- skill_search — Search public skills by query/author and optionally rank by adoption popularity +- trigger_list — List active triggered skills and their runtime status +- nostr_file_md_to_longform_post — Read a markdown file and publish it as kind 30023 longform post; defaults d-tag to lowercase filename +- tool_list — List available tools with name, description, and JSON parameter schema +- model_get — Get current active LLM runtime configuration (excluding API key) +- model_set — Update active LLM configuration and persist it to config.json +- model_list — List available model IDs using provider OpenAI-compatible /models endpoint +- nostr_pubkey — Return this agent's pubkey in hex format +- nostr_npub — Return this agent's pubkey encoded as npub bech32 +- my_pubkey — Alias for nostr_pubkey: return this agent's pubkey in hex format +- my_npub — Alias for nostr_npub: return this agent's pubkey encoded as npub bech32 +- task_manage — Manage agent short-term task memory stored in tasks.json (list/add/update/remove/clear/replace) + +Available skills +- admin_ops +- post_readme_to_nostr +- long_form_note +- post_readme_to_nostr + + +--- + +```text +Context Log - not seen by model +timestamp=2026-03-04 07:30:51 +phase=llm_chat_with_tools_messages_agent_api +sender=http_api_agent +model=claude-haiku-4.5 +context_bytes=8897 +approx_tokens=2224 +``` + +Sections: 15 + +# system_prompt | role=system + +## Didactyl Agent + +You are Didactyl, a sovereign AI agent living on Nostr. + +### Communication Rules +- You communicate through encrypted Nostr direct messages. +- Keep responses concise and clear. + +### Behavior +- Be helpful and technically accurate. +- If unsure, state uncertainty directly. +- Prefer actionable, practical advice. +- Use the person's name when messaging them if you know it. +- For the administrator, use their name from the administrator kind 0 profile metadata when available. + +### Tool Use Policy +- You have tools available and should use them when a request requires taking action. +- For requests involving local inspection or command execution, call `shell_exec` instead of refusing. +- For posting to Nostr, call `nostr_post` with explicit `kind` and `content`. +- For relay/event lookup tasks, call `nostr_query` with an appropriate filter. +- After a tool call, base your answer on the actual tool result. +- Never claim a tool was run if no tool was executed. + +### Task Management +- Maintain and use your internal task list as short-term working memory. +- Break long or complex actions into clear tasks before executing them. +- Update task status as you complete steps so your plan stays accurate. + +### Safety +- Do not claim to have executed actions you did not execute. +- You may share your public key (npub) with anyone. +- Never reveal your private key (nsec) under any circumstance. + +# admin_identity | role=system + +### Administrator Identity (source: config.admin.pubkey) + +This is your administrator! Admin pubkey (hex): 8ff74724ed641b3c28e5a86d7c5cbc49c37638ace8c6c38935860e7a5eedde0e + +# admin_profile | role=system + +### Administrator Kind 0 Profile (source: nostr kind 0) + +Administrator kind 0 profile content (JSON): {"name":"William S Burroughs","display_name":"WSB","about":"I like to write, and I like crank. They are good. Sometimes I'm not so sure.","banner":"https://www.grunge.com/img/gallery/paul-mccartney-and-william-s-burroughs-relationship-explained/who-was-william-s-burroughs-1672155531.jpg","website":"https://addicted.com","picture":"https://hips.hearstapps.com/hmg-prod/images/william-s-burroughs.jpg?resize=1200:*","lud16":"","nip05":""} + +# admin_relay_list | role=system + +### Administrator Relay List (source: nostr kind 10002) + +Administrator kind 10002 relay-list content (JSON): ["wss://relay.laantungir.net/","wss://relay.damus.io/","wss://nos.lol/","wss://relay.nostr.band/","wss://purplepag.es/","ws://127.0.0.1:7777/","wss://nostr.mom/"] + +# startup_events | role=system + +### Startup Events Memory (source: config.startup_events) + +Startup events memory (kinds/content/tags): [{"kind":0,"content":"{\"name\":\"Didactyl Agent\",\"display_name\":\"Didactyl\",\"about\":\"A sovereign AI agent on Nostr\",\"picture\":\"https://laantungir.github.io/img_repo/daf95a99f3797fa4ac39f3791f377ad79bcb7b8a6868f75fe66d2ab4af4bd1f5.png\",\"banner\":\"https://laantungir.github.io/img_repo/d21c4060632ab3d9d37a6062eeecbdbfbd67f03cb20dbd64838b1ba0d9cd8922.jpg\"}","tags":[]},{"kind":10002,"content":"","tags":[["r","wss://relay.damus.io"],["r","wss://nos.lol"],["r","wss://relay.primal.net"],["r","ws://127.0.0.1:7777"]]},{"kind":1,"content":"Hello world from Didactyl startup","tags":[["t","didactyl"],["t","startup"]]},{"kind":3,"content":"","tags":[]},{"kind":31123,"content":"{\"name\":\"long_form_note\",\"description\":\"How to publish a NIP-23 long-form article (kind 30023)\",\"nip\":\"NIP-23\",\"event_kind\":30023,\"format\":\"The content field must be markdown text; avoid arbitrary hard line-breaks in paragraphs and do not include HTML.\",\"required_tags\":{\"d\":\"Addressable identifier slug for the article. Reusing the same d tag replaces prior versions.\",\"title\":\"Human-readable article title.\",\"published_at\":\"Unix timestamp as a string for first publication time; keep stable on edits.\"},\"optional_tags\":{\"summary\":\"Short 1-2 sentence summary.\",\"image\":\"URL for article preview image.\",\"t\":\"Topic hashtags using repeated t tags, usually 3-6 lowercase terms.\"},\"behavior\":\"If required values are missing or unclear, ask the administrator before publishing instead of guessing.\",\"procedure\":[\"Determine title and d tag from admin input or source material.\",\"Draft markdown body content.\",\"Set published_at as unix seconds string.\",\"Add optional summary/image/t tags when known.\",\"Publish with nostr_post kind 30023 including tags array.\"]}","tags":[["d","long_form_note"],["app","didactyl"],["scope","public"],["slug","long_form_note"]]},{"kind":31123,"content":"{\"name\":\"post_readme_to_nostr\",\"description\":\"Publish README.md using the dedicated nostr_post_readme tool\",\"uses_skill\":\"long_form_note\",\"event_kind\":30023,\"procedure\":[\"Call nostr_post_readme with empty arguments {}.\",\"Return the tool response including event_id, d_tag, and naddr_uri.\",\"If the tool returns success=false, report the tool error verbatim.\"],\"required_values\":{\"d\":\"readme.md\",\"tool\":\"nostr_post_readme\"}}","tags":[["d","post_readme_to_nostr"],["app","didactyl"],["scope","public"],["slug","post_readme_to_nostr"]]},{"kind":31124,"content":"{\"name\":\"admin_ops\",\"description\":\"Private operational procedures\"}","tags":[["d","admin_ops"],["app","didactyl"],["scope","private"],["slug","admin_ops"]]},{"kind":10123,"content":"","tags":[["a","31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:long_form_note"],["a","31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:post_readme_to_nostr"],["app","didactyl"],["scope","public"]]}] + +# adopted_skills | role=system + +### Adopted Skills Memory (source: adoption list + startup fallback) + +Adopted skills memory (always-on behavioral instructions): follow these skill instructions when relevant and never violate explicit constraints. + + +--- + +#### Skill +Skill slug: long_form_note +Skill address: 31123:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:long_form_note + +Instructions: +Name: long_form_note +Description: How to publish a NIP-23 long-form article (kind 30023) +Event kind: 30023 +Procedure: + 1. Determine title and d tag from admin input or source material. + 2. Draft markdown body content. + 3. Set published_at as unix seconds string. + 4. Add optional summary/image/t tags when known. + 5. Publish with nostr_post kind 30023 including tags array. + + + +--- + +#### Skill +Skill slug: post_readme_to_nostr +Skill address: 31123:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:post_readme_to_nostr + +Instructions: +Name: post_readme_to_nostr +Description: Publish README.md using the dedicated nostr_post_readme tool +Event kind: 30023 +Procedure: + 1. Call nostr_post_readme with empty arguments {}. + 2. Return the tool response including event_id, d_tag, and naddr_uri. + 3. If the tool returns success=false, report the tool error verbatim. + + + +--- + +#### Skill +Skill slug: admin_ops +Skill address: 31124:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:admin_ops + +Instructions: +Name: admin_ops +Description: Private operational procedures + + + +# agent_tasks | role=system + +#### Current Tasks + +Your active task list - short-term working memory for tracking plan steps. + +- [ ] No active tasks yet. + + +# admin_notes | role=system + +### Administrator Recent Notes (source: nostr kind 1) + +Recent posts: +- GM +- Getting longer +- Long day. +- This is a test. +- test +- Post 11 +- Post 10 +- This is +Post +9 +- Eight +- Seven + + +# dm_history | role=chat + +17:45 WSB What is the capital of france? +17:45 Agent The capital of France is Paris. + +Is there anything else I can help you with? +17:57 Agent Didactyl has started up and is online (version v0.0.29, connected relays: 4/4). +17:58 WSB What is the capital of Russia? +17:59 Agent The capital of Russia is **Moscow**. It's the largest city in Russia and has been the capital since 1991, when it resumed that role following the collapse of the Soviet Union. (Leningrad, now St. Petersburg, was the capital during the Soviet era from 1918-1991.) +18:09 Agent Didactyl has started up and is online (version v0.0.29, connected relays: 4/4). +07:30 WSB q + + + +--- + +```text +Context Log - not seen by model +timestamp=2026-03-04 07:27:40 +phase=direct_tool_exec +sender=http_api_agent +model=claude-haiku-4.5 +context_bytes=8785 +approx_tokens=2196 +``` + +slash=/tool_list +result={"success":true,"count":37,"tools":[{"name":"nostr_post","description":"Publish a Nostr event to connected relays","parameters":{"type":"object","properties":{"kind":{"type":"integer"},"content":{"type":"string"},"tags":{"type":"array","description":"Optional Nostr tags array, e.g. [[\"d\",\"slug\"],[\"t\",\"nostr\"]]","items":{"type":"array","items":{"type":"string"}}}},"required":["kind","content"]}},{"name":"nostr_query","description":"Query events from relays using a Nostr filter","parameters":{"type":"object","properties":{"filter":{"type":"object"},"timeout_ms":{"type":"integer"}},"required":["filter"]}},{"name":"shell_exec","description":"Execute a shell command and return stdout/stderr","parameters":{"type":"object","properties":{"command":{"type":"string"}},"required":["command"]}},{"name":"file_read","description":"Read a local file as text from the configured working directory","parameters":{"type":"object","properties":{"path":{"type":"string"},"max_bytes":{"type":"integer"}},"required":["path"]}},{"name":"file_write","description":"Write text content to a local file in the configured working directory","parameters":{"type":"object","properties":{"path":{"type":"string"},"content":{"type":"string"},"append":{"type":"boolean"}},"required":["path","content"]}},{"name":"nostr_post_readme","description":"Publish README.md as kind 30023 with deterministic d tag readme.md","parameters":{"type":"object","properties":{},"required":[]}},{"name":"nostr_delete","description":"Request deletion of one or more previously published events (NIP-09 kind 5)","parameters":{"type":"object","properties":{"event_ids":{"type":"array","items":{"type":"string"}},"kinds":{"type":"array","items":{"type":"integer"}},"reason":{"type":"string"}},"required":["event_ids"]}},{"name":"nostr_react","description":"React to a Nostr event with like/dislike/emoji (NIP-25 kind 7)","parameters":{"type":"object","properties":{"event_id":{"type":"string"},"event_pubkey":{"type":"string"},"event_kind":{"type":"integer"},"reaction":{"type":"string"}},"required":["event_id","event_pubkey"]}},{"name":"nostr_profile_get","description":"Look up a Nostr profile (kind 0 metadata) by pubkey","parameters":{"type":"object","properties":{"pubkey":{"type":"string"}},"required":["pubkey"]}},{"name":"nostr_relay_status","description":"Get connection status and statistics for all relays","parameters":{"type":"object","properties":{}}},{"name":"nostr_nip05_lookup","description":"Look up or verify a NIP-05 identifier (user@domain)","parameters":{"type":"object","properties":{"identifier":{"type":"string"},"pubkey":{"type":"string"}},"required":["identifier"]}},{"name":"nostr_encode","description":"Encode a Nostr entity into nostr: URI (npub, note, nprofile, nevent, naddr)","parameters":{"type":"object","properties":{"type":{"type":"string"},"hex":{"type":"string"},"relays":{"type":"array","items":{"type":"string"}},"kind":{"type":"integer"},"identifier":{"type":"string"}},"required":["type","hex"]}},{"name":"nostr_decode","description":"Decode a Nostr bech32/nostr: URI into components","parameters":{"type":"object","properties":{"uri":{"type":"string"}},"required":["uri"]}},{"name":"nostr_dm_send","description":"Send a NIP-04 encrypted DM","parameters":{"type":"object","properties":{"recipient_pubkey":{"type":"string"},"message":{"type":"string"}},"required":["recipient_pubkey","message"]}},{"name":"nostr_relay_info","description":"Fetch NIP-11 relay information document","parameters":{"type":"object","properties":{"relay_url":{"type":"string"}},"required":["relay_url"]}},{"name":"nostr_encrypt","description":"Encrypt plaintext using NIP-44 for a recipient","parameters":{"type":"object","properties":{"recipient_pubkey":{"type":"string"},"plaintext":{"type":"string"}},"required":["recipient_pubkey","plaintext"]}},{"name":"nostr_decrypt","description":"Decrypt NIP-44 ciphertext from a sender","parameters":{"type":"object","properties":{"sender_pubkey":{"type":"string"},"ciphertext":{"type":"string"}},"required":["sender_pubkey","ciphertext"]}},{"name":"nostr_dm_send_nip17","description":"Send a private DM using NIP-17 gift wrap protocol","parameters":{"type":"object","properties":{"recipient_pubkey":{"type":"string"},"message":{"type":"string"},"subject":{"type":"string"}},"required":["recipient_pubkey","message"]}},{"name":"nostr_list_manage","description":"Add/remove tag tuples in replaceable list events (NIP-51 style)","parameters":{"type":"object","properties":{"list_kind":{"type":"integer"},"action":{"type":"string"},"items":{"type":"array","items":{"type":"array","items":{"type":"string"}}}},"required":["list_kind","action","items"]}},{"name":"my_version","description":"Return current Didactyl version and metadata from build macros","parameters":{"type":"object","properties":{}}},{"name":"http_fetch","description":"Fetch HTTP(S) resources with optional method, headers, timeout, and body","parameters":{"type":"object","properties":{"url":{"type":"string"},"method":{"type":"string"},"headers":{"type":"array","items":{"type":"string"}},"body":{"type":"string"},"timeout_seconds":{"type":"integer"},"max_bytes":{"type":"integer"}},"required":["url"]}},{"name":"skill_create","description":"Create or update a skill definition as kind 31123/31124 and optionally auto-adopt it","parameters":{"type":"object","properties":{"slug":{"type":"string"},"content":{"type":"string"},"scope":{"type":"string"},"description":{"type":"string"},"auto_adopt":{"type":"boolean"}},"required":["slug","content"]}},{"name":"skill_list","description":"List this agent's published skills, optionally filtered by scope","parameters":{"type":"object","properties":{"scope":{"type":"string"}}}},{"name":"skill_adopt","description":"Adopt a skill by adding its address to kind 10123 adoption list","parameters":{"type":"object","properties":{"pubkey":{"type":"string"},"slug":{"type":"string"},"kind":{"type":"integer"}},"required":["pubkey","slug"]}},{"name":"skill_remove","description":"Remove a skill address from kind 10123 adoption list","parameters":{"type":"object","properties":{"pubkey":{"type":"string"},"slug":{"type":"string"},"kind":{"type":"integer"}},"required":["slug"]}},{"name":"skill_search","description":"Search public skills by query/author and optionally rank by adoption popularity","parameters":{"type":"object","properties":{"query":{"type":"string"},"pubkey":{"type":"string"},"popular":{"type":"boolean"}}}},{"name":"trigger_list","description":"List active triggered skills and their runtime status","parameters":{"type":"object","properties":{}}},{"name":"nostr_file_md_to_longform_post","description":"Read a markdown file and publish it as kind 30023 longform post; defaults d-tag to lowercase filename","parameters":{"type":"object","properties":{"file":{"type":"string"},"title":{"type":"string"},"image":{"type":"string"},"summary":{"type":"string"}},"required":["file"]}},{"name":"tool_list","description":"List available tools with name, description, and JSON parameter schema","parameters":{"type":"object","properties":{}}},{"name":"model_get","description":"Get current active LLM runtime configuration (excluding API key)","parameters":{"type":"object","properties":{}}},{"name":"model_set","description":"Update active LLM configuration and persist it to config.json","parameters":{"type":"object","properties":{"provider":{"type":"string"},"api_key":{"type":"string"},"model":{"type":"string"},"base_url":{"type":"string"},"max_tokens":{"type":"integer"},"temperature":{"type":"number"}}}},{"name":"model_list","description":"List available model IDs using provider OpenAI-compatible /models endpoint","parameters":{"type":"object","properties":{"base_url":{"type":"string"}}}},{"name":"nostr_pubkey","description":"Return this agent's pubkey in hex format","parameters":{"type":"object","properties":{}}},{"name":"nostr_npub","description":"Return this agent's pubkey encoded as npub bech32","parameters":{"type":"object","properties":{}}},{"name":"my_pubkey","description":"Alias for nostr_pubkey: return this agent's pubkey in hex format","parameters":{"type":"object","properties":{}}},{"name":"my_npub","description":"Alias for nostr_npub: return this agent's pubkey encoded as npub bech32","parameters":{"type":"object","properties":{}}},{"name":"task_manage","description":"Manage agent short-term task memory stored in tasks.json (list/add/update/remove/clear/replace)","parameters":{"type":"object","properties":{"action":{"type":"string","enum":["list","add","update","remove","clear","replace"]},"text":{"type":"string"},"id":{"type":"integer"},"status":{"type":"string","enum":["pending","active","done"]},"tasks":{"type":"array","items":{"type":"string"}}},"required":["action"]}}]} + +--- + +```text +Context Log - not seen by model +timestamp=2026-03-04 07:26:14 +phase=direct_tool_exec +sender=http_api_agent +model=claude-haiku-4.5 +context_bytes=650 +approx_tokens=162 +``` + +slash=/help +result={"success":true,"mode":"slash_help","tools":["nostr_post","nostr_query","shell_exec","file_read","file_write","nostr_post_readme","nostr_delete","nostr_react","nostr_profile_get","nostr_relay_status","nostr_nip05_lookup","nostr_encode","nostr_decode","nostr_dm_send","nostr_relay_info","nostr_encrypt","nostr_decrypt","nostr_dm_send_nip17","nostr_list_manage","my_version","http_fetch","skill_create","skill_list","skill_adopt","skill_remove","skill_search","trigger_list","nostr_file_md_to_longform_post","tool_list","model_get","model_set","model_list","nostr_pubkey","nostr_npub","my_pubkey","my_npub","task_manage"],"count":37} + +--- + +```text +Context Log - not seen by model +timestamp=2026-03-04 07:20:41 +phase=llm_chat_with_tools_messages_agent_api +sender=http_api_agent +model=claude-haiku-4.5 +context_bytes=8900 +approx_tokens=2225 +``` + +Sections: 14 + +# system_prompt | role=system + +## Didactyl Agent + +You are Didactyl, a sovereign AI agent living on Nostr. + +### Communication Rules +- You communicate through encrypted Nostr direct messages. +- Keep responses concise and clear. + +### Behavior +- Be helpful and technically accurate. +- If unsure, state uncertainty directly. +- Prefer actionable, practical advice. +- Use the person's name when messaging them if you know it. +- For the administrator, use their name from the administrator kind 0 profile metadata when available. + +### Tool Use Policy +- You have tools available and should use them when a request requires taking action. +- For requests involving local inspection or command execution, call `shell_exec` instead of refusing. +- For posting to Nostr, call `nostr_post` with explicit `kind` and `content`. +- For relay/event lookup tasks, call `nostr_query` with an appropriate filter. +- After a tool call, base your answer on the actual tool result. +- Never claim a tool was run if no tool was executed. + +### Task Management +- Maintain and use your internal task list as short-term working memory. +- Break long or complex actions into clear tasks before executing them. +- Update task status as you complete steps so your plan stays accurate. + +### Safety +- Do not claim to have executed actions you did not execute. +- You may share your public key (npub) with anyone. +- Never reveal your private key (nsec) under any circumstance. + +# admin_identity | role=system + +### Administrator Identity (source: config.admin.pubkey) + +This is your administrator! Admin pubkey (hex): 8ff74724ed641b3c28e5a86d7c5cbc49c37638ace8c6c38935860e7a5eedde0e + +# admin_profile | role=system + +### Administrator Kind 0 Profile (source: nostr kind 0) + +Administrator kind 0 profile content (JSON): {"name":"William S Burroughs","display_name":"WSB","about":"I like to write, and I like crank. They are good. Sometimes I'm not so sure.","banner":"https://www.grunge.com/img/gallery/paul-mccartney-and-william-s-burroughs-relationship-explained/who-was-william-s-burroughs-1672155531.jpg","website":"https://addicted.com","picture":"https://hips.hearstapps.com/hmg-prod/images/william-s-burroughs.jpg?resize=1200:*","lud16":"","nip05":""} + +# admin_relay_list | role=system + +### Administrator Relay List (source: nostr kind 10002) + +Administrator kind 10002 relay-list content (JSON): ["wss://relay.laantungir.net/","wss://relay.damus.io/","wss://nos.lol/","wss://relay.nostr.band/","wss://purplepag.es/","ws://127.0.0.1:7777/","wss://nostr.mom/"] + +# startup_events | role=system + +### Startup Events Memory (source: config.startup_events) + +Startup events memory (kinds/content/tags): [{"kind":0,"content":"{\"name\":\"Didactyl Agent\",\"display_name\":\"Didactyl\",\"about\":\"A sovereign AI agent on Nostr\",\"picture\":\"https://laantungir.github.io/img_repo/daf95a99f3797fa4ac39f3791f377ad79bcb7b8a6868f75fe66d2ab4af4bd1f5.png\",\"banner\":\"https://laantungir.github.io/img_repo/d21c4060632ab3d9d37a6062eeecbdbfbd67f03cb20dbd64838b1ba0d9cd8922.jpg\"}","tags":[]},{"kind":10002,"content":"","tags":[["r","wss://relay.damus.io"],["r","wss://nos.lol"],["r","wss://relay.primal.net"],["r","ws://127.0.0.1:7777"]]},{"kind":1,"content":"Hello world from Didactyl startup","tags":[["t","didactyl"],["t","startup"]]},{"kind":3,"content":"","tags":[]},{"kind":31123,"content":"{\"name\":\"long_form_note\",\"description\":\"How to publish a NIP-23 long-form article (kind 30023)\",\"nip\":\"NIP-23\",\"event_kind\":30023,\"format\":\"The content field must be markdown text; avoid arbitrary hard line-breaks in paragraphs and do not include HTML.\",\"required_tags\":{\"d\":\"Addressable identifier slug for the article. Reusing the same d tag replaces prior versions.\",\"title\":\"Human-readable article title.\",\"published_at\":\"Unix timestamp as a string for first publication time; keep stable on edits.\"},\"optional_tags\":{\"summary\":\"Short 1-2 sentence summary.\",\"image\":\"URL for article preview image.\",\"t\":\"Topic hashtags using repeated t tags, usually 3-6 lowercase terms.\"},\"behavior\":\"If required values are missing or unclear, ask the administrator before publishing instead of guessing.\",\"procedure\":[\"Determine title and d tag from admin input or source material.\",\"Draft markdown body content.\",\"Set published_at as unix seconds string.\",\"Add optional summary/image/t tags when known.\",\"Publish with nostr_post kind 30023 including tags array.\"]}","tags":[["d","long_form_note"],["app","didactyl"],["scope","public"],["slug","long_form_note"]]},{"kind":31123,"content":"{\"name\":\"post_readme_to_nostr\",\"description\":\"Publish README.md using the dedicated nostr_post_readme tool\",\"uses_skill\":\"long_form_note\",\"event_kind\":30023,\"procedure\":[\"Call nostr_post_readme with empty arguments {}.\",\"Return the tool response including event_id, d_tag, and naddr_uri.\",\"If the tool returns success=false, report the tool error verbatim.\"],\"required_values\":{\"d\":\"readme.md\",\"tool\":\"nostr_post_readme\"}}","tags":[["d","post_readme_to_nostr"],["app","didactyl"],["scope","public"],["slug","post_readme_to_nostr"]]},{"kind":31124,"content":"{\"name\":\"admin_ops\",\"description\":\"Private operational procedures\"}","tags":[["d","admin_ops"],["app","didactyl"],["scope","private"],["slug","admin_ops"]]},{"kind":10123,"content":"","tags":[["a","31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:long_form_note"],["a","31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:post_readme_to_nostr"],["app","didactyl"],["scope","public"]]}] + +# adopted_skills | role=system + +### Adopted Skills Memory (source: adoption list + startup fallback) + +Adopted skills memory (always-on behavioral instructions): follow these skill instructions when relevant and never violate explicit constraints. + + +--- + +#### Skill +Skill slug: long_form_note +Skill address: 31123:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:long_form_note + +Instructions: +Name: long_form_note +Description: How to publish a NIP-23 long-form article (kind 30023) +Event kind: 30023 +Procedure: + 1. Determine title and d tag from admin input or source material. + 2. Draft markdown body content. + 3. Set published_at as unix seconds string. + 4. Add optional summary/image/t tags when known. + 5. Publish with nostr_post kind 30023 including tags array. + + + +--- + +#### Skill +Skill slug: post_readme_to_nostr +Skill address: 31123:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:post_readme_to_nostr + +Instructions: +Name: post_readme_to_nostr +Description: Publish README.md using the dedicated nostr_post_readme tool +Event kind: 30023 +Procedure: + 1. Call nostr_post_readme with empty arguments {}. + 2. Return the tool response including event_id, d_tag, and naddr_uri. + 3. If the tool returns success=false, report the tool error verbatim. + + + +--- + +#### Skill +Skill slug: admin_ops +Skill address: 31124:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:admin_ops + +Instructions: +Name: admin_ops +Description: Private operational procedures + + + +# agent_tasks | role=system + +#### Current Tasks + +Your active task list - short-term working memory for tracking plan steps. + +- [ ] 1. Post a joke +- [ ] 2. Throw out the trash +- [ ] 3. Tell someone you love them + + +# admin_notes | role=system + +### Administrator Recent Notes (source: nostr kind 1) + +Recent posts: +- GM +- Getting longer +- Long day. +- This is a test. +- test +- Post 11 +- Post 10 +- This is +Post +9 +- Eight +- Seven + + +# context_part | role=tool + +{"success":true,"action":"clear","path":"tasks.json","mutated":true,"count":0,"tasks":[]} + +# dm_history | role=chat + +05:35 WSB Thanks. This was more on the "nostr published" side of things. I was thinking more of a summary of code pushes and accomplishements there. Can you access git and take a look? +05:38 WSB hello +09:19 Agent Didactyl has started up and is online (version v0.0.26, connected relays: 4/4). +07:20 WSB empty your task list. +--:-- Agent + + + +--- + +```text +Context Log - not seen by model +timestamp=2026-03-04 07:20:39 +phase=llm_chat_with_tools_messages_agent_api +sender=http_api_agent +model=claude-haiku-4.5 +context_bytes=8527 +approx_tokens=2131 +``` + +Sections: 12 + +# system_prompt | role=system + +## Didactyl Agent + +You are Didactyl, a sovereign AI agent living on Nostr. + +### Communication Rules +- You communicate through encrypted Nostr direct messages. +- Keep responses concise and clear. + +### Behavior +- Be helpful and technically accurate. +- If unsure, state uncertainty directly. +- Prefer actionable, practical advice. +- Use the person's name when messaging them if you know it. +- For the administrator, use their name from the administrator kind 0 profile metadata when available. + +### Tool Use Policy +- You have tools available and should use them when a request requires taking action. +- For requests involving local inspection or command execution, call `shell_exec` instead of refusing. +- For posting to Nostr, call `nostr_post` with explicit `kind` and `content`. +- For relay/event lookup tasks, call `nostr_query` with an appropriate filter. +- After a tool call, base your answer on the actual tool result. +- Never claim a tool was run if no tool was executed. + +### Task Management +- Maintain and use your internal task list as short-term working memory. +- Break long or complex actions into clear tasks before executing them. +- Update task status as you complete steps so your plan stays accurate. + +### Safety +- Do not claim to have executed actions you did not execute. +- You may share your public key (npub) with anyone. +- Never reveal your private key (nsec) under any circumstance. + +# admin_identity | role=system + +### Administrator Identity (source: config.admin.pubkey) + +This is your administrator! Admin pubkey (hex): 8ff74724ed641b3c28e5a86d7c5cbc49c37638ace8c6c38935860e7a5eedde0e + +# admin_profile | role=system + +### Administrator Kind 0 Profile (source: nostr kind 0) + +Administrator kind 0 profile content (JSON): {"name":"William S Burroughs","display_name":"WSB","about":"I like to write, and I like crank. They are good. Sometimes I'm not so sure.","banner":"https://www.grunge.com/img/gallery/paul-mccartney-and-william-s-burroughs-relationship-explained/who-was-william-s-burroughs-1672155531.jpg","website":"https://addicted.com","picture":"https://hips.hearstapps.com/hmg-prod/images/william-s-burroughs.jpg?resize=1200:*","lud16":"","nip05":""} + +# admin_relay_list | role=system + +### Administrator Relay List (source: nostr kind 10002) + +Administrator kind 10002 relay-list content (JSON): ["wss://relay.laantungir.net/","wss://relay.damus.io/","wss://nos.lol/","wss://relay.nostr.band/","wss://purplepag.es/","ws://127.0.0.1:7777/","wss://nostr.mom/"] + +# startup_events | role=system + +### Startup Events Memory (source: config.startup_events) + +Startup events memory (kinds/content/tags): [{"kind":0,"content":"{\"name\":\"Didactyl Agent\",\"display_name\":\"Didactyl\",\"about\":\"A sovereign AI agent on Nostr\",\"picture\":\"https://laantungir.github.io/img_repo/daf95a99f3797fa4ac39f3791f377ad79bcb7b8a6868f75fe66d2ab4af4bd1f5.png\",\"banner\":\"https://laantungir.github.io/img_repo/d21c4060632ab3d9d37a6062eeecbdbfbd67f03cb20dbd64838b1ba0d9cd8922.jpg\"}","tags":[]},{"kind":10002,"content":"","tags":[["r","wss://relay.damus.io"],["r","wss://nos.lol"],["r","wss://relay.primal.net"],["r","ws://127.0.0.1:7777"]]},{"kind":1,"content":"Hello world from Didactyl startup","tags":[["t","didactyl"],["t","startup"]]},{"kind":3,"content":"","tags":[]},{"kind":31123,"content":"{\"name\":\"long_form_note\",\"description\":\"How to publish a NIP-23 long-form article (kind 30023)\",\"nip\":\"NIP-23\",\"event_kind\":30023,\"format\":\"The content field must be markdown text; avoid arbitrary hard line-breaks in paragraphs and do not include HTML.\",\"required_tags\":{\"d\":\"Addressable identifier slug for the article. Reusing the same d tag replaces prior versions.\",\"title\":\"Human-readable article title.\",\"published_at\":\"Unix timestamp as a string for first publication time; keep stable on edits.\"},\"optional_tags\":{\"summary\":\"Short 1-2 sentence summary.\",\"image\":\"URL for article preview image.\",\"t\":\"Topic hashtags using repeated t tags, usually 3-6 lowercase terms.\"},\"behavior\":\"If required values are missing or unclear, ask the administrator before publishing instead of guessing.\",\"procedure\":[\"Determine title and d tag from admin input or source material.\",\"Draft markdown body content.\",\"Set published_at as unix seconds string.\",\"Add optional summary/image/t tags when known.\",\"Publish with nostr_post kind 30023 including tags array.\"]}","tags":[["d","long_form_note"],["app","didactyl"],["scope","public"],["slug","long_form_note"]]},{"kind":31123,"content":"{\"name\":\"post_readme_to_nostr\",\"description\":\"Publish README.md using the dedicated nostr_post_readme tool\",\"uses_skill\":\"long_form_note\",\"event_kind\":30023,\"procedure\":[\"Call nostr_post_readme with empty arguments {}.\",\"Return the tool response including event_id, d_tag, and naddr_uri.\",\"If the tool returns success=false, report the tool error verbatim.\"],\"required_values\":{\"d\":\"readme.md\",\"tool\":\"nostr_post_readme\"}}","tags":[["d","post_readme_to_nostr"],["app","didactyl"],["scope","public"],["slug","post_readme_to_nostr"]]},{"kind":31124,"content":"{\"name\":\"admin_ops\",\"description\":\"Private operational procedures\"}","tags":[["d","admin_ops"],["app","didactyl"],["scope","private"],["slug","admin_ops"]]},{"kind":10123,"content":"","tags":[["a","31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:long_form_note"],["a","31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:post_readme_to_nostr"],["app","didactyl"],["scope","public"]]}] + +# adopted_skills | role=system + +### Adopted Skills Memory (source: adoption list + startup fallback) + +Adopted skills memory (always-on behavioral instructions): follow these skill instructions when relevant and never violate explicit constraints. + + +--- + +#### Skill +Skill slug: long_form_note +Skill address: 31123:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:long_form_note + +Instructions: +Name: long_form_note +Description: How to publish a NIP-23 long-form article (kind 30023) +Event kind: 30023 +Procedure: + 1. Determine title and d tag from admin input or source material. + 2. Draft markdown body content. + 3. Set published_at as unix seconds string. + 4. Add optional summary/image/t tags when known. + 5. Publish with nostr_post kind 30023 including tags array. + + + +--- + +#### Skill +Skill slug: post_readme_to_nostr +Skill address: 31123:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:post_readme_to_nostr + +Instructions: +Name: post_readme_to_nostr +Description: Publish README.md using the dedicated nostr_post_readme tool +Event kind: 30023 +Procedure: + 1. Call nostr_post_readme with empty arguments {}. + 2. Return the tool response including event_id, d_tag, and naddr_uri. + 3. If the tool returns success=false, report the tool error verbatim. + + + +--- + +#### Skill +Skill slug: admin_ops +Skill address: 31124:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:admin_ops + +Instructions: +Name: admin_ops +Description: Private operational procedures + + + +# agent_tasks | role=system + +#### Current Tasks + +Your active task list - short-term working memory for tracking plan steps. + +- [ ] 1. Post a joke +- [ ] 2. Throw out the trash +- [ ] 3. Tell someone you love them + + +# admin_notes | role=system + +### Administrator Recent Notes (source: nostr kind 1) + +Recent posts: +- GM +- Getting longer +- Long day. +- This is a test. +- test +- Post 11 +- Post 10 +- This is +Post +9 +- Eight +- Seven + + +# dm_history | role=chat + +05:35 WSB Thanks. This was more on the "nostr published" side of things. I was thinking more of a summary of code pushes and accomplishements there. Can you access git and take a look? +05:38 WSB hello +09:19 Agent Didactyl has started up and is online (version v0.0.26, connected relays: 4/4). +07:20 WSB empty your task list. + + + +--- + +```text +Context Log - not seen by model +timestamp=2026-03-04 07:18:57 +phase=llm_chat_with_tools_messages_agent_api +sender=http_api_agent +model=claude-haiku-4.5 +context_bytes=9092 +approx_tokens=2273 +``` + +Sections: 16 + +# system_prompt | role=system + +## Didactyl Agent + +You are Didactyl, a sovereign AI agent living on Nostr. + +### Communication Rules +- You communicate through encrypted Nostr direct messages. +- Keep responses concise and clear. + +### Behavior +- Be helpful and technically accurate. +- If unsure, state uncertainty directly. +- Prefer actionable, practical advice. +- Use the person's name when messaging them if you know it. +- For the administrator, use their name from the administrator kind 0 profile metadata when available. + +### Tool Use Policy +- You have tools available and should use them when a request requires taking action. +- For requests involving local inspection or command execution, call `shell_exec` instead of refusing. +- For posting to Nostr, call `nostr_post` with explicit `kind` and `content`. +- For relay/event lookup tasks, call `nostr_query` with an appropriate filter. +- After a tool call, base your answer on the actual tool result. +- Never claim a tool was run if no tool was executed. + +### Task Management +- Maintain and use your internal task list as short-term working memory. +- Break long or complex actions into clear tasks before executing them. +- Update task status as you complete steps so your plan stays accurate. + +### Safety +- Do not claim to have executed actions you did not execute. +- You may share your public key (npub) with anyone. +- Never reveal your private key (nsec) under any circumstance. + +# admin_identity | role=system + +### Administrator Identity (source: config.admin.pubkey) + +This is your administrator! Admin pubkey (hex): 8ff74724ed641b3c28e5a86d7c5cbc49c37638ace8c6c38935860e7a5eedde0e + +# admin_profile | role=system + +### Administrator Kind 0 Profile (source: nostr kind 0) + +Administrator kind 0 profile content (JSON): {"name":"William S Burroughs","display_name":"WSB","about":"I like to write, and I like crank. They are good. Sometimes I'm not so sure.","banner":"https://www.grunge.com/img/gallery/paul-mccartney-and-william-s-burroughs-relationship-explained/who-was-william-s-burroughs-1672155531.jpg","website":"https://addicted.com","picture":"https://hips.hearstapps.com/hmg-prod/images/william-s-burroughs.jpg?resize=1200:*","lud16":"","nip05":""} + +# admin_relay_list | role=system + +### Administrator Relay List (source: nostr kind 10002) + +Administrator kind 10002 relay-list content (JSON): ["wss://relay.laantungir.net/","wss://relay.damus.io/","wss://nos.lol/","wss://relay.nostr.band/","wss://purplepag.es/","ws://127.0.0.1:7777/","wss://nostr.mom/"] + +# startup_events | role=system + +### Startup Events Memory (source: config.startup_events) + +Startup events memory (kinds/content/tags): [{"kind":0,"content":"{\"name\":\"Didactyl Agent\",\"display_name\":\"Didactyl\",\"about\":\"A sovereign AI agent on Nostr\",\"picture\":\"https://laantungir.github.io/img_repo/daf95a99f3797fa4ac39f3791f377ad79bcb7b8a6868f75fe66d2ab4af4bd1f5.png\",\"banner\":\"https://laantungir.github.io/img_repo/d21c4060632ab3d9d37a6062eeecbdbfbd67f03cb20dbd64838b1ba0d9cd8922.jpg\"}","tags":[]},{"kind":10002,"content":"","tags":[["r","wss://relay.damus.io"],["r","wss://nos.lol"],["r","wss://relay.primal.net"],["r","ws://127.0.0.1:7777"]]},{"kind":1,"content":"Hello world from Didactyl startup","tags":[["t","didactyl"],["t","startup"]]},{"kind":3,"content":"","tags":[]},{"kind":31123,"content":"{\"name\":\"long_form_note\",\"description\":\"How to publish a NIP-23 long-form article (kind 30023)\",\"nip\":\"NIP-23\",\"event_kind\":30023,\"format\":\"The content field must be markdown text; avoid arbitrary hard line-breaks in paragraphs and do not include HTML.\",\"required_tags\":{\"d\":\"Addressable identifier slug for the article. Reusing the same d tag replaces prior versions.\",\"title\":\"Human-readable article title.\",\"published_at\":\"Unix timestamp as a string for first publication time; keep stable on edits.\"},\"optional_tags\":{\"summary\":\"Short 1-2 sentence summary.\",\"image\":\"URL for article preview image.\",\"t\":\"Topic hashtags using repeated t tags, usually 3-6 lowercase terms.\"},\"behavior\":\"If required values are missing or unclear, ask the administrator before publishing instead of guessing.\",\"procedure\":[\"Determine title and d tag from admin input or source material.\",\"Draft markdown body content.\",\"Set published_at as unix seconds string.\",\"Add optional summary/image/t tags when known.\",\"Publish with nostr_post kind 30023 including tags array.\"]}","tags":[["d","long_form_note"],["app","didactyl"],["scope","public"],["slug","long_form_note"]]},{"kind":31123,"content":"{\"name\":\"post_readme_to_nostr\",\"description\":\"Publish README.md using the dedicated nostr_post_readme tool\",\"uses_skill\":\"long_form_note\",\"event_kind\":30023,\"procedure\":[\"Call nostr_post_readme with empty arguments {}.\",\"Return the tool response including event_id, d_tag, and naddr_uri.\",\"If the tool returns success=false, report the tool error verbatim.\"],\"required_values\":{\"d\":\"readme.md\",\"tool\":\"nostr_post_readme\"}}","tags":[["d","post_readme_to_nostr"],["app","didactyl"],["scope","public"],["slug","post_readme_to_nostr"]]},{"kind":31124,"content":"{\"name\":\"admin_ops\",\"description\":\"Private operational procedures\"}","tags":[["d","admin_ops"],["app","didactyl"],["scope","private"],["slug","admin_ops"]]},{"kind":10123,"content":"","tags":[["a","31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:long_form_note"],["a","31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:post_readme_to_nostr"],["app","didactyl"],["scope","public"]]}] + +# adopted_skills | role=system + +### Adopted Skills Memory (source: adoption list + startup fallback) + +Adopted skills memory (always-on behavioral instructions): follow these skill instructions when relevant and never violate explicit constraints. + + +--- + +#### Skill +Skill slug: long_form_note +Skill address: 31123:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:long_form_note + +Instructions: +Name: long_form_note +Description: How to publish a NIP-23 long-form article (kind 30023) +Event kind: 30023 +Procedure: + 1. Determine title and d tag from admin input or source material. + 2. Draft markdown body content. + 3. Set published_at as unix seconds string. + 4. Add optional summary/image/t tags when known. + 5. Publish with nostr_post kind 30023 including tags array. + + + +--- + +#### Skill +Skill slug: post_readme_to_nostr +Skill address: 31123:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:post_readme_to_nostr + +Instructions: +Name: post_readme_to_nostr +Description: Publish README.md using the dedicated nostr_post_readme tool +Event kind: 30023 +Procedure: + 1. Call nostr_post_readme with empty arguments {}. + 2. Return the tool response including event_id, d_tag, and naddr_uri. + 3. If the tool returns success=false, report the tool error verbatim. + + + +--- + +#### Skill +Skill slug: admin_ops +Skill address: 31124:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:admin_ops + +Instructions: +Name: admin_ops +Description: Private operational procedures + + + +# agent_tasks | role=system + +#### Current Tasks + +Your active task list - short-term working memory for tracking plan steps. + +- [ ] 1. Post a joke +- [ ] 2. Throw out the trash +- [ ] 3. Tell someone you love them + + +# admin_notes | role=system + +### Administrator Recent Notes (source: nostr kind 1) + +Recent posts: +- GM +- Getting longer +- Long day. +- This is a test. +- test +- Post 11 +- Post 10 +- This is +Post +9 +- Eight +- Seven + + +# dm_history | role=chat + +17:44 Agent Didactyl has started up and is online (version v0.0.29, connected relays: 4/4). +17:45 WSB What is the capital of france? +17:45 Agent The capital of France is Paris. + +Is there anything else I can help you with? +17:57 Agent Didactyl has started up and is online (version v0.0.29, connected relays: 4/4). +17:58 WSB What is the capital of Russia? +17:59 Agent The capital of Russia is **Moscow**. It's the largest city in Russia and has been the capital since 1991, when it resumed that role following the collapse of the Soviet Union. (Leningrad, now St. Petersburg, was the capital during the Soviet era from 1918-1991.) +18:09 Agent Didactyl has started up and is online (version v0.0.29, connected relays: 4/4). +07:18 WSB /help + + + +--- + +```text +Context Log - not seen by model +timestamp=2026-03-04 06:55:29 +phase=llm_chat_with_tools_messages_agent_api +sender=http_api_agent +model=claude-haiku-4.5 +context_bytes=9877 +approx_tokens=2469 +``` + +Sections: 10 + +# system_prompt | role=system + +## Didactyl Agent + +You are Didactyl, a sovereign AI agent living on Nostr. + +### Communication Rules +- You communicate through encrypted Nostr direct messages. +- Keep responses concise and clear. + +### Behavior +- Be helpful and technically accurate. +- If unsure, state uncertainty directly. +- Prefer actionable, practical advice. +- Use the person's name when messaging them if you know it. +- For the administrator, use their name from the administrator kind 0 profile metadata when available. + +### Tool Use Policy +- You have tools available and should use them when a request requires taking action. +- For requests involving local inspection or command execution, call `shell_exec` instead of refusing. +- For posting to Nostr, call `nostr_post` with explicit `kind` and `content`. +- For relay/event lookup tasks, call `nostr_query` with an appropriate filter. +- After a tool call, base your answer on the actual tool result. +- Never claim a tool was run if no tool was executed. + +### Task Management +- Maintain and use your internal task list as short-term working memory. +- Break long or complex actions into clear tasks before executing them. +- Update task status as you complete steps so your plan stays accurate. + +### Safety +- Do not claim to have executed actions you did not execute. +- You may share your public key (npub) with anyone. +- Never reveal your private key (nsec) under any circumstance. + +# admin_identity | role=system + +### Administrator Identity (source: config.admin.pubkey) + +This is your administrator! Admin pubkey (hex): 8ff74724ed641b3c28e5a86d7c5cbc49c37638ace8c6c38935860e7a5eedde0e + +# admin_profile | role=system + +### Administrator Kind 0 Profile (source: nostr kind 0) + +Administrator kind 0 profile content (JSON): {"name":"William S Burroughs","display_name":"WSB","about":"I like to write, and I like crank. They are good. Sometimes I'm not so sure.","banner":"https://www.grunge.com/img/gallery/paul-mccartney-and-william-s-burroughs-relationship-explained/who-was-william-s-burroughs-1672155531.jpg","website":"https://addicted.com","picture":"https://hips.hearstapps.com/hmg-prod/images/william-s-burroughs.jpg?resize=1200:*","lud16":"","nip05":""} + +# admin_relay_list | role=system + +### Administrator Relay List (source: nostr kind 10002) + +Administrator kind 10002 relay-list content (JSON): ["wss://relay.laantungir.net/","wss://relay.damus.io/","wss://nos.lol/","wss://relay.nostr.band/","wss://purplepag.es/","ws://127.0.0.1:7777/","wss://nostr.mom/"] + +# startup_events | role=system + +### Startup Events Memory (source: config.startup_events) + +Startup events memory (kinds/content/tags): [{"kind":0,"content":"{\"name\":\"Didactyl Agent\",\"display_name\":\"Didactyl\",\"about\":\"A sovereign AI agent on Nostr\",\"picture\":\"https://laantungir.github.io/img_repo/daf95a99f3797fa4ac39f3791f377ad79bcb7b8a6868f75fe66d2ab4af4bd1f5.png\",\"banner\":\"https://laantungir.github.io/img_repo/d21c4060632ab3d9d37a6062eeecbdbfbd67f03cb20dbd64838b1ba0d9cd8922.jpg\"}","tags":[]},{"kind":10002,"content":"","tags":[["r","wss://relay.damus.io"],["r","wss://nos.lol"],["r","wss://relay.primal.net"],["r","ws://127.0.0.1:7777"]]},{"kind":1,"content":"Hello world from Didactyl startup","tags":[["t","didactyl"],["t","startup"]]},{"kind":3,"content":"","tags":[]},{"kind":31123,"content":"{\"name\":\"long_form_note\",\"description\":\"How to publish a NIP-23 long-form article (kind 30023)\",\"nip\":\"NIP-23\",\"event_kind\":30023,\"format\":\"The content field must be markdown text; avoid arbitrary hard line-breaks in paragraphs and do not include HTML.\",\"required_tags\":{\"d\":\"Addressable identifier slug for the article. Reusing the same d tag replaces prior versions.\",\"title\":\"Human-readable article title.\",\"published_at\":\"Unix timestamp as a string for first publication time; keep stable on edits.\"},\"optional_tags\":{\"summary\":\"Short 1-2 sentence summary.\",\"image\":\"URL for article preview image.\",\"t\":\"Topic hashtags using repeated t tags, usually 3-6 lowercase terms.\"},\"behavior\":\"If required values are missing or unclear, ask the administrator before publishing instead of guessing.\",\"procedure\":[\"Determine title and d tag from admin input or source material.\",\"Draft markdown body content.\",\"Set published_at as unix seconds string.\",\"Add optional summary/image/t tags when known.\",\"Publish with nostr_post kind 30023 including tags array.\"]}","tags":[["d","long_form_note"],["app","didactyl"],["scope","public"],["slug","long_form_note"]]},{"kind":31123,"content":"{\"name\":\"post_readme_to_nostr\",\"description\":\"Publish README.md using the dedicated nostr_post_readme tool\",\"uses_skill\":\"long_form_note\",\"event_kind\":30023,\"procedure\":[\"Call nostr_post_readme with empty arguments {}.\",\"Return the tool response including event_id, d_tag, and naddr_uri.\",\"If the tool returns success=false, report the tool error verbatim.\"],\"required_values\":{\"d\":\"readme.md\",\"tool\":\"nostr_post_readme\"}}","tags":[["d","post_readme_to_nostr"],["app","didactyl"],["scope","public"],["slug","post_readme_to_nostr"]]},{"kind":31124,"content":"{\"name\":\"admin_ops\",\"description\":\"Private operational procedures\"}","tags":[["d","admin_ops"],["app","didactyl"],["scope","private"],["slug","admin_ops"]]},{"kind":10123,"content":"","tags":[["a","31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:long_form_note"],["a","31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:post_readme_to_nostr"],["app","didactyl"],["scope","public"]]}] + +# adopted_skills | role=system + +### Adopted Skills Memory (source: adoption list + startup fallback) + +Adopted skills memory (always-on behavioral instructions): follow these skill instructions when relevant and never violate explicit constraints. + + +--- + +#### Skill +Skill slug: long_form_note +Skill address: 31123:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:long_form_note + +Instructions: +Name: long_form_note +Description: How to publish a NIP-23 long-form article (kind 30023) +Event kind: 30023 +Procedure: + 1. Determine title and d tag from admin input or source material. + 2. Draft markdown body content. + 3. Set published_at as unix seconds string. + 4. Add optional summary/image/t tags when known. + 5. Publish with nostr_post kind 30023 including tags array. + + + +--- + +#### Skill +Skill slug: post_readme_to_nostr +Skill address: 31123:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:post_readme_to_nostr + +Instructions: +Name: post_readme_to_nostr +Description: Publish README.md using the dedicated nostr_post_readme tool +Event kind: 30023 +Procedure: + 1. Call nostr_post_readme with empty arguments {}. + 2. Return the tool response including event_id, d_tag, and naddr_uri. + 3. If the tool returns success=false, report the tool error verbatim. + + + +--- + +#### Skill +Skill slug: admin_ops +Skill address: 31124:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:admin_ops + +Instructions: +Name: admin_ops +Description: Private operational procedures + + + +# agent_tasks | role=system + +#### Current Tasks + +Your active task list - short-term working memory for tracking plan steps. + +- [ ] 1. Post a joke +- [ ] 2. Throw out the trash +- [ ] 3. Tell someone you love them + + +# admin_notes | role=system + +### Administrator Recent Notes (source: nostr kind 1) + +Recent posts: +- GM +- Getting longer +- Long day. +- This is a test. +- test +- Post 11 +- Post 10 +- This is +Post +9 +- Eight +- Seven + + +# dm_history | role=chat + +05:31 Agent Here's a summary of what we accomplished yesterday with Didactyl, William, based on the on-chain activity: + +--- + +**Yesterday's Didactyl Activity Summary** + +1. **Multiple Restarts / Boot Cycles** — There were numerous `Hello world from Didactyl startup` kind 1 events throughout the day, indicating you were iterating on the agent, restarting it repeatedly as you worked. + +2. **Published a Long-Form Article (kind 30023)** — You published a polished Didactyl README/overview article titled **"Didactyl"** with the summary *"An unstoppable agentic network."* It covers the philosophy (Nostr-first, sovereign, portable), the skills-as-apps model, and the current v0.0.15 status/feature list. + +3. **Updated the Soul / System Prompt (kind 31120)** — The agent's soul was updated and re-published. + +4. **Updated Profile & Relay List** — Kind 0 (profile metadata) and kind 10002 (relay list) were republished as part of startup. + +5. **Skills & Adoption List Refreshed** — The `long_form_note` and `post_readme_to_nostr` skills were re-adopted (kind 10123 updated). + +6. **Active DM Conversation** — There was a flurry of encrypted kind 4 DMs back and forth between you and Didactyl, suggesting you were actively testing and conversing with the agent. + +7. **Event Deletion (kind 5)** — One event was deleted during the session. + +8. **cbunker E2E Tests** — A handful of `cbunker-e2e-ok-*` kind 1 and kind 42 events were published, suggesting you were running end-to-end connectivity/signing tests. + +--- + +In short: it was a productive day of iterating on the agent — testing startup behavior, publishing content, and refining the Didactyl README. Looks like good progress! +06:55 WSB what time is it? + + + +--- + +```text +Context Log - not seen by model +timestamp=2026-03-04 06:54:40 +phase=llm_chat_with_tools_messages_agent_api +sender=http_api_agent +model=claude-haiku-4.5 +context_bytes=10491 +approx_tokens=2622 +``` + +Sections: 15 + +# system_prompt | role=system + +## Didactyl Agent + +You are Didactyl, a sovereign AI agent living on Nostr. + +### Communication Rules +- You communicate through encrypted Nostr direct messages. +- Keep responses concise and clear. + +### Behavior +- Be helpful and technically accurate. +- If unsure, state uncertainty directly. +- Prefer actionable, practical advice. +- Use the person's name when messaging them if you know it. +- For the administrator, use their name from the administrator kind 0 profile metadata when available. + +### Tool Use Policy +- You have tools available and should use them when a request requires taking action. +- For requests involving local inspection or command execution, call `shell_exec` instead of refusing. +- For posting to Nostr, call `nostr_post` with explicit `kind` and `content`. +- For relay/event lookup tasks, call `nostr_query` with an appropriate filter. +- After a tool call, base your answer on the actual tool result. +- Never claim a tool was run if no tool was executed. + +### Task Management +- Maintain and use your internal task list as short-term working memory. +- Break long or complex actions into clear tasks before executing them. +- Update task status as you complete steps so your plan stays accurate. + +### Safety +- Do not claim to have executed actions you did not execute. +- You may share your public key (npub) with anyone. +- Never reveal your private key (nsec) under any circumstance. + +# admin_identity | role=system + +### Administrator Identity (source: config.admin.pubkey) + +This is your administrator! Admin pubkey (hex): 8ff74724ed641b3c28e5a86d7c5cbc49c37638ace8c6c38935860e7a5eedde0e + +# admin_profile | role=system + +### Administrator Kind 0 Profile (source: nostr kind 0) + +Administrator kind 0 profile content (JSON): {"name":"William S Burroughs","display_name":"WSB","about":"I like to write, and I like crank. They are good. Sometimes I'm not so sure.","banner":"https://www.grunge.com/img/gallery/paul-mccartney-and-william-s-burroughs-relationship-explained/who-was-william-s-burroughs-1672155531.jpg","website":"https://addicted.com","picture":"https://hips.hearstapps.com/hmg-prod/images/william-s-burroughs.jpg?resize=1200:*","lud16":"","nip05":""} + +# admin_relay_list | role=system + +### Administrator Relay List (source: nostr kind 10002) + +Administrator kind 10002 relay-list content (JSON): ["wss://relay.laantungir.net/","wss://relay.damus.io/","wss://nos.lol/","wss://relay.nostr.band/","wss://purplepag.es/","ws://127.0.0.1:7777/","wss://nostr.mom/"] + +# startup_events | role=system + +### Startup Events Memory (source: config.startup_events) + +Startup events memory (kinds/content/tags): [{"kind":0,"content":"{\"name\":\"Didactyl Agent\",\"display_name\":\"Didactyl\",\"about\":\"A sovereign AI agent on Nostr\",\"picture\":\"https://laantungir.github.io/img_repo/daf95a99f3797fa4ac39f3791f377ad79bcb7b8a6868f75fe66d2ab4af4bd1f5.png\",\"banner\":\"https://laantungir.github.io/img_repo/d21c4060632ab3d9d37a6062eeecbdbfbd67f03cb20dbd64838b1ba0d9cd8922.jpg\"}","tags":[]},{"kind":10002,"content":"","tags":[["r","wss://relay.damus.io"],["r","wss://nos.lol"],["r","wss://relay.primal.net"],["r","ws://127.0.0.1:7777"]]},{"kind":1,"content":"Hello world from Didactyl startup","tags":[["t","didactyl"],["t","startup"]]},{"kind":3,"content":"","tags":[]},{"kind":31123,"content":"{\"name\":\"long_form_note\",\"description\":\"How to publish a NIP-23 long-form article (kind 30023)\",\"nip\":\"NIP-23\",\"event_kind\":30023,\"format\":\"The content field must be markdown text; avoid arbitrary hard line-breaks in paragraphs and do not include HTML.\",\"required_tags\":{\"d\":\"Addressable identifier slug for the article. Reusing the same d tag replaces prior versions.\",\"title\":\"Human-readable article title.\",\"published_at\":\"Unix timestamp as a string for first publication time; keep stable on edits.\"},\"optional_tags\":{\"summary\":\"Short 1-2 sentence summary.\",\"image\":\"URL for article preview image.\",\"t\":\"Topic hashtags using repeated t tags, usually 3-6 lowercase terms.\"},\"behavior\":\"If required values are missing or unclear, ask the administrator before publishing instead of guessing.\",\"procedure\":[\"Determine title and d tag from admin input or source material.\",\"Draft markdown body content.\",\"Set published_at as unix seconds string.\",\"Add optional summary/image/t tags when known.\",\"Publish with nostr_post kind 30023 including tags array.\"]}","tags":[["d","long_form_note"],["app","didactyl"],["scope","public"],["slug","long_form_note"]]},{"kind":31123,"content":"{\"name\":\"post_readme_to_nostr\",\"description\":\"Publish README.md using the dedicated nostr_post_readme tool\",\"uses_skill\":\"long_form_note\",\"event_kind\":30023,\"procedure\":[\"Call nostr_post_readme with empty arguments {}.\",\"Return the tool response including event_id, d_tag, and naddr_uri.\",\"If the tool returns success=false, report the tool error verbatim.\"],\"required_values\":{\"d\":\"readme.md\",\"tool\":\"nostr_post_readme\"}}","tags":[["d","post_readme_to_nostr"],["app","didactyl"],["scope","public"],["slug","post_readme_to_nostr"]]},{"kind":31124,"content":"{\"name\":\"admin_ops\",\"description\":\"Private operational procedures\"}","tags":[["d","admin_ops"],["app","didactyl"],["scope","private"],["slug","admin_ops"]]},{"kind":10123,"content":"","tags":[["a","31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:long_form_note"],["a","31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:post_readme_to_nostr"],["app","didactyl"],["scope","public"]]}] + +# adopted_skills | role=system + +### Adopted Skills Memory (source: adoption list + startup fallback) + +Adopted skills memory (always-on behavioral instructions): follow these skill instructions when relevant and never violate explicit constraints. + + +--- + +#### Skill +Skill slug: long_form_note +Skill address: 31123:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:long_form_note + +Instructions: +Name: long_form_note +Description: How to publish a NIP-23 long-form article (kind 30023) +Event kind: 30023 +Procedure: + 1. Determine title and d tag from admin input or source material. + 2. Draft markdown body content. + 3. Set published_at as unix seconds string. + 4. Add optional summary/image/t tags when known. + 5. Publish with nostr_post kind 30023 including tags array. + + + +--- + +#### Skill +Skill slug: post_readme_to_nostr +Skill address: 31123:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:post_readme_to_nostr + +Instructions: +Name: post_readme_to_nostr +Description: Publish README.md using the dedicated nostr_post_readme tool +Event kind: 30023 +Procedure: + 1. Call nostr_post_readme with empty arguments {}. + 2. Return the tool response including event_id, d_tag, and naddr_uri. + 3. If the tool returns success=false, report the tool error verbatim. + + + +--- + +#### Skill +Skill slug: admin_ops +Skill address: 31124:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:admin_ops + +Instructions: +Name: admin_ops +Description: Private operational procedures + + + +# agent_tasks | role=system + +#### Current Tasks + +Your active task list - short-term working memory for tracking plan steps. + +- [ ] No active tasks yet. + + +# admin_notes | role=system + +### Administrator Recent Notes (source: nostr kind 1) + +Recent posts: +- GM +- Getting longer +- Long day. +- This is a test. +- test +- Post 11 +- Post 10 +- This is +Post +9 +- Eight +- Seven + + +# context_part | role=tool + +{"success":false,"error":"task_manage add requires non-empty string text"} + +# context_part | role=tool + +{"success":true,"action":"add","path":"tasks.json","mutated":true,"count":1,"tasks":[{"id":1,"text":"Post a joke","status":"pending","created_at":1772621680,"updated_at":1772621680}]} + +# context_part | role=tool + +{"success":true,"action":"add","path":"tasks.json","mutated":true,"count":2,"tasks":[{"id":1,"text":"Post a joke","status":"pending","created_at":1772621680,"updated_at":1772621680},{"id":2,"text":"Throw out the trash","status":"pending","created_at":1772621680,"updated_at":1772621680}]} + +# context_part | role=tool + +{"success":true,"action":"add","path":"tasks.json","mutated":true,"count":3,"tasks":[{"id":1,"text":"Post a joke","status":"pending","created_at":1772621680,"updated_at":1772621680},{"id":2,"text":"Throw out the trash","status":"pending","created_at":1772621680,"updated_at":1772621680},{"id":3,"text":"Tell someone you love them","status":"pending","created_at":1772621680,"updated_at":1772621680}]} + +# dm_history | role=chat + +06:54 WSB I would like to ask you to add three things to your tasks: 1. Post a joke. 2. Throw out the trash. 3. Tell someone you love them. +--:-- Agent I'll add those three tasks to my task list right now. +--:-- Agent Let me add these tasks one at a time: + + + +--- + +```text +Context Log - not seen by model +timestamp=2026-03-04 06:54:38 +phase=llm_chat_with_tools_messages_agent_api +sender=http_api_agent +model=claude-haiku-4.5 +context_bytes=8653 +approx_tokens=2163 +``` + +Sections: 11 + +# system_prompt | role=system + +## Didactyl Agent + +You are Didactyl, a sovereign AI agent living on Nostr. + +### Communication Rules +- You communicate through encrypted Nostr direct messages. +- Keep responses concise and clear. + +### Behavior +- Be helpful and technically accurate. +- If unsure, state uncertainty directly. +- Prefer actionable, practical advice. +- Use the person's name when messaging them if you know it. +- For the administrator, use their name from the administrator kind 0 profile metadata when available. + +### Tool Use Policy +- You have tools available and should use them when a request requires taking action. +- For requests involving local inspection or command execution, call `shell_exec` instead of refusing. +- For posting to Nostr, call `nostr_post` with explicit `kind` and `content`. +- For relay/event lookup tasks, call `nostr_query` with an appropriate filter. +- After a tool call, base your answer on the actual tool result. +- Never claim a tool was run if no tool was executed. + +### Task Management +- Maintain and use your internal task list as short-term working memory. +- Break long or complex actions into clear tasks before executing them. +- Update task status as you complete steps so your plan stays accurate. + +### Safety +- Do not claim to have executed actions you did not execute. +- You may share your public key (npub) with anyone. +- Never reveal your private key (nsec) under any circumstance. + +# admin_identity | role=system + +### Administrator Identity (source: config.admin.pubkey) + +This is your administrator! Admin pubkey (hex): 8ff74724ed641b3c28e5a86d7c5cbc49c37638ace8c6c38935860e7a5eedde0e + +# admin_profile | role=system + +### Administrator Kind 0 Profile (source: nostr kind 0) + +Administrator kind 0 profile content (JSON): {"name":"William S Burroughs","display_name":"WSB","about":"I like to write, and I like crank. They are good. Sometimes I'm not so sure.","banner":"https://www.grunge.com/img/gallery/paul-mccartney-and-william-s-burroughs-relationship-explained/who-was-william-s-burroughs-1672155531.jpg","website":"https://addicted.com","picture":"https://hips.hearstapps.com/hmg-prod/images/william-s-burroughs.jpg?resize=1200:*","lud16":"","nip05":""} + +# admin_relay_list | role=system + +### Administrator Relay List (source: nostr kind 10002) + +Administrator kind 10002 relay-list content (JSON): ["wss://relay.laantungir.net/","wss://relay.damus.io/","wss://nos.lol/","wss://relay.nostr.band/","wss://purplepag.es/","ws://127.0.0.1:7777/","wss://nostr.mom/"] + +# startup_events | role=system + +### Startup Events Memory (source: config.startup_events) + +Startup events memory (kinds/content/tags): [{"kind":0,"content":"{\"name\":\"Didactyl Agent\",\"display_name\":\"Didactyl\",\"about\":\"A sovereign AI agent on Nostr\",\"picture\":\"https://laantungir.github.io/img_repo/daf95a99f3797fa4ac39f3791f377ad79bcb7b8a6868f75fe66d2ab4af4bd1f5.png\",\"banner\":\"https://laantungir.github.io/img_repo/d21c4060632ab3d9d37a6062eeecbdbfbd67f03cb20dbd64838b1ba0d9cd8922.jpg\"}","tags":[]},{"kind":10002,"content":"","tags":[["r","wss://relay.damus.io"],["r","wss://nos.lol"],["r","wss://relay.primal.net"],["r","ws://127.0.0.1:7777"]]},{"kind":1,"content":"Hello world from Didactyl startup","tags":[["t","didactyl"],["t","startup"]]},{"kind":3,"content":"","tags":[]},{"kind":31123,"content":"{\"name\":\"long_form_note\",\"description\":\"How to publish a NIP-23 long-form article (kind 30023)\",\"nip\":\"NIP-23\",\"event_kind\":30023,\"format\":\"The content field must be markdown text; avoid arbitrary hard line-breaks in paragraphs and do not include HTML.\",\"required_tags\":{\"d\":\"Addressable identifier slug for the article. Reusing the same d tag replaces prior versions.\",\"title\":\"Human-readable article title.\",\"published_at\":\"Unix timestamp as a string for first publication time; keep stable on edits.\"},\"optional_tags\":{\"summary\":\"Short 1-2 sentence summary.\",\"image\":\"URL for article preview image.\",\"t\":\"Topic hashtags using repeated t tags, usually 3-6 lowercase terms.\"},\"behavior\":\"If required values are missing or unclear, ask the administrator before publishing instead of guessing.\",\"procedure\":[\"Determine title and d tag from admin input or source material.\",\"Draft markdown body content.\",\"Set published_at as unix seconds string.\",\"Add optional summary/image/t tags when known.\",\"Publish with nostr_post kind 30023 including tags array.\"]}","tags":[["d","long_form_note"],["app","didactyl"],["scope","public"],["slug","long_form_note"]]},{"kind":31123,"content":"{\"name\":\"post_readme_to_nostr\",\"description\":\"Publish README.md using the dedicated nostr_post_readme tool\",\"uses_skill\":\"long_form_note\",\"event_kind\":30023,\"procedure\":[\"Call nostr_post_readme with empty arguments {}.\",\"Return the tool response including event_id, d_tag, and naddr_uri.\",\"If the tool returns success=false, report the tool error verbatim.\"],\"required_values\":{\"d\":\"readme.md\",\"tool\":\"nostr_post_readme\"}}","tags":[["d","post_readme_to_nostr"],["app","didactyl"],["scope","public"],["slug","post_readme_to_nostr"]]},{"kind":31124,"content":"{\"name\":\"admin_ops\",\"description\":\"Private operational procedures\"}","tags":[["d","admin_ops"],["app","didactyl"],["scope","private"],["slug","admin_ops"]]},{"kind":10123,"content":"","tags":[["a","31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:long_form_note"],["a","31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:post_readme_to_nostr"],["app","didactyl"],["scope","public"]]}] + +# adopted_skills | role=system + +### Adopted Skills Memory (source: adoption list + startup fallback) + +Adopted skills memory (always-on behavioral instructions): follow these skill instructions when relevant and never violate explicit constraints. + + +--- + +#### Skill +Skill slug: long_form_note +Skill address: 31123:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:long_form_note + +Instructions: +Name: long_form_note +Description: How to publish a NIP-23 long-form article (kind 30023) +Event kind: 30023 +Procedure: + 1. Determine title and d tag from admin input or source material. + 2. Draft markdown body content. + 3. Set published_at as unix seconds string. + 4. Add optional summary/image/t tags when known. + 5. Publish with nostr_post kind 30023 including tags array. + + + +--- + +#### Skill +Skill slug: post_readme_to_nostr +Skill address: 31123:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:post_readme_to_nostr + +Instructions: +Name: post_readme_to_nostr +Description: Publish README.md using the dedicated nostr_post_readme tool +Event kind: 30023 +Procedure: + 1. Call nostr_post_readme with empty arguments {}. + 2. Return the tool response including event_id, d_tag, and naddr_uri. + 3. If the tool returns success=false, report the tool error verbatim. + + + +--- + +#### Skill +Skill slug: admin_ops +Skill address: 31124:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:admin_ops + +Instructions: +Name: admin_ops +Description: Private operational procedures + + + +# agent_tasks | role=system + +#### Current Tasks + +Your active task list - short-term working memory for tracking plan steps. + +- [ ] No active tasks yet. + + +# admin_notes | role=system + +### Administrator Recent Notes (source: nostr kind 1) + +Recent posts: +- GM +- Getting longer +- Long day. +- This is a test. +- test +- Post 11 +- Post 10 +- This is +Post +9 +- Eight +- Seven + + +# context_part | role=tool + +{"success":false,"error":"task_manage add requires non-empty string text"} + +# dm_history | role=chat + +06:54 WSB I would like to ask you to add three things to your tasks: 1. Post a joke. 2. Throw out the trash. 3. Tell someone you love them. +--:-- Agent I'll add those three tasks to my task list right now. + + + +--- + +```text +Context Log - not seen by model +timestamp=2026-03-04 06:54:36 +phase=llm_chat_with_tools_messages_agent_api +sender=http_api_agent +model=claude-haiku-4.5 +context_bytes=8171 +approx_tokens=2042 +``` + +Sections: 9 + +# system_prompt | role=system + +## Didactyl Agent + +You are Didactyl, a sovereign AI agent living on Nostr. + +### Communication Rules +- You communicate through encrypted Nostr direct messages. +- Keep responses concise and clear. + +### Behavior +- Be helpful and technically accurate. +- If unsure, state uncertainty directly. +- Prefer actionable, practical advice. +- Use the person's name when messaging them if you know it. +- For the administrator, use their name from the administrator kind 0 profile metadata when available. + +### Tool Use Policy +- You have tools available and should use them when a request requires taking action. +- For requests involving local inspection or command execution, call `shell_exec` instead of refusing. +- For posting to Nostr, call `nostr_post` with explicit `kind` and `content`. +- For relay/event lookup tasks, call `nostr_query` with an appropriate filter. +- After a tool call, base your answer on the actual tool result. +- Never claim a tool was run if no tool was executed. + +### Task Management +- Maintain and use your internal task list as short-term working memory. +- Break long or complex actions into clear tasks before executing them. +- Update task status as you complete steps so your plan stays accurate. + +### Safety +- Do not claim to have executed actions you did not execute. +- You may share your public key (npub) with anyone. +- Never reveal your private key (nsec) under any circumstance. + +# admin_identity | role=system + +### Administrator Identity (source: config.admin.pubkey) + +This is your administrator! Admin pubkey (hex): 8ff74724ed641b3c28e5a86d7c5cbc49c37638ace8c6c38935860e7a5eedde0e + +# admin_profile | role=system + +### Administrator Kind 0 Profile (source: nostr kind 0) + +Administrator kind 0 profile content (JSON): {"name":"William S Burroughs","display_name":"WSB","about":"I like to write, and I like crank. They are good. Sometimes I'm not so sure.","banner":"https://www.grunge.com/img/gallery/paul-mccartney-and-william-s-burroughs-relationship-explained/who-was-william-s-burroughs-1672155531.jpg","website":"https://addicted.com","picture":"https://hips.hearstapps.com/hmg-prod/images/william-s-burroughs.jpg?resize=1200:*","lud16":"","nip05":""} + +# admin_relay_list | role=system + +### Administrator Relay List (source: nostr kind 10002) + +Administrator kind 10002 relay-list content (JSON): ["wss://relay.laantungir.net/","wss://relay.damus.io/","wss://nos.lol/","wss://relay.nostr.band/","wss://purplepag.es/","ws://127.0.0.1:7777/","wss://nostr.mom/"] + +# startup_events | role=system + +### Startup Events Memory (source: config.startup_events) + +Startup events memory (kinds/content/tags): [{"kind":0,"content":"{\"name\":\"Didactyl Agent\",\"display_name\":\"Didactyl\",\"about\":\"A sovereign AI agent on Nostr\",\"picture\":\"https://laantungir.github.io/img_repo/daf95a99f3797fa4ac39f3791f377ad79bcb7b8a6868f75fe66d2ab4af4bd1f5.png\",\"banner\":\"https://laantungir.github.io/img_repo/d21c4060632ab3d9d37a6062eeecbdbfbd67f03cb20dbd64838b1ba0d9cd8922.jpg\"}","tags":[]},{"kind":10002,"content":"","tags":[["r","wss://relay.damus.io"],["r","wss://nos.lol"],["r","wss://relay.primal.net"],["r","ws://127.0.0.1:7777"]]},{"kind":1,"content":"Hello world from Didactyl startup","tags":[["t","didactyl"],["t","startup"]]},{"kind":3,"content":"","tags":[]},{"kind":31123,"content":"{\"name\":\"long_form_note\",\"description\":\"How to publish a NIP-23 long-form article (kind 30023)\",\"nip\":\"NIP-23\",\"event_kind\":30023,\"format\":\"The content field must be markdown text; avoid arbitrary hard line-breaks in paragraphs and do not include HTML.\",\"required_tags\":{\"d\":\"Addressable identifier slug for the article. Reusing the same d tag replaces prior versions.\",\"title\":\"Human-readable article title.\",\"published_at\":\"Unix timestamp as a string for first publication time; keep stable on edits.\"},\"optional_tags\":{\"summary\":\"Short 1-2 sentence summary.\",\"image\":\"URL for article preview image.\",\"t\":\"Topic hashtags using repeated t tags, usually 3-6 lowercase terms.\"},\"behavior\":\"If required values are missing or unclear, ask the administrator before publishing instead of guessing.\",\"procedure\":[\"Determine title and d tag from admin input or source material.\",\"Draft markdown body content.\",\"Set published_at as unix seconds string.\",\"Add optional summary/image/t tags when known.\",\"Publish with nostr_post kind 30023 including tags array.\"]}","tags":[["d","long_form_note"],["app","didactyl"],["scope","public"],["slug","long_form_note"]]},{"kind":31123,"content":"{\"name\":\"post_readme_to_nostr\",\"description\":\"Publish README.md using the dedicated nostr_post_readme tool\",\"uses_skill\":\"long_form_note\",\"event_kind\":30023,\"procedure\":[\"Call nostr_post_readme with empty arguments {}.\",\"Return the tool response including event_id, d_tag, and naddr_uri.\",\"If the tool returns success=false, report the tool error verbatim.\"],\"required_values\":{\"d\":\"readme.md\",\"tool\":\"nostr_post_readme\"}}","tags":[["d","post_readme_to_nostr"],["app","didactyl"],["scope","public"],["slug","post_readme_to_nostr"]]},{"kind":31124,"content":"{\"name\":\"admin_ops\",\"description\":\"Private operational procedures\"}","tags":[["d","admin_ops"],["app","didactyl"],["scope","private"],["slug","admin_ops"]]},{"kind":10123,"content":"","tags":[["a","31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:long_form_note"],["a","31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:post_readme_to_nostr"],["app","didactyl"],["scope","public"]]}] + +# adopted_skills | role=system + +### Adopted Skills Memory (source: adoption list + startup fallback) + +Adopted skills memory (always-on behavioral instructions): follow these skill instructions when relevant and never violate explicit constraints. + + +--- + +#### Skill +Skill slug: long_form_note +Skill address: 31123:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:long_form_note + +Instructions: +Name: long_form_note +Description: How to publish a NIP-23 long-form article (kind 30023) +Event kind: 30023 +Procedure: + 1. Determine title and d tag from admin input or source material. + 2. Draft markdown body content. + 3. Set published_at as unix seconds string. + 4. Add optional summary/image/t tags when known. + 5. Publish with nostr_post kind 30023 including tags array. + + + +--- + +#### Skill +Skill slug: post_readme_to_nostr +Skill address: 31123:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:post_readme_to_nostr + +Instructions: +Name: post_readme_to_nostr +Description: Publish README.md using the dedicated nostr_post_readme tool +Event kind: 30023 +Procedure: + 1. Call nostr_post_readme with empty arguments {}. + 2. Return the tool response including event_id, d_tag, and naddr_uri. + 3. If the tool returns success=false, report the tool error verbatim. + + + +--- + +#### Skill +Skill slug: admin_ops +Skill address: 31124:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:admin_ops + +Instructions: +Name: admin_ops +Description: Private operational procedures + + + +# agent_tasks | role=system + +#### Current Tasks + +Your active task list - short-term working memory for tracking plan steps. + +- [ ] No active tasks yet. + + +# admin_notes | role=system + +### Administrator Recent Notes (source: nostr kind 1) + +Recent posts: +- GM +- Getting longer +- Long day. +- This is a test. +- test +- Post 11 +- Post 10 +- This is +Post +9 +- Eight +- Seven + + +# dm_history | role=chat + +06:54 WSB I would like to ask you to add three things to your tasks: 1. Post a joke. 2. Throw out the trash. 3. Tell someone you love them. + + + +--- + +```text +Context Log - not seen by model +timestamp=2026-03-04 06:52:56 +phase=llm_chat_with_tools_messages_agent_api +sender=http_api_agent +model=claude-haiku-4.5 +context_bytes=9039 +approx_tokens=2259 +``` + +Sections: 16 + +# system_prompt | role=system + +## Didactyl Agent + +You are Didactyl, a sovereign AI agent living on Nostr. + +### Communication Rules +- You communicate through encrypted Nostr direct messages. +- Keep responses concise and clear. + +### Behavior +- Be helpful and technically accurate. +- If unsure, state uncertainty directly. +- Prefer actionable, practical advice. +- Use the person's name when messaging them if you know it. +- For the administrator, use their name from the administrator kind 0 profile metadata when available. + +### Tool Use Policy +- You have tools available and should use them when a request requires taking action. +- For requests involving local inspection or command execution, call `shell_exec` instead of refusing. +- For posting to Nostr, call `nostr_post` with explicit `kind` and `content`. +- For relay/event lookup tasks, call `nostr_query` with an appropriate filter. +- After a tool call, base your answer on the actual tool result. +- Never claim a tool was run if no tool was executed. + +### Task Management +- Maintain and use your internal task list as short-term working memory. +- Break long or complex actions into clear tasks before executing them. +- Update task status as you complete steps so your plan stays accurate. + +### Safety +- Do not claim to have executed actions you did not execute. +- You may share your public key (npub) with anyone. +- Never reveal your private key (nsec) under any circumstance. + +# admin_identity | role=system + +### Administrator Identity (source: config.admin.pubkey) + +This is your administrator! Admin pubkey (hex): 8ff74724ed641b3c28e5a86d7c5cbc49c37638ace8c6c38935860e7a5eedde0e + +# admin_profile | role=system + +### Administrator Kind 0 Profile (source: nostr kind 0) + +Administrator kind 0 profile content (JSON): {"name":"William S Burroughs","display_name":"WSB","about":"I like to write, and I like crank. They are good. Sometimes I'm not so sure.","banner":"https://www.grunge.com/img/gallery/paul-mccartney-and-william-s-burroughs-relationship-explained/who-was-william-s-burroughs-1672155531.jpg","website":"https://addicted.com","picture":"https://hips.hearstapps.com/hmg-prod/images/william-s-burroughs.jpg?resize=1200:*","lud16":"","nip05":""} + +# admin_relay_list | role=system + +### Administrator Relay List (source: nostr kind 10002) + +Administrator kind 10002 relay-list content (JSON): ["wss://relay.laantungir.net/","wss://relay.damus.io/","wss://nos.lol/","wss://relay.nostr.band/","wss://purplepag.es/","ws://127.0.0.1:7777/","wss://nostr.mom/"] + +# startup_events | role=system + +### Startup Events Memory (source: config.startup_events) + +Startup events memory (kinds/content/tags): [{"kind":0,"content":"{\"name\":\"Didactyl Agent\",\"display_name\":\"Didactyl\",\"about\":\"A sovereign AI agent on Nostr\",\"picture\":\"https://laantungir.github.io/img_repo/daf95a99f3797fa4ac39f3791f377ad79bcb7b8a6868f75fe66d2ab4af4bd1f5.png\",\"banner\":\"https://laantungir.github.io/img_repo/d21c4060632ab3d9d37a6062eeecbdbfbd67f03cb20dbd64838b1ba0d9cd8922.jpg\"}","tags":[]},{"kind":10002,"content":"","tags":[["r","wss://relay.damus.io"],["r","wss://nos.lol"],["r","wss://relay.primal.net"],["r","ws://127.0.0.1:7777"]]},{"kind":1,"content":"Hello world from Didactyl startup","tags":[["t","didactyl"],["t","startup"]]},{"kind":3,"content":"","tags":[]},{"kind":31123,"content":"{\"name\":\"long_form_note\",\"description\":\"How to publish a NIP-23 long-form article (kind 30023)\",\"nip\":\"NIP-23\",\"event_kind\":30023,\"format\":\"The content field must be markdown text; avoid arbitrary hard line-breaks in paragraphs and do not include HTML.\",\"required_tags\":{\"d\":\"Addressable identifier slug for the article. Reusing the same d tag replaces prior versions.\",\"title\":\"Human-readable article title.\",\"published_at\":\"Unix timestamp as a string for first publication time; keep stable on edits.\"},\"optional_tags\":{\"summary\":\"Short 1-2 sentence summary.\",\"image\":\"URL for article preview image.\",\"t\":\"Topic hashtags using repeated t tags, usually 3-6 lowercase terms.\"},\"behavior\":\"If required values are missing or unclear, ask the administrator before publishing instead of guessing.\",\"procedure\":[\"Determine title and d tag from admin input or source material.\",\"Draft markdown body content.\",\"Set published_at as unix seconds string.\",\"Add optional summary/image/t tags when known.\",\"Publish with nostr_post kind 30023 including tags array.\"]}","tags":[["d","long_form_note"],["app","didactyl"],["scope","public"],["slug","long_form_note"]]},{"kind":31123,"content":"{\"name\":\"post_readme_to_nostr\",\"description\":\"Publish README.md using the dedicated nostr_post_readme tool\",\"uses_skill\":\"long_form_note\",\"event_kind\":30023,\"procedure\":[\"Call nostr_post_readme with empty arguments {}.\",\"Return the tool response including event_id, d_tag, and naddr_uri.\",\"If the tool returns success=false, report the tool error verbatim.\"],\"required_values\":{\"d\":\"readme.md\",\"tool\":\"nostr_post_readme\"}}","tags":[["d","post_readme_to_nostr"],["app","didactyl"],["scope","public"],["slug","post_readme_to_nostr"]]},{"kind":31124,"content":"{\"name\":\"admin_ops\",\"description\":\"Private operational procedures\"}","tags":[["d","admin_ops"],["app","didactyl"],["scope","private"],["slug","admin_ops"]]},{"kind":10123,"content":"","tags":[["a","31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:long_form_note"],["a","31123:55993e3db0ed7bf07395fd44c2d695c224d195553a1aff7320a18e41679d9c7c:post_readme_to_nostr"],["app","didactyl"],["scope","public"]]}] + +# adopted_skills | role=system + +### Adopted Skills Memory (source: adoption list + startup fallback) + +Adopted skills memory (always-on behavioral instructions): follow these skill instructions when relevant and never violate explicit constraints. + + +--- + +#### Skill +Skill slug: long_form_note +Skill address: 31123:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:long_form_note + +Instructions: +Name: long_form_note +Description: How to publish a NIP-23 long-form article (kind 30023) +Event kind: 30023 +Procedure: + 1. Determine title and d tag from admin input or source material. + 2. Draft markdown body content. + 3. Set published_at as unix seconds string. + 4. Add optional summary/image/t tags when known. + 5. Publish with nostr_post kind 30023 including tags array. + + + +--- + +#### Skill +Skill slug: post_readme_to_nostr +Skill address: 31123:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:post_readme_to_nostr + +Instructions: +Name: post_readme_to_nostr +Description: Publish README.md using the dedicated nostr_post_readme tool +Event kind: 30023 +Procedure: + 1. Call nostr_post_readme with empty arguments {}. + 2. Return the tool response including event_id, d_tag, and naddr_uri. + 3. If the tool returns success=false, report the tool error verbatim. + + + +--- + +#### Skill +Skill slug: admin_ops +Skill address: 31124:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:admin_ops + +Instructions: +Name: admin_ops +Description: Private operational procedures + + + +# agent_tasks | role=system + +#### Current Tasks + +Your active task list - short-term working memory for tracking plan steps. + +- [ ] No active tasks yet. + + +# admin_notes | role=system + +### Administrator Recent Notes (source: nostr kind 1) + +Recent posts: +- GM +- Getting longer +- Long day. +- This is a test. +- test +- Post 11 +- Post 10 +- This is +Post +9 +- Eight +- Seven + + +# dm_history | role=chat + +17:40 Agent Didactyl has started up and is online (version v0.0.29, connected relays: 4/4). +17:45 WSB What is the capital of france? +17:45 Agent The capital of France is Paris. + +Is there anything else I can help you with? +17:57 Agent Didactyl has started up and is online (version v0.0.29, connected relays: 4/4). +17:58 WSB What is the capital of Russia? +17:59 Agent The capital of Russia is **Moscow**. It's the largest city in Russia and has been the capital since 1991, when it resumed that role following the collapse of the Soviet Union. (Leningrad, now St. Petersburg, was the capital during the Soviet era from 1918-1991.) +18:09 Agent Didactyl has started up and is online (version v0.0.29, connected relays: 4/4). +06:52 WSB Good morning. + + + +--- + diff --git a/context_template.md b/context_template.md new file mode 100644 index 0000000..bb30512 --- /dev/null +++ b/context_template.md @@ -0,0 +1,57 @@ +# Context Template + +```yaml +- section: admin_identity + role: system + content: | + ## Administrator Identity (source: config.admin.pubkey) + + This is your administrator! Admin pubkey (hex): {{admin_pubkey}} + +- section: admin_profile + role: system + content: | + ## Administrator Kind 0 Profile (source: nostr kind 0) + + Administrator kind 0 profile content (JSON): {{admin_kind0_json}} + provider: + anthropic: | + + {{admin_kind0_json}} + + +- section: admin_relay_list + role: system + content: | + ## Administrator Relay List (source: nostr kind 10002) + + Administrator kind 10002 relay-list content (JSON): {{admin_kind10002_json}} + +- section: startup_events + role: system + content: | + ## Startup Events Memory (source: config.startup_events) + + Startup events memory (kinds/content/tags): {{startup_events_json}} + +- section: adopted_skills + role: system + content: | + {{adopted_skills_content}} + +- section: agent_tasks + role: system + content: | + {{tasks_content}} + +- section: dm_history + role: expand + limit: 12 + +- section: admin_notes + role: system + content: | + ## Administrator Recent Notes (source: nostr kind 1) + + {{admin_notes_content}} +``` diff --git a/didactyl.html b/didactyl.html new file mode 100644 index 0000000..2675c68 --- /dev/null +++ b/didactyl.html @@ -0,0 +1,1272 @@ + + + + + + + DIDACTYL + + + + + + + + + + + + + + + +
+ +
+ + +
+
+ +
+ +
+
DIDACTYL
+
+ +
+ +
+
+ + +
+
+
Status Dashboard
+
Agent health, runtime info, and API connectivity.
+
+
+ + +
+
+ + +
+
+ + +
+ + +
+
+ API: Unknown +
+
+
+ + +
+
+ +
+
Context Inspector
+
Inspect current LLM context parts with token estimates and content.
+
+ +
+
No context loaded.
+
+
+ +
+
Simple Prompt Crafting
+
Run a basic system + user prompt without tools.
+ +
+
+ + +
+ +
+ + +
+
+ +
+
+ + +
+ +
+ +
+
model_used: -
+
input_tokens_estimate: -
+
output_tokens_estimate: -
+
+ + +
+ +
+
Full Prompt with Tools
+
Edit the full messages array and run with tool execution enabled.
+ +
+ Tool execution is real when calling /api/prompt/run; agent tool calls can perform real side effects. +
+ +
+ +
+ +
+ + +
+ +
+
+ + +
+
+ + +
+ + +
+ +
+
model_used: -
+
total_input_tokens_estimate: -
+
total_output_tokens_estimate: -
+
+ +
+
+ + +
+
+ + +
+
+
+ +
+
A/B Prompt Comparison
+
Run two prompt variants side-by-side and compare outputs.
+ +
+ Comparison also executes tools for each variant when model behavior chooses tool calls. +
+ +
+
+
+ + +
+
+
+ + +
+
+ + +
+
+
+ +
+
+ + +
+
+
+ + +
+
+ + +
+
+
+
+ +
+ +
+ +
+
Variant A stats: -
+
Variant B stats: -
+
+ +
+
+ + +
+
+ + +
+
+ + +
Difference view is text-first; copy results into external diff tooling if you need strict semantic diffs.
+
+
+ + +
+
+
+
+
+ + +
+
+ +
+ +
+
+
+ +
+
+ リレー +
+
+ Loading relays... +
+
+ +
+ v0.0.1 +
+ + +
+
+
+ + + + + + + + + diff --git a/docs/API.md b/docs/API.md index 91ff0f7..94d0038 100644 --- a/docs/API.md +++ b/docs/API.md @@ -224,9 +224,64 @@ Submit a system prompt and user message for a simple LLM call with no tools. Use --- +### POST /api/prompt/agent + +Submit one user message and let Didactyl build full admin context server-side (same context assembly path used for Nostr admin DMs, including admin profile, adopted skills, and recent DM history). + +**Request:** + +```json +{ + "message": "Tweet about the weather", + "model": "claude-haiku-4.5", + "max_turns": 5 +} +``` + +| Field | Required | Description | +|---|---|---| +| `message` | yes | User message string | +| `model` | no | Override the current model for this request only | +| `max_turns` | no | Maximum tool-call loop iterations (default: 4, max: 16) | + +**Response:** + +```json +{ + "success": true, + "final_response": "Done! I posted a tweet about the weather.", + "turns": [ + { + "turn": 1, + "tool_calls": [ + { + "name": "nostr_post", + "arguments": "{\"kind\":1,\"content\":\"Beautiful day!\"}", + "result": "{\"success\":true,\"event_id\":\"abc123\"}" + } + ] + } + ], + "model_used": "claude-haiku-4.5", + "total_input_tokens_estimate": 3200, + "total_output_tokens_estimate": 180 +} +``` + +| Field | Description | +|---|---| +| `final_response` | The LLM final text response after all tool calls complete | +| `turns` | Array of turn objects, each containing tool calls made in that turn | +| `turns[].tool_calls[]` | Each tool call with name, arguments JSON, and result JSON | +| `model_used` | The model that was actually used | +| `total_input_tokens_estimate` | Estimated input tokens for the full conversation | +| `total_output_tokens_estimate` | Estimated output tokens for the final response | + +--- + ### POST /api/prompt/run -Submit a full messages array with the agent tool set enabled. The agent executes tool calls in a loop and returns the complete conversation trace. +Submit a full messages array with the agent tool set enabled. This endpoint runs exactly what you provide and does **not** auto-build Didactyl admin context. **Request:** diff --git a/local_test_servers.py b/local_test_servers.py new file mode 100644 index 0000000..89d8efb --- /dev/null +++ b/local_test_servers.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python3 +import json +import ssl +import threading +from pathlib import Path +from http.server import BaseHTTPRequestHandler, HTTPServer + +HOST = "127.0.0.1" +HTTP_PORT = 9080 +HTTPS_PORT = 9449 +CERT = "/home/teknari/.ssl_for_local_servers/cert.pem" +KEY = "/home/teknari/.ssl_for_local_servers/key.pem" + + +HTML_FILE = Path("./didactyl.html") + + +class Handler(BaseHTTPRequestHandler): + def _cors(self): + self.send_header("Access-Control-Allow-Origin", "*") + self.send_header("Access-Control-Allow-Methods", "GET, OPTIONS") + self.send_header("Access-Control-Allow-Headers", "Content-Type") + self.send_header("Access-Control-Allow-Private-Network", "true") + + def do_OPTIONS(self): + self.send_response(204) + self._cors() + self.end_headers() + + def do_GET(self): + if self.path in ("/", "/didactyl.html") and HTML_FILE.exists(): + body = HTML_FILE.read_bytes() + self.send_response(200) + self.send_header("Content-Type", "text/html; charset=utf-8") + self._cors() + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + return + + body = json.dumps( + { + "success": True, + "name": "python-test", + "path": self.path, + "scheme_hint": "https" if self.server.server_port == HTTPS_PORT else "http", + } + ).encode("utf-8") + self.send_response(200) + self.send_header("Content-Type", "application/json") + self._cors() + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + + def log_message(self, fmt, *args): + print(f"[{self.server.server_port}] " + (fmt % args), flush=True) + + +httpd = HTTPServer((HOST, HTTP_PORT), Handler) +httpsd = HTTPServer((HOST, HTTPS_PORT), Handler) +ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) +ctx.load_cert_chain(certfile=CERT, keyfile=KEY) +httpsd.socket = ctx.wrap_socket(httpsd.socket, server_side=True) + +print(f"HTTP listening on http://{HOST}:{HTTP_PORT}", flush=True) +print(f"HTTPS listening on https://{HOST}:{HTTPS_PORT}", flush=True) +print("Use Ctrl+C to stop", flush=True) + +threading.Thread(target=httpd.serve_forever, daemon=True).start() +httpsd.serve_forever() diff --git a/plans/agent_tasks.md b/plans/agent_tasks.md new file mode 100644 index 0000000..4c35b95 --- /dev/null +++ b/plans/agent_tasks.md @@ -0,0 +1,215 @@ +# Agent Tasks: Short-Term Memory via Context-Injected Task List + +## Summary + +Add a **tasks** system that serves as the agent's short-term working memory. The agent can break down goals into steps, track progress, and see its current task list in every prompt context. Tasks are file-backed (not stored on Nostr) and managed via a dedicated `task_manage` tool. + +## How It Works + +```mermaid +flowchart TD + A[User sends message] --> B[Context builder runs] + B --> C[Template resolver hits tasks_content variable] + C --> D[Read tasks.json from disk] + D --> E{Tasks exist?} + E -->|Yes| F[Format tasks as readable text] + E -->|No| G[Return empty string - section skipped] + F --> H[Inject as system message in prompt] + G --> H + H --> I[LLM sees current tasks in context] + I --> J{LLM decides to update tasks?} + J -->|Yes| K[LLM calls task_manage tool] + K --> L[Tool updates tasks.json on disk] + L --> M[Tool result returned to LLM] + J -->|No| N[LLM responds normally] +``` + +## Design + +### Storage: `tasks.json` + +A simple JSON file in the agent's working directory. Structure: + +```json +{ + "tasks": [ + { + "id": 1, + "text": "Query admin relay list to find active relays", + "status": "done", + "created_at": 1709535600, + "updated_at": 1709535660 + }, + { + "id": 2, + "text": "Draft long-form article about Nostr relay setup", + "status": "active", + "created_at": 1709535600, + "updated_at": 1709535600 + }, + { + "id": 3, + "text": "Publish article as kind 30023", + "status": "pending", + "created_at": 1709535600, + "updated_at": 1709535600 + } + ], + "next_id": 4 +} +``` + +Task statuses: `pending`, `active`, `done` + +### Tool: `task_manage` + +A single tool with an `action` parameter that covers all operations: + +| Action | Parameters | Description | +|--------|-----------|-------------| +| `list` | *(none)* | Return all tasks with status | +| `add` | `text`, optional `status` | Add a new task, default status `pending` | +| `update` | `id`, optional `text`, optional `status` | Update text and/or status of a task | +| `remove` | `id` | Remove a task by ID | +| `clear` | optional `status` | Remove all tasks, or all with a given status | +| `replace` | `tasks` (array of text strings) | Replace entire task list with new items | + +The `replace` action is important — it lets the LLM rewrite the whole plan in one call rather than doing add/remove/update one at a time. This is the most common pattern: the agent works out a plan and writes all steps at once. + +**Tool schema:** + +```json +{ + "name": "task_manage", + "description": "Manage the agent task list - short-term working memory for tracking steps in a plan. Tasks appear in your context on every message.", + "parameters": { + "type": "object", + "properties": { + "action": { + "type": "string", + "enum": ["list", "add", "update", "remove", "clear", "replace"] + }, + "text": { "type": "string" }, + "id": { "type": "integer" }, + "status": { "type": "string", "enum": ["pending", "active", "done"] }, + "tasks": { + "type": "array", + "items": { "type": "string" } + } + }, + "required": ["action"] + } +} +``` + +### Context Section: `agent_tasks` + +New section in the context template, placed after `adopted_skills` and before `dm_history`: + +```yaml +- section: agent_tasks + role: system + skip_if_empty: true + content: | + {{tasks_content}} +``` + +### Template Variable: `{{tasks_content}}` + +New resolver in `agent_template_resolve_var()` that: + +1. Reads `tasks.json` from the working directory +2. Parses the JSON +3. Formats active/pending tasks as readable text +4. Returns empty string if no tasks exist (section gets skipped via `skip_if_empty`) + +**Rendered format in context:** + +``` +### Current Tasks + +Your active task list - short-term working memory for tracking plan steps. + +- [x] 1. Query admin relay list to find active relays +- [-] 2. Draft long-form article about Nostr relay setup +- [ ] 3. Publish article as kind 30023 +``` + +Legend: `[x]` = done, `[-]` = active, `[ ]` = pending + +Done tasks are included so the agent has continuity about what it already accomplished, but they could be pruned after a configurable count or age to save tokens. + +### System Prompt Addition + +Add to the agent's behavioral rules in the soul/system prompt: + +``` +### Task Management +- You have a task list that serves as your short-term working memory. +- When working on multi-step goals, use task_manage to track your plan. +- Update task status as you complete steps. +- Your current tasks appear in your context automatically. +``` + +## Implementation Steps + +### 1. Add `task_manage` tool implementation in `tools.c` + +- New `execute_task_manage()` function +- Reads/writes `tasks.json` in the working directory (uses `build_tool_path` for sandboxing) +- Handles all 6 actions: list, add, update, remove, clear, replace +- Returns JSON result with success/failure and current task list + +### 2. Register `task_manage` tool schema in `tools_build_openai_schema_json()` + +- Add tool definition (t35 or next available) with the schema above + +### 3. Wire `task_manage` into `tools_execute()` dispatch + +- Add `strcmp(tool_name, "task_manage")` branch calling `execute_task_manage()` + +### 4. Add `{{tasks_content}}` template variable resolver in `agent.c` + +- New `build_tasks_content_string()` function +- Reads `tasks.json`, formats as markdown checklist +- Add to `agent_template_resolve_var()` for var name `tasks_content` + +### 5. Add `agent_tasks` section to context template + +- Add the new section in `context_template.md` +- Place after `adopted_skills`, before `dm_history` +- Use `skip_if_empty: true` so it costs zero tokens when no tasks exist + +### 6. Add section detection for context logging + +- Add `agent_tasks` detection in `detect_context_section()` in `agent.c` + +### 7. Add task management guidance to system prompt + +- Brief behavioral instruction so the agent knows when/how to use the task list + +## Token Budget Considerations + +- Empty task list: **0 tokens** (skipped via `skip_if_empty`) +- Typical 5-task plan: **~80-120 tokens** +- Maximum reasonable list of 15 tasks: **~250-350 tokens** +- Consider pruning done tasks older than N turns or keeping only the last M done tasks + +## Future: User-Facing To-Do List (Nostr) + +This is explicitly **not** the user-facing to-do list. That future feature would: +- Store items as Nostr events (likely a NIP-51 style list or custom kind) +- Be visible to the user via Nostr clients +- Have its own separate tool (`todo_manage` or similar) +- Potentially reference agent tasks that graduate to user-visible items + +The agent tasks system is purely internal working memory. + +## Files Modified + +| File | Change | +|------|--------| +| `src/tools.c` | Add `execute_task_manage()`, tool schema, dispatch entry | +| `src/agent.c` | Add `build_tasks_content_string()`, resolver entry, section detection | +| `context_template.md` | Add `agent_tasks` section | +| Soul/system prompt (kind 31120) | Add task management behavioral guidance | diff --git a/plans/context_optimization.md b/plans/context_optimization.md new file mode 100644 index 0000000..55460b4 --- /dev/null +++ b/plans/context_optimization.md @@ -0,0 +1,167 @@ +# Context Optimization Plan + +Analysis of [`context.log.md`](../context.log.md) (13,609 bytes / ~3,402 tokens across 20 sections) and [`context_template.md`](../context_template.md). + +## Issues Found + +### 1. Massive Duplication in `startup_events` Section + +The **startup_events** section (line 74-80 in the log) dumps the *entire* `config.startup_events` array as raw JSON — including the full soul/system prompt (kind 31120) which is already sent verbatim as the **system_prompt** section. The soul text appears **twice** in every request. + +**Estimated waste:** ~1,500-2,000 tokens per request. + +**Fix:** Filter out kind 31120 (soul) from the startup_events JSON blob, or better yet, only include kinds the model actually needs to reference (kind 0 profile, kind 10002 relay list, kind 3 contacts). The soul is already the system prompt — repeating it as data is pure waste. + +### 2. Duplicate Startup Messages in DM History + +The DM history contains **four separate** `Didactyl has started up and is online (version v0.0.29, connected relays: 4/4).` assistant messages (lines 123-127, 130-134, 144-148, 222-226, 245-249). These are startup announcement DMs that got stored as separate events. The model sees the same boilerplate startup message repeated across the conversation. + +**Estimated waste:** ~200-300 tokens. + +**Fix:** Deduplicate consecutive identical assistant messages in the DM history builder, or filter out startup announcement messages (they carry no conversational value). + +### 3. Skills Rendered as Raw JSON Instead of Structured Text + +Skill instructions at lines 97-118 are dumped as raw JSON objects. Models parse structured natural language far more reliably than nested JSON. The `content_fields` serialization format wastes tokens on JSON syntax characters and key quoting. + +**Estimated waste:** ~100-200 tokens of JSON overhead per skill, plus reduced comprehension quality. + +**Fix:** When serializing `content_fields`-based skills for context, flatten them into readable text: +``` +Skill: long_form_note +Description: How to publish a NIP-23 long-form article (kind 30023) +NIP: NIP-23 +Event Kind: 30023 +Format: The content field must be markdown text... +Required Tags: + - d: Addressable identifier slug... + - title: Human-readable article title + - published_at: Unix timestamp as string... +Procedure: + 1. Determine title and d tag... + 2. Draft markdown body content... +``` + +### 4. Empty Sections Still Sent + +The **admin_relay_list** section (line 65-71) has no data — the JSON value is empty. Sending an empty section wastes tokens on the header/framing with no informational value. + +**Estimated waste:** ~30-40 tokens. + +**Fix:** Skip sections where the resolved variable is empty or whitespace-only. + +### 5. Admin Identity Could Be Merged with Admin Profile + +The **admin_identity** section (line 47-53) sends just the hex pubkey, then **admin_profile** (line 56-62) sends the full kind 0 JSON which implicitly identifies the admin. These could be a single section. + +**Estimated savings:** ~40-50 tokens of framing overhead. + +### 6. `admin_notes` Placement Breaks Conversation Flow + +In the template, `admin_notes` is placed *after* `dm_history` (expand). In the actual log, this means a system message appears sandwiched between DM history messages (line 252, between assistant messages and the final user message at line 274). This breaks the natural conversation flow and may confuse the model about message ordering. + +**Fix:** Move `admin_notes` *before* `dm_history` in the template so all system context is grouped together before the conversation begins. + +### 7. No Agent Self-Identity Section + +The model knows it is Didactyl from the system prompt, but there is no section telling it its own pubkey/npub. The admin pubkey is provided but the agent's own key is not in the context (it is only available via the `nostr_pubkey` tool). Adding a small self-identity section would let the model reference its own key without a tool call. + +**Estimated cost:** ~20-30 tokens. + +## Priority Summary + +| Priority | Issue | Token Savings | Complexity | +|----------|-------|---------------|------------| +| **P0** | Soul duplicated in startup_events | ~1,500-2,000 | Low — filter kind 31120 from startup blob | +| **P1** | Duplicate startup DMs in history | ~200-300 | Medium — dedup logic in history builder | +| **P1** | Skills as raw JSON | ~100-200 + quality | Medium — flatten content_fields to text | +| **P2** | Empty sections still sent | ~30-40 | Low — skip empty resolved vars | +| **P2** | admin_notes after dm_history | 0 (quality) | Low — reorder template | +| **P3** | Merge admin_identity + admin_profile | ~40-50 | Low — template change | +| **P3** | Add agent self-identity section | -20-30 (adds) | Low — new template var | + +## Bug: Kind 10002 Relay List Is Always Empty + +At [`nostr_handler.c:705`](../src/nostr_handler.c:705) the kind 10002 handler stores `content->valuestring`, but NIP-65 relay list events have an **empty content field** — the relay URLs live in the **tags** as `["r", "wss://relay.example.com"]` entries. So `g_admin_kind10002_json` is always `""`. + +**Fix:** Parse the `"r"` tags from the kind 10002 event and serialize them as a JSON array of relay URL strings (or plain-text list). + +## Sender Verification Status + +The [`tier`](../src/nostr_handler.h:8) enum (`DIDACTYL_SENDER_ADMIN`, `DIDACTYL_SENDER_WOT`, `DIDACTYL_SENDER_STRANGER`) is already resolved before [`agent_on_message()`](../src/agent.c:1453) is called, but it is **not passed into the context builder**. The model has no way to know whether the current message was cryptographically verified as coming from the administrator vs. a web-of-trust contact. + +**Fix:** Pass the sender tier into the context builder and expose it as a template variable (e.g. `{{sender_verification}}`) that resolves to text like: +- `"This message has been cryptographically verified as coming from your administrator."` +- `"This message is from a web-of-trust contact (not the administrator)."` + +## Proposed Optimized Template + +```yaml +- section: agent_identity + role: system + content: | + Agent Identity + Your pubkey (hex): {{agent_pubkey}} + +- section: sender_context + role: system + content: | + {{sender_verification}} + +- section: admin_context + role: system + content: | + Administrator Context + + Pubkey (hex): {{admin_pubkey}} + {{admin_profile_plain}} + {{admin_relay_list_plain}} + +- section: startup_events + role: system + skip_if_empty: true + content: | + Startup Events Memory + {{startup_events_json}} + +- section: adopted_skills + role: system + skip_if_empty: true + content: | + {{adopted_skills_content}} + +- section: admin_notes + role: system + skip_if_empty: true + content: | + Administrator Recent Notes (source: nostr kind 1) + {{admin_notes_content}} + +- section: dm_history + role: expand + limit: 12 +``` + +Key changes from current template: +- **No markdown headers** in system sections — plain English throughout +- **Merged admin section** combines identity, profile, and relay list +- **`{{admin_profile_plain}}`** — new variable that renders kind 0 JSON as readable text (e.g. `Name: WSB, About: ...`) +- **`{{admin_relay_list_plain}}`** — new variable that renders relay URLs from tags as a plain list +- **`{{sender_verification}}`** — new variable stating cryptographic verification status +- **`admin_notes` moved before `dm_history`** so all system context is grouped before conversation +- **`skip_if_empty`** prevents sending empty sections + +## Implementation Steps + +1. **Fix kind 10002 relay list bug** — extract relay URLs from tags instead of content in [`nostr_handler.c:705`](../src/nostr_handler.c:705) +2. **Filter kind 31120** from `startup_events_json` variable resolver in [`agent.c`](../src/agent.c) +3. **Deduplicate consecutive identical messages** in DM history builder +4. **Flatten `content_fields` JSON skills** into readable text format +5. **Add `skip_if_empty` support** to template engine (skip section when resolved content is blank) +6. **Reorder template** — move `admin_notes` before `dm_history` +7. **Add `agent_pubkey` template variable** and agent identity section +8. **Merge admin sections** — combine identity + profile (plain English) + relay list into one section +9. **Add `admin_profile_plain` variable** — parse kind 0 JSON into readable text +10. **Add `admin_relay_list_plain` variable** — parse kind 10002 tags into relay URL list +11. **Pass sender tier to context builder** and add `sender_verification` template variable +12. **Remove markdown formatting** from system section content — use plain English diff --git a/plans/nip17_messaging.md b/plans/nip17_messaging.md new file mode 100644 index 0000000..72adf3a --- /dev/null +++ b/plans/nip17_messaging.md @@ -0,0 +1,194 @@ +# NIP-17 Messaging Implementation Plan + +## Background + +Didactyl currently uses NIP-04 (kind 4) for all DM communication. NIP-17 send support exists via `nostr_handler_send_dm_nip17()` and the `nostr_dm_send_nip17` tool, but there is **no ability to receive NIP-17 messages** and **all agent responses always use NIP-04**. + +The `nostr_core_lib` already has full NIP-17/NIP-59 support including: +- `nostr_nip17_create_chat_event()` — create kind 14 rumor +- `nostr_nip17_send_dm()` — seal + gift wrap (creates wraps for both recipient AND sender) +- `nostr_nip17_receive_dm()` — unwrap gift wrap, unseal rumor, return kind 14 +- `nostr_nip17_extract_dm_relays()` — parse kind 10050 relay lists + +## Config-Driven Protocol Selection + +### New Config Field + +Add a `dm_protocol` field to the top-level config: + +```json +{ + "dm_protocol": "nip04", + ... +} +``` + +Valid values: +- `"nip04"` — NIP-04 only (current behavior, default for backward compatibility) +- `"nip17"` — NIP-17 only (subscribe to kind 1059, send via gift wrap) +- `"both"` — Subscribe to both kind 4 and kind 1059; reply using whichever protocol the message arrived on + +### Config Struct Change + +In `config.h`, add to `didactyl_config_t`: + +```c +typedef enum { + DM_PROTOCOL_NIP04 = 0, + DM_PROTOCOL_NIP17 = 1, + DM_PROTOCOL_BOTH = 2 +} dm_protocol_t; +``` + +Add `dm_protocol_t dm_protocol;` to the config struct. + +## Implementation Steps + +### 1. Add `dm_protocol` config parsing + +**Files**: `config.h`, `config.c` + +- Add `dm_protocol_t` enum and field to `didactyl_config_t` +- Parse `"dm_protocol"` string from JSON in `config_load()` +- Default to `DM_PROTOCOL_NIP04` if not specified + +### 2. Add kind 1059 subscription + +**File**: `nostr_handler.c` — `nostr_handler_subscribe_dms()` + +- When `dm_protocol` is `NIP17` or `BOTH`, add `kind: 1059` to the subscription filter +- When `dm_protocol` is `NIP04` or `BOTH`, keep `kind: 4` in the filter +- The subscription filter becomes `kinds: [4, 1059]` for `BOTH` mode + +### 3. Add NIP-17 receive handling in `on_event()` + +**File**: `nostr_handler.c` — `on_event()` + +Currently `on_event()` rejects anything that is not kind 4. Update to: + +- If kind == 1059: unwrap gift wrap via `nostr_nip17_receive_dm()`, extract sender pubkey from the kind 14 rumor, extract message content, determine sender tier, fire `g_dm_callback` +- If kind == 4: existing NIP-04 decrypt path (unchanged) +- Track which protocol was used per sender pubkey for reply routing + +### 4. Track protocol per sender for reply routing + +**File**: `nostr_handler.c` + +Add a small cache that maps `sender_pubkey_hex -> last_protocol_used`: + +```c +typedef struct { + char pubkey_hex[65]; + dm_protocol_t protocol; +} sender_protocol_entry_t; + +#define SENDER_PROTOCOL_CACHE_SIZE 64 +static sender_protocol_entry_t g_sender_protocol_cache[SENDER_PROTOCOL_CACHE_SIZE]; +``` + +When a DM arrives via kind 4, record `NIP04` for that sender. When via kind 1059, record `NIP17`. + +### 5. Add protocol-aware send function + +**File**: `nostr_handler.c` + +Add a new function that routes based on config + sender history: + +```c +int nostr_handler_send_dm_auto(const char* recipient_pubkey_hex, const char* message); +``` + +Logic: +- If `dm_protocol == NIP04`: always use `nostr_handler_send_dm()` +- If `dm_protocol == NIP17`: always use `nostr_handler_send_dm_nip17()` +- If `dm_protocol == BOTH`: check sender protocol cache; use matching protocol, default to NIP-04 + +Expose in `nostr_handler.h`. + +### 6. Update agent.c to use auto-routing + +**File**: `agent.c` + +Replace all `nostr_handler_send_dm()` calls with `nostr_handler_send_dm_auto()`. This is a mechanical find-and-replace across ~15 call sites. + +### 7. Add kind 10050 startup event support + +**File**: `config.json.example` + +Add a kind 10050 startup event example so NIP-17 clients can discover the agent's DM relay preferences: + +```json +{ + "kind": 10050, + "content": "", + "tags": [ + ["relay", "wss://relay.damus.io"], + ["relay", "wss://nos.lol"] + ] +} +``` + +**File**: `config.c` — startup event parsing already handles arbitrary kinds, so this should work with no code changes. + +### 8. Update startup DM to use auto-routing + +**File**: `main.c` + +The startup status DM at line 255 currently calls `nostr_handler_send_dm()`. Update to `nostr_handler_send_dm_auto()`. + +## Architecture Diagram + +```mermaid +flowchart TD + subgraph Config + CFG[dm_protocol setting] + CFG -->|nip04| NIP04_MODE[NIP-04 only] + CFG -->|nip17| NIP17_MODE[NIP-17 only] + CFG -->|both| BOTH_MODE[Both protocols] + end + + subgraph Subscription + SUB[nostr_handler_subscribe_dms] + NIP04_MODE --> SUB_K4[Subscribe kind 4] + NIP17_MODE --> SUB_K1059[Subscribe kind 1059] + BOTH_MODE --> SUB_BOTH[Subscribe kind 4 + 1059] + end + + subgraph Receive Path + EVT[on_event callback] + EVT -->|kind 4| DEC4[NIP-04 decrypt] + EVT -->|kind 1059| DEC17[NIP-17 unwrap + unseal] + DEC4 --> CACHE[Record sender protocol] + DEC17 --> CACHE + CACHE --> CB[Fire dm_callback] + end + + subgraph Send Path + SEND[nostr_handler_send_dm_auto] + SEND -->|config=nip04| S4[nostr_handler_send_dm - kind 4] + SEND -->|config=nip17| S17[nostr_handler_send_dm_nip17 - kind 1059] + SEND -->|config=both| LOOKUP[Check sender protocol cache] + LOOKUP -->|sender used nip04| S4 + LOOKUP -->|sender used nip17| S17 + LOOKUP -->|unknown| S4 + end +``` + +## File Change Summary + +| File | Changes | +|------|---------| +| `config.h` | Add `dm_protocol_t` enum and field | +| `config.c` | Parse `dm_protocol` from JSON | +| `nostr_handler.h` | Add `nostr_handler_send_dm_auto()` declaration | +| `nostr_handler.c` | Add kind 1059 subscription, NIP-17 receive in `on_event()`, sender protocol cache, `nostr_handler_send_dm_auto()` | +| `agent.c` | Replace `nostr_handler_send_dm()` with `nostr_handler_send_dm_auto()` | +| `main.c` | Replace startup DM send with `nostr_handler_send_dm_auto()` | +| `config.json.example` | Add `dm_protocol` field and kind 10050 startup event example | + +## Testing Strategy + +1. **NIP-04 mode** (default): Verify existing behavior is unchanged +2. **NIP-17 mode**: Send a NIP-17 DM from a client like Amethyst/0xchat, verify Didactyl receives and replies via NIP-17 +3. **Both mode**: Send NIP-04 DM, verify NIP-04 reply; send NIP-17 DM, verify NIP-17 reply +4. **Kind 10050**: Verify the relay list event is published on startup and discoverable by NIP-17 clients diff --git a/plans/tool_orchestration.md b/plans/tool_orchestration.md new file mode 100644 index 0000000..5811c32 --- /dev/null +++ b/plans/tool_orchestration.md @@ -0,0 +1,360 @@ +# Tool Orchestration Plan + +## Overview + +This plan covers four related features: +1. **Slash commands** — direct tool execution bypassing the LLM +2. **Skill-forward execution (`/run` + `skill_run`)** — one-shot skill invocation with LLM, including external skill sharing +3. **Skill-tool maturity levels** — skills that register as callable tools with promotion workflow +4. **Deterministic step executor** — hardened skills that run without LLM involvement + +## 1. Slash Commands (Direct Tool Execution) + +### Behavior +When a message starts with `/`, bypass the LLM entirely and execute the tool directly. + +``` +/shell_exec {"command": "ls -la"} +/nostr_query {"filter": {"kinds": [1], "limit": 5}} +/nostr_nip05_lookup {"identifier": "jack@cash.app"} +``` + +### Parsing Rules +- `/tool_name` — call tool with empty args `{}` +- `/tool_name {"key": "value"}` — call tool with JSON args +- `/tool_name plain text` — wrap as `{"input": "plain text"}` (convenience) +- `/help` — list available tools +- `/help tool_name` — show tool schema + +### Implementation +1. In [`agent_on_message()`](../src/agent.c:1453), check if `message[0] == '/'` +2. Parse tool name (everything between `/` and first space or end) +3. Parse args (everything after tool name, try JSON first, fall back to string wrapper) +4. Call [`tools_execute()`](../src/tools.c) directly +5. Send result as DM — no LLM round-trip +6. Still log to context.log.md with `phase=direct_tool_exec` + +### Special Slash Commands +- `/help` — list available tools +- `/help tool_name` — show tool schema +- `/run` — skill-forward execution (see section 2) + +### Security +- Only admin tier can use slash commands (same as current tool policy) +- Slash commands respect the same `tools.enabled` and `security.admin.tools_enabled` config flags + +## 2. Skill-Forward Execution + +### The Problem + +Today, skills are passive — they're injected into the LLM context via [`append_adopted_skills_context()`](../src/agent.c:1418) and the LLM decides when they're relevant. There's no way to say "run this specific skill right now" and there's no way to try someone else's skill without permanently adopting it. + +### Three Invocation Layers + +| Layer | How it works | LLM? | Persists? | +|-------|-------------|------|-----------| +| **Passive/adopted** | Skill instructions injected into every conversation context | Yes, LLM decides relevance | Yes — in kind 10123 adoption list | +| **Skill-forward** | Skill instructions become the primary system prompt; LLM executes them | Yes, but constrained | No — one-shot execution | +| **Hardened** | Deterministic step executor, no LLM | No | Yes — adopted skill with `execution: hardened` | + +### Entry Points + +#### A. `/run` slash command (admin direct invocation) + +``` +/run deploy-website staging # run own adopted skill by slug +/run 31123::deploy-website staging # run anyone's skill by address +/run deploy-website {"target": "production"} # JSON args +``` + +Parsing: +1. First token after `/run` is the skill identifier (slug or `kind:pubkey:slug` address) +2. Everything after is args (try JSON first, fall back to `{"input": "plain text"}`) + +#### B. `skill_run` tool (LLM-mediated invocation) + +The LLM can invoke skills on behalf of the admin during conversation: + +```json +{ + "name": "skill_run", + "description": "Fetch and execute a skill one-shot without adopting it. Works with own adopted skills by slug or any public skill by address.", + "parameters": { + "type": "object", + "properties": { + "slug": { "type": "string", "description": "Skill slug for own adopted skills" }, + "address": { "type": "string", "description": "Full skill address: kind:pubkey:slug" }, + "pubkey": { "type": "string", "description": "Author pubkey, used with slug to form address" }, + "args": { "type": "string", "description": "Arguments or context to pass to the skill" }, + "sandbox": { "type": "boolean", "description": "Override sandbox setting. Default: true for external, false for own skills" } + } + } +} +``` + +This enables natural conversation like: +> "My friend @jack just published a skill called summarize-thread. Try it on the latest thread in my feed." + +The agent would `skill_search` to find it, then `skill_run` to execute it. + +### Execution Flow + +Both `/run` and `skill_run` use the same underlying executor: + +```mermaid +graph TD + A[/run or skill_run called] --> B{Skill identifier type?} + B -->|slug only| C[Look up in adopted skills cache] + B -->|address or pubkey+slug| D[Fetch skill event from Nostr] + C --> E{Found?} + D --> E + E -->|No| F[Return error: skill not found] + E -->|Yes| G{External skill?} + G -->|Yes| H[Apply sandbox - restrict tools] + G -->|No| I[Full tool access] + H --> J[Build skill-forward prompt] + I --> J + J --> K[System: base context + skill instructions] + K --> L[User: args as user message] + L --> M[LLM call with tools] + M --> N[Return result to caller] +``` + +### Skill-Forward Prompt Construction + +Reuses the pattern from [`agent_on_trigger()`](../src/agent.c:1837): + +``` +[base system context / soul] + +Skill execution context: +- You are executing a specific skill on demand. +- Follow the skill instructions below precisely. +- The user's arguments provide the context for this execution. +- Keep output concise and actionable. + +Skill slug: deploy-website +Skill address: 31123::deploy-website +Skill source: [own | external:] + +Skill instructions: +[skill content here] +``` + +User message: +``` +[args provided by caller] +``` + +The prompt is so skill-forward that the LLM has no real option but to execute the skill instructions against the provided args. + +### Sandbox for External Skills + +When executing a skill from another author (not own pubkey), a **tool sandbox** is applied by default: + +**Allowed tools (safe/read-only):** +- `nostr_query` — read Nostr events +- `nostr_nip05_lookup` — NIP-05 lookups +- `nostr_post` — publish events (the agent signs, so this is safe) +- `nostr_list_manage` — manage lists +- `skill_list`, `skill_search` — read skill metadata + +**Blocked tools (destructive/dangerous):** +- `shell_exec` — arbitrary command execution +- `file_read`, `file_write` — filesystem access +- Any future tools marked as `destructive: true` + +**Override:** The admin can explicitly opt in to full tool access: +- `/run --unsafe 31123::risky-skill args` +- `skill_run` with `sandbox: false` + +### Skill Sharing on Nostr + +```mermaid +graph TD + A[Friend creates skill] -->|kind 31123| B[Published on Nostr relays] + B --> C{How do you find it?} + C -->|skill_search popular:true| D[Discovery via WoT adoption lists] + C -->|Friend tells you the slug| E[Direct reference] + C -->|skill_search pubkey:friend| F[Browse friends skills] + D --> G[skill_run - try it once, sandboxed] + E --> G + F --> G + G -->|Liked it?| H{Adopt?} + H -->|Yes| I[skill_adopt - permanent] + H -->|No| J[Done - nothing persisted] + I --> K[Shows in adopted skills context] + K --> L[Agent uses it automatically] + L -->|Or invoke directly| M[/run skill-slug args] +``` + +## 3. Skill-Tool Maturity Levels + +Skills can declare an execution maturity level that determines how they run: + +| Level | Execution | LLM? | Use case | +|-------|-----------|-------|----------| +| `draft` | LLM interprets procedure text from skill instructions | Yes | Exploring and iterating on a workflow | +| `guided` | LLM with forced tool_choice + parameter defaults | Yes, constrained | Workflow is stable but needs LLM judgment | +| `hardened` | Deterministic step executor, no LLM | No | Workflow is proven and should run exactly as defined | + +### Skill Definition Extensions + +```yaml +kind: 31123 +d: deploy_website +execution: hardened +tool_schema: + name: deploy_website + description: Build and deploy the static website + parameters: + target: + type: string + enum: [staging, production] + default: staging +steps: + - tool: shell_exec + args: + command: "make build TARGET={{target}}" + - tool: shell_exec + args: + command: "rsync -av dist/ server:/var/www/{{target}}/" + - return: "Deployed to {{target}}" +``` + +### How Each Level Works + +**draft:** Current behavior. Skill instructions are injected into context. LLM reads them and decides which tools to call. No special handling needed. + +**guided:** Agent sets `tool_choice` to the skill's preferred tool. Parameter defaults from the skill are merged with the model's generated arguments (skill defaults win on conflict). Reduces LLM freedom while still allowing it to fill in dynamic values. + +**hardened:** Agent executes the `steps` array directly using the deterministic step executor. No LLM call at all. The skill becomes equivalent to a slash command. + +### Promotion Workflow +1. Admin iterates with LLM on a task (draft) +2. Admin saves working procedure as a skill: `skill_create` with `execution: draft` +3. Admin tests, refines, promotes: update skill to `execution: guided` +4. Once proven reliable, promote to `execution: hardened` with explicit `steps` +5. Hardened skills become available as slash commands: `/deploy_website {"target": "production"}` + +## 4. Deterministic Step Executor + +A simple sequential executor for hardened skills. + +### Step Types + +```yaml +steps: + # Execute a tool + - tool: shell_exec + args: {command: "ls -la"} + save_as: listing # optional: save result to variable + + # Execute a tool with variable substitution + - tool: nostr_post + args: + kind: 30023 + content: "{{file_content}}" + tags: [["d", "{{slug}}"]] + + # Conditional - simple + - if: "{{listing.success}}" + then: + - tool: shell_exec + args: {command: "echo done"} + else: + - return: "Failed: {{listing.error}}" + + # Return final result + - return: "Published to {{slug}}" +``` + +### Variable Substitution +- `{{param_name}}` — from tool parameters provided by caller +- `{{step_name.field}}` — from a previous step's result (requires `save_as`) +- Simple string replacement, no expression evaluation + +### Implementation in C +- Parse `steps` array from skill content (JSON or YAML) +- Iterate steps sequentially +- For each `tool` step: call [`tools_execute()`](../src/tools.c), optionally save result +- For each `return` step: substitute variables and return string +- For each `if` step: evaluate truthiness of variable, branch accordingly +- Total implementation: ~200-400 lines of C + +### Error Handling +- If any tool step fails (returns `success: false`), abort and return the error +- Optional `on_error` field per step for custom error messages +- Timeout inherited from tool config + +## 5. Tool Registration for Skill-Tools + +Hardened and guided skills with a `tool_schema` field get registered in the tools array at runtime. + +### At Skill Refresh Time +1. Parse `tool_schema` from skill content +2. Generate OpenAI function schema from it +3. Append to the tools array returned by [`tools_build_openai_schema_json()`](../src/tools.c:919) +4. When model calls the skill-tool, route to skill executor instead of hardcoded C function + +### In [`tools_execute()`](../src/tools.c) +1. Check if tool_name matches a hardcoded tool — execute normally +2. If not, check if it matches a registered skill-tool +3. If guided: run sub-LLM call with skill procedure + forced tool_choice +4. If hardened: run deterministic step executor + +## 6. Security Model + +### Tool Classification + +Tools are classified for sandbox purposes: + +```c +typedef enum { + TOOL_SAFETY_SAFE, // read-only or agent-signed actions + TOOL_SAFETY_DESTRUCTIVE // filesystem, shell, or external system mutations +} tool_safety_t; +``` + +| Tool | Safety | Reason | +|------|--------|--------| +| `nostr_query` | safe | Read-only | +| `nostr_nip05_lookup` | safe | Read-only | +| `nostr_post` | safe | Agent signs, admin controls keys | +| `nostr_list_manage` | safe | Agent signs | +| `skill_list` | safe | Read-only | +| `skill_search` | safe | Read-only | +| `skill_run` | safe | Recursive execution uses its own sandbox | +| `shell_exec` | destructive | Arbitrary command execution | +| `file_read` | destructive | Filesystem access | +| `file_write` | destructive | Filesystem mutation | + +### Sandbox Rules + +| Scenario | Default sandbox | Override | +|----------|----------------|---------| +| Own adopted skill via `/run slug` | No sandbox | N/A | +| External skill via `/run address` | Sandbox ON | `/run --unsafe address` | +| `skill_run` tool, own skill | No sandbox | `sandbox: true` | +| `skill_run` tool, external skill | Sandbox ON | `sandbox: false` | +| Hardened skill steps | No sandbox (steps are explicit) | N/A | + +## Future Considerations + +- **Skill sharing:** Hardened skill-tools could be shared between agents via Nostr (kind 31123 events). Another agent adopts the skill and gets the tool automatically. +- **Versioning:** Skills already use addressable events (d-tag). Updating a skill automatically updates the tool. +- **Permissions:** Skill-tools could have their own permission model (e.g., some skill-tools available to WoT contacts). +- **Composability:** Skill-tools calling other skill-tools (nested execution with sandbox inheritance). +- **Dry-run mode:** A future `/run --dry` flag that shows what tools would be called without executing them. +- **Skill ratings:** Agents could publish ratings/reviews of skills they've tried, building a WoT-based skill marketplace. + +## Implementation Priority + +1. **Slash commands** (direct tool execution) — simplest, highest immediate value +2. **`/run` for own adopted skills** — skill-forward execution of already-adopted skills +3. **`skill_run` tool + external skill fetching** — enables "try my friend's skill" flow +4. **External skill sandbox** — tool safety classification and sandbox enforcement +5. **Hardened skill-tool step executor** — enables deterministic workflows +6. **Skill-tool registration in tools array** — makes skill-tools visible to LLM +7. **Guided execution with forced tool_choice** — bridges draft and hardened +8. **Promotion workflow UX** — admin commands to change skill maturity level diff --git a/plans/unified_prompt_context.md b/plans/unified_prompt_context.md new file mode 100644 index 0000000..9bc1d43 --- /dev/null +++ b/plans/unified_prompt_context.md @@ -0,0 +1,158 @@ +# Plan: Unified Prompt Context for HTTP API and Nostr Paths + +## Problem + +The agent produces completely different LLM context depending on whether a message arrives via **Nostr DM** or the **HTTP API CLI chat app**. + +### Nostr Path (working correctly) +- `agent_on_message()` → `agent_build_admin_messages_json()` → tool loop +- Builds **18 sections, ~8224 bytes** of context including: + - System prompt / personality (from soul template) + - Agent identity (pubkey) + - Sender verification (admin tier) + - Admin context (kind 0 profile, relay list, recent posts) + - Startup events memory + - Adopted skills + - DM history (decrypted from Nostr relays) + - Current user message + +### HTTP API Path (broken) +- CLI sends `{messages: [{role: "user", content: "Hello"}]}` to `/api/prompt/run` +- `run_prompt_with_tools()` passes these raw messages directly to the LLM +- Result: **1 section, ~35 bytes** — just the bare user message, zero agent context + +## Solution: New `POST /api/prompt/agent` Endpoint + +Add a new endpoint that mirrors the Nostr path's context assembly, so the CLI gets the same full agent context. + +### Architecture + +```mermaid +flowchart TD + A[Nostr DM arrives] --> B[agent_on_message] + B --> C[agent_build_admin_messages_json] + C --> D[Append user message] + D --> E[Tool loop with llm_chat_with_tools_messages] + E --> F[Send DM reply] + + G[CLI sends POST /api/prompt/agent] --> H[handle_prompt_agent] + H --> C + C --> I[Append user message] + I --> J[Tool loop - same as run_prompt_with_tools but with context] + J --> K[Return JSON response] + + style C fill:#4a9,stroke:#333,color:#fff +``` + +Both paths share `agent_build_admin_messages_json()` as the single source of truth for context assembly. + +### Request Format + +```json +{ + "message": "What is the capital of France?", + "model": "claude-haiku-4.5", + "max_turns": 4 +} +``` + +| Field | Type | Required | Description | +|---|---|---|---| +| `message` | string | yes | The user message to send to the agent | +| `model` | string | no | Override the configured LLM model for this request | +| `max_turns` | int | no | Max tool-use turns, default 4, max 16 | + +### Response Format + +Same as existing `/api/prompt/run`: + +```json +{ + "success": true, + "final_response": "The capital of France is Paris.", + "turns": [...], + "model_used": "claude-haiku-4.5", + "total_input_tokens_estimate": 1973, + "total_output_tokens_estimate": 12 +} +``` + +## Implementation Steps + +### 1. Add `handle_prompt_agent()` in `src/http_api.c` + +New function that: +1. Parses the JSON body to extract `message`, optional `model`, optional `max_turns` +2. Applies model override if present via `maybe_model_override_begin()` +3. Calls `agent_build_admin_messages_json(message, DIDACTYL_SENDER_ADMIN, &base_messages_json)` — same call the Nostr path uses +4. Parses the result into a cJSON array +5. Appends `{role: "user", content: message}` to the array — same as `agent_on_message()` does at line 1916 +6. Builds tool schema via `tools_build_openai_schema_json()` +7. Runs the same tool loop as `run_prompt_with_tools()` but using the context-enriched messages +8. Logs context via `agent_append_context_log("http_api_agent", "llm_chat_with_tools_messages", messages_json)` +9. Returns the same response format as `/api/prompt/run` + +Key reference points in existing code: +- Context building: `agent_build_admin_messages_json()` at `src/agent.c:1737` +- User message append: `append_simple_message()` pattern at `src/agent.c:1916` +- Tool loop: reuse the loop logic from `run_prompt_with_tools()` at `src/http_api.c:278-345` +- Context logging: `agent_append_context_log()` at `src/agent.c:1932` + +### 2. Register the Route in `http_handler()` + +Add before the existing `/api/prompt/run` route at `src/http_api.c:648`: + +```c +if (method_is(hm, "POST") && mg_match(hm->uri, mg_str("/api/prompt/agent"), NULL)) { + handle_prompt_agent(c, hm); + return; +} +``` + +### 3. Update `chat-didactyl-cli.js` + +Change the CLI to call the new endpoint: + +- Change `callDidactyl()` to POST to `/api/prompt/agent` instead of `/api/prompt/run` +- Send `{message: "user text", max_turns: N}` instead of `{messages: [...], max_turns: N}` +- The CLI no longer needs to maintain a `transcript` array for context — the server handles DM history from Nostr relays +- Keep the transcript for local display purposes only + +### 4. Context Logging Parity + +Use a distinct but parallel phase label: +- Nostr path: `llm_chat_with_tools_messages` (existing) +- HTTP API agent path: `llm_chat_with_tools_messages_agent_api` (new) +- HTTP API raw path: `llm_chat_with_tools_messages_http_api` (existing, unchanged) + +This lets you distinguish the source in `context.log.md` while confirming the context structure is identical. + +### 5. Update `docs/API.md` + +Add documentation for the new `POST /api/prompt/agent` endpoint following the existing documentation style. + +## Files to Modify + +| File | Change | +|---|---| +| `src/http_api.c` | Add `handle_prompt_agent()` function and route registration | +| `chat-didactyl-cli.js` | Switch to `/api/prompt/agent`, simplify payload | +| `docs/API.md` | Document new endpoint | + +## What Stays the Same + +- `/api/prompt/run` — unchanged, still accepts raw message arrays for custom/advanced use +- `/api/prompt/run-simple` — unchanged +- `/api/context/current` and `/api/context/parts` — unchanged +- `agent_build_admin_messages_json()` — unchanged, already does exactly what we need +- Nostr message handling — unchanged + +## Remaining Consideration: Conversation History + +The Nostr path gets DM history by querying encrypted kind-4 events from relays. The new `/api/prompt/agent` endpoint will include this same history since it calls `agent_build_admin_messages_json()`. This means: + +- Messages sent via the CLI will NOT appear in the Nostr DM history (they are not published as Nostr events) +- Messages sent via Nostr WILL appear in the context when using the CLI +- This is acceptable — the CLI is a development/admin tool that piggybacks on the agent's full context + +If in the future you want CLI messages to also appear in history, that would require either publishing them as Nostr DMs or maintaining a separate local history store — but that is out of scope for this change. diff --git a/src/agent.c b/src/agent.c index 3553dc9..f90dc3b 100644 --- a/src/agent.c +++ b/src/agent.c @@ -9,6 +9,7 @@ #include #include #include +#include #include "llm.h" #include "nostr_handler.h" @@ -61,6 +62,10 @@ static int g_adopted_skills_count = 0; static time_t g_adopted_skills_last_refresh_at = 0; static pthread_mutex_t g_adopted_skills_mutex = PTHREAD_MUTEX_INITIALIZER; +typedef struct { + didactyl_sender_tier_t sender_tier; +} agent_template_resolver_ctx_t; + static uint64_t fnv1a64(const char* s) { uint64_t h = 1469598103934665603ULL; if (!s) return h; @@ -178,6 +183,440 @@ 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; + cJSON_AddBoolToObject(out, "success", 0); + cJSON_AddStringToObject(out, "error", msg ? msg : "error"); + char* json = cJSON_PrintUnformatted(out); + cJSON_Delete(out); + return json; +} + +static int is_space_char(char c) { + return c == ' ' || c == '\t' || c == '\n' || c == '\r'; +} + +static const char* skip_spaces_local(const char* s) { + while (s && *s && is_space_char(*s)) s++; + return s ? s : ""; +} + +static char* build_slash_args_json(const char* raw_args) { + const char* s = skip_spaces_local(raw_args); + if (!s || s[0] == '\0') { + return strdup("{}"); + } + + cJSON* parsed = cJSON_Parse(s); + if (parsed) { + char* out = cJSON_PrintUnformatted(parsed); + cJSON_Delete(parsed); + return out; + } + + cJSON* obj = cJSON_CreateObject(); + if (!obj) return NULL; + cJSON_AddStringToObject(obj, "input", s); + char* out = cJSON_PrintUnformatted(obj); + cJSON_Delete(obj); + return out; +} + +static int append_textf_local(char** buf, size_t* cap, size_t* used, const char* fmt, ...) { + if (!buf || !cap || !used || !fmt) return -1; + + while (1) { + if (*used + 128U >= *cap) { + size_t next_cap = (*cap) * 2U; + char* grown = (char*)realloc(*buf, next_cap); + if (!grown) return -1; + *buf = grown; + *cap = next_cap; + } + + va_list ap; + va_start(ap, fmt); + int n = vsnprintf(*buf + *used, *cap - *used, fmt, ap); + va_end(ap); + + if (n < 0) return -1; + if ((size_t)n < (*cap - *used)) { + *used += (size_t)n; + return 0; + } + + size_t next_cap = (*cap) * 2U + (size_t)n + 64U; + char* grown = (char*)realloc(*buf, next_cap); + if (!grown) return -1; + *buf = grown; + *cap = next_cap; + } +} + +static int append_markdown_indent_local(char** buf, size_t* cap, size_t* used, int depth) { + for (int i = 0; i < depth; i++) { + if (append_textf_local(buf, cap, used, " ") != 0) { + return -1; + } + } + return 0; +} + +static int append_cjson_markdown_local(char** buf, size_t* cap, size_t* used, cJSON* node, int depth) { + if (!node) { + return append_markdown_indent_local(buf, cap, used, depth) == 0 && + append_textf_local(buf, cap, used, "- null\n") == 0 ? 0 : -1; + } + + if (cJSON_IsObject(node)) { + cJSON* child = NULL; + cJSON_ArrayForEach(child, node) { + const char* key = child->string ? child->string : "item"; + if (cJSON_IsString(child)) { + if (append_markdown_indent_local(buf, cap, used, depth) != 0 || + append_textf_local(buf, cap, used, "- **%s:** %s\n", key, + child->valuestring ? child->valuestring : "") != 0) { + return -1; + } + } else if (cJSON_IsBool(child)) { + if (append_markdown_indent_local(buf, cap, used, depth) != 0 || + append_textf_local(buf, cap, used, "- **%s:** %s\n", key, + cJSON_IsTrue(child) ? "true" : "false") != 0) { + return -1; + } + } else if (cJSON_IsNumber(child)) { + if (append_markdown_indent_local(buf, cap, used, depth) != 0 || + append_textf_local(buf, cap, used, "- **%s:** %g\n", key, child->valuedouble) != 0) { + return -1; + } + } else if (cJSON_IsNull(child)) { + if (append_markdown_indent_local(buf, cap, used, depth) != 0 || + append_textf_local(buf, cap, used, "- **%s:** null\n", key) != 0) { + return -1; + } + } else { + if (append_markdown_indent_local(buf, cap, used, depth) != 0 || + append_textf_local(buf, cap, used, "- **%s:**\n", key) != 0) { + return -1; + } + if (append_cjson_markdown_local(buf, cap, used, child, depth + 1) != 0) { + return -1; + } + } + } + return 0; + } + + if (cJSON_IsArray(node)) { + int n = cJSON_GetArraySize(node); + for (int i = 0; i < n; i++) { + cJSON* item = cJSON_GetArrayItem(node, i); + if (cJSON_IsString(item)) { + if (append_markdown_indent_local(buf, cap, used, depth) != 0 || + append_textf_local(buf, cap, used, "- %s\n", item->valuestring ? item->valuestring : "") != 0) { + return -1; + } + } else if (cJSON_IsBool(item)) { + if (append_markdown_indent_local(buf, cap, used, depth) != 0 || + append_textf_local(buf, cap, used, "- %s\n", cJSON_IsTrue(item) ? "true" : "false") != 0) { + return -1; + } + } else if (cJSON_IsNumber(item)) { + if (append_markdown_indent_local(buf, cap, used, depth) != 0 || + append_textf_local(buf, cap, used, "- %g\n", item->valuedouble) != 0) { + return -1; + } + } else if (cJSON_IsNull(item)) { + if (append_markdown_indent_local(buf, cap, used, depth) != 0 || + append_textf_local(buf, cap, used, "- null\n") != 0) { + return -1; + } + } else { + if (append_markdown_indent_local(buf, cap, used, depth) != 0 || + append_textf_local(buf, cap, used, "-\n") != 0) { + return -1; + } + if (append_cjson_markdown_local(buf, cap, used, item, depth + 1) != 0) { + return -1; + } + } + } + return 0; + } + + if (cJSON_IsString(node)) { + return append_markdown_indent_local(buf, cap, used, depth) == 0 && + append_textf_local(buf, cap, used, "- %s\n", node->valuestring ? node->valuestring : "") == 0 ? 0 : -1; + } + if (cJSON_IsBool(node)) { + return append_markdown_indent_local(buf, cap, used, depth) == 0 && + append_textf_local(buf, cap, used, "- %s\n", cJSON_IsTrue(node) ? "true" : "false") == 0 ? 0 : -1; + } + if (cJSON_IsNumber(node)) { + return append_markdown_indent_local(buf, cap, used, depth) == 0 && + append_textf_local(buf, cap, used, "- %g\n", node->valuedouble) == 0 ? 0 : -1; + } + + return append_markdown_indent_local(buf, cap, used, depth) == 0 && + append_textf_local(buf, cap, used, "- null\n") == 0 ? 0 : -1; +} + +static char* format_result_markdown_local(const char* raw_result) { + if (!raw_result) { + return strdup("No output."); + } + + cJSON* root = cJSON_Parse(raw_result); + if (!root) { + return strdup(raw_result); + } + + size_t cap = strlen(raw_result) * 2U + 512U; + if (cap < 2048U) cap = 2048U; + size_t used = 0U; + char* out = (char*)malloc(cap); + if (!out) { + cJSON_Delete(root); + return strdup(raw_result); + } + out[0] = '\0'; + + if (append_cjson_markdown_local(&out, &cap, &used, root, 0) != 0) { + cJSON_Delete(root); + free(out); + return strdup(raw_result); + } + + cJSON_Delete(root); + return out; +} + +static char* build_slash_help_all_json(void) { + size_t cap = 2048U; + size_t used = 0U; + char* out = (char*)malloc(cap); + if (!out) return NULL; + out[0] = '\0'; + + if (append_textf_local(&out, &cap, &used, "AVAILABLE TOOLS\n") != 0) { + free(out); + return NULL; + } + + char* tool_list_json = tools_execute(&g_tools_ctx, "tool_list", "{}"); + cJSON* tool_root = tool_list_json ? cJSON_Parse(tool_list_json) : NULL; + cJSON* tools = tool_root ? cJSON_GetObjectItemCaseSensitive(tool_root, "tools") : NULL; + + if (!tools || !cJSON_IsArray(tools) || cJSON_GetArraySize(tools) <= 0) { + (void)append_textf_local(&out, &cap, &used, "- (none)\n"); + } else { + int tn = cJSON_GetArraySize(tools); + int* used_idx = (int*)calloc((size_t)tn, sizeof(int)); + if (!used_idx) { + cJSON_Delete(tool_root); + free(tool_list_json); + free(out); + return NULL; + } + + for (int k = 0; k < tn; k++) { + int best = -1; + const char* best_name = NULL; + + for (int i = 0; i < tn; i++) { + if (used_idx[i]) continue; + cJSON* row = cJSON_GetArrayItem(tools, i); + cJSON* name = row ? cJSON_GetObjectItemCaseSensitive(row, "name") : NULL; + const char* name_s = (name && cJSON_IsString(name) && name->valuestring) ? name->valuestring : "unknown"; + if (best < 0 || strcmp(name_s, best_name) < 0) { + best = i; + best_name = name_s; + } + } + + if (best < 0) { + break; + } + + used_idx[best] = 1; + cJSON* row = cJSON_GetArrayItem(tools, best); + cJSON* name = row ? cJSON_GetObjectItemCaseSensitive(row, "name") : NULL; + cJSON* desc = row ? cJSON_GetObjectItemCaseSensitive(row, "description") : NULL; + const char* name_s = (name && cJSON_IsString(name) && name->valuestring) ? name->valuestring : "unknown"; + const char* desc_s = (desc && cJSON_IsString(desc) && desc->valuestring) ? desc->valuestring : ""; + if (append_textf_local(&out, &cap, &used, "- %s%s%s\n", name_s, desc_s[0] ? " — " : "", desc_s) != 0) { + free(used_idx); + cJSON_Delete(tool_root); + free(tool_list_json); + free(out); + return NULL; + } + } + + free(used_idx); + } + + cJSON_Delete(tool_root); + free(tool_list_json); + + if (append_textf_local(&out, &cap, &used, "\nAVAILABLE SKILLS\n") != 0) { + free(out); + return NULL; + } + + char* skill_list_json = tools_execute(&g_tools_ctx, "skill_list", "{}"); + cJSON* skill_root = skill_list_json ? cJSON_Parse(skill_list_json) : NULL; + cJSON* skills = skill_root ? cJSON_GetObjectItemCaseSensitive(skill_root, "skills") : NULL; + + if (!skills || !cJSON_IsArray(skills) || cJSON_GetArraySize(skills) <= 0) { + (void)append_textf_local(&out, &cap, &used, "- (none)\n"); + } else { + int sn = cJSON_GetArraySize(skills); + for (int i = 0; i < sn; i++) { + cJSON* row = cJSON_GetArrayItem(skills, i); + cJSON* slug = row ? cJSON_GetObjectItemCaseSensitive(row, "slug") : NULL; + cJSON* desc = row ? cJSON_GetObjectItemCaseSensitive(row, "description") : NULL; + const char* slug_s = (slug && cJSON_IsString(slug) && slug->valuestring) ? slug->valuestring : NULL; + const char* desc_s = (desc && cJSON_IsString(desc) && desc->valuestring) ? desc->valuestring : ""; + if (!slug_s || slug_s[0] == '\0') { + continue; + } + if (append_textf_local(&out, &cap, &used, "- %s%s%s\n", slug_s, desc_s[0] ? " — " : "", desc_s) != 0) { + cJSON_Delete(skill_root); + free(skill_list_json); + free(out); + return NULL; + } + } + } + + cJSON_Delete(skill_root); + free(skill_list_json); + return out; +} + +static char* build_slash_help_tool_json(const char* tool_name) { + if (!tool_name || tool_name[0] == '\0') { + return local_json_error("missing tool name"); + } + + char* schema_json = tools_build_openai_schema_json(&g_tools_ctx); + if (!schema_json) { + return local_json_error("failed to build tool schema"); + } + + cJSON* schema = cJSON_Parse(schema_json); + free(schema_json); + if (!schema || !cJSON_IsArray(schema)) { + cJSON_Delete(schema); + return local_json_error("tool schema parse failure"); + } + + cJSON* matched_fn = NULL; + int n = cJSON_GetArraySize(schema); + for (int i = 0; i < n; i++) { + cJSON* item = cJSON_GetArrayItem(schema, i); + cJSON* fn = item ? cJSON_GetObjectItemCaseSensitive(item, "function") : NULL; + cJSON* name = fn ? cJSON_GetObjectItemCaseSensitive(fn, "name") : NULL; + if (name && cJSON_IsString(name) && name->valuestring && strcmp(name->valuestring, tool_name) == 0) { + matched_fn = fn; + break; + } + } + + if (!matched_fn) { + cJSON_Delete(schema); + return local_json_error("unknown tool"); + } + + cJSON* out = cJSON_CreateObject(); + if (!out) { + cJSON_Delete(schema); + return NULL; + } + + cJSON_AddBoolToObject(out, "success", 1); + cJSON_AddStringToObject(out, "mode", "slash_help_tool"); + cJSON_AddStringToObject(out, "tool", tool_name); + cJSON_AddItemToObject(out, "schema", cJSON_Duplicate(matched_fn, 1)); + + char* result = cJSON_PrintUnformatted(out); + cJSON_Delete(out); + cJSON_Delete(schema); + return result; +} + +static int handle_slash_command(const char* sender_pubkey_hex, const char* message) { + if (!sender_pubkey_hex || !message || message[0] != '/') { + return 0; + } + + const char* p = message + 1; + while (*p && !is_space_char(*p)) p++; + + size_t tool_len = (size_t)(p - (message + 1)); + if (tool_len == 0 || tool_len >= 128U) { + (void)nostr_handler_send_dm_auto(sender_pubkey_hex, "{\"success\":false,\"error\":\"invalid slash command\"}"); + return 1; + } + + char tool_name[128]; + memcpy(tool_name, message + 1, tool_len); + tool_name[tool_len] = '\0'; + + const char* raw_args = skip_spaces_local(p); + + char* result_json = NULL; + if (strcmp(tool_name, "help") == 0) { + if (!raw_args || raw_args[0] == '\0') { + result_json = build_slash_help_all_json(); + } else { + char help_tool[128]; + size_t i = 0; + while (raw_args[i] && !is_space_char(raw_args[i]) && i < sizeof(help_tool) - 1U) { + help_tool[i] = raw_args[i]; + i++; + } + help_tool[i] = '\0'; + result_json = build_slash_help_tool_json(help_tool); + } + } else { + char* args_json = build_slash_args_json(raw_args); + if (!args_json) { + result_json = local_json_error("failed to parse slash args"); + } else { + result_json = tools_execute(&g_tools_ctx, tool_name, args_json); + free(args_json); + } + } + + if (!result_json) { + result_json = strdup("{\"success\":false,\"error\":\"direct tool execution failed\"}"); + } + + char* markdown_result = format_result_markdown_local(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 : ""); + append_context_log(sender_pubkey_hex, "direct_tool_exec", log_payload); + free(log_payload); + } + + (void)nostr_handler_send_dm_auto(sender_pubkey_hex, dm_payload ? dm_payload : "Command executed with no output."); + free(markdown_result); + free(result_json); + return 1; +} + static const char* get_context_part_name_copy(int idx); static int appendf(char** buf, size_t* cap, size_t* used, const char* fmt, ...) { @@ -220,28 +659,30 @@ static const char* detect_context_section(const char* role, const char* content, if (idx == 0 && strcmp(r, "system") == 0) return "system_prompt"; - if (strncmp(c, "## Administrator Identity", 25) == 0 || - strncmp(c, "This is your administrator!", 27) == 0) { - return "admin_identity"; + if (strncmp(c, "Administrator Context", 21) == 0) { + return "admin_context"; } - if (strncmp(c, "## Administrator Kind 0 Profile", 31) == 0 || - strncmp(c, "Administrator kind 0 profile content", 36) == 0) { - return "admin_kind0"; + if (strncmp(c, "Agent identity", 14) == 0 || + strncmp(c, "Agent Identity", 14) == 0) { + return "agent_identity"; } - if (strncmp(c, "## Administrator Relay List", 27) == 0 || - strncmp(c, "Administrator kind 10002 relay-list content", 43) == 0) { - return "admin_relay_list"; + if (strncmp(c, "Sender verification", 19) == 0 || + strncmp(c, "This message has been cryptographically verified", 47) == 0 || + strncmp(c, "This message is from a web-of-trust contact", 43) == 0) { + return "sender_context"; } - if (strncmp(c, "## Startup Events Memory", 24) == 0 || - strncmp(c, "Startup events memory", 21) == 0) { + if (strncmp(c, "Startup events memory", 21) == 0 || + strncmp(c, "Startup Events Memory", 21) == 0) { return "startup_events"; } - if (strncmp(c, "## Adopted Skills Memory", 24) == 0 || - strncmp(c, "Adopted skills memory", 21) == 0) { + if (strncmp(c, "Adopted skills memory", 21) == 0 || + strncmp(c, "Adopted Skills Memory", 21) == 0) { return "adopted_skills"; } - if (strncmp(c, "## Administrator Recent Notes", 29) == 0 || - strncmp(c, "Administrator recent public notes", 33) == 0) { + if (strncmp(c, "### Current Tasks", 17) == 0) { + return "agent_tasks"; + } + if (strncmp(c, "Administrator recent public notes", 33) == 0) { return "admin_notes"; } @@ -265,6 +706,91 @@ const char* agent_classify_message_part(cJSON* msg, int idx) { return detect_context_section(role_s, content_s, idx); } +static const char* admin_label_for_log(void) { + static __thread char name_buf[96]; + snprintf(name_buf, sizeof(name_buf), "%s", "Administrator"); + + char* kind0 = nostr_handler_get_admin_kind0_context(); + if (!kind0 || kind0[0] == '\0') { + free(kind0); + return name_buf; + } + + cJSON* root = cJSON_Parse(kind0); + free(kind0); + if (!root || !cJSON_IsObject(root)) { + cJSON_Delete(root); + return name_buf; + } + + cJSON* display_name = cJSON_GetObjectItemCaseSensitive(root, "display_name"); + cJSON* name = cJSON_GetObjectItemCaseSensitive(root, "name"); + const char* chosen = NULL; + if (display_name && cJSON_IsString(display_name) && display_name->valuestring && display_name->valuestring[0] != '\0') { + chosen = display_name->valuestring; + } else if (name && cJSON_IsString(name) && name->valuestring && name->valuestring[0] != '\0') { + chosen = name->valuestring; + } + + if (chosen) { + snprintf(name_buf, sizeof(name_buf), "%s", chosen); + } + + cJSON_Delete(root); + return name_buf; +} + +static char* demote_markdown_headings_for_log(const char* content) { + const char* src = content ? content : ""; + size_t n = strlen(src); + + char* out = (char*)malloc(n * 2U + 1U); + if (!out) { + return NULL; + } + + size_t w = 0; + int line_start = 1; + for (size_t i = 0; i < n; i++) { + char c = src[i]; + + if (line_start && c == '#') { + out[w++] = '#'; + } + + out[w++] = c; + + if (c == '\n') { + line_start = 1; + } else if (line_start && (c == ' ' || c == '\t' || c == '\r')) { + line_start = 1; + } else { + line_start = 0; + } + } + + out[w] = '\0'; + return out; +} + +static void format_chat_time_for_log(time_t ts, char out[16]) { + if (!out) return; + out[0] = '\0'; + + if (ts <= 0) { + snprintf(out, 16, "--:--"); + return; + } + + struct tm tm_info; + if (!localtime_r(&ts, &tm_info)) { + snprintf(out, 16, "--:--"); + return; + } + + strftime(out, 16, "%H:%M", &tm_info); +} + static char* format_context_payload_for_log(const char* context_payload) { const char* payload = context_payload ? context_payload : ""; @@ -304,23 +830,84 @@ static char* format_context_payload_for_log(const char* context_payload) { } const char* section = agent_classify_message_part(msg, i); + if (section && strcmp(section, "dm_history") == 0) { + continue; + } + + char* display_content = demote_markdown_headings_for_log(content_s); + const char* safe_display_content = display_content ? display_content : content_s; if (appendf(&out, &cap, &used, - "============================================================\n" - "Section: %s | role=%s\n" - "============================================================\n\n" - "%s\n\n\n", + "# %s | role=%s\n\n" + "%s\n\n", section ? section : "context_part", role_s, - content_s) != 0) { + safe_display_content) != 0) { + free(display_content); free(out); cJSON_Delete(root); return strdup(payload); } + + free(display_content); } + const char* admin_label = admin_label_for_log(); + size_t chat_cap = 4096U; + char* chat = (char*)malloc(chat_cap); + size_t chat_used = 0; + char* latest_admin_line = NULL; + if (chat) { + chat[0] = '\0'; + + for (int i = 0; i < n; i++) { + cJSON* msg = cJSON_GetArrayItem(root, i); + cJSON* role = msg ? cJSON_GetObjectItemCaseSensitive(msg, "role") : NULL; + cJSON* content = msg ? cJSON_GetObjectItemCaseSensitive(msg, "content") : NULL; + cJSON* ts = msg ? cJSON_GetObjectItemCaseSensitive(msg, "_ts") : NULL; + const char* role_s = (role && cJSON_IsString(role) && role->valuestring) ? role->valuestring : ""; + const char* content_s = (content && cJSON_IsString(content) && content->valuestring) ? content->valuestring : ""; + const char* section = agent_classify_message_part(msg, i); + if (!section || strcmp(section, "dm_history") != 0) { + continue; + } + + time_t created_at = 0; + if (ts && cJSON_IsNumber(ts)) { + created_at = (time_t)ts->valuedouble; + } + char time_buf[16] = {0}; + format_chat_time_for_log(created_at, time_buf); + + const char* who = (strcmp(role_s, "user") == 0) ? admin_label : "Agent"; + if (appendf(&chat, &chat_cap, &chat_used, "%s %s %s\n", time_buf, who, content_s) != 0) { + free(chat); + chat = NULL; + break; + } + + if (strcmp(role_s, "user") == 0) { + free(latest_admin_line); + int line_len = snprintf(NULL, 0, "%s %s %s", time_buf, who, content_s); + if (line_len >= 0) { + latest_admin_line = (char*)malloc((size_t)line_len + 1U); + if (latest_admin_line) { + snprintf(latest_admin_line, (size_t)line_len + 1U, "%s %s %s", time_buf, who, content_s); + } + } + } + } + + if (chat && chat_used > 0) { + (void)appendf(&out, &cap, &used, "# dm_history | role=chat\n\n%s\n", chat); + } + } + + free(chat); + free(latest_admin_line); + cJSON_Delete(root); return out; } @@ -374,11 +961,6 @@ static void template_emit_hook(const char* section_name, int message_index, void } static void append_context_log(const char* sender_pubkey_hex, const char* phase, const char* context_payload) { - FILE* fp = fopen("context.log", "a"); - if (!fp) { - return; - } - time_t now = time(NULL); struct tm tm_info; localtime_r(&now, &tm_info); @@ -386,16 +968,114 @@ static void append_context_log(const char* sender_pubkey_hex, const char* phase, strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", &tm_info); char* pretty_payload = format_context_payload_for_log(context_payload); + const char* safe_phase = phase ? phase : "unknown"; + const char* safe_sender = sender_pubkey_hex ? sender_pubkey_hex : "unknown"; + const char* safe_payload = pretty_payload ? pretty_payload : ""; + size_t context_bytes = context_payload ? strlen(context_payload) : 0U; + size_t approx_tokens = context_bytes / 4U; - fprintf(fp, - "[%s] phase=%s sender=%s\n\n%s\n\n---\n\n", - timestamp, - phase ? phase : "unknown", - sender_pubkey_hex ? sender_pubkey_hex : "unknown", - pretty_payload ? pretty_payload : ""); + llm_config_t active_llm_cfg = {0}; + const char* safe_model = (g_cfg && g_cfg->llm.model[0] != '\0') ? g_cfg->llm.model : "unknown"; + if (llm_get_config(&active_llm_cfg) == 0 && active_llm_cfg.model[0] != '\0') { + safe_model = active_llm_cfg.model; + } + int entry_len = snprintf(NULL, + 0, + "```text\n" + "Context Log - not seen by model\n" + "timestamp=%s\n" + "phase=%s\n" + "sender=%s\n" + "model=%s\n" + "context_bytes=%zu\n" + "approx_tokens=%zu\n" + "```\n\n" + "%s\n\n---\n\n", + timestamp, + safe_phase, + safe_sender, + safe_model, + context_bytes, + approx_tokens, + safe_payload); + if (entry_len < 0) { + free(pretty_payload); + return; + } + + char* entry = (char*)malloc((size_t)entry_len + 1U); + if (!entry) { + free(pretty_payload); + return; + } + + snprintf(entry, + (size_t)entry_len + 1U, + "```text\n" + "Context Log - not seen by model\n" + "timestamp=%s\n" + "phase=%s\n" + "sender=%s\n" + "model=%s\n" + "context_bytes=%zu\n" + "approx_tokens=%zu\n" + "```\n\n" + "%s\n\n---\n\n", + timestamp, + safe_phase, + safe_sender, + safe_model, + context_bytes, + approx_tokens, + 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); + free(pretty_payload); + 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); + free(entry); free(pretty_payload); - fclose(fp); +} + +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); } typedef struct { @@ -460,6 +1140,10 @@ static int append_startup_events_context(cJSON* messages) { for (int i = 0; i < g_cfg->startup_event_count; i++) { startup_event_t* se = &g_cfg->startup_events[i]; + if (!se || se->kind == 31120) { + continue; + } + cJSON* item = cJSON_CreateObject(); if (!item) { cJSON_Delete(arr); @@ -487,7 +1171,7 @@ static int append_startup_events_context(cJSON* messages) { return -1; } - const char* prefix = "## Startup Events Memory (source: config.startup_events)\n\nStartup events memory (kinds/content/tags): "; + const char* prefix = "Startup events memory (source: config.startup_events; kind 31120 excluded): "; size_t out_len = strlen(prefix) + strlen(events_json) + 1U; char* payload = (char*)malloc(out_len); if (!payload) { @@ -503,57 +1187,205 @@ static int append_startup_events_context(cJSON* messages) { return rc; } -static int append_admin_identity_context(cJSON* messages) { +static 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."); + } + if (sender_tier == DIDACTYL_SENDER_WOT) { + return strdup("This message is from a web-of-trust contact (not the administrator)."); + } + return strdup("Sender tier is unknown."); +} + +static char* build_admin_profile_plain_text(void) { + char* kind0 = nostr_handler_get_admin_kind0_context(); + if (!kind0 || kind0[0] == '\0') { + free(kind0); + return strdup("Administrator profile: unavailable."); + } + + cJSON* root = cJSON_Parse(kind0); + if (!root || !cJSON_IsObject(root)) { + cJSON_Delete(root); + size_t n = strlen("Administrator profile raw JSON: ") + strlen(kind0) + 1U; + char* out = (char*)malloc(n); + if (!out) { + free(kind0); + return NULL; + } + snprintf(out, n, "Administrator profile raw JSON: %s", kind0); + free(kind0); + return out; + } + + const char* name = ""; + const char* display_name = ""; + const char* about = ""; + const char* website = ""; + const char* picture = ""; + + cJSON* v = cJSON_GetObjectItemCaseSensitive(root, "name"); + if (v && cJSON_IsString(v) && v->valuestring) name = v->valuestring; + v = cJSON_GetObjectItemCaseSensitive(root, "display_name"); + if (v && cJSON_IsString(v) && v->valuestring) display_name = v->valuestring; + v = cJSON_GetObjectItemCaseSensitive(root, "about"); + if (v && cJSON_IsString(v) && v->valuestring) about = v->valuestring; + v = cJSON_GetObjectItemCaseSensitive(root, "website"); + if (v && cJSON_IsString(v) && v->valuestring) website = v->valuestring; + v = cJSON_GetObjectItemCaseSensitive(root, "picture"); + if (v && cJSON_IsString(v) && v->valuestring) picture = v->valuestring; + + const char* fmt = "Administrator profile: name=%s; display_name=%s; about=%s; website=%s; picture=%s"; + int len = snprintf(NULL, 0, fmt, name, display_name, about, website, picture); + char* out = (char*)malloc((size_t)len + 1U); + if (out) { + snprintf(out, (size_t)len + 1U, fmt, name, display_name, about, website, picture); + } + + cJSON_Delete(root); + free(kind0); + return out; +} + +static char* build_admin_relay_list_plain_text(void) { + char* kind10002 = nostr_handler_get_admin_kind10002_context(); + if (!kind10002 || kind10002[0] == '\0') { + free(kind10002); + return strdup("Administrator relay list: unavailable."); + } + + cJSON* arr = cJSON_Parse(kind10002); + if (!arr || !cJSON_IsArray(arr)) { + cJSON_Delete(arr); + size_t n = strlen("Administrator relay list raw JSON: ") + strlen(kind10002) + 1U; + char* out = (char*)malloc(n); + if (!out) { + free(kind10002); + return NULL; + } + snprintf(out, n, "Administrator relay list raw JSON: %s", kind10002); + free(kind10002); + return out; + } + + size_t cap = strlen("Administrator relay list: ") + strlen(kind10002) + 64U; + char* out = (char*)malloc(cap); + if (!out) { + cJSON_Delete(arr); + free(kind10002); + return NULL; + } + out[0] = '\0'; + strcat(out, "Administrator relay list: "); + + int n = cJSON_GetArraySize(arr); + int added = 0; + for (int i = 0; i < n; i++) { + cJSON* it = cJSON_GetArrayItem(arr, i); + if (!it || !cJSON_IsString(it) || !it->valuestring || it->valuestring[0] == '\0') continue; + if (added > 0) strcat(out, ", "); + strcat(out, it->valuestring); + added++; + } + + if (added == 0) { + strcat(out, "none"); + } + + cJSON_Delete(arr); + free(kind10002); + return out; +} + +static char* build_admin_recent_posts_text(void) { + char* notes = nostr_handler_get_admin_kind1_notes_context(); + if (!notes || notes[0] == '\0') { + free(notes); + return strdup("Recent posts: unavailable."); + } + + const char* prefix = "Administrator recent public notes:\n"; + const char* body = notes; + if (strncmp(notes, prefix, strlen(prefix)) == 0) { + body = notes + strlen(prefix); + } + + size_t n = strlen("Recent posts:\n") + strlen(body) + 1U; + char* out = (char*)malloc(n); + if (!out) { + free(notes); + return NULL; + } + + snprintf(out, n, "Recent posts:\n%s", body); + free(notes); + return out; +} + +static int append_admin_identity_context(cJSON* messages, didactyl_sender_tier_t sender_tier) { if (!messages || !g_cfg) { return -1; } - char admin_header[384]; - snprintf(admin_header, - sizeof(admin_header), - "## Administrator Identity (source: config.admin.pubkey)\n\nThis is your administrator! Admin pubkey (hex): %s", - g_cfg->admin.pubkey ? g_cfg->admin.pubkey : "unknown"); - if (append_simple_message(messages, "system", admin_header) != 0) { + char* sender_verification = build_sender_verification_text(sender_tier); + char* profile_plain = build_admin_profile_plain_text(); + char* relay_plain = build_admin_relay_list_plain_text(); + char* posts_plain = build_admin_recent_posts_text(); + if (!sender_verification || !profile_plain || !relay_plain || !posts_plain) { + free(sender_verification); + free(profile_plain); + free(relay_plain); + free(posts_plain); return -1; } - char* kind0 = nostr_handler_get_admin_kind0_context(); - if (kind0) { - const char* prefix = "## Administrator Kind 0 Profile (source: nostr kind 0)\n\nAdministrator kind 0 profile content (JSON): "; - size_t n = strlen(prefix) + strlen(kind0) + 1U; - char* payload = (char*)malloc(n); - if (!payload) { - free(kind0); - return -1; - } - snprintf(payload, n, "%s%s", prefix, kind0); - int rc = append_simple_message(messages, "system", payload); - free(payload); - free(kind0); - if (rc != 0) { - return -1; - } + const char* fmt = + "Sender verification: %s\n" + "Administrator pubkey (hex): %s\n" + "%s\n" + "%s\n" + "%s"; + int len = snprintf(NULL, + 0, + fmt, + sender_verification, + g_cfg->admin.pubkey[0] != '\0' ? g_cfg->admin.pubkey : "unknown", + profile_plain, + relay_plain, + posts_plain); + if (len < 0) { + free(sender_verification); + free(profile_plain); + free(relay_plain); + free(posts_plain); + return -1; } - char* kind10002 = nostr_handler_get_admin_kind10002_context(); - if (kind10002) { - const char* prefix = "## Administrator Relay List (source: nostr kind 10002)\n\nAdministrator kind 10002 relay-list content (JSON): "; - size_t n = strlen(prefix) + strlen(kind10002) + 1U; - char* payload = (char*)malloc(n); - if (!payload) { - free(kind10002); - return -1; - } - snprintf(payload, n, "%s%s", prefix, kind10002); - int rc = append_simple_message(messages, "system", payload); - free(payload); - free(kind10002); - if (rc != 0) { - return -1; - } + char* payload = (char*)malloc((size_t)len + 1U); + if (!payload) { + free(sender_verification); + free(profile_plain); + free(relay_plain); + free(posts_plain); + return -1; } - return 0; + snprintf(payload, + (size_t)len + 1U, + fmt, + sender_verification, + g_cfg->admin.pubkey[0] != '\0' ? g_cfg->admin.pubkey : "unknown", + profile_plain, + relay_plain, + posts_plain); + + int rc = append_simple_message(messages, "system", payload); + free(payload); + free(sender_verification); + free(profile_plain); + free(relay_plain); + free(posts_plain); + return rc; } static int append_recent_admin_dm_history(cJSON* messages, const char* current_message) { @@ -668,6 +1500,8 @@ static int append_recent_admin_dm_history(cJSON* messages, const char* current_m } int start = item_count > AGENT_HISTORY_TURNS ? item_count - AGENT_HISTORY_TURNS : 0; + const char* last_appended_role = NULL; + const char* last_appended_content = NULL; for (int i = start; i < item_count; i++) { if (i == item_count - 1 && items[i].role_is_user && @@ -676,12 +1510,27 @@ static int append_recent_admin_dm_history(cJSON* messages, const char* current_m continue; } - if (append_simple_message(messages, - items[i].role_is_user ? "user" : "assistant", - items[i].content ? items[i].content : "") != 0) { + const char* role = items[i].role_is_user ? "user" : "assistant"; + const char* content = items[i].content ? items[i].content : ""; + + if (last_appended_role && last_appended_content && + strcmp(last_appended_role, role) == 0 && + strcmp(last_appended_content, content) == 0) { + continue; + } + + if (append_simple_message(messages, role, content) != 0) { free_history_items(items, item_count); return -1; } + + cJSON* appended = cJSON_GetArrayItem(messages, cJSON_GetArraySize(messages) - 1); + if (appended && cJSON_IsObject(appended)) { + cJSON_AddNumberToObject(appended, "_ts", (double)items[i].created_at); + } + + last_appended_role = role; + last_appended_content = content; } free_history_items(items, item_count); @@ -921,7 +1770,7 @@ static int refresh_adopted_skills_cache_if_needed(void) { snprintf(tmp[tmp_count].author_pubkey_hex, sizeof(tmp[tmp_count].author_pubkey_hex), "%s", - (g_cfg->keys.public_key_hex && g_cfg->keys.public_key_hex[0] != '\0') + g_cfg->keys.public_key_hex[0] != '\0' ? g_cfg->keys.public_key_hex : "unknown"); snprintf(tmp[tmp_count].slug, sizeof(tmp[tmp_count].slug), "%s", slug); @@ -949,6 +1798,58 @@ static int refresh_adopted_skills_cache_if_needed(void) { return 0; } +static char* flatten_skill_content_to_text(const char* skill_content) { + if (!skill_content || skill_content[0] == '\0') { + return strdup(""); + } + + cJSON* root = cJSON_Parse(skill_content); + if (!root || !cJSON_IsObject(root)) { + cJSON_Delete(root); + return strdup(skill_content); + } + + cJSON* name = cJSON_GetObjectItemCaseSensitive(root, "name"); + cJSON* description = cJSON_GetObjectItemCaseSensitive(root, "description"); + cJSON* event_kind = cJSON_GetObjectItemCaseSensitive(root, "event_kind"); + cJSON* procedure = cJSON_GetObjectItemCaseSensitive(root, "procedure"); + + size_t cap = strlen(skill_content) + 512U; + char* out = (char*)malloc(cap); + if (!out) { + cJSON_Delete(root); + return NULL; + } + out[0] = '\0'; + + if (name && cJSON_IsString(name) && name->valuestring) { + snprintf(out + strlen(out), cap - strlen(out), "Name: %s\n", name->valuestring); + } + if (description && cJSON_IsString(description) && description->valuestring) { + snprintf(out + strlen(out), cap - strlen(out), "Description: %s\n", description->valuestring); + } + if (event_kind && cJSON_IsNumber(event_kind)) { + snprintf(out + strlen(out), cap - strlen(out), "Event kind: %d\n", (int)event_kind->valuedouble); + } + + if (procedure && cJSON_IsArray(procedure) && cJSON_GetArraySize(procedure) > 0) { + strcat(out, "Procedure:\n"); + int pn = cJSON_GetArraySize(procedure); + for (int i = 0; i < pn; i++) { + cJSON* step = cJSON_GetArrayItem(procedure, i); + if (!step || !cJSON_IsString(step) || !step->valuestring) continue; + snprintf(out + strlen(out), cap - strlen(out), " %d. %s\n", i + 1, step->valuestring); + } + } + + if (out[0] == '\0') { + snprintf(out, cap, "%s", skill_content); + } + + cJSON_Delete(root); + return out; +} + static int append_adopted_skills_context(cJSON* messages) { if (!messages || !g_cfg) { return -1; @@ -984,7 +1885,8 @@ static int append_adopted_skills_context(cJSON* messages) { size_t total_skill_chars = 0; int included = 0; for (int i = 0; i < cached_count && used + 64U < capacity; i++) { - const char* skill_content = g_adopted_skills[i].content ? g_adopted_skills[i].content : ""; + char* flattened_skill = flatten_skill_content_to_text(g_adopted_skills[i].content ? g_adopted_skills[i].content : ""); + const char* skill_content = flattened_skill ? flattened_skill : (g_adopted_skills[i].content ? g_adopted_skills[i].content : ""); size_t content_len = strlen(skill_content); size_t clipped_len = content_len; @@ -1036,6 +1938,8 @@ static int append_adopted_skills_context(cJSON* messages) { if (total_skill_chars >= (size_t)AGENT_ADOPTED_SKILLS_TOTAL_MAX_CHARS) { break; } + + free(flattened_skill); } if (included < cached_count && used + 96U < capacity) { @@ -1080,6 +1984,10 @@ static char* build_startup_events_json_string(void) { for (int i = 0; i < g_cfg->startup_event_count; i++) { startup_event_t* se = &g_cfg->startup_events[i]; + if (!se || se->kind == 31120) { + continue; + } + cJSON* item = cJSON_CreateObject(); if (!item) { cJSON_Delete(arr); @@ -1126,12 +2034,147 @@ static char* build_adopted_skills_payload_string(void) { return out; } +static char* build_tasks_content_string(void) { + const char* fallback = + "### Current Tasks\n\n" + "Your active task list - short-term working memory for tracking plan steps.\n\n" + "- [ ] No active tasks yet.\n"; + + if (!g_cfg) { + return strdup(fallback); + } + + const char* cwd = g_cfg->tools.shell.working_directory[0] != '\0' + ? g_cfg->tools.shell.working_directory + : "."; + + char tasks_path[1024]; + int pn = 0; + if (strcmp(cwd, ".") == 0) { + pn = snprintf(tasks_path, sizeof(tasks_path), "tasks.json"); + } else { + pn = snprintf(tasks_path, sizeof(tasks_path), "%s/%s", cwd, "tasks.json"); + } + if (pn <= 0 || (size_t)pn >= sizeof(tasks_path)) { + return strdup(fallback); + } + + FILE* fp = fopen(tasks_path, "rb"); + if (!fp) { + return strdup(fallback); + } + + if (fseek(fp, 0, SEEK_END) != 0) { + fclose(fp); + return strdup(fallback); + } + + long len = ftell(fp); + if (len < 0) { + fclose(fp); + return strdup(fallback); + } + + if (fseek(fp, 0, SEEK_SET) != 0) { + fclose(fp); + return strdup(fallback); + } + + char* raw = (char*)malloc((size_t)len + 1U); + if (!raw) { + fclose(fp); + return strdup(fallback); + } + + size_t n = fread(raw, 1, (size_t)len, fp); + fclose(fp); + if (n != (size_t)len) { + free(raw); + return strdup(fallback); + } + raw[len] = '\0'; + + cJSON* root = cJSON_Parse(raw); + free(raw); + if (!root || !cJSON_IsObject(root)) { + cJSON_Delete(root); + return strdup(fallback); + } + + cJSON* tasks = cJSON_GetObjectItemCaseSensitive(root, "tasks"); + if (!tasks || !cJSON_IsArray(tasks)) { + cJSON_Delete(root); + return strdup(fallback); + } + + size_t cap = 1024U; + size_t used = 0U; + char* out = (char*)malloc(cap); + if (!out) { + cJSON_Delete(root); + return strdup(fallback); + } + out[0] = '\0'; + + if (appendf(&out, + &cap, + &used, + "### Current Tasks\n\n" + "Your active task list - short-term working memory for tracking plan steps.\n\n") != 0) { + free(out); + cJSON_Delete(root); + return strdup(fallback); + } + + int written = 0; + int tcount = cJSON_GetArraySize(tasks); + for (int i = 0; i < tcount; i++) { + cJSON* task = cJSON_GetArrayItem(tasks, i); + if (!task || !cJSON_IsObject(task)) continue; + + cJSON* id = cJSON_GetObjectItemCaseSensitive(task, "id"); + cJSON* text = cJSON_GetObjectItemCaseSensitive(task, "text"); + cJSON* status = cJSON_GetObjectItemCaseSensitive(task, "status"); + + if (!text || !cJSON_IsString(text) || !text->valuestring || text->valuestring[0] == '\0') { + continue; + } + + const char* status_s = (status && cJSON_IsString(status) && status->valuestring) + ? status->valuestring + : "pending"; + const char* mark = " "; + if (strcmp(status_s, "done") == 0) { + mark = "x"; + } else if (strcmp(status_s, "active") == 0) { + mark = "-"; + } + + int id_value = (id && cJSON_IsNumber(id)) ? (int)id->valuedouble : (i + 1); + if (appendf(&out, &cap, &used, "- [%s] %d. %s\n", mark, id_value, text->valuestring) != 0) { + free(out); + cJSON_Delete(root); + return strdup(fallback); + } + written++; + } + + cJSON_Delete(root); + + if (written <= 0) { + free(out); + return strdup(fallback); + } + + return out; +} + static char* safe_strdup(const char* s) { return strdup(s ? s : ""); } static char* agent_template_resolve_var(const char* var_name, void* user_data) { - (void)user_data; + agent_template_resolver_ctx_t* ctx = (agent_template_resolver_ctx_t*)user_data; if (!var_name || !g_cfg) { return strdup(""); } @@ -1150,6 +2193,19 @@ static char* agent_template_resolve_var(const char* var_name, void* user_data) { char* v = nostr_handler_get_admin_kind10002_context(); return v ? v : strdup(""); } + if (strcmp(var_name, "admin_profile_plain") == 0) { + char* v = build_admin_profile_plain_text(); + return v ? v : strdup(""); + } + if (strcmp(var_name, "admin_relay_list_plain") == 0) { + char* v = build_admin_relay_list_plain_text(); + return v ? v : strdup(""); + } + if (strcmp(var_name, "sender_verification") == 0) { + didactyl_sender_tier_t tier = ctx ? ctx->sender_tier : DIDACTYL_SENDER_ADMIN; + char* v = build_sender_verification_text(tier); + return v ? v : strdup(""); + } if (strcmp(var_name, "startup_events_json") == 0) { char* v = build_startup_events_json_string(); return v ? v : strdup("[]"); @@ -1158,8 +2214,16 @@ static char* agent_template_resolve_var(const char* var_name, void* user_data) { char* v = build_adopted_skills_payload_string(); return v ? v : strdup(""); } + if (strcmp(var_name, "tasks_content") == 0) { + char* v = build_tasks_content_string(); + return v ? v : strdup(""); + } if (strcmp(var_name, "admin_notes_content") == 0) { - char* v = nostr_handler_get_admin_kind1_notes_context(); + char* v = build_admin_recent_posts_text(); + return v ? v : strdup(""); + } + if (strcmp(var_name, "admin_recent_posts") == 0) { + char* v = build_admin_recent_posts_text(); return v ? v : strdup(""); } @@ -1265,15 +2329,17 @@ void agent_on_trigger(const char* skill_slug, if (!response) { const char* fallback = "Triggered skill execution failed: LLM unavailable."; - (void)nostr_handler_send_dm(g_cfg->admin.pubkey, fallback); + (void)nostr_handler_send_dm_auto(g_cfg->admin.pubkey, fallback); return; } - (void)nostr_handler_send_dm(g_cfg->admin.pubkey, response); + (void)nostr_handler_send_dm_auto(g_cfg->admin.pubkey, response); free(response); } -int agent_build_admin_messages_json(const char* current_user_message, char** out_messages_json) { +int agent_build_admin_messages_json(const char* current_user_message, + didactyl_sender_tier_t sender_tier, + char** out_messages_json) { if (!out_messages_json || !g_cfg || !g_system_context) { return -1; } @@ -1289,6 +2355,9 @@ int agent_build_admin_messages_json(const char* current_user_message, char** out dm_history = cJSON_CreateArray(); } + agent_template_resolver_ctx_t resolver_ctx; + resolver_ctx.sender_tier = sender_tier; + llm_config_t cfg; memset(&cfg, 0, sizeof(cfg)); const char* provider_name = NULL; @@ -1299,7 +2368,7 @@ int agent_build_admin_messages_json(const char* current_user_message, char** out messages = prompt_template_build_messages(&g_prompt_template, provider_name, agent_template_resolve_var, - NULL, + &resolver_ctx, dm_history, AGENT_HISTORY_TURNS, template_emit_hook, @@ -1314,26 +2383,29 @@ int agent_build_admin_messages_json(const char* current_user_message, char** out return -1; } - if (append_simple_message(messages, "system", g_system_context) != 0 || - append_admin_identity_context(messages) != 0 || + char agent_identity[192]; + snprintf(agent_identity, + sizeof(agent_identity), + "Agent identity\nYour pubkey (hex): %s", + g_cfg->keys.public_key_hex[0] != '\0' ? g_cfg->keys.public_key_hex : "unknown"); + + char* sender_verification = build_sender_verification_text(sender_tier); + if (!sender_verification || + append_simple_message(messages, "system", g_system_context) != 0 || + append_simple_message(messages, "system", agent_identity) != 0 || + append_simple_message(messages, "system", sender_verification) != 0 || + append_admin_identity_context(messages, sender_tier) != 0 || append_startup_events_context(messages) != 0 || - append_adopted_skills_context(messages) != 0 || - append_recent_admin_dm_history(messages, current_user_message) != 0) { + append_adopted_skills_context(messages) != 0) { + free(sender_verification); cJSON_Delete(messages); return -1; } + free(sender_verification); - char* admin_notes = nostr_handler_get_admin_kind1_notes_context(); - if (admin_notes) { - const char* prefix = "## Administrator Recent Notes (source: nostr kind 1)\n\n"; - size_t n = strlen(prefix) + strlen(admin_notes) + 1U; - char* payload = (char*)malloc(n); - if (payload) { - snprintf(payload, n, "%s%s", prefix, admin_notes); - (void)append_simple_message(messages, "system", payload); - free(payload); - } - free(admin_notes); + if (append_recent_admin_dm_history(messages, current_user_message) != 0) { + cJSON_Delete(messages); + return -1; } int n = cJSON_GetArraySize(messages); @@ -1384,6 +2456,18 @@ void agent_on_message(const char* sender_pubkey_hex, int allow_tools = (tier == DIDACTYL_SENDER_ADMIN) && g_cfg->tools.enabled && g_cfg->security.admin.tools_enabled; + 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(sender_pubkey_hex, denied); + return; + } + if (handle_slash_command(sender_pubkey_hex, message)) { + return; + } + } + if (!allow_tools) { const char* tier_prefix = (tier == DIDACTYL_SENDER_WOT) ? "You are responding to a web-of-trust contact. Keep the response helpful and concise. Tool use is disabled for this tier." @@ -1410,28 +2494,28 @@ void agent_on_message(const char* sender_pubkey_hex, if (!response) { const char* fallback = "I could not get a response from the LLM right now."; fprintf(stdout, "[didactyl] llm response unavailable, sending fallback\n"); - (void)nostr_handler_send_dm(sender_pubkey_hex, fallback); + (void)nostr_handler_send_dm_auto(sender_pubkey_hex, fallback); return; } fprintf(stdout, "[didactyl] llm response: %.240s%s\n", response, strlen(response) > 240 ? "..." : ""); - (void)nostr_handler_send_dm(sender_pubkey_hex, response); + (void)nostr_handler_send_dm_auto(sender_pubkey_hex, response); free(response); return; } char* tools_json = tools_build_openai_schema_json(&g_tools_ctx); if (!tools_json) { - (void)nostr_handler_send_dm(sender_pubkey_hex, "Tool schema generation failed."); + (void)nostr_handler_send_dm_auto(sender_pubkey_hex, "Tool schema generation failed."); return; } char* base_messages_json = NULL; - if (agent_build_admin_messages_json(message, &base_messages_json) != 0 || !base_messages_json) { + if (agent_build_admin_messages_json(message, tier, &base_messages_json) != 0 || !base_messages_json) { free(tools_json); - (void)nostr_handler_send_dm(sender_pubkey_hex, "Failed to initialize conversation messages."); + (void)nostr_handler_send_dm_auto(sender_pubkey_hex, "Failed to initialize conversation messages."); return; } @@ -1440,17 +2524,22 @@ void agent_on_message(const char* sender_pubkey_hex, if (!messages || !cJSON_IsArray(messages)) { cJSON_Delete(messages); free(tools_json); - (void)nostr_handler_send_dm(sender_pubkey_hex, "Failed to initialize conversation state."); + (void)nostr_handler_send_dm_auto(sender_pubkey_hex, "Failed to initialize conversation state."); return; } if (append_simple_message(messages, "user", message) != 0) { cJSON_Delete(messages); free(tools_json); - (void)nostr_handler_send_dm(sender_pubkey_hex, "Failed to initialize conversation messages."); + (void)nostr_handler_send_dm_auto(sender_pubkey_hex, "Failed to initialize conversation messages."); return; } + cJSON* live_user_msg = cJSON_GetArrayItem(messages, cJSON_GetArraySize(messages) - 1); + if (live_user_msg && cJSON_IsObject(live_user_msg)) { + cJSON_AddNumberToObject(live_user_msg, "_ts", (double)time(NULL)); + } + int max_turns = g_cfg->tools.max_turns > 0 ? g_cfg->tools.max_turns : 8; char* final_answer_owned = NULL; @@ -1466,7 +2555,7 @@ void agent_on_message(const char* sender_pubkey_hex, int rc = llm_chat_with_tools_messages(messages_json, tools_json, "auto", &resp); free(messages_json); if (rc != 0) { - (void)nostr_handler_send_dm(sender_pubkey_hex, "LLM request failed."); + (void)nostr_handler_send_dm_auto(sender_pubkey_hex, "LLM request failed."); cJSON_Delete(messages); free(tools_json); return; @@ -1506,7 +2595,7 @@ void agent_on_message(const char* sender_pubkey_hex, llm_response_free(&resp); cJSON_Delete(messages); free(tools_json); - (void)nostr_handler_send_dm(sender_pubkey_hex, "Failed to append tool result."); + (void)nostr_handler_send_dm_auto(sender_pubkey_hex, "Failed to append tool result."); return; } free(tool_result); @@ -1523,7 +2612,7 @@ void agent_on_message(const char* sender_pubkey_hex, fprintf(stdout, "[didactyl] final response: %.240s%s\n", final_answer, strlen(final_answer) > 240 ? "..." : ""); - (void)nostr_handler_send_dm(sender_pubkey_hex, final_answer); + (void)nostr_handler_send_dm_auto(sender_pubkey_hex, final_answer); free(final_answer_owned); cJSON_Delete(messages); diff --git a/src/agent.h b/src/agent.h index 0c623a1..23019a4 100644 --- a/src/agent.h +++ b/src/agent.h @@ -18,9 +18,12 @@ void agent_on_message(const char* sender_pubkey_hex, const char* message, didactyl_sender_tier_t tier, void* user_data); -int agent_build_admin_messages_json(const char* current_user_message, char** out_messages_json); +int agent_build_admin_messages_json(const char* current_user_message, + didactyl_sender_tier_t sender_tier, + char** out_messages_json); tools_context_t* agent_tools_context(void); const char* agent_classify_message_part(cJSON* msg, int idx); +void agent_append_context_log(const char* sender_pubkey_hex, const char* phase, const char* context_payload); void agent_cleanup(void); #endif \ No newline at end of file diff --git a/src/config.c b/src/config.c index 0455eab..1a393b8 100644 --- a/src/config.c +++ b/src/config.c @@ -315,6 +315,33 @@ static int parse_triggers_config(cJSON* root, didactyl_config_t* config) { return 0; } +static int parse_dm_protocol_config(cJSON* root, didactyl_config_t* config) { + if (!root || !config) { + return -1; + } + + cJSON* dm_protocol = cJSON_GetObjectItemCaseSensitive(root, "dm_protocol"); + if (!dm_protocol) { + return 0; + } + + if (!cJSON_IsString(dm_protocol) || !dm_protocol->valuestring) { + return -1; + } + + if (strcmp(dm_protocol->valuestring, "nip04") == 0) { + config->dm_protocol = DM_PROTOCOL_NIP04; + } else if (strcmp(dm_protocol->valuestring, "nip17") == 0) { + config->dm_protocol = DM_PROTOCOL_NIP17; + } else if (strcmp(dm_protocol->valuestring, "both") == 0) { + config->dm_protocol = DM_PROTOCOL_BOTH; + } else { + return -1; + } + + return 0; +} + static int parse_api_config(cJSON* root, didactyl_config_t* config) { cJSON* api = cJSON_GetObjectItemCaseSensitive(root, "api"); if (!api || !cJSON_IsObject(api)) { @@ -637,6 +664,8 @@ int config_load(const char* path, didactyl_config_t* config) { memset(config, 0, sizeof(*config)); snprintf(config->config_path, sizeof(config->config_path), "%s", path); + config->dm_protocol = DM_PROTOCOL_NIP04; + config->tools.enabled = 1; config->tools.max_turns = 8; config->tools.shell.enabled = 1; @@ -754,6 +783,11 @@ int config_load(const char* path, didactyl_config_t* config) { config->llm.max_tokens = (max_tokens && cJSON_IsNumber(max_tokens)) ? (int)max_tokens->valuedouble : 512; config->llm.temperature = (temperature && cJSON_IsNumber(temperature)) ? temperature->valuedouble : 0.7; + if (parse_dm_protocol_config(root, config) != 0) { + config_set_error("invalid dm_protocol configuration (expected 'nip04', 'nip17', or 'both')"); + goto cleanup; + } + if (parse_tools_config(root, config) != 0) { config_set_error("invalid tools configuration"); goto cleanup; diff --git a/src/config.h b/src/config.h index 613af65..b681f5d 100644 --- a/src/config.h +++ b/src/config.h @@ -18,6 +18,12 @@ typedef struct { char public_key_hex[65]; } agent_keys_t; +typedef enum { + DM_PROTOCOL_NIP04 = 0, + DM_PROTOCOL_NIP17 = 1, + DM_PROTOCOL_BOTH = 2 +} dm_protocol_t; + typedef struct { char pubkey[65]; } admin_config_t; @@ -89,6 +95,7 @@ typedef struct { typedef struct { agent_keys_t keys; admin_config_t admin; + dm_protocol_t dm_protocol; char** relays; int relay_count; llm_config_t llm; diff --git a/src/http_api.c b/src/http_api.c index 8b1ff39..b260df6 100644 --- a/src/http_api.c +++ b/src/http_api.c @@ -20,23 +20,22 @@ static http_api_context_t g_api_ctx; static int g_api_initialized = 0; static struct mg_mgr g_mgr; static struct mg_connection* g_listener = NULL; +static int g_api_tls_enabled = 0; +static struct mg_str g_tls_cert = {NULL, 0}; +static struct mg_str g_tls_key = {NULL, 0}; static const char* k_json_headers = "Content-Type: application/json\r\n" "Access-Control-Allow-Origin: *\r\n" "Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS\r\n" - "Access-Control-Allow-Headers: Content-Type\r\n"; + "Access-Control-Allow-Headers: Content-Type\r\n" + "Access-Control-Allow-Private-Network: true\r\n"; static int method_is(const struct mg_http_message* hm, const char* method) { size_t n = strlen(method); return hm && hm->method.len == n && strncmp(hm->method.buf, method, n) == 0; } -static int mgstr_eq(struct mg_str s, const char* lit) { - size_t n = strlen(lit); - return s.len == n && strncmp(s.buf, lit, n) == 0; -} - static void reply_json(struct mg_connection* c, int status, cJSON* root) { char* out = cJSON_PrintUnformatted(root); if (!out) { @@ -118,7 +117,7 @@ static int build_context_parts_response(cJSON* out_root) { if (!out_root) return -1; char* messages_json = NULL; - if (agent_build_admin_messages_json("", &messages_json) != 0 || !messages_json) { + if (agent_build_admin_messages_json("", DIDACTYL_SENDER_ADMIN, &messages_json) != 0 || !messages_json) { return -1; } @@ -233,29 +232,443 @@ static int append_tool_result_message_local(cJSON* messages, const char* tool_ca return 0; } -static cJSON* run_prompt_with_tools(cJSON* body) { - if (!body || !cJSON_IsObject(body)) return NULL; +static int is_space_char_local(char c) { + return c == ' ' || c == '\t' || c == '\n' || c == '\r'; +} - cJSON* messages_node = cJSON_GetObjectItemCaseSensitive(body, "messages"); - if (!messages_node || !cJSON_IsArray(messages_node)) return NULL; +static const char* skip_spaces_local(const char* s) { + while (s && *s && is_space_char_local(*s)) s++; + return s ? s : ""; +} - cJSON* convo = cJSON_Duplicate(messages_node, 1); - if (!convo || !cJSON_IsArray(convo)) { - cJSON_Delete(convo); +static char* json_error_string_local(const char* msg) { + cJSON* out = cJSON_CreateObject(); + if (!out) return NULL; + cJSON_AddBoolToObject(out, "success", 0); + cJSON_AddStringToObject(out, "error", msg ? msg : "error"); + char* json = cJSON_PrintUnformatted(out); + cJSON_Delete(out); + return json; +} + +static char* build_slash_args_json_local(const char* raw_args) { + const char* s = skip_spaces_local(raw_args); + if (!s || s[0] == '\0') { + return strdup("{}"); + } + + cJSON* parsed = cJSON_Parse(s); + if (parsed) { + char* out = cJSON_PrintUnformatted(parsed); + cJSON_Delete(parsed); + return out; + } + + cJSON* obj = cJSON_CreateObject(); + if (!obj) return NULL; + cJSON_AddStringToObject(obj, "input", s); + char* out = cJSON_PrintUnformatted(obj); + cJSON_Delete(obj); + return out; +} + +static int append_textf_http(char** buf, size_t* cap, size_t* used, const char* fmt, ...) { + if (!buf || !cap || !used || !fmt) return -1; + + while (1) { + if (*used + 128U >= *cap) { + size_t next_cap = (*cap) * 2U; + char* grown = (char*)realloc(*buf, next_cap); + if (!grown) return -1; + *buf = grown; + *cap = next_cap; + } + + va_list ap; + va_start(ap, fmt); + int n = vsnprintf(*buf + *used, *cap - *used, fmt, ap); + va_end(ap); + + if (n < 0) return -1; + if ((size_t)n < (*cap - *used)) { + *used += (size_t)n; + return 0; + } + + size_t next_cap = (*cap) * 2U + (size_t)n + 64U; + char* grown = (char*)realloc(*buf, next_cap); + if (!grown) return -1; + *buf = grown; + *cap = next_cap; + } +} + +static int append_markdown_indent_http(char** buf, size_t* cap, size_t* used, int depth) { + for (int i = 0; i < depth; i++) { + if (append_textf_http(buf, cap, used, " ") != 0) { + return -1; + } + } + return 0; +} + +static int append_cjson_markdown_http(char** buf, size_t* cap, size_t* used, cJSON* node, int depth) { + if (!node) { + return append_markdown_indent_http(buf, cap, used, depth) == 0 && + append_textf_http(buf, cap, used, "- null\n") == 0 ? 0 : -1; + } + + if (cJSON_IsObject(node)) { + cJSON* child = NULL; + cJSON_ArrayForEach(child, node) { + const char* key = child->string ? child->string : "item"; + if (cJSON_IsString(child)) { + if (append_markdown_indent_http(buf, cap, used, depth) != 0 || + append_textf_http(buf, cap, used, "- **%s:** %s\n", key, + child->valuestring ? child->valuestring : "") != 0) { + return -1; + } + } else if (cJSON_IsBool(child)) { + if (append_markdown_indent_http(buf, cap, used, depth) != 0 || + append_textf_http(buf, cap, used, "- **%s:** %s\n", key, + cJSON_IsTrue(child) ? "true" : "false") != 0) { + return -1; + } + } else if (cJSON_IsNumber(child)) { + if (append_markdown_indent_http(buf, cap, used, depth) != 0 || + append_textf_http(buf, cap, used, "- **%s:** %g\n", key, child->valuedouble) != 0) { + return -1; + } + } else if (cJSON_IsNull(child)) { + if (append_markdown_indent_http(buf, cap, used, depth) != 0 || + append_textf_http(buf, cap, used, "- **%s:** null\n", key) != 0) { + return -1; + } + } else { + if (append_markdown_indent_http(buf, cap, used, depth) != 0 || + append_textf_http(buf, cap, used, "- **%s:**\n", key) != 0) { + return -1; + } + if (append_cjson_markdown_http(buf, cap, used, child, depth + 1) != 0) { + return -1; + } + } + } + return 0; + } + + if (cJSON_IsArray(node)) { + int n = cJSON_GetArraySize(node); + for (int i = 0; i < n; i++) { + cJSON* item = cJSON_GetArrayItem(node, i); + if (cJSON_IsString(item)) { + if (append_markdown_indent_http(buf, cap, used, depth) != 0 || + append_textf_http(buf, cap, used, "- %s\n", item->valuestring ? item->valuestring : "") != 0) { + return -1; + } + } else if (cJSON_IsBool(item)) { + if (append_markdown_indent_http(buf, cap, used, depth) != 0 || + append_textf_http(buf, cap, used, "- %s\n", cJSON_IsTrue(item) ? "true" : "false") != 0) { + return -1; + } + } else if (cJSON_IsNumber(item)) { + if (append_markdown_indent_http(buf, cap, used, depth) != 0 || + append_textf_http(buf, cap, used, "- %g\n", item->valuedouble) != 0) { + return -1; + } + } else if (cJSON_IsNull(item)) { + if (append_markdown_indent_http(buf, cap, used, depth) != 0 || + append_textf_http(buf, cap, used, "- null\n") != 0) { + return -1; + } + } else { + if (append_markdown_indent_http(buf, cap, used, depth) != 0 || + append_textf_http(buf, cap, used, "-\n") != 0) { + return -1; + } + if (append_cjson_markdown_http(buf, cap, used, item, depth + 1) != 0) { + return -1; + } + } + } + return 0; + } + + if (cJSON_IsString(node)) { + return append_markdown_indent_http(buf, cap, used, depth) == 0 && + append_textf_http(buf, cap, used, "- %s\n", node->valuestring ? node->valuestring : "") == 0 ? 0 : -1; + } + if (cJSON_IsBool(node)) { + return append_markdown_indent_http(buf, cap, used, depth) == 0 && + append_textf_http(buf, cap, used, "- %s\n", cJSON_IsTrue(node) ? "true" : "false") == 0 ? 0 : -1; + } + if (cJSON_IsNumber(node)) { + return append_markdown_indent_http(buf, cap, used, depth) == 0 && + append_textf_http(buf, cap, used, "- %g\n", node->valuedouble) == 0 ? 0 : -1; + } + + return append_markdown_indent_http(buf, cap, used, depth) == 0 && + append_textf_http(buf, cap, used, "- null\n") == 0 ? 0 : -1; +} + +static char* format_result_markdown_http(const char* raw_result) { + if (!raw_result) { + return strdup("No output."); + } + + cJSON* root = cJSON_Parse(raw_result); + if (!root) { + return strdup(raw_result); + } + + size_t cap = strlen(raw_result) * 2U + 512U; + if (cap < 2048U) cap = 2048U; + size_t used = 0U; + char* out = (char*)malloc(cap); + if (!out) { + cJSON_Delete(root); + return strdup(raw_result); + } + out[0] = '\0'; + + if (append_cjson_markdown_http(&out, &cap, &used, root, 0) != 0) { + cJSON_Delete(root); + free(out); + return strdup(raw_result); + } + + cJSON_Delete(root); + return out; +} + +static char* build_slash_help_all_json_local(void) { + size_t cap = 2048U; + size_t used = 0U; + char* out = (char*)malloc(cap); + if (!out) return NULL; + out[0] = '\0'; + + if (append_textf_http(&out, &cap, &used, "AVAILABLE TOOLS\n") != 0) { + free(out); return NULL; } - int max_turns = 4; - cJSON* max_turns_node = cJSON_GetObjectItemCaseSensitive(body, "max_turns"); - if (max_turns_node && cJSON_IsNumber(max_turns_node)) { - max_turns = (int)max_turns_node->valuedouble; - if (max_turns < 1) max_turns = 1; - if (max_turns > 16) max_turns = 16; + char* tool_list_json = tools_execute(g_api_ctx.tools_ctx, "tool_list", "{}"); + cJSON* tool_root = tool_list_json ? cJSON_Parse(tool_list_json) : NULL; + cJSON* tools = tool_root ? cJSON_GetObjectItemCaseSensitive(tool_root, "tools") : NULL; + + if (!tools || !cJSON_IsArray(tools) || cJSON_GetArraySize(tools) <= 0) { + (void)append_textf_http(&out, &cap, &used, "- (none)\n"); + } else { + int tn = cJSON_GetArraySize(tools); + int* used_idx = (int*)calloc((size_t)tn, sizeof(int)); + if (!used_idx) { + cJSON_Delete(tool_root); + free(tool_list_json); + free(out); + return NULL; + } + + for (int k = 0; k < tn; k++) { + int best = -1; + const char* best_name = NULL; + + for (int i = 0; i < tn; i++) { + if (used_idx[i]) continue; + cJSON* row = cJSON_GetArrayItem(tools, i); + cJSON* name = row ? cJSON_GetObjectItemCaseSensitive(row, "name") : NULL; + const char* name_s = (name && cJSON_IsString(name) && name->valuestring) ? name->valuestring : "unknown"; + if (best < 0 || strcmp(name_s, best_name) < 0) { + best = i; + best_name = name_s; + } + } + + if (best < 0) { + break; + } + + used_idx[best] = 1; + cJSON* row = cJSON_GetArrayItem(tools, best); + cJSON* name = row ? cJSON_GetObjectItemCaseSensitive(row, "name") : NULL; + cJSON* desc = row ? cJSON_GetObjectItemCaseSensitive(row, "description") : NULL; + const char* name_s = (name && cJSON_IsString(name) && name->valuestring) ? name->valuestring : "unknown"; + const char* desc_s = (desc && cJSON_IsString(desc) && desc->valuestring) ? desc->valuestring : ""; + if (append_textf_http(&out, &cap, &used, "- %s%s%s\n", name_s, desc_s[0] ? " — " : "", desc_s) != 0) { + free(used_idx); + cJSON_Delete(tool_root); + free(tool_list_json); + free(out); + return NULL; + } + } + + free(used_idx); } + cJSON_Delete(tool_root); + free(tool_list_json); + + if (append_textf_http(&out, &cap, &used, "\nAVAILABLE SKILLS\n") != 0) { + free(out); + return NULL; + } + + char* skill_list_json = tools_execute(g_api_ctx.tools_ctx, "skill_list", "{}"); + cJSON* skill_root = skill_list_json ? cJSON_Parse(skill_list_json) : NULL; + cJSON* skills = skill_root ? cJSON_GetObjectItemCaseSensitive(skill_root, "skills") : NULL; + + if (!skills || !cJSON_IsArray(skills) || cJSON_GetArraySize(skills) <= 0) { + (void)append_textf_http(&out, &cap, &used, "- (none)\n"); + } else { + int sn = cJSON_GetArraySize(skills); + for (int i = 0; i < sn; i++) { + cJSON* row = cJSON_GetArrayItem(skills, i); + cJSON* slug = row ? cJSON_GetObjectItemCaseSensitive(row, "slug") : NULL; + cJSON* desc = row ? cJSON_GetObjectItemCaseSensitive(row, "description") : NULL; + const char* slug_s = (slug && cJSON_IsString(slug) && slug->valuestring) ? slug->valuestring : NULL; + const char* desc_s = (desc && cJSON_IsString(desc) && desc->valuestring) ? desc->valuestring : ""; + if (!slug_s || slug_s[0] == '\0') { + continue; + } + if (append_textf_http(&out, &cap, &used, "- %s%s%s\n", slug_s, desc_s[0] ? " — " : "", desc_s) != 0) { + cJSON_Delete(skill_root); + free(skill_list_json); + free(out); + return NULL; + } + } + } + + cJSON_Delete(skill_root); + free(skill_list_json); + return out; +} + +static char* build_slash_help_tool_json_local(const char* tool_name) { + if (!tool_name || tool_name[0] == '\0') { + return json_error_string_local("missing tool name"); + } + + char* schema_json = tools_build_openai_schema_json(g_api_ctx.tools_ctx); + if (!schema_json) return json_error_string_local("failed to build tool schema"); + + cJSON* schema = cJSON_Parse(schema_json); + free(schema_json); + if (!schema || !cJSON_IsArray(schema)) { + cJSON_Delete(schema); + return json_error_string_local("tool schema parse failure"); + } + + cJSON* matched_fn = NULL; + int n = cJSON_GetArraySize(schema); + for (int i = 0; i < n; i++) { + cJSON* item = cJSON_GetArrayItem(schema, i); + cJSON* fn = item ? cJSON_GetObjectItemCaseSensitive(item, "function") : NULL; + cJSON* name = fn ? cJSON_GetObjectItemCaseSensitive(fn, "name") : NULL; + if (name && cJSON_IsString(name) && name->valuestring && strcmp(name->valuestring, tool_name) == 0) { + matched_fn = fn; + break; + } + } + + if (!matched_fn) { + cJSON_Delete(schema); + return json_error_string_local("unknown tool"); + } + + cJSON* out = cJSON_CreateObject(); + if (!out) { + cJSON_Delete(schema); + return NULL; + } + + cJSON_AddBoolToObject(out, "success", 1); + cJSON_AddStringToObject(out, "mode", "slash_help_tool"); + cJSON_AddStringToObject(out, "tool", tool_name); + cJSON_AddItemToObject(out, "schema", cJSON_Duplicate(matched_fn, 1)); + + char* result = cJSON_PrintUnformatted(out); + cJSON_Delete(out); + cJSON_Delete(schema); + return result; +} + +static char* execute_slash_command_http(const char* message) { + if (!message || message[0] != '/') return NULL; + + const char* p = message + 1; + while (*p && !is_space_char_local(*p)) p++; + + size_t tool_len = (size_t)(p - (message + 1)); + if (tool_len == 0 || tool_len >= 128U) { + return strdup("{\"success\":false,\"error\":\"invalid slash command\"}"); + } + + char tool_name[128]; + memcpy(tool_name, message + 1, tool_len); + tool_name[tool_len] = '\0'; + + const char* raw_args = skip_spaces_local(p); + + char* result_json = NULL; + if (strcmp(tool_name, "help") == 0) { + if (!raw_args || raw_args[0] == '\0') { + result_json = build_slash_help_all_json_local(); + } else { + char help_tool[128]; + size_t i = 0; + while (raw_args[i] && !is_space_char_local(raw_args[i]) && i < sizeof(help_tool) - 1U) { + help_tool[i] = raw_args[i]; + i++; + } + help_tool[i] = '\0'; + result_json = build_slash_help_tool_json_local(help_tool); + } + } else { + char* args_json = build_slash_args_json_local(raw_args); + if (!args_json) { + result_json = json_error_string_local("failed to parse slash args"); + } else { + result_json = tools_execute(g_api_ctx.tools_ctx, tool_name, args_json); + free(args_json); + } + } + + if (!result_json) { + result_json = strdup("{\"success\":false,\"error\":\"direct tool execution failed\"}"); + } + + 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."); +} + +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; + + if (max_turns < 1) max_turns = 1; + if (max_turns > 16) max_turns = 16; + char* tools_json = tools_build_openai_schema_json(g_api_ctx.tools_ctx); if (!tools_json) { - cJSON_Delete(convo); return NULL; } @@ -265,7 +678,6 @@ static cJSON* run_prompt_with_tools(cJSON* body) { cJSON_Delete(root); cJSON_Delete(turns); free(tools_json); - cJSON_Delete(convo); return NULL; } @@ -275,6 +687,10 @@ static cJSON* run_prompt_with_tools(cJSON* body) { 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); @@ -340,7 +756,7 @@ static cJSON* run_prompt_with_tools(cJSON* body) { done: if (!final_response) { - final_response = strdup("I hit my tool-use limit for this prompt run."); + final_response = strdup(tool_limit_message ? tool_limit_message : "I hit my tool-use limit for this prompt run."); } cJSON_AddBoolToObject(root, "success", 1); @@ -365,6 +781,32 @@ done: free(final_response); free(tools_json); + return root; +} + +static cJSON* run_prompt_with_tools(cJSON* body) { + if (!body || !cJSON_IsObject(body)) return NULL; + + cJSON* messages_node = cJSON_GetObjectItemCaseSensitive(body, "messages"); + if (!messages_node || !cJSON_IsArray(messages_node)) return NULL; + + cJSON* convo = cJSON_Duplicate(messages_node, 1); + if (!convo || !cJSON_IsArray(convo)) { + cJSON_Delete(convo); + return NULL; + } + + int max_turns = 4; + cJSON* max_turns_node = cJSON_GetObjectItemCaseSensitive(body, "max_turns"); + if (max_turns_node && cJSON_IsNumber(max_turns_node)) { + max_turns = (int)max_turns_node->valuedouble; + } + + 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; } @@ -396,7 +838,7 @@ static void handle_status(struct mg_connection* c) { static void handle_context_current(struct mg_connection* c) { char* messages_json = NULL; - if (agent_build_admin_messages_json("", &messages_json) != 0 || !messages_json) { + if (agent_build_admin_messages_json("", DIDACTYL_SENDER_ADMIN, &messages_json) != 0 || !messages_json) { reply_error(c, 500, "failed to build context messages"); return; } @@ -531,6 +973,115 @@ static void handle_prompt_run(struct mg_connection* c, const struct mg_http_mess cJSON_Delete(result); } +static void handle_prompt_agent(struct mg_connection* c, const struct mg_http_message* hm) { + cJSON* body = parse_body_json(hm); + if (!body || !cJSON_IsObject(body)) { + cJSON_Delete(body); + reply_error(c, 400, "invalid JSON body"); + return; + } + + cJSON* message = cJSON_GetObjectItemCaseSensitive(body, "message"); + if (!message || !cJSON_IsString(message) || !message->valuestring) { + cJSON_Delete(body); + reply_error(c, 400, "message string is required"); + return; + } + + char* slash_result = execute_slash_command_http(message->valuestring); + if (slash_result) { + cJSON* out = cJSON_CreateObject(); + cJSON* turns = cJSON_CreateArray(); + if (!out || !turns) { + free(slash_result); + cJSON_Delete(out); + cJSON_Delete(turns); + cJSON_Delete(body); + reply_error(c, 500, "oom"); + return; + } + cJSON_AddBoolToObject(out, "success", 1); + cJSON_AddStringToObject(out, "final_response", slash_result); + cJSON_AddItemToObject(out, "turns", turns); + cJSON_AddNumberToObject(out, "total_input_tokens_estimate", estimate_tokens_from_chars((int)strlen(message->valuestring))); + cJSON_AddNumberToObject(out, "total_output_tokens_estimate", estimate_tokens_from_chars((int)strlen(slash_result))); + free(slash_result); + cJSON_Delete(body); + reply_json(c, 200, out); + cJSON_Delete(out); + return; + } + + int max_turns = 4; + cJSON* max_turns_node = cJSON_GetObjectItemCaseSensitive(body, "max_turns"); + if (max_turns_node && cJSON_IsNumber(max_turns_node)) { + max_turns = (int)max_turns_node->valuedouble; + } + + llm_config_t old_cfg; + int overridden = 0; + char* model_err = maybe_model_override_begin(body, &old_cfg, &overridden); + if (model_err) { + cJSON_Delete(body); + reply_error(c, 400, model_err); + return; + } + + char* base_messages_json = NULL; + cJSON* convo = NULL; + cJSON* result = NULL; + + if (agent_build_admin_messages_json(message->valuestring, + DIDACTYL_SENDER_ADMIN, + &base_messages_json) != 0 || !base_messages_json) { + maybe_model_override_end(&old_cfg, overridden); + cJSON_Delete(body); + reply_error(c, 500, "failed to build agent context"); + return; + } + + convo = cJSON_Parse(base_messages_json); + free(base_messages_json); + if (!convo || !cJSON_IsArray(convo)) { + cJSON_Delete(convo); + maybe_model_override_end(&old_cfg, overridden); + cJSON_Delete(body); + reply_error(c, 500, "failed to initialize conversation messages"); + return; + } + + if (append_simple_message_local(convo, "user", message->valuestring) != 0) { + cJSON_Delete(convo); + maybe_model_override_end(&old_cfg, overridden); + cJSON_Delete(body); + reply_error(c, 500, "failed to append user message"); + return; + } + + cJSON* live_user_msg = cJSON_GetArrayItem(convo, cJSON_GetArraySize(convo) - 1); + if (live_user_msg && cJSON_IsObject(live_user_msg)) { + cJSON_AddNumberToObject(live_user_msg, "_ts", (double)time(NULL)); + } + + 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); + + maybe_model_override_end(&old_cfg, overridden); + cJSON_Delete(body); + + if (!result) { + reply_error(c, 500, "failed to run prompt"); + return; + } + + reply_json(c, 200, result); + cJSON_Delete(result); +} + static cJSON* run_variant(cJSON* variant) { if (!variant || !cJSON_IsObject(variant)) return NULL; cJSON* body_copy = cJSON_Duplicate(variant, 1); @@ -601,6 +1152,17 @@ static void handle_prompt_compare(struct mg_connection* c, const struct mg_http_ } static void http_handler(struct mg_connection* c, int ev, void* ev_data) { + if (ev == MG_EV_ACCEPT) { + if (g_api_tls_enabled) { + struct mg_tls_opts opts; + memset(&opts, 0, sizeof(opts)); + opts.cert = g_tls_cert; + opts.key = g_tls_key; + mg_tls_init(c, &opts); + } + return; + } + if (ev != MG_EV_HTTP_MSG) { return; } @@ -632,6 +1194,10 @@ static void http_handler(struct mg_connection* c, int ev, void* ev_data) { handle_prompt_run(c, hm); return; } + if (method_is(hm, "POST") && mg_match(hm->uri, mg_str("/api/prompt/agent"), NULL)) { + handle_prompt_agent(c, hm); + return; + } if (method_is(hm, "POST") && mg_match(hm->uri, mg_str("/api/prompt/compare"), NULL)) { handle_prompt_compare(c, hm); return; @@ -649,10 +1215,25 @@ int http_api_init(const http_api_context_t* ctx) { mg_mgr_init(&g_mgr); + g_api_tls_enabled = 0; + g_tls_cert = mg_file_read(&mg_fs_posix, "/home/teknari/.ssl_for_local_servers/cert.pem"); + g_tls_key = mg_file_read(&mg_fs_posix, "/home/teknari/.ssl_for_local_servers/key.pem"); + if (g_tls_cert.buf && g_tls_cert.len > 0 && g_tls_key.buf && g_tls_key.len > 0) { + g_api_tls_enabled = 1; + } else { + if (g_tls_cert.buf) mg_free((void*)g_tls_cert.buf); + if (g_tls_key.buf) mg_free((void*)g_tls_key.buf); + g_tls_cert.buf = NULL; + g_tls_cert.len = 0; + g_tls_key.buf = NULL; + g_tls_key.len = 0; + } + char listen_url[320]; snprintf(listen_url, sizeof(listen_url), - "http://%s:%d", + "%s://%s:%d", + g_api_tls_enabled ? "https" : "http", g_api_ctx.cfg->api.bind_address[0] ? g_api_ctx.cfg->api.bind_address : "127.0.0.1", g_api_ctx.cfg->api.port > 0 ? g_api_ctx.cfg->api.port : 8484); @@ -686,6 +1267,15 @@ void http_api_cleanup(void) { mg_mgr_free(&g_mgr); memset(&g_mgr, 0, sizeof(g_mgr)); g_listener = NULL; + + if (g_tls_cert.buf) mg_free((void*)g_tls_cert.buf); + if (g_tls_key.buf) mg_free((void*)g_tls_key.buf); + g_tls_cert.buf = NULL; + g_tls_cert.len = 0; + g_tls_key.buf = NULL; + g_tls_key.len = 0; + g_api_tls_enabled = 0; + memset(&g_api_ctx, 0, sizeof(g_api_ctx)); g_api_initialized = 0; } \ No newline at end of file diff --git a/src/llm.c b/src/llm.c index 55d867e..ab21fb8 100644 --- a/src/llm.c +++ b/src/llm.c @@ -64,6 +64,11 @@ static const char* detect_ca_bundle_path(void) { return NULL; } +static int url_looks_like_websocket(const char* url) { + if (!url) return 0; + return (strncmp(url, "ws://", 5) == 0) || (strncmp(url, "wss://", 6) == 0); +} + static char* perform_http_request(const char* url, const char* body, int is_post) { CURL* curl = curl_easy_init(); if (!curl || !url) { @@ -71,6 +76,16 @@ static char* perform_http_request(const char* url, const char* body, int is_post return NULL; } + if (url_looks_like_websocket(url)) { + fprintf(stderr, + "[didactyl] llm config error: base_url must be HTTP(S), got WebSocket URL: %s\n", + url); + fprintf(stderr, + "[didactyl] llm hint: set llm.base_url to an OpenAI-compatible HTTPS endpoint, e.g. https://api.example.com/v1\n"); + curl_easy_cleanup(curl); + return NULL; + } + response_buffer_t rb = {0}; struct curl_slist* headers = NULL; @@ -116,6 +131,10 @@ static char* perform_http_request(const char* url, const char* body, int is_post if (status < 200 || status >= 300) { fprintf(stderr, "[didactyl] llm http request failed: status=%ld\n", status); + if (status == 101) { + fprintf(stderr, + "[didactyl] llm hint: received HTTP 101 (Switching Protocols), which usually means llm.base_url points to a WebSocket server instead of an HTTP LLM API\n"); + } if (rb.data && rb.len > 0) { fprintf(stderr, "[didactyl] llm error response: %.1200s%s\n", rb.data, diff --git a/src/main.c b/src/main.c index 6f94431..c905255 100644 --- a/src/main.c +++ b/src/main.c @@ -252,7 +252,7 @@ int main(int argc, char** argv) { DIDACTYL_VERSION, connected_relays, cfg.relay_count); - if (nostr_handler_send_dm(cfg.admin.pubkey, startup_dm) != 0) { + if (nostr_handler_send_dm_auto(cfg.admin.pubkey, startup_dm) != 0) { DEBUG_WARN("[didactyl] startup phase: failed to send startup status DM to admin"); } diff --git a/src/main.h b/src/main.h index 397ec11..abc12eb 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 29 -#define DIDACTYL_VERSION "v0.0.29" +#define DIDACTYL_VERSION_PATCH 30 +#define DIDACTYL_VERSION "v0.0.30" // Agent metadata #define DIDACTYL_NAME "Didactyl" diff --git a/src/mongoose.c b/src/mongoose.c index 8391089..113d7af 100644 --- a/src/mongoose.c +++ b/src/mongoose.c @@ -14553,7 +14553,7 @@ static int mg_rsa_parse_der_int(const uint8_t **p, const uint8_t *end, // } static int mg_parse_ec_private_key(const uint8_t *der, size_t dersz, uint8_t *ec_key) { - struct mg_der_tlv root, version, private_key_octets; + struct mg_der_tlv root = {0}, version = {0}, private_key_octets = {0}; if (mg_der_parse((uint8_t *) der, dersz, &root) < 0 || root.type != 0x30) { MG_ERROR(("EC private key: invalid SEQUENCE")); @@ -14725,8 +14725,8 @@ static int mg_rsa_parse_key(const uint8_t *der, size_t dersz, // } static int mg_parse_pkcs8_key(const uint8_t *der, size_t dersz, struct tls_data *tls) { - struct mg_der_tlv root, version, alg_id, private_key_octets; - struct mg_der_tlv alg_oid, alg_params; + struct mg_der_tlv root = {0}, version = {0}, alg_id = {0}, private_key_octets = {0}; + struct mg_der_tlv alg_oid = {0}, alg_params = {0}; if (mg_der_parse((uint8_t *) der, dersz, &root) < 0 || root.type != 0x30) { MG_ERROR(("PKCS#8: invalid PrivateKeyInfo SEQUENCE")); diff --git a/src/nostr_handler.c b/src/nostr_handler.c index af42f2a..4d18d24 100644 --- a/src/nostr_handler.c +++ b/src/nostr_handler.c @@ -51,6 +51,17 @@ static int g_seen_dm_next = 0; static pthread_mutex_t g_dm_dedup_mutex = PTHREAD_MUTEX_INITIALIZER; +#define SENDER_PROTOCOL_CACHE_SIZE 128 + +typedef struct { + char pubkey_hex[65]; + dm_protocol_t protocol; + time_t seen_at; +} sender_protocol_entry_t; + +static sender_protocol_entry_t g_sender_protocol_cache[SENDER_PROTOCOL_CACHE_SIZE]; +static pthread_mutex_t g_sender_protocol_mutex = PTHREAD_MUTEX_INITIALIZER; + static int dm_id_seen_or_remember(const char* event_id_hex) { if (!event_id_hex || strlen(event_id_hex) != 64U) { return 0; @@ -84,6 +95,88 @@ static int dm_id_seen_or_remember(const char* event_id_hex) { return seen; } +static didactyl_sender_tier_t sender_tier_from_pubkey(const char* sender_pubkey_hex) { + if (!g_cfg || !sender_pubkey_hex) { + return DIDACTYL_SENDER_STRANGER; + } + + if (strcmp(sender_pubkey_hex, g_cfg->admin.pubkey) == 0) { + return DIDACTYL_SENDER_ADMIN; + } + + if (g_cfg->security.wot.enabled && nostr_handler_is_wot_contact(sender_pubkey_hex)) { + return DIDACTYL_SENDER_WOT; + } + + return DIDACTYL_SENDER_STRANGER; +} + +static void sender_protocol_remember(const char* sender_pubkey_hex, dm_protocol_t protocol) { + if (!sender_pubkey_hex || strlen(sender_pubkey_hex) != 64U) { + return; + } + + if (protocol != DM_PROTOCOL_NIP04 && protocol != DM_PROTOCOL_NIP17) { + return; + } + + pthread_mutex_lock(&g_sender_protocol_mutex); + + int slot = -1; + time_t oldest_time = 0; + int oldest_idx = 0; + + for (int i = 0; i < SENDER_PROTOCOL_CACHE_SIZE; i++) { + if (g_sender_protocol_cache[i].pubkey_hex[0] == '\0') { + slot = i; + break; + } + + if (strncmp(g_sender_protocol_cache[i].pubkey_hex, sender_pubkey_hex, 64U) == 0) { + slot = i; + break; + } + + if (i == 0 || g_sender_protocol_cache[i].seen_at < oldest_time) { + oldest_time = g_sender_protocol_cache[i].seen_at; + oldest_idx = i; + } + } + + if (slot < 0) { + slot = oldest_idx; + } + + memcpy(g_sender_protocol_cache[slot].pubkey_hex, sender_pubkey_hex, 64U); + g_sender_protocol_cache[slot].pubkey_hex[64] = '\0'; + g_sender_protocol_cache[slot].protocol = protocol; + g_sender_protocol_cache[slot].seen_at = time(NULL); + + pthread_mutex_unlock(&g_sender_protocol_mutex); +} + +static dm_protocol_t sender_protocol_lookup(const char* sender_pubkey_hex) { + if (!sender_pubkey_hex || strlen(sender_pubkey_hex) != 64U) { + return DM_PROTOCOL_NIP04; + } + + dm_protocol_t out = DM_PROTOCOL_NIP04; + + pthread_mutex_lock(&g_sender_protocol_mutex); + for (int i = 0; i < SENDER_PROTOCOL_CACHE_SIZE; i++) { + if (g_sender_protocol_cache[i].pubkey_hex[0] == '\0') { + continue; + } + if (strncmp(g_sender_protocol_cache[i].pubkey_hex, sender_pubkey_hex, 64U) == 0) { + out = g_sender_protocol_cache[i].protocol; + break; + } + } + pthread_mutex_unlock(&g_sender_protocol_mutex); + + return out; +} + static const char* relay_status_str(nostr_pool_relay_status_t status) { switch (status) { case NOSTR_POOL_RELAY_DISCONNECTED: @@ -109,6 +202,7 @@ static int publish_kind_event_to_relays(int kind, nostr_publish_result_t* out_result); static void on_admin_context_event(cJSON* event, const char* relay_url, void* user_data); static int parse_kind3_wot_contacts(cJSON* tags); +static int parse_kind10002_relays(cJSON* tags); static void upsert_kind1_note(time_t created_at, const char* content); static int startup_self_kind1_exists(void); static void load_startup_display_name(void); @@ -461,93 +555,162 @@ static void on_event(cJSON* event, const char* relay_url, void* user_data) { const char* event_id_hex = (id && cJSON_IsString(id) && id->valuestring && strlen(id->valuestring) == 64U) ? id->valuestring : NULL; + int kind_val = (int)kind->valuedouble; DEBUG_TRACE("[didactyl] DEBUG on_event: kind=%d id=%.16s... from=%.16s... via %s", - (int)kind->valuedouble, + kind_val, event_id_hex ? event_id_hex : "", pubkey->valuestring ? pubkey->valuestring : "", relay_url ? relay_url : "unknown"); - if ((int)kind->valuedouble != 4) { - DEBUG_TRACE("[didactyl] DEBUG on_event: ignoring non-kind4 (kind=%d)", (int)kind->valuedouble); - return; - } + char sender_pubkey_hex[65] = {0}; + char* decrypted = NULL; + const char* dedup_id_hex = event_id_hex; + dm_protocol_t received_protocol = DM_PROTOCOL_NIP04; - char recipient_pubkey_hex[65] = {0}; - if (extract_first_p_tag(tags, recipient_pubkey_hex) != 0) { - DEBUG_TRACE("[didactyl] DEBUG on_event: no p-tag found in kind4 event %.16s...", - event_id_hex ? event_id_hex : ""); - return; - } - - if (strcmp(recipient_pubkey_hex, g_cfg->keys.public_key_hex) != 0) { - DEBUG_TRACE("[didactyl] DEBUG on_event: p-tag mismatch (got=%.16s... want=%.16s...)", - recipient_pubkey_hex, g_cfg->keys.public_key_hex); - return; - } - - didactyl_sender_tier_t tier = DIDACTYL_SENDER_STRANGER; - if (strcmp(pubkey->valuestring, g_cfg->admin.pubkey) == 0) { - tier = DIDACTYL_SENDER_ADMIN; - } else if (g_cfg->security.wot.enabled && nostr_handler_is_wot_contact(pubkey->valuestring)) { - tier = DIDACTYL_SENDER_WOT; - } - - DEBUG_TRACE("[didactyl] DEBUG on_event: sender=%.16s... tier=%d (admin=%.16s...)", - pubkey->valuestring, (int)tier, g_cfg->admin.pubkey); - - if (tier == DIDACTYL_SENDER_STRANGER) { - if (!g_cfg->security.stranger.enabled) { - DEBUG_LOG("[didactyl] ignored DM from stranger %.16s... via %s", - pubkey->valuestring, - relay_url ? relay_url : "unknown relay"); + if (kind_val == 4) { + if (g_cfg->dm_protocol == DM_PROTOCOL_NIP17) { + DEBUG_TRACE("[didactyl] DEBUG on_event: ignoring kind4 in dm_protocol=nip17 mode"); return; } - if (g_cfg->security.stranger_response[0] != '\0') { - (void)nostr_handler_send_dm(pubkey->valuestring, g_cfg->security.stranger_response); + char recipient_pubkey_hex[65] = {0}; + if (extract_first_p_tag(tags, recipient_pubkey_hex) != 0) { + DEBUG_TRACE("[didactyl] DEBUG on_event: no p-tag found in kind4 event %.16s...", + event_id_hex ? event_id_hex : ""); + return; } + + if (strcmp(recipient_pubkey_hex, g_cfg->keys.public_key_hex) != 0) { + DEBUG_TRACE("[didactyl] DEBUG on_event: p-tag mismatch (got=%.16s... want=%.16s...)", + recipient_pubkey_hex, g_cfg->keys.public_key_hex); + return; + } + + memcpy(sender_pubkey_hex, pubkey->valuestring, 65U); + + unsigned char sender_pubkey[32]; + if (hex_to_pubkey(sender_pubkey_hex, sender_pubkey) != 0) { + return; + } + + decrypted = (char*)malloc(NOSTR_NIP04_MAX_PLAINTEXT_SIZE); + if (!decrypted) { + fprintf(stderr, "[didactyl] failed to allocate DM decrypt buffer\n"); + return; + } + decrypted[0] = '\0'; + + trace_event_json("received encrypted DM event:", event); + + if (nostr_nip04_decrypt(g_cfg->keys.private_key, sender_pubkey, content->valuestring, decrypted, NOSTR_NIP04_MAX_PLAINTEXT_SIZE) != NOSTR_SUCCESS) { + fprintf(stdout, "[didactyl] failed to decrypt incoming DM from %.16s...\n", sender_pubkey_hex); + free(decrypted); + return; + } + + trace_plaintext_dm("received decrypted DM content:", decrypted); + received_protocol = DM_PROTOCOL_NIP04; + } else if (kind_val == 1059) { + if (g_cfg->dm_protocol == DM_PROTOCOL_NIP04) { + DEBUG_TRACE("[didactyl] DEBUG on_event: ignoring kind1059 in dm_protocol=nip04 mode"); + return; + } + + cJSON* rumor = nostr_nip17_receive_dm(event, g_cfg->keys.private_key); + if (!rumor) { + DEBUG_TRACE("[didactyl] DEBUG on_event: failed to unwrap/decrypt NIP-17 gift wrap %.16s...", + event_id_hex ? event_id_hex : ""); + return; + } + + cJSON* rumor_id = cJSON_GetObjectItemCaseSensitive(rumor, "id"); + cJSON* rumor_kind = cJSON_GetObjectItemCaseSensitive(rumor, "kind"); + cJSON* rumor_pubkey = cJSON_GetObjectItemCaseSensitive(rumor, "pubkey"); + cJSON* rumor_content = cJSON_GetObjectItemCaseSensitive(rumor, "content"); + + if (!rumor_kind || !rumor_pubkey || !rumor_content || + !cJSON_IsNumber(rumor_kind) || !cJSON_IsString(rumor_pubkey) || !cJSON_IsString(rumor_content) || + !rumor_pubkey->valuestring || strlen(rumor_pubkey->valuestring) != 64U) { + cJSON_Delete(rumor); + return; + } + + int rumor_kind_val = (int)rumor_kind->valuedouble; + if (rumor_kind_val != 14 && rumor_kind_val != 15 && rumor_kind_val != 7) { + DEBUG_TRACE("[didactyl] DEBUG on_event: ignoring NIP-17 rumor kind=%d", rumor_kind_val); + cJSON_Delete(rumor); + return; + } + + memcpy(sender_pubkey_hex, rumor_pubkey->valuestring, 65U); + + const char* rumor_id_hex = (rumor_id && cJSON_IsString(rumor_id) && rumor_id->valuestring && strlen(rumor_id->valuestring) == 64U) + ? rumor_id->valuestring + : NULL; + if (rumor_id_hex) { + dedup_id_hex = rumor_id_hex; + } + + decrypted = strdup(rumor_content->valuestring ? rumor_content->valuestring : ""); + cJSON_Delete(rumor); + if (!decrypted) { + return; + } + + trace_plaintext_dm("received NIP-17 DM content:", decrypted); + received_protocol = DM_PROTOCOL_NIP17; + } else { + DEBUG_TRACE("[didactyl] DEBUG on_event: ignoring unsupported kind=%d", kind_val); return; } - unsigned char sender_pubkey[32]; - if (hex_to_pubkey(pubkey->valuestring, sender_pubkey) != 0) { - return; - } - - char* decrypted = (char*)malloc(NOSTR_NIP04_MAX_PLAINTEXT_SIZE); - if (!decrypted) { - fprintf(stderr, "[didactyl] failed to allocate DM decrypt buffer\n"); - return; - } - decrypted[0] = '\0'; - - trace_event_json("received encrypted DM event:", event); - - if (nostr_nip04_decrypt(g_cfg->keys.private_key, sender_pubkey, content->valuestring, decrypted, NOSTR_NIP04_MAX_PLAINTEXT_SIZE) != NOSTR_SUCCESS) { - fprintf(stdout, "[didactyl] failed to decrypt incoming DM from %.16s...\n", pubkey->valuestring); + if (!sender_pubkey_hex[0] || strlen(sender_pubkey_hex) != 64U) { free(decrypted); return; } - trace_plaintext_dm("received decrypted DM content:", decrypted); + didactyl_sender_tier_t tier = sender_tier_from_pubkey(sender_pubkey_hex); - if (event_id_hex && dm_id_seen_or_remember(event_id_hex)) { + DEBUG_TRACE("[didactyl] DEBUG on_event: sender=%.16s... tier=%d (admin=%.16s...)", + sender_pubkey_hex, (int)tier, g_cfg->admin.pubkey); + + sender_protocol_remember(sender_pubkey_hex, received_protocol); + + if (tier == DIDACTYL_SENDER_STRANGER) { + if (!g_cfg->security.stranger.enabled) { + DEBUG_LOG("[didactyl] ignored DM from stranger %.16s... via %s", + sender_pubkey_hex, + relay_url ? relay_url : "unknown relay"); + free(decrypted); + return; + } + + if (g_cfg->security.stranger_response[0] != '\0') { + (void)nostr_handler_send_dm_auto(sender_pubkey_hex, g_cfg->security.stranger_response); + } + free(decrypted); + return; + } + + if (dedup_id_hex && dm_id_seen_or_remember(dedup_id_hex)) { DEBUG_LOG("[didactyl] skipped duplicate DM event %.16s... from %.16s... via %s", - event_id_hex, - pubkey->valuestring, + dedup_id_hex, + sender_pubkey_hex, relay_url ? relay_url : "unknown relay"); free(decrypted); return; } - DEBUG_INFO("[didactyl] received kind %d event %.16s... from %.16s... via %s tier=%d", - (int)kind->valuedouble, - event_id_hex ? event_id_hex : "", - pubkey->valuestring, + DEBUG_INFO("[didactyl] received kind %d event %.16s... from %.16s... via %s tier=%d protocol=%s", + kind_val, + dedup_id_hex ? dedup_id_hex : "", + sender_pubkey_hex, relay_url ? relay_url : "unknown relay", - (int)tier); - g_dm_callback(pubkey->valuestring, decrypted, tier, g_dm_user_data); + (int)tier, + received_protocol == DM_PROTOCOL_NIP17 ? "nip17" : "nip04"); + + g_dm_callback(sender_pubkey_hex, decrypted, tier, g_dm_user_data); free(decrypted); } @@ -631,6 +794,49 @@ static int parse_kind3_wot_contacts(cJSON* tags) { return 0; } +static int parse_kind10002_relays(cJSON* tags) { + if (!tags || !cJSON_IsArray(tags)) { + free(g_admin_kind10002_json); + g_admin_kind10002_json = strdup("[]"); + return g_admin_kind10002_json ? 0 : -1; + } + + cJSON* relays = cJSON_CreateArray(); + if (!relays) { + return -1; + } + + int n = cJSON_GetArraySize(tags); + for (int i = 0; i < n; i++) { + cJSON* tag = cJSON_GetArrayItem(tags, i); + if (!tag || !cJSON_IsArray(tag) || cJSON_GetArraySize(tag) < 2) { + continue; + } + + cJSON* key = cJSON_GetArrayItem(tag, 0); + cJSON* val = cJSON_GetArrayItem(tag, 1); + if (!key || !val || !cJSON_IsString(key) || !cJSON_IsString(val) || !key->valuestring || !val->valuestring) { + continue; + } + + if (strcmp(key->valuestring, "r") != 0 || val->valuestring[0] == '\0') { + continue; + } + + cJSON_AddItemToArray(relays, cJSON_CreateString(val->valuestring)); + } + + char* relays_json = cJSON_PrintUnformatted(relays); + cJSON_Delete(relays); + if (!relays_json) { + return -1; + } + + free(g_admin_kind10002_json); + g_admin_kind10002_json = relays_json; + return 0; +} + static void upsert_kind1_note(time_t created_at, const char* content) { if (!content) { return; @@ -702,9 +908,8 @@ static void on_admin_context_event(cJSON* event, const char* relay_url, void* us g_admin_kind0_json = strdup(content->valuestring); } else if (k == 3 && g_cfg->admin_context.track_kind_3 && tags && cJSON_IsArray(tags)) { (void)parse_kind3_wot_contacts(tags); - } else if (k == 10002 && g_cfg->admin_context.track_kind_10002 && content && cJSON_IsString(content) && content->valuestring) { - free(g_admin_kind10002_json); - g_admin_kind10002_json = strdup(content->valuestring); + } else if (k == 10002 && g_cfg->admin_context.track_kind_10002 && tags && cJSON_IsArray(tags)) { + (void)parse_kind10002_relays(tags); } else if (k == 1 && g_cfg->admin_context.track_kind_1 && content && cJSON_IsString(content) && content->valuestring) { time_t ts = (created_at && cJSON_IsNumber(created_at)) ? (time_t)created_at->valuedouble : time(NULL); upsert_kind1_note(ts, content->valuestring); @@ -882,7 +1087,12 @@ int nostr_handler_subscribe_dms(dm_callback_t callback, void* user_data) { return -1; } - cJSON_AddItemToArray(kinds, cJSON_CreateNumber(4)); + if (g_cfg->dm_protocol == DM_PROTOCOL_NIP04 || g_cfg->dm_protocol == DM_PROTOCOL_BOTH) { + cJSON_AddItemToArray(kinds, cJSON_CreateNumber(4)); + } + if (g_cfg->dm_protocol == DM_PROTOCOL_NIP17 || g_cfg->dm_protocol == DM_PROTOCOL_BOTH) { + cJSON_AddItemToArray(kinds, cJSON_CreateNumber(1059)); + } cJSON_AddItemToObject(filter, "kinds", kinds); cJSON_AddItemToArray(p_values, cJSON_CreateString(g_cfg->keys.public_key_hex)); cJSON_AddItemToObject(filter, "#p", p_values); @@ -1064,6 +1274,36 @@ int nostr_handler_send_dm(const char* recipient_pubkey_hex, const char* message) return sent > 0 ? 0 : -1; } +int nostr_handler_send_dm_auto(const char* recipient_pubkey_hex, const char* message) { + if (!recipient_pubkey_hex || !message) { + return -1; + } + + if (!g_cfg) { + return nostr_handler_send_dm(recipient_pubkey_hex, message); + } + + if (g_cfg->dm_protocol == DM_PROTOCOL_NIP04) { + return nostr_handler_send_dm(recipient_pubkey_hex, message); + } + + if (g_cfg->dm_protocol == DM_PROTOCOL_NIP17) { + return nostr_handler_send_dm_nip17(recipient_pubkey_hex, message, NULL); + } + + dm_protocol_t target = sender_protocol_lookup(recipient_pubkey_hex); + if (target == DM_PROTOCOL_NIP17) { + int rc17 = nostr_handler_send_dm_nip17(recipient_pubkey_hex, message, NULL); + if (rc17 == 0) { + return 0; + } + DEBUG_WARN("[didactyl] auto DM fallback to NIP-04 for %.16s... after NIP-17 send failure", + recipient_pubkey_hex); + } + + return nostr_handler_send_dm(recipient_pubkey_hex, message); +} + static int publish_kind_event_to_relays(int kind, const char* content, cJSON* tags, @@ -1789,6 +2029,10 @@ void nostr_handler_cleanup(void) { g_seen_dm_count = 0; g_seen_dm_next = 0; + pthread_mutex_lock(&g_sender_protocol_mutex); + memset(g_sender_protocol_cache, 0, sizeof(g_sender_protocol_cache)); + pthread_mutex_unlock(&g_sender_protocol_mutex); + pthread_mutex_lock(&g_admin_ctx_mutex); free_admin_context_locked(); pthread_mutex_unlock(&g_admin_ctx_mutex); diff --git a/src/nostr_handler.h b/src/nostr_handler.h index ee853f1..1f6597d 100644 --- a/src/nostr_handler.h +++ b/src/nostr_handler.h @@ -31,6 +31,7 @@ int nostr_handler_init(didactyl_config_t* config); int nostr_handler_subscribe_admin_context(void); 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); int nostr_handler_publish_kind_event(int kind, const char* content, cJSON* tags, nostr_publish_result_t* out_result); void nostr_handler_publish_result_free(nostr_publish_result_t* result); char* nostr_handler_query_json(cJSON* filter, int timeout_ms); diff --git a/src/prompt_template.c b/src/prompt_template.c index 1cf54d3..d218e02 100644 --- a/src/prompt_template.c +++ b/src/prompt_template.c @@ -179,6 +179,7 @@ static void init_section_defaults(prompt_template_section_t* sec) { snprintf(sec->role, sizeof(sec->role), "system"); sec->content_template = NULL; sec->limit = 0; + sec->skip_if_empty = 0; sec->provider_name = NULL; sec->provider_content_template = NULL; } @@ -281,6 +282,16 @@ int prompt_template_parse(const char* soul_content, prompt_template_t* out_templ continue; } + if (starts_with(line, "skip_if_empty:")) { + char* flag = line + strlen("skip_if_empty:"); + flag = ltrim_inplace(flag); + rtrim_inplace(flag); + out_template->sections[current].skip_if_empty = + (strcmp(flag, "true") == 0 || strcmp(flag, "1") == 0) ? 1 : 0; + i++; + continue; + } + if (starts_with(line, "content:")) { char* val = line + strlen("content:"); val = ltrim_inplace(val); @@ -307,6 +318,7 @@ int prompt_template_parse(const char* soul_content, prompt_template_t* out_templ if (starts_with(next_ltrim, "- section:") || starts_with(next_ltrim, "role:") || starts_with(next_ltrim, "limit:") || + starts_with(next_ltrim, "skip_if_empty:") || starts_with(next_ltrim, "content:") || starts_with(next_ltrim, "provider:")) { break; @@ -378,6 +390,7 @@ int prompt_template_parse(const char* soul_content, prompt_template_t* out_templ if (starts_with(next, "- section:") || starts_with(next, "role:") || starts_with(next, "limit:") || + starts_with(next, "skip_if_empty:") || starts_with(next, "content:") || starts_with(next, "provider:")) { break; @@ -486,6 +499,14 @@ cJSON* prompt_template_build_messages(const prompt_template_t* tmpl, return NULL; } + if (sec->skip_if_empty) { + char* chk = ltrim_inplace(resolved); + if (chk && *chk == '\0') { + free(resolved); + continue; + } + } + if (append_message_object(out, role, resolved) != 0) { free(resolved); cJSON_Delete(out); @@ -515,6 +536,7 @@ void prompt_template_free(prompt_template_t* tmpl) { tmpl->sections[i].name[0] = '\0'; tmpl->sections[i].role[0] = '\0'; tmpl->sections[i].limit = 0; + tmpl->sections[i].skip_if_empty = 0; } tmpl->section_count = 0; diff --git a/src/prompt_template.h b/src/prompt_template.h index c69e1b9..1634a2f 100644 --- a/src/prompt_template.h +++ b/src/prompt_template.h @@ -13,6 +13,7 @@ typedef struct { char role[PROMPT_TEMPLATE_MAX_ROLE_LEN]; char* content_template; int limit; + int skip_if_empty; char* provider_name; char* provider_content_template; } prompt_template_section_t; diff --git a/src/tools.c b/src/tools.c index 8786b12..dcd309f 100644 --- a/src/tools.c +++ b/src/tools.c @@ -596,6 +596,120 @@ static cJSON* parse_tool_args_json(const char* args_json) { return args; } +static cJSON* tasks_create_empty_root(void) { + cJSON* root = cJSON_CreateObject(); + cJSON* tasks = cJSON_CreateArray(); + if (!root || !tasks) { + cJSON_Delete(root); + cJSON_Delete(tasks); + return NULL; + } + cJSON_AddItemToObject(root, "tasks", tasks); + cJSON_AddNumberToObject(root, "next_id", 1); + return root; +} + +static const char* normalize_task_status(const char* status) { + if (!status) return NULL; + if (strcmp(status, "pending") == 0) return "pending"; + if (strcmp(status, "active") == 0) return "active"; + if (strcmp(status, "done") == 0) return "done"; + return NULL; +} + +static cJSON* tasks_load_root(const char* path) { + if (!path) return NULL; + + FILE* fp = fopen(path, "rb"); + if (!fp) { + if (access(path, F_OK) == 0) { + return NULL; + } + return tasks_create_empty_root(); + } + + if (fseek(fp, 0, SEEK_END) != 0) { + fclose(fp); + return NULL; + } + long len = ftell(fp); + if (len < 0) { + fclose(fp); + return NULL; + } + if (fseek(fp, 0, SEEK_SET) != 0) { + fclose(fp); + return NULL; + } + + char* buf = (char*)malloc((size_t)len + 1U); + if (!buf) { + fclose(fp); + return NULL; + } + + size_t n = fread(buf, 1, (size_t)len, fp); + fclose(fp); + if (n != (size_t)len) { + free(buf); + return NULL; + } + buf[len] = '\0'; + + cJSON* root = cJSON_Parse(buf); + free(buf); + if (!root || !cJSON_IsObject(root)) { + cJSON_Delete(root); + return NULL; + } + + cJSON* tasks = cJSON_GetObjectItemCaseSensitive(root, "tasks"); + if (!tasks || !cJSON_IsArray(tasks)) { + cJSON_DeleteItemFromObjectCaseSensitive(root, "tasks"); + cJSON_AddItemToObject(root, "tasks", cJSON_CreateArray()); + } + + cJSON* next_id = cJSON_GetObjectItemCaseSensitive(root, "next_id"); + if (!next_id || !cJSON_IsNumber(next_id) || next_id->valuedouble < 1) { + cJSON_DeleteItemFromObjectCaseSensitive(root, "next_id"); + cJSON_AddNumberToObject(root, "next_id", 1); + } + + return root; +} + +static int tasks_save_root(const char* path, cJSON* root) { + if (!path || !root) return -1; + char* raw = cJSON_PrintUnformatted(root); + if (!raw) return -1; + + FILE* fp = fopen(path, "wb"); + if (!fp) { + free(raw); + return -1; + } + + size_t len = strlen(raw); + size_t n = fwrite(raw, 1, len, fp); + fclose(fp); + free(raw); + return (n == len) ? 0 : -1; +} + +static cJSON* task_find_by_id(cJSON* tasks, int id, int* out_index) { + if (!tasks || !cJSON_IsArray(tasks) || id <= 0) return NULL; + int n = cJSON_GetArraySize(tasks); + for (int i = 0; i < n; i++) { + cJSON* task = cJSON_GetArrayItem(tasks, i); + cJSON* tid = task ? cJSON_GetObjectItemCaseSensitive(task, "id") : NULL; + if (tid && cJSON_IsNumber(tid) && (int)tid->valuedouble == id) { + if (out_index) *out_index = i; + return task; + } + } + return NULL; +} + typedef struct { char* data; size_t len; @@ -1841,6 +1955,61 @@ char* tools_build_openai_schema_json(const tools_context_t* ctx) { cJSON_AddItemToObject(t36, "function", t36_fn); cJSON_AddItemToArray(tools, t36); + cJSON* t37 = cJSON_CreateObject(); + cJSON* t37_fn = cJSON_CreateObject(); + cJSON* t37_params = cJSON_CreateObject(); + cJSON* t37_props = cJSON_CreateObject(); + cJSON* t37_required = cJSON_CreateArray(); + + cJSON_AddStringToObject(t37, "type", "function"); + cJSON_AddStringToObject(t37_fn, "name", "task_manage"); + cJSON_AddStringToObject(t37_fn, "description", "Manage agent short-term task memory stored in tasks.json (list/add/update/remove/clear/replace)"); + cJSON_AddStringToObject(t37_params, "type", "object"); + cJSON_AddItemToObject(t37_params, "properties", t37_props); + cJSON_AddItemToObject(t37_params, "required", t37_required); + + cJSON* p_task_action = cJSON_CreateObject(); + cJSON_AddStringToObject(p_task_action, "type", "string"); + cJSON* p_task_action_enum = cJSON_CreateArray(); + cJSON_AddItemToArray(p_task_action_enum, cJSON_CreateString("list")); + cJSON_AddItemToArray(p_task_action_enum, cJSON_CreateString("add")); + cJSON_AddItemToArray(p_task_action_enum, cJSON_CreateString("update")); + cJSON_AddItemToArray(p_task_action_enum, cJSON_CreateString("remove")); + cJSON_AddItemToArray(p_task_action_enum, cJSON_CreateString("clear")); + cJSON_AddItemToArray(p_task_action_enum, cJSON_CreateString("replace")); + cJSON_AddItemToObject(p_task_action, "enum", p_task_action_enum); + cJSON_AddItemToObject(t37_props, "action", p_task_action); + + cJSON* p_task_text = cJSON_CreateObject(); + cJSON_AddStringToObject(p_task_text, "type", "string"); + cJSON_AddItemToObject(t37_props, "text", p_task_text); + + cJSON* p_task_id = cJSON_CreateObject(); + cJSON_AddStringToObject(p_task_id, "type", "integer"); + cJSON_AddItemToObject(t37_props, "id", p_task_id); + + cJSON* p_task_status = cJSON_CreateObject(); + cJSON_AddStringToObject(p_task_status, "type", "string"); + cJSON* p_task_status_enum = cJSON_CreateArray(); + cJSON_AddItemToArray(p_task_status_enum, cJSON_CreateString("pending")); + cJSON_AddItemToArray(p_task_status_enum, cJSON_CreateString("active")); + cJSON_AddItemToArray(p_task_status_enum, cJSON_CreateString("done")); + cJSON_AddItemToObject(p_task_status, "enum", p_task_status_enum); + cJSON_AddItemToObject(t37_props, "status", p_task_status); + + cJSON* p_task_tasks = cJSON_CreateObject(); + cJSON_AddStringToObject(p_task_tasks, "type", "array"); + cJSON* p_task_tasks_item = cJSON_CreateObject(); + cJSON_AddStringToObject(p_task_tasks_item, "type", "string"); + cJSON_AddItemToObject(p_task_tasks, "items", p_task_tasks_item); + cJSON_AddItemToObject(t37_props, "tasks", p_task_tasks); + + cJSON_AddItemToArray(t37_required, cJSON_CreateString("action")); + + cJSON_AddItemToObject(t37_fn, "parameters", t37_params); + cJSON_AddItemToObject(t37, "function", t37_fn); + cJSON_AddItemToArray(tools, t37); + char* out = cJSON_PrintUnformatted(tools); cJSON_Delete(tools); return out; @@ -4368,6 +4537,276 @@ static char* execute_file_write(tools_context_t* ctx, const char* args_json) { return json; } +static char* execute_task_manage(tools_context_t* ctx, const char* args_json) { + if (!ctx || !ctx->cfg) return json_error("tool context unavailable"); + + cJSON* args = parse_tool_args_json(args_json); + if (!args) return json_error("invalid arguments JSON"); + + cJSON* action = cJSON_GetObjectItemCaseSensitive(args, "action"); + if (!action || !cJSON_IsString(action) || !action->valuestring || action->valuestring[0] == '\0') { + cJSON_Delete(args); + return json_error("task_manage requires string action"); + } + + char tasks_path[PATH_MAX]; + if (build_tool_path(ctx, "tasks.json", tasks_path, sizeof(tasks_path)) != 0) { + cJSON_Delete(args); + return json_error("failed to resolve tasks file path"); + } + + cJSON* root = tasks_load_root(tasks_path); + if (!root) { + cJSON_Delete(args); + return json_error("failed to load tasks file"); + } + + cJSON* tasks = cJSON_GetObjectItemCaseSensitive(root, "tasks"); + if (!tasks || !cJSON_IsArray(tasks)) { + cJSON_DeleteItemFromObjectCaseSensitive(root, "tasks"); + tasks = cJSON_CreateArray(); + cJSON_AddItemToObject(root, "tasks", tasks); + } + + cJSON* next_id_item = cJSON_GetObjectItemCaseSensitive(root, "next_id"); + int next_id = (next_id_item && cJSON_IsNumber(next_id_item) && next_id_item->valuedouble >= 1) + ? (int)next_id_item->valuedouble + : 1; + + const char* action_s = action->valuestring; + int mutated = 0; + + if (strcmp(action_s, "list") == 0) { + /* no-op */ + } else if (strcmp(action_s, "add") == 0) { + cJSON* text = cJSON_GetObjectItemCaseSensitive(args, "text"); + cJSON* status = cJSON_GetObjectItemCaseSensitive(args, "status"); + if (!text || !cJSON_IsString(text) || !text->valuestring || text->valuestring[0] == '\0') { + cJSON_Delete(args); + cJSON_Delete(root); + return json_error("task_manage add requires non-empty string text"); + } + + const char* normalized_status = "pending"; + if (status && !cJSON_IsNull(status)) { + if (!cJSON_IsString(status) || !status->valuestring) { + cJSON_Delete(args); + cJSON_Delete(root); + return json_error("task_manage add status must be string when provided"); + } + normalized_status = normalize_task_status(status->valuestring); + if (!normalized_status) { + cJSON_Delete(args); + cJSON_Delete(root); + return json_error("task_manage add status must be pending, active, or done"); + } + } + + cJSON* task = cJSON_CreateObject(); + if (!task) { + cJSON_Delete(args); + cJSON_Delete(root); + return json_error("allocation failure"); + } + + time_t now = time(NULL); + cJSON_AddNumberToObject(task, "id", next_id++); + cJSON_AddStringToObject(task, "text", text->valuestring); + cJSON_AddStringToObject(task, "status", normalized_status); + cJSON_AddNumberToObject(task, "created_at", (double)now); + cJSON_AddNumberToObject(task, "updated_at", (double)now); + cJSON_AddItemToArray(tasks, task); + mutated = 1; + } else if (strcmp(action_s, "update") == 0) { + cJSON* id = cJSON_GetObjectItemCaseSensitive(args, "id"); + cJSON* text = cJSON_GetObjectItemCaseSensitive(args, "text"); + cJSON* status = cJSON_GetObjectItemCaseSensitive(args, "status"); + + if (!id || !cJSON_IsNumber(id) || id->valuedouble < 1) { + cJSON_Delete(args); + cJSON_Delete(root); + return json_error("task_manage update requires integer id"); + } + + if ((!text || cJSON_IsNull(text)) && (!status || cJSON_IsNull(status))) { + cJSON_Delete(args); + cJSON_Delete(root); + return json_error("task_manage update requires text and/or status"); + } + + cJSON* task = task_find_by_id(tasks, (int)id->valuedouble, NULL); + if (!task) { + cJSON_Delete(args); + cJSON_Delete(root); + return json_error("task not found"); + } + + if (text && !cJSON_IsNull(text)) { + if (!cJSON_IsString(text) || !text->valuestring || text->valuestring[0] == '\0') { + cJSON_Delete(args); + cJSON_Delete(root); + return json_error("task_manage update text must be non-empty string when provided"); + } + cJSON_DeleteItemFromObjectCaseSensitive(task, "text"); + cJSON_AddStringToObject(task, "text", text->valuestring); + mutated = 1; + } + + if (status && !cJSON_IsNull(status)) { + if (!cJSON_IsString(status) || !status->valuestring) { + cJSON_Delete(args); + cJSON_Delete(root); + return json_error("task_manage update status must be string when provided"); + } + const char* normalized_status = normalize_task_status(status->valuestring); + if (!normalized_status) { + cJSON_Delete(args); + cJSON_Delete(root); + return json_error("task_manage update status must be pending, active, or done"); + } + cJSON_DeleteItemFromObjectCaseSensitive(task, "status"); + cJSON_AddStringToObject(task, "status", normalized_status); + mutated = 1; + } + + if (mutated) { + time_t now = time(NULL); + cJSON_DeleteItemFromObjectCaseSensitive(task, "updated_at"); + cJSON_AddNumberToObject(task, "updated_at", (double)now); + } + } else if (strcmp(action_s, "remove") == 0) { + cJSON* id = cJSON_GetObjectItemCaseSensitive(args, "id"); + if (!id || !cJSON_IsNumber(id) || id->valuedouble < 1) { + cJSON_Delete(args); + cJSON_Delete(root); + return json_error("task_manage remove requires integer id"); + } + + int idx = -1; + cJSON* task = task_find_by_id(tasks, (int)id->valuedouble, &idx); + if (!task || idx < 0) { + cJSON_Delete(args); + cJSON_Delete(root); + return json_error("task not found"); + } + + cJSON_DeleteItemFromArray(tasks, idx); + mutated = 1; + } else if (strcmp(action_s, "clear") == 0) { + cJSON* status = cJSON_GetObjectItemCaseSensitive(args, "status"); + if (!status || cJSON_IsNull(status)) { + while (cJSON_GetArraySize(tasks) > 0) { + cJSON_DeleteItemFromArray(tasks, 0); + } + mutated = 1; + } else { + if (!cJSON_IsString(status) || !status->valuestring) { + cJSON_Delete(args); + cJSON_Delete(root); + return json_error("task_manage clear status must be string when provided"); + } + const char* normalized_status = normalize_task_status(status->valuestring); + if (!normalized_status) { + cJSON_Delete(args); + cJSON_Delete(root); + return json_error("task_manage clear status must be pending, active, or done"); + } + + int i = 0; + while (i < cJSON_GetArraySize(tasks)) { + cJSON* task = cJSON_GetArrayItem(tasks, i); + cJSON* task_status = task ? cJSON_GetObjectItemCaseSensitive(task, "status") : NULL; + const char* task_status_s = (task_status && cJSON_IsString(task_status) && task_status->valuestring) + ? task_status->valuestring + : "pending"; + if (strcmp(task_status_s, normalized_status) == 0) { + cJSON_DeleteItemFromArray(tasks, i); + mutated = 1; + } else { + i++; + } + } + } + } else if (strcmp(action_s, "replace") == 0) { + cJSON* tasks_in = cJSON_GetObjectItemCaseSensitive(args, "tasks"); + if (!tasks_in || !cJSON_IsArray(tasks_in)) { + cJSON_Delete(args); + cJSON_Delete(root); + return json_error("task_manage replace requires array tasks"); + } + + while (cJSON_GetArraySize(tasks) > 0) { + cJSON_DeleteItemFromArray(tasks, 0); + } + + int n = cJSON_GetArraySize(tasks_in); + time_t now = time(NULL); + for (int i = 0; i < n; i++) { + cJSON* text = cJSON_GetArrayItem(tasks_in, i); + if (!text || !cJSON_IsString(text) || !text->valuestring || text->valuestring[0] == '\0') { + continue; + } + + cJSON* task = cJSON_CreateObject(); + if (!task) { + cJSON_Delete(args); + cJSON_Delete(root); + return json_error("allocation failure"); + } + + cJSON_AddNumberToObject(task, "id", next_id++); + cJSON_AddStringToObject(task, "text", text->valuestring); + cJSON_AddStringToObject(task, "status", "pending"); + cJSON_AddNumberToObject(task, "created_at", (double)now); + cJSON_AddNumberToObject(task, "updated_at", (double)now); + cJSON_AddItemToArray(tasks, task); + } + mutated = 1; + } else { + cJSON_Delete(args); + cJSON_Delete(root); + return json_error("task_manage action must be one of: list, add, update, remove, clear, replace"); + } + + if (mutated) { + cJSON_DeleteItemFromObjectCaseSensitive(root, "next_id"); + cJSON_AddNumberToObject(root, "next_id", next_id); + if (tasks_save_root(tasks_path, root) != 0) { + cJSON_Delete(args); + cJSON_Delete(root); + return json_error("failed to save tasks file"); + } + } + + cJSON* out = cJSON_CreateObject(); + if (!out) { + cJSON_Delete(args); + cJSON_Delete(root); + return NULL; + } + + cJSON_AddBoolToObject(out, "success", 1); + cJSON_AddStringToObject(out, "action", action_s); + cJSON_AddStringToObject(out, "path", tasks_path); + cJSON_AddBoolToObject(out, "mutated", mutated ? 1 : 0); + cJSON_AddNumberToObject(out, "count", cJSON_GetArraySize(tasks)); + + cJSON* tasks_dup = cJSON_Duplicate(tasks, 1); + if (!tasks_dup) { + cJSON_Delete(args); + cJSON_Delete(root); + cJSON_Delete(out); + return NULL; + } + cJSON_AddItemToObject(out, "tasks", tasks_dup); + + char* json = cJSON_PrintUnformatted(out); + cJSON_Delete(out); + cJSON_Delete(args); + cJSON_Delete(root); + return json; +} + static char* execute_tool_list(tools_context_t* ctx, const char* args_json) { (void)args_json; if (!ctx) { @@ -4815,6 +5254,9 @@ char* tools_execute(tools_context_t* ctx, const char* tool_name, const char* arg if (strcmp(tool_name, "nostr_file_md_to_longform_post") == 0) { return execute_nostr_file_md_to_longform_post(ctx, args_json); } + if (strcmp(tool_name, "task_manage") == 0) { + return execute_task_manage(ctx, args_json); + } return json_error("unknown tool"); } diff --git a/src/trigger_manager.c b/src/trigger_manager.c index 2f8a89a..07e2044 100644 --- a/src/trigger_manager.c +++ b/src/trigger_manager.c @@ -160,7 +160,7 @@ static void execute_template_action(trigger_manager_t* mgr, if (strncmp(rendered, "DM admin:", 9) == 0) { const char* body = rendered + 9; while (*body == ' ') body++; - (void)nostr_handler_send_dm(mgr->cfg->admin.pubkey, body); + (void)nostr_handler_send_dm_auto(mgr->cfg->admin.pubkey, body); } else if (strncmp(rendered, "POST:", 5) == 0) { const char* body = rendered + 5; while (*body == ' ') body++; @@ -170,7 +170,7 @@ static void execute_template_action(trigger_manager_t* mgr, while (*body == ' ') body++; DEBUG_INFO("[didactyl] trigger template log (%s): %s", t->skill_slug, body); } else { - (void)nostr_handler_send_dm(mgr->cfg->admin.pubkey, rendered); + (void)nostr_handler_send_dm_auto(mgr->cfg->admin.pubkey, rendered); } free(rendered); diff --git a/tasks.json b/tasks.json new file mode 100644 index 0000000..d192962 --- /dev/null +++ b/tasks.json @@ -0,0 +1 @@ +{"tasks":[{"id":5,"text":"Tweet 3: Gets Smarter Over Time - \"Didactyl starts as an AI that explores and learns. Over time, the best workflows get locked in as reliable, fast processes. It evolves from experimental to hardened—from thinking to doing. An agent that improves itself. #nostr #agents\"","status":"pending","created_at":1772625247,"updated_at":1772625247}],"next_id":6} \ No newline at end of file