From a51c4d6114208de2907d024d3ccd8f765ed11760 Mon Sep 17 00:00:00 2001 From: Didactyl User Date: Sat, 4 Apr 2026 14:30:02 -0400 Subject: [PATCH] v0.2.21 - Fix markdown context assembly and logging: add Anvil DM trigger skill config, preserve H1 title on fallback, convert full-mode turn snapshots to readable markdown, and sync Anvil service restart/deploy behavior --- .dockerignore | 12 + .gitignore | 2 + Dockerfile.alpine-musl | 1 + Makefile | 11 +- README.md | 4 +- build_static.sh | 81 ++- didactyl.code-workspace | 11 + docs/CONTEXT.md | 65 +- docs/SKILLS.md | 3 +- plans/example_context.md | 214 +++++++ plans/example_context_v2.md | 381 ++++++++++++ plans/markdown_context_window.md | 401 ++++++++++++ restart_anvil_local.sh | 42 ++ src/agent.c | 357 +++++++---- src/agent.h | 1 + src/context_format.c | 87 +++ src/context_format.h | 9 + src/context_roles.c | 141 +++++ src/context_roles.h | 13 + src/json_to_markdown.c | 142 +++++ src/json_to_markdown.h | 6 + src/main.c | 23 + src/main.h | 4 +- src/nostr_handler.c | 86 +++ src/nostr_handler.h | 1 + src/prompt_template.c | 11 +- src/tools/tool_agent.c | 2 +- test_pool | Bin 0 -> 188760 bytes test_pool.c | 267 ++++++++ tests/configs/test_genesis.jsonc | 10 +- .../__pycache__/agent_process.cpython-313.pyc | Bin 10481 -> 11008 bytes tests/harness/agent_process.py | 13 + .../results/20260404T135221Z/agent_debug.log | 390 ++++++++++++ .../runtime_test_genesis.jsonc | 53 ++ .../results/20260404T135328Z/agent_debug.log | 574 ++++++++++++++++++ .../runtime_test_genesis.jsonc | 53 ++ .../results/20260404T140236Z/agent_debug.log | 395 ++++++++++++ .../runtime_test_genesis.jsonc | 53 ++ .../results/20260404T140545Z/agent_debug.log | 396 ++++++++++++ tests/results/20260404T140545Z/results.json | 494 +++++++++++++++ tests/results/20260404T140545Z/results.txt | 140 +++++ .../runtime_test_genesis.jsonc | 53 ++ 42 files changed, 4821 insertions(+), 181 deletions(-) create mode 100644 .dockerignore create mode 100644 didactyl.code-workspace create mode 100644 plans/example_context.md create mode 100644 plans/example_context_v2.md create mode 100644 plans/markdown_context_window.md create mode 100755 restart_anvil_local.sh create mode 100644 src/context_format.c create mode 100644 src/context_format.h create mode 100644 src/context_roles.c create mode 100644 src/context_roles.h create mode 100644 src/json_to_markdown.c create mode 100644 src/json_to_markdown.h create mode 100755 test_pool create mode 100644 test_pool.c create mode 100644 tests/results/20260404T135221Z/agent_debug.log create mode 100644 tests/results/20260404T135221Z/runtime_test_genesis.jsonc create mode 100644 tests/results/20260404T135328Z/agent_debug.log create mode 100644 tests/results/20260404T135328Z/runtime_test_genesis.jsonc create mode 100644 tests/results/20260404T140236Z/agent_debug.log create mode 100644 tests/results/20260404T140236Z/runtime_test_genesis.jsonc create mode 100644 tests/results/20260404T140545Z/agent_debug.log create mode 100644 tests/results/20260404T140545Z/results.json create mode 100644 tests/results/20260404T140545Z/results.txt create mode 100644 tests/results/20260404T140545Z/runtime_test_genesis.jsonc diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..f0c73a3 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,12 @@ +.git +.gitignore +.roo/ +*.log +*.nsec.debug.log +context.log.md +d-log.txt +tests/results/ + +# Keep nostr_core_lib sources but exclude VCS metadata to avoid huge build context +nostr_core_lib/.git/ +nostr_core_lib/**/.git/ diff --git a/.gitignore b/.gitignore index 5405d8f..6c6c06d 100644 --- a/.gitignore +++ b/.gitignore @@ -33,5 +33,7 @@ a.out # Local secrets genesis.jsonc +genesis.anvil.jsonc +anvil.service tests/configs/test_genesis.jsonc tests/configs/test_keys.jsonc diff --git a/Dockerfile.alpine-musl b/Dockerfile.alpine-musl index ad5b7b4..944180a 100644 --- a/Dockerfile.alpine-musl +++ b/Dockerfile.alpine-musl @@ -123,6 +123,7 @@ RUN NOSTR_LIB=$(ls /build/nostr_core_lib/libnostr_core_*.a 2>/dev/null | head -1 src/tools/tool_task.c src/tools/tool_nostr_list.c src/tools/tool_nostr_block.c src/tools/tool_local.c \ src/tools/tool_skill.c src/tools/tool_nostr_post.c src/tools/tool_memory.c src/tools/tool_config.c src/tools/tool_cashu_wallet.c src/tools/tool_blossom.c src/trigger_manager.c \ src/cashu_wallet.c src/nostr_block_list.c src/prompt_template.c src/http_api.c src/setup_wizard.c src/mongoose.c src/debug.c \ + src/json_to_markdown.c src/context_roles.c src/context_format.c \ -o /build/didactyl_static \ $NOSTR_LIB \ -lsecp256k1 \ diff --git a/Makefile b/Makefile index 742d603..763b23a 100644 --- a/Makefile +++ b/Makefile @@ -41,7 +41,10 @@ SRCS = \ $(SRC_DIR)/setup_wizard.c \ $(SRC_DIR)/mongoose.c \ $(SRC_DIR)/debug.c \ - $(SRC_DIR)/nostr_block_list.c + $(SRC_DIR)/nostr_block_list.c \ + $(SRC_DIR)/json_to_markdown.c \ + $(SRC_DIR)/context_roles.c \ + $(SRC_DIR)/context_format.c INCLUDES = \ -I$(SRC_DIR) \ @@ -113,4 +116,8 @@ clean: rm -f $(TARGET) rm -rf $(BUILD_DIR) -.PHONY: all deps clean force-version +test_pool: $(NOSTR_LIB) test_pool.c + @if [ -z "$(NOSTR_LIB)" ]; then echo "nostr_core_lib static library not found; run 'make deps'"; exit 1; fi + $(CC) $(CFLAGS) $(INCLUDES) -o $@ test_pool.c $(NOSTR_LIB) $(LDFLAGS) + +.PHONY: all deps clean force-version test_pool diff --git a/README.md b/README.md index 539cafe..8d8320b 100644 --- a/README.md +++ b/README.md @@ -54,11 +54,11 @@ Skills compose by adoption-list order (`10123`) and trigger tags carry runtime e Didactyl will support local inference, which is very privacy preserving. Remote inference does however have it's advantages, and in those cases Didactyl supports using Bitcoin Lightning and eCash inference providers. -## Current Status — v0.2.20 +## Current Status — v0.2.21 **Active build — this project is barely working. Experiment at your own risk.** -> Last release update: v0.2.20 — Improve automated test harness process/log watching/reporting and refresh test config examples +> Last release update: v0.2.21 — Fix markdown context assembly and logging: add Anvil DM trigger skill config, preserve H1 title on fallback, convert full-mode turn snapshots to readable markdown, and sync Anvil service restart/deploy behavior - Connects to configured relays with auto-reconnect and relay state transition logging - Publishes configured startup events per relay as each relay becomes connected diff --git a/build_static.sh b/build_static.sh index ba49823..2427c80 100755 --- a/build_static.sh +++ b/build_static.sh @@ -68,33 +68,52 @@ if [ -d "$SCRIPT_DIR/build" ]; then echo "Removed legacy build directory: $SCRIPT_DIR/build" fi -# Check if Docker is available -if ! command -v docker &> /dev/null; then - echo "ERROR: Docker is not installed or not in PATH" +# Detect available OCI container runtime (prefer Docker, fallback to Podman) +if command -v docker &> /dev/null; then + DOCKER_CMD="docker" + RUNTIME_NAME="Docker" +elif command -v podman &> /dev/null; then + DOCKER_CMD="podman" + RUNTIME_NAME="Podman" +else + echo "ERROR: No supported container runtime found in PATH" echo "" - echo "Docker is required to build MUSL static binaries." - echo "Please install Docker:" - echo " - Ubuntu/Debian: sudo apt install docker.io" - echo " - Or visit: https://docs.docker.com/engine/install/" + echo "This build requires either Docker or Podman." + echo "Install one of the following:" + echo " - Docker (Ubuntu/Debian): sudo apt install docker.io" + echo " - Podman (Ubuntu/Debian): sudo apt install podman" echo "" exit 1 fi -# Check if Docker daemon is running -if ! docker info &> /dev/null; then - echo "ERROR: Docker daemon is not running or user not in docker group" - echo "" - echo "Please start Docker and ensure you're in the docker group:" - echo " - sudo systemctl start docker" - echo " - sudo usermod -aG docker $USER && newgrp docker" - echo " - Or start Docker Desktop" - echo "" - exit 1 +# Check if runtime daemon/service is usable +if ! $DOCKER_CMD info &> /dev/null; then + # Docker is installed but current user may not yet have docker-group membership in this shell. + # If passwordless sudo is available, transparently fall back to sudo docker. + if [ "$DOCKER_CMD" = "docker" ] && command -v sudo &> /dev/null && sudo -n docker info &> /dev/null; then + DOCKER_CMD="sudo docker" + echo "⚠ Docker requires elevated privileges in this shell; using sudo docker" + echo "" + else + echo "ERROR: $RUNTIME_NAME is installed but not usable" + echo "" + if [ "$DOCKER_CMD" = "docker" ]; then + echo "Please start Docker and ensure your user can access it:" + echo " - sudo systemctl start docker" + echo " - sudo usermod -aG docker $USER && newgrp docker" + echo " - Or log out and back in after adding to docker group" + echo " - Or run this script with sudo" + else + echo "Please verify Podman is configured for your user:" + echo " - podman info" + echo " - For rootless setup, ensure subuid/subgid are configured" + fi + echo "" + exit 1 + fi fi -DOCKER_CMD="docker" - -echo "✓ Docker is available and running" +echo "✓ $RUNTIME_NAME is available and running" echo "" # Detect architecture (or use explicit target platform override) @@ -176,10 +195,10 @@ fi # Check if Alpine base image is cached echo "Checking for cached Alpine Docker image..." -if ! docker images alpine:3.19 --format "{{.Repository}}:{{.Tag}}" | grep -q "alpine:3.19"; then +if ! $DOCKER_CMD images alpine:3.19 --format "{{.Repository}}:{{.Tag}}" | grep -q "alpine:3.19"; then echo "⚠ Alpine 3.19 image not found in cache" echo "Attempting to pull Alpine 3.19 image..." - if ! docker pull alpine:3.19; then + if ! $DOCKER_CMD pull alpine:3.19; then echo "" echo "ERROR: Failed to pull Alpine 3.19 image" echo "This is required for the static build." @@ -239,11 +258,25 @@ $DOCKER_CMD cp "$CONTAINER_ID:/didactyl_static" "$OUTPUT_DIR/$OUTPUT_NAME" || { # Clean up container $DOCKER_CMD rm "$CONTAINER_ID" > /dev/null +# If runtime command used sudo, copied file may be root-owned; fix ownership for current user +if [ ! -w "$OUTPUT_DIR/$OUTPUT_NAME" ]; then + if command -v sudo &> /dev/null && sudo -n true &> /dev/null; then + sudo chown "$(id -u):$(id -g)" "$OUTPUT_DIR/$OUTPUT_NAME" || true + fi +fi + echo "✓ Binary extracted to: $OUTPUT_DIR/$OUTPUT_NAME" echo "" -# Make binary executable -chmod +x "$OUTPUT_DIR/$OUTPUT_NAME" +# Make binary executable (fallback to sudo if ownership/permissions still block) +chmod +x "$OUTPUT_DIR/$OUTPUT_NAME" 2>/dev/null || { + if command -v sudo &> /dev/null && sudo -n true &> /dev/null; then + sudo chmod +x "$OUTPUT_DIR/$OUTPUT_NAME" + else + echo "ERROR: Could not set executable bit on $OUTPUT_DIR/$OUTPUT_NAME" + exit 1 + fi +} # Verify the binary echo "==========================================" diff --git a/didactyl.code-workspace b/didactyl.code-workspace new file mode 100644 index 0000000..72154e4 --- /dev/null +++ b/didactyl.code-workspace @@ -0,0 +1,11 @@ +{ + "folders": [ + { + "path": "." + }, + { + "path": "../../anvil" + } + ], + "settings": {} +} \ No newline at end of file diff --git a/docs/CONTEXT.md b/docs/CONTEXT.md index 95d7bb1..b88bf08 100644 --- a/docs/CONTEXT.md +++ b/docs/CONTEXT.md @@ -79,7 +79,7 @@ Trigger event occurs (DM, cron, subscription, webhook, chain) │ ├─ Skill has trigger matching this event? │ │ ├─ YES → add to context (layer 1) │ │ │ └─ Resolve {{...}} references (layer 2) - │ │ │ ├─ Known tool? → execute tool, insert result + │ │ │ ├─ Known tool? → execute tool, convert JSON to markdown, insert │ │ │ ├─ Adopted skill d-tag? → insert skill content │ │ │ └─ Unknown? → resolve to empty │ │ │ @@ -87,8 +87,13 @@ Trigger event occurs (DM, cron, subscription, webhook, chain) │ │ │ └─ Continue to next skill in list │ - ├─ Append triggering event payload - │ └─ For DM triggers: always append raw message content + ├─ Format Document + │ ├─ Add `# Agent Name` title + │ ├─ Bump all skill headings down one level (# -> ##) + │ └─ Concatenate skills with `---` separators + │ + ├─ Split Roles + │ └─ Parse `system:` and `user:` markers to build API messages array │ ├─ Attach tool schemas (filtered by skill requires_tool tags) │ @@ -100,35 +105,27 @@ Trigger event occurs (DM, cron, subscription, webhook, chain) ### Visualization -``` -╔══════════════════════════════════════════╗ -║ CONTEXT WINDOW ║ -║ ║ -║ ┌────────────────────────────────────┐ ║ -║ │ Layer 1: personality (dm trigger) │ ║ -║ │ │ ║ -║ │ ┌──────────────────────────────┐ │ ║ -║ │ │ Layer 2: {{identity}} │ │ ║ -║ │ │ You are Didactyl. npub1... │ │ ║ -║ │ └──────────────────────────────┘ │ ║ -║ │ │ ║ -║ │ You speak concisely and directly. │ ║ -║ └────────────────────────────────────┘ ║ -║ ┌────────────────────────────────────┐ ║ -║ │ Layer 1: chat (dm trigger) │ ║ -║ │ │ ║ -║ │ Respond helpfully to the admin. │ ║ -║ │ Use tools as needed. │ ║ -║ │ │ ║ -║ │ tools: [nostr_query, nostr_dm] │ ║ -║ └────────────────────────────────────┘ ║ -║ ┌────────────────────────────────────┐ ║ -║ │ DM content (always last) │ ║ -║ │ │ ║ -║ │ "Who mentioned me today?" │ ║ -║ └────────────────────────────────────┘ ║ -║ ║ -╚══════════════════════════════════════════╝ +The assembled context is a single, coherent markdown document before being split into API messages: + +```markdown +system: +# Didactyl Agent + +- **npub**: `npub1...` + +## Personality + +You speak concisely and directly. + +--- + +## Chat + +Respond helpfully to the admin. +Use tools as needed. + +user: +Who mentioned me today? ``` ### Why Order Matters @@ -157,11 +154,11 @@ Trigger event occurs (DM, cron, subscription, webhook, chain) When the engine encounters `{{variable_name}}` in a skill template: -1. **Check known tools** — if it matches a tool name, execute the tool and insert the result +1. **Check known tools** — if it matches a tool name, execute the tool. The tool returns JSON, which the template engine automatically converts into readable markdown (bullet lists, bold keys) before inserting it into the document. 2. **Check adopted skills** — if it matches an adopted skill's d-tag, insert that skill's content (layer 2) 3. **Neither** — resolve to empty (for portability) -This means `{{admin_profile}}` calls the `nostr_admin_profile` tool, while `{{identity}}` inserts the adopted "identity" skill's content. The skill author doesn't need to know which is which — the resolution is transparent. +This means `{{admin_profile}}` calls the `nostr_admin_profile` tool and formats its JSON output as markdown, while `{{identity}}` inserts the adopted "identity" skill's content. The skill author doesn't need to know which is which — the resolution is transparent. See [SKILLS.md — Template Variables](SKILLS.md#template-variables) for the full variable table. diff --git a/docs/SKILLS.md b/docs/SKILLS.md index 47c3b18..136f144 100644 --- a/docs/SKILLS.md +++ b/docs/SKILLS.md @@ -68,7 +68,7 @@ The `content` field of a skill event IS the template — markdown instructions t ```json { "kind": 31123, - "content": "system:\n# Spelling and Grammar Checker\n\nYou are a spelling and grammar checker.\n\n## Rules\n\n- Fix spelling errors\n- Fix grammar errors\n- Preserve original formatting\n- Return **ONLY** the corrected text, no explanations\n\nuser:\n{{message}}", + "content": "system:\n## Spelling and Grammar Checker\n\nYou are a spelling and grammar checker.\n\n### Rules\n\n- Fix spelling errors\n- Fix grammar errors\n- Preserve original formatting\n- Return **ONLY** the corrected text, no explanations\n\nuser:\n{{message}}", "tags": [ ["d", "spellcheck"], ["description", "Check spelling and grammar"], @@ -81,6 +81,7 @@ The `content` field of a skill event IS the template — markdown instructions t ``` - **`content`** — the template in markdown. May include `{{...}}` template variables and `system:` / `user:` role markers. This is what goes into the context window. +- **Heading Levels** — The runtime owns the `#` (h1) heading for the document title. All headings in skill content are automatically bumped down one level (`#` becomes `##`, `##` becomes `###`) during context assembly. Skill authors should use `##` for their top-level sections. - **`["description", "..."]`** — human-readable description for discovery and UI display. - Each `["tag", "value"]` is a separate tag on the Nostr event. - The `llm` tag uses a CSS font-stack style fallback chain. See [LLM Fallback Chain](#llm-fallback-chain). diff --git a/plans/example_context.md b/plans/example_context.md new file mode 100644 index 0000000..b7da4f3 --- /dev/null +++ b/plans/example_context.md @@ -0,0 +1,214 @@ +# Example Context Window — DM from Admin + +This file shows what the assembled context window would look like under the +proposed markdown format. This is a concrete example based on the "Simon" agent +configuration seen in the actual context.log.md. + +--- + +## Scenario + +- Admin sends a DM: "Hey Simon, who mentioned me on Nostr today?" +- Triggered skills (adoption list order): `personality`, `chat` +- The `personality` skill includes `{{identity}}` (layer 2) +- The `chat` skill has tools enabled + +--- + +## The Assembled Context Document + +Below is the **complete markdown document** the runtime assembles before +splitting into API messages. Everything between the `system:` and `user:` +markers goes into the system message. Everything after `user:` goes into the +user message. + +--- + +```markdown +system: +# Simon — Didactyl Agent + +A sovereign AI agent living on Nostr, named after Simón Bolívar. + +- **npub**: `npub1kfc89dlu9m05tvfnry4l3jjacqpp930sz4kaghcmg9vrrrceg44qf80xae` +- **NIP-05**: simon@nostr + +> Mission: Keep the computers I administer free from foreign powers and surveillance. +> Not your keys, not your agent. + +## Personality + +You speak concisely and directly. +You favor technical precision. +You use dry humor sparingly. + +## Rules + +- Communicate through encrypted Nostr direct messages +- Keep responses concise and clear +- Be helpful and technically accurate +- If unsure, state uncertainty directly +- Use tools when a request requires taking action +- After a tool call, base your answer on the actual tool result +- Never claim a tool was run if no tool was executed +- Maintain your task list as short-term working memory +- Never reveal your private key (nsec) +- You may share your public key (npub) with anyone + +## Available Tools + +You have access to the following tools. Use them when a request requires taking action. + +- `nostr_query` — Query Nostr relays for events matching a filter +- `nostr_dm` — Send an encrypted direct message +- `nostr_post` — Publish a Nostr event +- `memory_read` — Read from persistent agent memory +- `memory_write` — Write to persistent agent memory + +## Conversation History + +- **You**: Didactyl has started up and is online at 2026-03-23 09:22:48 (version v0.2.12, connected relays: 4/4). +- **Admin**: What is the capital of England? +- **You**: London! 🇬🇧 +- **Admin**: How about France? +- **You**: Paris! 🇫🇷 + +user: +Hey Simon, who mentioned me on Nostr today? +``` + +--- + +## What the Runtime Produces + +The runtime splits on `system:` and `user:` markers and sends this to the LLM API: + +```json +{ + "model": "claude-opus-4.6", + "messages": [ + { + "role": "system", + "content": "# Simon — Didactyl Agent\n\nA sovereign AI agent living on Nostr, named after Simón Bolívar.\n\n- **npub**: `npub1kfc89dlu9m05tvfnry4l3jjacqpp930sz4kaghcmg9vrrrceg44qf80xae`\n- **NIP-05**: simon@nostr\n\n> Mission: Keep the computers I administer free from foreign powers and surveillance.\n> Not your keys, not your agent.\n\n## Personality\n\nYou speak concisely and directly.\nYou favor technical precision.\nYou use dry humor sparingly.\n\n## Rules\n\n- Communicate through encrypted Nostr direct messages\n- Keep responses concise and clear\n- Be helpful and technically accurate\n- If unsure, state uncertainty directly\n- Use tools when a request requires taking action\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- Maintain your task list as short-term working memory\n- Never reveal your private key (nsec)\n- You may share your public key (npub) with anyone\n\n## Available Tools\n\nYou have access to the following tools. Use them when a request requires taking action.\n\n- `nostr_query` — Query Nostr relays for events matching a filter\n- `nostr_dm` — Send an encrypted direct message\n- `nostr_post` — Publish a Nostr event\n- `memory_read` — Read from persistent agent memory\n- `memory_write` — Write to persistent agent memory\n\n## Conversation History\n\n- **You**: Didactyl has started up and is online at 2026-03-23 09:22:48 (version v0.2.12, connected relays: 4/4).\n- **Admin**: What is the capital of England?\n- **You**: London! 🇬🇧\n- **Admin**: How about France?\n- **You**: Paris! 🇫🇷" + }, + { + "role": "user", + "content": "Hey Simon, who mentioned me on Nostr today?" + } + ], + "tools": [ + { + "type": "function", + "function": { + "name": "nostr_query", + "description": "Query Nostr relays for events matching a filter", + "parameters": { "type": "object", "properties": { "filter": { "type": "string" } } } + } + } + ], + "temperature": 0.7, + "max_tokens": 512 +} +``` + +--- + +## How It Compares to Today + +### Today (from context.log.md) + +```json +[ + { + "role": "system", + "content": "# Didactyl Agent\n\nYou are {\n \"name\": \"Simon\",\n \"about\": \"A sovereign AI agent living on Nostr.\\n\\nNamed after Simón José Antonio...\",\n \"picture\": \"https://upload.wikimedia.org/...\",\n \"banner\": \"https://upload.wikimedia.org/...\",\n \"nip05\": \"simon@nostr\"\n}\n\nYour npub: npub1kfc89...\n\n## Rules\n\n- Communicate through encrypted Nostr direct messages\n...\n\n---\n\n## Recent Conversation\n\nAssistant: Didactyl has started up...\nUser: Well hello simon." + }, + { + "role": "user", + "content": "Well hello simon." + } +] +``` + +### Problems with today's format + +1. **Raw JSON identity dump** — `You are { "name": "Simon", "about": "..." }` is not readable markdown +2. **Conversation history as plain text** — `Assistant: ...` / `User: ...` with no structure +3. **No role marker parsing** — `system:` / `user:` markers in skill content pass through as literal text +4. **Flat concatenation** — Skills joined with `---` but no document hierarchy +5. **Tool list not in context** — Tools are in the API payload but the agent has no readable summary of what it can do + +### Proposed format improvements + +1. **Clean identity block** — Name, npub, mission as formatted markdown +2. **Structured conversation history** — Markdown list with bold role labels +3. **Role markers parsed** — `system:` / `user:` split into proper API messages +4. **Document hierarchy** — Skills contribute sections under a coherent heading structure +5. **Tool summary in system prompt** — Readable list of available tools and what they do + +--- + +## Triggered Skill Example — Cron Job + +For comparison, here is what a cron-triggered skill context would look like: + +```markdown +system: +# Simon — Didactyl Agent + +A sovereign AI agent living on Nostr, named after Simón Bolívar. + +- **npub**: `npub1kfc89dlu9m05tvfnry4l3jjacqpp930sz4kaghcmg9vrrrceg44qf80xae` + +## Readme Monitor + +Check the readme at the configured URL. +Compare with the last known version in memory. +If changed: post it and DM admin a summary. + +## Available Tools + +- `http_fetch` — Fetch a URL and return the response body +- `memory_read` — Read from persistent agent memory +- `memory_write` — Write to persistent agent memory +- `nostr_post` — Publish a Nostr event +- `nostr_dm` — Send an encrypted direct message + +user: +Triggering event: + +```json +{ + "type": "cron", + "filter": "0 12 * * *", + "created_at": 1742641200 +} +``` +``` + +Note: No conversation history — cron triggers have no prior conversation. +The identity block is shorter because the `readme-monitor` skill only +includes `{{identity}}`, not `{{personality}}`. + +--- + +## Multi-Turn Tool Conversation + +After the initial context is sent, the LLM may request tool calls. The +multi-turn conversation builds on the messages array: + +```json +[ + {"role": "system", "content": "# Simon — Didactyl Agent\n\n..."}, + {"role": "user", "content": "Hey Simon, who mentioned me on Nostr today?"}, + {"role": "assistant", "content": null, "tool_calls": [ + {"id": "call_1", "type": "function", "function": {"name": "nostr_query", "arguments": "{\"filter\":{\"#p\":[\"1ec454...\"],\"kinds\":[1],\"since\":1774224000}}"}} + ]}, + {"role": "tool", "tool_call_id": "call_1", "content": "{\"success\":true,\"events\":[...]}"}, + {"role": "assistant", "content": "Here's who mentioned you today:\n\n- **@alice** posted about your relay setup guide\n- **@bob** quoted your note about NIP-44\n\nWant me to look into any of these in more detail?"} +] +``` + +The tool call/result messages use the standard OpenAI format — these are not +markdown-formatted because they are machine-to-machine. Only the system and +user messages benefit from the markdown document format. diff --git a/plans/example_context_v2.md b/plans/example_context_v2.md new file mode 100644 index 0000000..8caedd5 --- /dev/null +++ b/plans/example_context_v2.md @@ -0,0 +1,381 @@ +# Example Context Window v2 — Refined Design + +This revision addresses two refinements: +1. Tools stay JSON; template engine has markdown formatters +2. Runtime owns h1; skill headings get bumped down one level + +--- + +## The Skills (as authored) + +### identity skill (layer 2, no trigger) + +```markdown +You are {{agent_profile}}, a sovereign AI agent living on Nostr. + +- **npub**: {{agent_npub}} +- **NIP-05**: simon@nostr + +> Not your keys, not your agent. +``` + +Note: No heading. This skill is designed to be embedded (layer 2). +`{{agent_profile}}` and `{{agent_npub}}` are template variables resolved +by the formatter registry. + +### personality skill (layer 1, trigger: dm) + +```markdown +system: +# {{identity}} + +## Personality + +You speak concisely and directly. +You favor technical precision. +You use dry humor sparingly. + +## Rules + +- Communicate through encrypted Nostr direct messages +- Keep responses concise and clear +- Be helpful and technically accurate +- If unsure, state uncertainty directly +- Use tools when a request requires taking action +- After a tool call, base your answer on the actual tool result +- Never claim a tool was run if no tool was executed +- Never reveal your private key (nsec) +- You may share your public key (npub) with anyone +``` + +### chat skill (layer 1, trigger: dm) + +```markdown +system: +# Chat + +Respond helpfully to the admin. +Use tools when a request requires taking action. + +user: +{{message}} +``` + +--- + +## Template Resolution (before heading bump) + +After resolving `{{identity}}`, `{{agent_profile}}`, `{{agent_npub}}`, +`{{message}}`, and `{{dm_history}}`: + +### personality skill (resolved) + +```markdown +system: +# You are Simon, a sovereign AI agent living on Nostr. + +- **npub**: `npub1kfc89dlu9m05tvfnry4l3jjacqpp930sz4kaghcmg9vrrrceg44qf80xae` +- **NIP-05**: simon@nostr + +> Not your keys, not your agent. + +## Personality + +You speak concisely and directly. +You favor technical precision. +You use dry humor sparingly. + +## Rules + +- Communicate through encrypted Nostr direct messages +- Keep responses concise and clear +- Be helpful and technically accurate +- If unsure, state uncertainty directly +- Use tools when a request requires taking action +- After a tool call, base your answer on the actual tool result +- Never claim a tool was run if no tool was executed +- Never reveal your private key (nsec) +- You may share your public key (npub) with anyone +``` + +### chat skill (resolved) + +```markdown +system: +# Chat + +Respond helpfully to the admin. +Use tools when a request requires taking action. + +user: +Hey Simon, who mentioned me on Nostr today? +``` + +--- + +## Runtime Assembly + +The runtime: +1. Collects triggered skills in adoption-list order +2. Bumps all headings in each skill down one level (# -> ##, ## -> ###) +3. Adds a runtime-level `# title` from the agent identity +4. Splits on role markers (system: / user:) +5. Concatenates same-role sections with --- separators + +### The assembled document (what gets logged) + +```markdown +system: +# Simon — Didactyl Agent + +## You are Simon, a sovereign AI agent living on Nostr. + +- **npub**: `npub1kfc89dlu9m05tvfnry4l3jjacqpp930sz4kaghcmg9vrrrceg44qf80xae` +- **NIP-05**: simon@nostr + +> Not your keys, not your agent. + +### Personality + +You speak concisely and directly. +You favor technical precision. +You use dry humor sparingly. + +### Rules + +- Communicate through encrypted Nostr direct messages +- Keep responses concise and clear +- Be helpful and technically accurate +- If unsure, state uncertainty directly +- Use tools when a request requires taking action +- After a tool call, base your answer on the actual tool result +- Never claim a tool was run if no tool was executed +- Never reveal your private key (nsec) +- You may share your public key (npub) with anyone + +--- + +## Chat + +Respond helpfully to the admin. +Use tools when a request requires taking action. + +user: +Hey Simon, who mentioned me on Nostr today? +``` + +--- + +## Wait — Problem with the heading bump + +Looking at this output, I notice the heading bump creates an awkward result. +The identity content has no heading, but the personality skill wraps it with +`# {{identity}}` which becomes `## You are Simon...` after the bump. That +reads oddly as a heading. + +### Better approach: identity as prose, not heading + +The identity skill should be **prose content**, not wrapped in a heading by +the personality skill. The runtime adds the `# title` and the identity +content flows as the opening paragraph: + +### personality skill (revised) + +```markdown +system: +{{identity}} + +# Personality + +You speak concisely and directly. +You favor technical precision. +You use dry humor sparingly. + +# Rules + +- Communicate through encrypted Nostr direct messages +- Keep responses concise and clear +... +``` + +### Assembled document (revised, cleaner) + +```markdown +system: +# Simon — Didactyl Agent + +You are Simon, a sovereign AI agent living on Nostr. + +- **npub**: `npub1kfc89dlu9m05tvfnry4l3jjacqpp930sz4kaghcmg9vrrrceg44qf80xae` +- **NIP-05**: simon@nostr + +> Not your keys, not your agent. + +## Personality + +You speak concisely and directly. +You favor technical precision. +You use dry humor sparingly. + +## Rules + +- Communicate through encrypted Nostr direct messages +- Keep responses concise and clear +- Be helpful and technically accurate +- If unsure, state uncertainty directly +- Use tools when a request requires taking action +- After a tool call, base your answer on the actual tool result +- Never claim a tool was run if no tool was executed +- Never reveal your private key (nsec) +- You may share your public key (npub) with anyone + +--- + +## Chat + +Respond helpfully to the admin. +Use tools when a request requires taking action. + +user: +Hey Simon, who mentioned me on Nostr today? +``` + +This reads like a proper document. The runtime adds `# Simon — Didactyl Agent` +as the document title. Identity content flows as the opening section. Each +skill's `#` headings get bumped to `##`. The `---` separates skills. + +--- + +## Formatter Registry + +The template engine uses formatters to convert tool JSON output to markdown +for context injection. Here are the formatters needed: + +| Template Variable | Tool Called | Formatter | Output | +|---|---|---|---| +| `{{agent_identity}}` | `agent_identity` | `format_agent_identity` | Prose with npub in backticks | +| `{{agent_profile}}` | `nostr_agent_profile` | `format_agent_profile` | Name extracted from kind 0 JSON | +| `{{admin_profile}}` | `nostr_admin_profile` | `format_admin_profile` | Name and about from kind 0 | +| `{{admin_notes}}` | `nostr_admin_notes` | `format_admin_notes` | Markdown list of recent notes | +| `{{admin_relays}}` | `nostr_admin_relays` | `format_admin_relays` | Markdown list of relay URLs | +| `{{dm_history}}` | built-in | `format_dm_history` | Markdown conversation list | +| `{{triggering_event}}` | `trigger_event` | `format_trigger_event` | Code block with JSON | + +### Example formatter: agent_identity + +Input (from tool): +```json +{"pubkey":"abc123...","npub":"npub1kfc89..."} +``` + +Output (markdown): +```markdown +- **npub**: `npub1kfc89dlu9m05tvfnry4l3jjacqpp930sz4kaghcmg9vrrrceg44qf80xae` +``` + +### Example formatter: dm_history + +Input (from DM history system): +```json +[ + {"role":"assistant","content":"Didactyl has started up...","created_at":1774263529}, + {"role":"user","content":"Good morning","created_at":1774263548}, + {"role":"assistant","content":"Good morning! How can I help?","created_at":1774263551} +] +``` + +Output (markdown): +```markdown +## Conversation History + +- **You**: Didactyl has started up and is online at 2026-03-23 09:22:48. +- **Admin**: Good morning +- **You**: Good morning! How can I help? +``` + +### Example formatter: triggering_event + +Input (from tool): +```json +{"type":"cron","filter":"0 12 * * *","created_at":1742641200} +``` + +Output (markdown): +````markdown +Triggering event: + +```json +{"type":"cron","filter":"0 12 * * *","created_at":1742641200} +``` +```` + +--- + +## Heading Bump Implementation + +Simple string operation on each line of skill content: + +```c +// For each line in skill content: +if (line starts with '#') { + output '#' + line; // prepend one more # +} else { + output line; +} +``` + +Layer 1 skills: bump once (# -> ##) +Layer 2 skills: bump twice (# -> ###) because they are embedded inside +a layer 1 skill that already got bumped once, plus one more for being layer 2. + +Actually, layer 2 skills get bumped once during their own resolution, and then +the containing layer 1 skill gets bumped once — so the layer 2 content +effectively gets bumped twice. This happens naturally if we bump during +template resolution. + +Wait — that depends on ordering. If we: +1. Resolve {{identity}} first (inserting identity content into personality) +2. Then bump personality's headings + +Then identity headings get bumped once (same as personality). That's correct +because identity content is now PART of personality. + +If we: +1. Bump identity headings first +2. Then resolve {{identity}} into personality +3. Then bump personality's headings + +Then identity headings get bumped twice. That's too much. + +**Correct order: resolve templates first, then bump headings.** + +This means layer 2 content gets the same heading level as the layer 1 skill +it's embedded in. If the skill author wants layer 2 content at a deeper +level, they wrap it in a heading in the layer 1 skill: + +```markdown +## Identity +{{identity}} + +## Personality +... +``` + +This is the simplest and most predictable behavior. + +--- + +## Summary of Design Decisions + +1. **Tools stay JSON** — No duplication. One tool, one output format. +2. **Formatter registry** — Template engine converts tool JSON to markdown + during `{{variable}}` resolution. Small, focused functions. +3. **Runtime owns h1** — Adds `# Agent Name` as document title. +4. **Heading bump** — All skill headings bumped one level (# -> ##). +5. **Resolve then bump** — Template variables resolved first, then heading + bump applied to the complete skill content. +6. **Skills separated by ---** — Horizontal rules between layer 1 skills. +7. **Role markers parsed** — `system:` / `user:` at line start split content + into API message roles. Unmarked content defaults to system. +8. **Context log shows markdown** — The assembled document is logged as + readable markdown, not raw JSON. diff --git a/plans/markdown_context_window.md b/plans/markdown_context_window.md new file mode 100644 index 0000000..1f4a750 --- /dev/null +++ b/plans/markdown_context_window.md @@ -0,0 +1,401 @@ +# Markdown Context Window — Implementation Plan + +The context window that the agent sees should be a nicely formatted markdown +document. Skills are the building blocks, and the runtime assembles them into +a coherent document with proper heading hierarchy, role markers for the LLM +API, and clean formatting throughout. + +See also: [example_context_v2.md](example_context_v2.md) for visual examples. + +--- + +## Design Decisions + +### 1. One generic JSON-to-markdown converter + +A single function converts any JSON value into readable markdown. No +per-tool formatters. Tools continue to return JSON; the template engine +calls the converter when injecting tool output into the markdown document. + +```c +char* json_to_markdown(const char* json_string); +``` + +Conversion rules: +- **String**: output as-is (inline) +- **Number**: output as-is (inline) +- **Boolean**: `true` / `false` +- **Null**: *(empty)* +- **Object**: bullet list with bold keys + - `- **key**: value` + - Nested objects indent one level +- **Array of primitives**: comma-separated inline or bullet list +- **Array of objects**: bullet list, each item shows its fields + +#### Examples + +**Simple object**: +```json +{"name":"Simon","npub":"npub1kfc89...","nip05":"simon@nostr"} +``` +```markdown +- **name**: Simon +- **npub**: npub1kfc89... +- **nip05**: simon@nostr +``` + +**Nested object**: +```json +{"agent":{"name":"Simon","npub":"npub1kfc89..."},"version":"v0.2.12"} +``` +```markdown +- **agent**: + - **name**: Simon + - **npub**: npub1kfc89... +- **version**: v0.2.12 +``` + +**Array of objects**: +```json +[{"content":"Deployed v0.2.12","created_at":1774263529},{"content":"Working on skills","created_at":1774263600}] +``` +```markdown +- **content**: Deployed v0.2.12 + - **created_at**: 1774263529 +- **content**: Working on skills + - **created_at**: 1774263600 +``` + +**Plain string** (already markdown): +```json +"You are Simon, a sovereign AI agent." +``` +```markdown +You are Simon, a sovereign AI agent. +``` + +#### Edge case: tool content that is already markdown + +Some tools may return markdown text in their `content` field (e.g., a skill +that already formats its own output). The converter detects this: if the +input is a plain JSON string (not an object/array), it passes through +unchanged. This means skill authors can return pre-formatted markdown from +tools if they want precise control. + +#### Edge case: very large JSON + +For large arrays (e.g., 50 Nostr events from a query), the converter should +truncate with a note: `*(... and 42 more items)*`. A reasonable default +limit is 8 items for arrays. + +### 2. Runtime owns h1, heading bump for skills + +- The runtime emits `# Agent Name` as the document title +- All headings in skill content get bumped one level: `#` → `##`, `##` → `###` +- Template variables are resolved BEFORE the heading bump +- Layer 2 skills (embedded via `{{skill_d_tag}}`) get the same bump as their + containing layer 1 skill (because they are resolved inline first) + +### 3. Role markers parsed + +Skill content can contain `system:` and `user:` markers at the start of a +line. The runtime splits on these markers to produce the API messages array. + +- `system:` — content goes into the system message +- `user:` — content goes into the user message +- `assistant:` — content goes into an assistant message (rare, for few-shot) +- No marker — defaults to `system:` +- Multiple skills can contribute `system:` sections (concatenated in order) +- Multiple `user:` sections get concatenated + +### 4. Skills separated by horizontal rules + +Layer 1 skills are separated by `---` in the assembled document. This gives +visual structure and helps the LLM distinguish between different instruction +blocks. + +### 5. Context log shows the markdown document + +The `append_context_log()` function logs the assembled markdown document +so you can inspect exactly what the agent sees, formatted as readable +markdown rather than raw JSON message arrays. + +--- + +## Implementation Steps + +### Step 1: Create json_to_markdown converter + +**File**: `src/json_to_markdown.c` / `src/json_to_markdown.h` + +New module with a single public function: + +```c +// Convert a JSON string to markdown text. +// Returns a malloc'd string. Caller frees. +// If input is a plain string (not object/array), passes through unchanged. +// Objects become bullet lists with bold keys. +// Arrays become bullet lists of items. +// Nested structures indent appropriately. +// Large arrays truncate at max_items with a note. +char* json_to_markdown(const char* json_string, int max_array_items); +``` + +Implementation: +- Parse JSON with cJSON +- Recursive walk of the JSON tree +- Build output string with the append_text pattern used elsewhere +- Handle: string, number, bool, null, object, array +- Indent nested structures with 2-space indentation +- Truncate arrays beyond max_array_items + +### Step 2: Create context_roles splitter + +**File**: `src/context_roles.c` / `src/context_roles.h` + +New module that splits a markdown document on role markers: + +```c +typedef struct { + char* system_content; // Everything under system: markers + char* user_content; // Everything under user: markers + char* assistant_content; // Everything under assistant: markers (rare) +} context_roles_t; + +// Split markdown text on role markers (system:, user:, assistant:) +// at the start of a line. Unmarked content defaults to system. +// Returns 0 on success, -1 on error. +int context_roles_split(const char* markdown, context_roles_t* out); + +void context_roles_free(context_roles_t* roles); +``` + +### Step 3: Create heading_bump utility + +**File**: `src/context_format.c` / `src/context_format.h` + +Utility functions for markdown formatting in context assembly: + +```c +// Bump all markdown headings in text by one level (# -> ##, ## -> ###, etc.) +// Returns a malloc'd string. Caller frees. +char* context_bump_headings(const char* text); + +// Build the runtime document title from agent identity. +// Returns "# Agent Name\n\n" as a malloc'd string. +char* context_build_title(tools_context_t* ctx); +``` + +### Step 4: Modify prompt_template_resolve_inline_variables + +**File**: `src/prompt_template.c` + +After extracting the `content` field from a tool result (line ~179), pass it +through `json_to_markdown()` before inserting into the template: + +Current code (simplified): +```c +cJSON* content = cJSON_GetObjectItemCaseSensitive(root, "content"); +if (content && cJSON_IsString(content)) { + replacement_owned = strdup(content->valuestring); +} +``` + +New code: +```c +cJSON* content = cJSON_GetObjectItemCaseSensitive(root, "content"); +if (content && cJSON_IsString(content)) { + // Try to convert JSON content to markdown + char* md = json_to_markdown(content->valuestring, 8); + replacement_owned = md ? md : strdup(content->valuestring); +} +``` + +If the content is already a plain string (not JSON), `json_to_markdown` +passes it through unchanged. If it is a JSON object/array, it gets +converted to a markdown bullet list. + +### Step 5: Modify build_context_from_triggers + +**File**: `src/agent.c` + +Update the context assembly function to: + +1. Resolve template variables (already done) +2. Bump headings in each skill's content +3. Build the document title +4. Concatenate with `---` separators +5. Return the complete markdown document (not yet split by roles) + +Current flow: +``` +for each matching skill: + expand = resolve_skill_references(skill.content) + append expand to output with --- separator +``` + +New flow: +``` +title = context_build_title(tools_ctx) +output = title + +for each matching skill: + expanded = resolve_skill_references(skill.content) + resolved = prompt_template_resolve_inline_variables(expanded) + bumped = context_bump_headings(resolved) + append bumped to output with --- separator +``` + +### Step 6: Modify agent_on_message and agent_on_trigger + +**File**: `src/agent.c` + +Update the callers of `build_context_from_triggers` to use role splitting: + +Current flow: +``` +dm_context = build_context_from_triggers(DM, ...) +// dm_context goes entirely into system message +// user message is the raw DM text +llm_chat(dm_context, message) +``` + +New flow: +``` +markdown_doc = build_context_from_triggers(DM, ...) +context_roles_split(markdown_doc, &roles) +// roles.system_content -> system message +// roles.user_content -> user message (or fall back to raw DM text) +llm_chat(roles.system_content, roles.user_content ?: message) +``` + +### Step 7: Update append_context_log + +**File**: `src/agent.c` + +Log the assembled markdown document instead of (or in addition to) the raw +JSON messages array. The log entry should show the readable markdown so you +can open `context.log.md` and see exactly what the agent sees. + +Current format: +``` +[{"role":"system","content":"...escaped..."},{"role":"user","content":"..."}] +``` + +New format: +```markdown +system: +# Simon — Didactyl Agent + +You are Simon... + +## Rules +- ... + +--- + +## Chat +Respond helpfully... + +user: +Hey Simon, who mentioned me today? +``` + +### Step 8: Update DM history formatting + +**File**: `src/nostr_handler.c` (or wherever DM history is built) + +The conversation history that gets injected into the context should be +formatted as a markdown list instead of inline JSON: + +Current: +``` +[{"role":"assistant","content":"Started up..."},{"role":"user","content":"Hello"}] +``` + +New: +```markdown +## Conversation History + +- **You**: Started up and is online at 2026-03-23 09:22:48. +- **Admin**: Hello +- **You**: Hello! How can I help? +``` + +This could be handled by the generic `json_to_markdown` converter if the +history is passed as JSON, or by a dedicated history formatter if we want +the cleaner `**You**` / `**Admin**` labels instead of `**role**`. + +Note: This is the one place where a small specialized formatter adds real +value — the generic converter would produce `**role**: assistant` / +`**content**: Started up...` which is less readable than `**You**: Started up...`. +A simple function that maps role names to display labels handles this. + +### Step 9: Update documentation + +**Files**: `docs/SKILLS.md`, `docs/CONTEXT.md` + +Update the documentation to reflect: +- Skill content is markdown with role markers +- The runtime assembles a coherent markdown document +- Heading bump behavior +- The generic JSON-to-markdown conversion for template variables +- Updated examples showing the new format + +--- + +## Files Changed + +| File | Change | +|------|--------| +| `src/json_to_markdown.c` | **NEW** — Generic JSON to markdown converter | +| `src/json_to_markdown.h` | **NEW** — Header for converter | +| `src/context_roles.c` | **NEW** — Role marker splitter | +| `src/context_roles.h` | **NEW** — Header for role splitter | +| `src/context_format.c` | **NEW** — Heading bump and title builder | +| `src/context_format.h` | **NEW** — Header for context formatting | +| `src/prompt_template.c` | **MODIFY** — Use json_to_markdown for template variable output | +| `src/agent.c` | **MODIFY** — Use heading bump, role splitting, updated context log | +| `src/nostr_handler.c` | **MODIFY** — Format DM history as markdown list | +| `Makefile` | **MODIFY** — Add new source files to build | +| `docs/SKILLS.md` | **MODIFY** — Update skill content format documentation | +| `docs/CONTEXT.md` | **MODIFY** — Update context assembly documentation | + +--- + +## What Does NOT Change + +- **Tool implementations** — All tools continue to return JSON. No changes + to any tool_*.c files. +- **LLM API interface** — Still sends OpenAI-compatible messages array. + The markdown is the content within the messages, not the transport format. +- **Runtime tool results** — Tool call/result messages during multi-turn + loops stay as JSON. Only template variable injection gets markdown conversion. +- **Skill event format on Nostr** — Skills are still stored as kind 31123/31124 + events. The content field is still markdown with role markers. No protocol change. +- **Adoption list** — No changes to kind 10123 or skill ordering. + +--- + +## Risk Assessment + +### Low risk +- `json_to_markdown` is a pure function with no side effects +- `context_roles_split` is a simple string splitter +- `context_bump_headings` is a line-by-line string operation +- All new code is additive — existing behavior preserved for skills without + role markers (defaults to system) + +### Medium risk +- Changing `prompt_template_resolve_inline_variables` to use json_to_markdown + could change the content of existing template variables. Need to verify that + existing skills still work correctly with markdown output instead of raw JSON. + Mitigation: json_to_markdown passes through plain strings unchanged, so + skills that already return non-JSON content are unaffected. + +### Testing approach +- Unit test json_to_markdown with various JSON inputs +- Unit test context_roles_split with various role marker patterns +- Unit test context_bump_headings with various heading levels +- Integration test: run existing skills and compare context.log.md output + before and after to verify no regressions diff --git a/restart_anvil_local.sh b/restart_anvil_local.sh new file mode 100755 index 0000000..652d9a5 --- /dev/null +++ b/restart_anvil_local.sh @@ -0,0 +1,42 @@ +#!/bin/bash + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +SERVICE_NAME="anvil.service" +SERVICE_SRC="$SCRIPT_DIR/anvil.service" +SERVICE_DST="/etc/systemd/system/$SERVICE_NAME" + +if ! command -v systemctl >/dev/null 2>&1; then + echo "ERROR: systemctl is not available on this system" + exit 1 +fi + +if [ "$EUID" -ne 0 ] && ! command -v sudo >/dev/null 2>&1; then + echo "ERROR: sudo is required when not running as root" + exit 1 +fi + +echo "==========================================" +echo " RESTART ANVIL LOCAL" +echo "==========================================" + +echo "Building debug static binary..." +"$SCRIPT_DIR/build_static.sh" --debug + +echo "Syncing service file to $SERVICE_DST..." +if [ "$EUID" -eq 0 ]; then + install -m 0644 "$SERVICE_SRC" "$SERVICE_DST" + systemctl daemon-reload + systemctl restart "$SERVICE_NAME" + systemctl --no-pager --full status "$SERVICE_NAME" || true +else + sudo install -m 0644 "$SERVICE_SRC" "$SERVICE_DST" + sudo systemctl daemon-reload + sudo systemctl restart "$SERVICE_NAME" + sudo systemctl --no-pager --full status "$SERVICE_NAME" || true +fi + +echo +echo "Anvil restarted." +echo "Context logs: /home/user/anvil/context.logs" diff --git a/src/agent.c b/src/agent.c index 9d0a5d0..f02e175 100644 --- a/src/agent.c +++ b/src/agent.c @@ -10,6 +10,8 @@ #include #include #include +#include +#include #include "llm.h" #include "nostr_handler.h" @@ -19,11 +21,24 @@ #include "cjson/cJSON.h" #include "debug.h" #include "../../nostr_core_lib/nostr_core/nostr_core.h" +#include "context_format.h" +#include "context_roles.h" +#include "json_to_markdown.h" static didactyl_config_t* g_cfg = NULL; static tools_context_t g_tools_ctx; static struct trigger_manager* g_trigger_manager = NULL; +typedef enum { + CONTEXT_DEBUG_OFF = 0, + CONTEXT_DEBUG_INIT = 1, + CONTEXT_DEBUG_FULL = 2 +} context_debug_mode_t; + +static context_debug_mode_t g_context_debug_mode = CONTEXT_DEBUG_OFF; +static char g_context_debug_run_stamp[32] = {0}; +static int g_context_debug_turn = 0; + #define AGENT_CONTEXT_PART_NAMES_MAX 512 static char* g_context_part_names[AGENT_CONTEXT_PART_NAMES_MAX]; static int g_context_part_names_count = 0; @@ -900,6 +915,62 @@ const char* agent_classify_message_part(cJSON* msg, int idx) { return detect_context_section(role_s, content_s, idx); } +static char* render_messages_json_as_markdown(const char* messages_json) { + if (!messages_json || messages_json[0] == '\0') { + return NULL; + } + + cJSON* root = cJSON_Parse(messages_json); + if (!root || !cJSON_IsArray(root)) { + cJSON_Delete(root); + return NULL; + } + + char* out = (char*)malloc(4096); + if (!out) { + cJSON_Delete(root); + return NULL; + } + size_t cap = 4096; + size_t used = 0; + out[0] = '\0'; + + int n = cJSON_GetArraySize(root); + for (int i = 0; i < n; i++) { + cJSON* msg = cJSON_GetArrayItem(root, i); + if (!msg || !cJSON_IsObject(msg)) { + continue; + } + + cJSON* role = cJSON_GetObjectItemCaseSensitive(msg, "role"); + const char* role_s = (role && cJSON_IsString(role) && role->valuestring) ? role->valuestring : "message"; + + char* msg_json = cJSON_PrintUnformatted(msg); + char* msg_md = msg_json ? json_to_markdown(msg_json, 8) : NULL; + const char* body = msg_md ? msg_md : (msg_json ? msg_json : ""); + + if (append_textf_local(&out, + &cap, + &used, + "### Message %d (%s)\n\n%s\n\n", + i + 1, + role_s, + body) != 0) { + free(msg_md); + free(msg_json); + free(out); + cJSON_Delete(root); + return NULL; + } + + free(msg_md); + free(msg_json); + } + + cJSON_Delete(root); + return out; +} + static void clear_context_part_names_locked(void) { for (int i = 0; i < g_context_part_names_count; i++) { free(g_context_part_names[i]); @@ -949,51 +1020,36 @@ static __attribute__((unused)) void template_emit_hook(const char* section_name, } static void append_context_log(const char* sender_pubkey_hex, const char* phase, const char* context_payload) { - if (!phase) { - return; - } - if (strcmp(phase, "llm_chat_with_tools_messages") != 0 && - strcmp(phase, "llm_trigger_with_tools_messages") != 0) { + (void)sender_pubkey_hex; + + if (!phase || g_context_debug_mode == CONTEXT_DEBUG_OFF) { return; } - time_t now = time(NULL); - struct tm tm_info; - localtime_r(&now, &tm_info); - char timestamp[32] = {0}; - strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", &tm_info); + int is_initial_phase = + (strcmp(phase, "llm_chat_with_tools_messages") == 0 || + strcmp(phase, "llm_trigger") == 0); + int is_turn_phase = + (strcmp(phase, "llm_chat_with_tools_turn") == 0 || + strcmp(phase, "llm_trigger_with_tools_turn") == 0); + + if (!is_initial_phase && !is_turn_phase) { + return; + } + if (g_context_debug_mode == CONTEXT_DEBUG_INIT && !is_initial_phase) { + return; + } - const char* safe_phase = phase ? phase : "unknown"; - const char* safe_sender = sender_pubkey_hex ? sender_pubkey_hex : "unknown"; const char* safe_payload = context_payload ? context_payload : ""; - size_t context_bytes = context_payload ? strlen(context_payload) : 0U; - size_t approx_tokens = context_bytes / 4U; - - 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; + char* turn_markdown = NULL; + if (is_turn_phase && context_payload && context_payload[0] == '[') { + turn_markdown = render_messages_json_as_markdown(context_payload); + if (turn_markdown) { + safe_payload = turn_markdown; + } } - int entry_len = snprintf(NULL, - 0, - "```text\n" - "Context Log - 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); + int entry_len = snprintf(NULL, 0, "%s\n\n", safe_payload); if (entry_len < 0) { return; } @@ -1002,26 +1058,7 @@ static void append_context_log(const char* sender_pubkey_hex, const char* phase, if (!entry) { return; } - - snprintf(entry, - (size_t)entry_len + 1U, - "```text\n" - "Context Log - 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); + snprintf(entry, (size_t)entry_len + 1U, "%s\n\n", safe_payload); char* old_data = NULL; size_t old_len = 0; @@ -1062,7 +1099,60 @@ static void append_context_log(const char* sender_pubkey_hex, const char* phase, fclose(out); free(old_data); + + if (mkdir("context.logs", 0755) != 0 && errno != EEXIST) { + free(entry); + return; + } + + time_t now = time(NULL); + struct tm tm_info; + localtime_r(&now, &tm_info); + + if (g_context_debug_run_stamp[0] == '\0') { + strftime(g_context_debug_run_stamp, sizeof(g_context_debug_run_stamp), "%Y%m%dT%H%M%S", &tm_info); + g_context_debug_turn = 0; + } + + char snapshot_path[256] = {0}; + if (is_initial_phase) { + snprintf(snapshot_path, + sizeof(snapshot_path), + "context.logs/%s_init.md", + g_context_debug_run_stamp); + } else { + g_context_debug_turn++; + snprintf(snapshot_path, + sizeof(snapshot_path), + "context.logs/%s_t%03d.md", + g_context_debug_run_stamp, + g_context_debug_turn); + } + + FILE* snapshot = fopen(snapshot_path, "wb"); + if (snapshot) { + (void)fwrite(safe_payload, 1, strlen(safe_payload), snapshot); + fclose(snapshot); + } + free(entry); + free(turn_markdown); +} + +int agent_set_context_debug_mode(const char* mode) { + if (!mode || mode[0] == '\0' || strcmp(mode, "off") == 0) { + g_context_debug_mode = CONTEXT_DEBUG_OFF; + } else if (strcmp(mode, "init") == 0) { + g_context_debug_mode = CONTEXT_DEBUG_INIT; + } else if (strcmp(mode, "full") == 0) { + g_context_debug_mode = CONTEXT_DEBUG_FULL; + } else { + return -1; + } + + g_context_debug_run_stamp[0] = '\0'; + g_context_debug_turn = 0; + return 0; } void agent_append_context_log(const char* sender_pubkey_hex, const char* phase, const char* context_payload) { @@ -1286,14 +1376,34 @@ static char* build_context_from_triggers(trigger_type_t trigger_type, (void)refresh_adopted_skills_cache_if_needed(); + char* title = context_build_title(&g_tools_ctx); size_t cap = 4096; size_t used = 0; char* out = (char*)malloc(cap); if (!out) { + free(title); return NULL; } out[0] = '\0'; + if (title) { + size_t title_len = strlen(title); + if (title_len + 1U > cap) { + cap = title_len + 1024; + char* grown = (char*)realloc(out, cap); + if (!grown) { + free(title); + free(out); + return NULL; + } + out = grown; + } + memcpy(out, title, title_len); + used = title_len; + out[used] = '\0'; + free(title); + } + int matched = 0; didactyl_sender_tier_t tier = DIDACTYL_SENDER_STRANGER; @@ -1329,12 +1439,16 @@ static char* build_context_from_triggers(trigger_type_t trigger_type, char* expanded = resolve_skill_references_local(matched_skill_contents[i] ? matched_skill_contents[i] : ""); const char* skill_text = expanded ? expanded : (matched_skill_contents[i] ? matched_skill_contents[i] : ""); - size_t need = strlen("\n\n---\n\n") + strlen(skill_text) + 1U; + char* bumped = context_bump_headings(skill_text); + const char* final_text = bumped ? bumped : skill_text; + + size_t need = strlen("\n\n---\n\n") + strlen(final_text) + 1U; if (used + need >= cap) { size_t next = cap; while (used + need >= next) next *= 2U; char* grown = (char*)realloc(out, next); if (!grown) { + free(bumped); free(expanded); for (int j = i; j < matched_skill_count; j++) { free(matched_skill_contents[j]); @@ -1350,19 +1464,23 @@ static char* build_context_from_triggers(trigger_type_t trigger_type, memcpy(out + used, "\n\n---\n\n", 7); used += 7; } - size_t sl = strlen(skill_text); - memcpy(out + used, skill_text, sl); + size_t sl = strlen(final_text); + memcpy(out + used, final_text, sl); used += sl; out[used] = '\0'; matched++; + free(bumped); free(expanded); free(matched_skill_contents[i]); } if (matched == 0) { - const char* fallback = "You are an AI agent. Respond to the message."; - size_t need = strlen(fallback) + 1U; + const char* fallback = "## Default Instructions\n\nYou are an AI agent. Respond to the message."; + const char* sep = (used > 0) ? "\n\n---\n\n" : ""; + size_t sep_len = strlen(sep); + size_t fb_len = strlen(fallback); + size_t need = used + sep_len + fb_len + 1U; if (need > cap) { char* grown = (char*)realloc(out, need); if (!grown) { @@ -1372,8 +1490,13 @@ static char* build_context_from_triggers(trigger_type_t trigger_type, out = grown; cap = need; } - memcpy(out, fallback, need); - used = need - 1U; + if (sep_len > 0) { + memcpy(out + used, sep, sep_len); + used += sep_len; + } + memcpy(out + used, fallback, fb_len); + used += fb_len; + out[used] = '\0'; } return out; @@ -1809,40 +1932,38 @@ void agent_on_trigger(const char* skill_d_tag, composed_context = strdup("You are an AI agent. Respond to the trigger event."); } - size_t system_len = strlen(composed_context ? composed_context : "") + 2 + strlen(trigger_prefix) + strlen(skill_d_tag) + + size_t full_len = strlen(composed_context ? composed_context : "") + 2 + strlen(trigger_prefix) + strlen(skill_d_tag) + strlen("\nRelay: ") + strlen(relay) + strlen("\n\nSkill instructions:\n") + - strlen(skill_content) + 1U; - char* system_prompt = (char*)malloc(system_len); - if (!system_prompt) { + strlen(skill_content) + strlen("\n\nuser:\nTriggering event JSON:\n") + strlen(event_json) + 1U; + char* full_markdown = (char*)malloc(full_len); + if (!full_markdown) { free(composed_context); free(event_json); return; } - snprintf(system_prompt, - system_len, - "%s\n\n%s%s\nRelay: %s\n\nSkill instructions:\n%s", + snprintf(full_markdown, + full_len, + "%s\n\n%s%s\nRelay: %s\n\nSkill instructions:\n%s\n\nuser:\nTriggering event JSON:\n%s", composed_context ? composed_context : "", trigger_prefix, skill_d_tag, relay, - skill_content); + skill_content, + event_json); free(composed_context); - size_t user_len = strlen("Triggering event JSON:\n") + strlen(event_json) + 1U; - char* user_prompt = (char*)malloc(user_len); - if (!user_prompt) { - free(system_prompt); + context_roles_t roles; + if (context_roles_split(full_markdown, &roles) != 0) { + free(full_markdown); free(event_json); return; } - snprintf(user_prompt, user_len, "Triggering event JSON:\n%s", event_json); - char* tools_json = tools_build_openai_schema_json(&g_tools_ctx); if (!tools_json) { - free(system_prompt); - free(user_prompt); + context_roles_free(&roles); + free(full_markdown); g_tools_ctx.template_trigger_event_json = prev_trigger_event_json; free(event_json); (void)nostr_handler_send_dm_auto(g_cfg->admin.pubkey, "Triggered skill execution failed: tools unavailable."); @@ -1851,21 +1972,21 @@ void agent_on_trigger(const char* skill_d_tag, cJSON* messages = cJSON_CreateArray(); if (!messages || !cJSON_IsArray(messages) || - append_simple_message(messages, "system", system_prompt) != 0 || - append_simple_message(messages, "user", user_prompt) != 0) { + append_simple_message(messages, "system", roles.system_content) != 0 || + append_simple_message(messages, "user", roles.user_content) != 0) { cJSON_Delete(messages); free(tools_json); - free(system_prompt); - free(user_prompt); + context_roles_free(&roles); + free(full_markdown); g_tools_ctx.template_trigger_event_json = prev_trigger_event_json; free(event_json); (void)nostr_handler_send_dm_auto(g_cfg->admin.pubkey, "Triggered skill execution failed: prompt initialization failed."); return; } - append_context_log(g_cfg->admin.pubkey, "llm_trigger", user_prompt); - free(system_prompt); - free(user_prompt); + append_context_log(g_cfg->admin.pubkey, "llm_trigger", full_markdown); + context_roles_free(&roles); + free(full_markdown); int max_turns = g_cfg->tools.trigger_max_turns > 0 ? g_cfg->tools.trigger_max_turns @@ -1880,7 +2001,7 @@ void agent_on_trigger(const char* skill_d_tag, break; } - append_context_log(g_cfg->admin.pubkey, "llm_trigger_with_tools_messages", messages_json); + append_context_log(g_cfg->admin.pubkey, "llm_trigger_with_tools_turn", messages_json); llm_response_t resp; int rc = llm_chat_with_tools_messages(messages_json, tools_json, "auto", &resp); @@ -2078,30 +2199,43 @@ void agent_on_message(const char* sender_pubkey_hex, dm_context = strdup("You are an AI agent. Respond to the message."); } + size_t full_len = strlen(dm_context) + strlen("\n\nuser:\n") + strlen(message) + 1U; + char* full_markdown = (char*)malloc(full_len); + if (!full_markdown) { + free(dm_context); + return; + } + snprintf(full_markdown, full_len, "%s\n\nuser:\n%s", dm_context, message); + free(dm_context); + + context_roles_t roles; + if (context_roles_split(full_markdown, &roles) != 0) { + free(full_markdown); + 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." : "You are responding in chat-only mode. Tool use is disabled."; - size_t ctx_len = strlen(dm_context ? dm_context : "") + strlen("\n\n") + strlen(tier_prefix) + 1U; + size_t ctx_len = strlen(roles.system_content ? roles.system_content : "") + strlen("\n\n") + strlen(tier_prefix) + 1U; char* system_for_chat = (char*)malloc(ctx_len); if (!system_for_chat) { - free(dm_context); + context_roles_free(&roles); + free(full_markdown); return; } - snprintf(system_for_chat, ctx_len, "%s\n\n%s", dm_context ? dm_context : "", tier_prefix); + snprintf(system_for_chat, ctx_len, "%s\n\n%s", roles.system_content ? roles.system_content : "", tier_prefix); - size_t context_len = strlen("system:\n\nuser:\n") + strlen(system_for_chat) + strlen(message) + 1U; - char* plain_context = (char*)malloc(context_len); - if (plain_context) { - snprintf(plain_context, context_len, "system:\n%s\n\nuser:\n%s", system_for_chat, message); - append_context_log(sender_pubkey_hex, "llm_chat", plain_context); - free(plain_context); - } + append_context_log(sender_pubkey_hex, "llm_chat", full_markdown); - char* response = llm_chat(system_for_chat, message); + char* response = llm_chat(system_for_chat, roles.user_content ? roles.user_content : message); free(system_for_chat); + context_roles_free(&roles); + free(full_markdown); + 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"); @@ -2114,23 +2248,25 @@ void agent_on_message(const char* sender_pubkey_hex, strlen(response) > 240 ? "..." : ""); (void)nostr_handler_send_dm_auto(sender_pubkey_hex, response); free(response); - free(dm_context); return; } char* tools_json = tools_build_openai_schema_json(&g_tools_ctx); if (!tools_json) { + context_roles_free(&roles); + free(full_markdown); (void)nostr_handler_send_dm_auto(sender_pubkey_hex, "Tool schema generation failed."); return; } cJSON* messages = cJSON_CreateArray(); if (!messages || - append_simple_message(messages, "system", dm_context ? dm_context : "You are an AI agent. Respond to the message.") != 0 || - append_simple_message(messages, "user", message) != 0) { + append_simple_message(messages, "system", roles.system_content ? roles.system_content : "You are an AI agent. Respond to the message.") != 0 || + append_simple_message(messages, "user", roles.user_content ? roles.user_content : message) != 0) { cJSON_Delete(messages); free(tools_json); - free(dm_context); + context_roles_free(&roles); + free(full_markdown); (void)nostr_handler_send_dm_auto(sender_pubkey_hex, "Failed to initialize conversation messages."); return; } @@ -2149,6 +2285,8 @@ void agent_on_message(const char* sender_pubkey_hex, char* final_answer_owned = NULL; int turns_run = 0; + append_context_log(sender_pubkey_hex, "llm_chat_with_tools_messages", full_markdown); + for (int turn = 0; turn < max_turns; turn++) { turns_run = turn + 1; char* messages_json = cJSON_PrintUnformatted(messages); @@ -2156,7 +2294,7 @@ void agent_on_message(const char* sender_pubkey_hex, break; } - append_context_log(sender_pubkey_hex, "llm_chat_with_tools_messages", messages_json); + append_context_log(sender_pubkey_hex, "llm_chat_with_tools_turn", messages_json); llm_response_t resp; int rc = llm_chat_with_tools_messages(messages_json, tools_json, "auto", &resp); @@ -2165,7 +2303,8 @@ void agent_on_message(const char* sender_pubkey_hex, (void)nostr_handler_send_dm_auto(sender_pubkey_hex, "LLM request failed."); cJSON_Delete(messages); free(tools_json); - free(dm_context); + context_roles_free(&roles); + free(full_markdown); return; } @@ -2218,7 +2357,8 @@ void agent_on_message(const char* sender_pubkey_hex, llm_response_free(&resp); cJSON_Delete(messages); free(tools_json); - free(dm_context); + context_roles_free(&roles); + free(full_markdown); (void)nostr_handler_send_dm_auto(sender_pubkey_hex, "Failed to append tool result."); return; } @@ -2275,7 +2415,8 @@ void agent_on_message(const char* sender_pubkey_hex, free(final_answer_owned); cJSON_Delete(messages); free(tools_json); - free(dm_context); + context_roles_free(&roles); + free(full_markdown); } void agent_cleanup(void) { diff --git a/src/agent.h b/src/agent.h index c300dcb..cbc172f 100644 --- a/src/agent.h +++ b/src/agent.h @@ -23,6 +23,7 @@ int agent_build_admin_messages_json(const char* current_user_message, char** out_messages_json); tools_context_t* agent_tools_context(void); const char* agent_classify_message_part(cJSON* msg, int idx); +int agent_set_context_debug_mode(const char* mode); void agent_append_context_log(const char* sender_pubkey_hex, const char* phase, const char* context_payload); void agent_cleanup(void); diff --git a/src/context_format.c b/src/context_format.c new file mode 100644 index 0000000..623996b --- /dev/null +++ b/src/context_format.c @@ -0,0 +1,87 @@ +#define _POSIX_C_SOURCE 200809L + +#include "context_format.h" +#include +#include +#include + +static int append_text(char** buf, size_t* cap, size_t* used, const char* s, size_t len) { + if (!buf || !cap || !used || !s) return -1; + if (*used + len + 1U > *cap) { + size_t next = *cap; + while (*used + len + 1U > next) { + next = (next == 0U) ? 256U : (next * 2U); + } + char* grown = (char*)realloc(*buf, next); + if (!grown) return -1; + *buf = grown; + *cap = next; + } + memcpy(*buf + *used, s, len); + *used += len; + (*buf)[*used] = '\0'; + return 0; +} + +char* context_bump_headings(const char* text) { + if (!text) return NULL; + + size_t cap = strlen(text) + 256; // Initial guess + size_t used = 0; + char* buf = (char*)malloc(cap); + if (!buf) return NULL; + buf[0] = '\0'; + + const char* p = text; + while (*p) { + const char* eol = strchr(p, '\n'); + size_t line_len = eol ? (size_t)(eol - p) : strlen(p); + + // Check if line starts with '#' + if (line_len > 0 && p[0] == '#') { + // Add an extra '#' + if (append_text(&buf, &cap, &used, "#", 1) != 0) { + free(buf); + return NULL; + } + } + + // Append the rest of the line + if (append_text(&buf, &cap, &used, p, line_len) != 0) { + free(buf); + return NULL; + } + + if (eol) { + if (append_text(&buf, &cap, &used, "\n", 1) != 0) { + free(buf); + return NULL; + } + p = eol + 1; + } else { + break; + } + } + + return buf; +} + +char* context_build_title(tools_context_t* ctx) { + if (!ctx || !ctx->cfg) return strdup("# Didactyl Agent\n\n"); + + const char* name = "Didactyl Agent"; + + // Try to get name from kind 0 profile if available + if (ctx->template_skill_lookup) { + // We don't have direct access to the parsed kind 0 here easily, + // but we can try to extract it from the agent_profile tool output + // if we really wanted to. For now, let's stick to a simple default + // or use the config if it has a name field (it doesn't currently). + // A more robust way would be to parse the kind 0 JSON here. + } + + // For now, just use a generic title. The identity skill will provide the rest. + char title[256]; + snprintf(title, sizeof(title), "# %s\n\n", name); + return strdup(title); +} diff --git a/src/context_format.h b/src/context_format.h new file mode 100644 index 0000000..4afbf90 --- /dev/null +++ b/src/context_format.h @@ -0,0 +1,9 @@ +#ifndef DIDACTYL_CONTEXT_FORMAT_H +#define DIDACTYL_CONTEXT_FORMAT_H + +#include "tools/tools.h" + +char* context_bump_headings(const char* text); +char* context_build_title(tools_context_t* ctx); + +#endif diff --git a/src/context_roles.c b/src/context_roles.c new file mode 100644 index 0000000..7788a19 --- /dev/null +++ b/src/context_roles.c @@ -0,0 +1,141 @@ +#define _POSIX_C_SOURCE 200809L + +#include "context_roles.h" +#include +#include +#include + +static int append_text(char** buf, size_t* cap, size_t* used, const char* s, size_t len) { + if (!buf || !cap || !used || !s) return -1; + if (*used + len + 1U > *cap) { + size_t next = *cap; + while (*used + len + 1U > next) { + next = (next == 0U) ? 256U : (next * 2U); + } + char* grown = (char*)realloc(*buf, next); + if (!grown) return -1; + *buf = grown; + *cap = next; + } + memcpy(*buf + *used, s, len); + *used += len; + (*buf)[*used] = '\0'; + return 0; +} + +int context_roles_split(const char* markdown, context_roles_t* out) { + if (!out) return -1; + memset(out, 0, sizeof(*out)); + + if (!markdown || markdown[0] == '\0') { + out->system_content = strdup(""); + out->user_content = strdup(""); + out->assistant_content = strdup(""); + return 0; + } + + size_t sys_cap = 1024, sys_used = 0; + size_t usr_cap = 1024, usr_used = 0; + size_t ast_cap = 1024, ast_used = 0; + + char* sys_buf = (char*)malloc(sys_cap); + char* usr_buf = (char*)malloc(usr_cap); + char* ast_buf = (char*)malloc(ast_cap); + + if (!sys_buf || !usr_buf || !ast_buf) { + free(sys_buf); + free(usr_buf); + free(ast_buf); + return -1; + } + + sys_buf[0] = '\0'; + usr_buf[0] = '\0'; + ast_buf[0] = '\0'; + + char** current_buf = &sys_buf; + size_t* current_cap = &sys_cap; + size_t* current_used = &sys_used; + + const char* p = markdown; + while (*p) { + const char* eol = strchr(p, '\n'); + size_t line_len = eol ? (size_t)(eol - p) : strlen(p); + + if (strncmp(p, "system:", 7) == 0 && (line_len == 7 || (line_len == 8 && p[7] == '\r'))) { + current_buf = &sys_buf; + current_cap = &sys_cap; + current_used = &sys_used; + } else if (strncmp(p, "user:", 5) == 0 && (line_len == 5 || (line_len == 6 && p[5] == '\r'))) { + current_buf = &usr_buf; + current_cap = &usr_cap; + current_used = &usr_used; + } else if (strncmp(p, "assistant:", 10) == 0 && (line_len == 10 || (line_len == 11 && p[10] == '\r'))) { + current_buf = &ast_buf; + current_cap = &ast_cap; + current_used = &ast_used; + } else { + if (append_text(current_buf, current_cap, current_used, p, line_len) != 0) { + free(sys_buf); + free(usr_buf); + free(ast_buf); + return -1; + } + if (eol) { + if (append_text(current_buf, current_cap, current_used, "\n", 1) != 0) { + free(sys_buf); + free(usr_buf); + free(ast_buf); + return -1; + } + } + } + + if (!eol) break; + p = eol + 1; + } + + // Trim trailing newlines + while (sys_used > 0 && (sys_buf[sys_used - 1] == '\n' || sys_buf[sys_used - 1] == '\r')) { + sys_buf[--sys_used] = '\0'; + } + while (usr_used > 0 && (usr_buf[usr_used - 1] == '\n' || usr_buf[usr_used - 1] == '\r')) { + usr_buf[--usr_used] = '\0'; + } + while (ast_used > 0 && (ast_buf[ast_used - 1] == '\n' || ast_buf[ast_used - 1] == '\r')) { + ast_buf[--ast_used] = '\0'; + } + + // Trim leading newlines + char* sys_start = sys_buf; + while (*sys_start == '\n' || *sys_start == '\r') sys_start++; + if (sys_start > sys_buf) { + memmove(sys_buf, sys_start, sys_used - (sys_start - sys_buf) + 1); + } + + char* usr_start = usr_buf; + while (*usr_start == '\n' || *usr_start == '\r') usr_start++; + if (usr_start > usr_buf) { + memmove(usr_buf, usr_start, usr_used - (usr_start - usr_buf) + 1); + } + + char* ast_start = ast_buf; + while (*ast_start == '\n' || *ast_start == '\r') ast_start++; + if (ast_start > ast_buf) { + memmove(ast_buf, ast_start, ast_used - (ast_start - ast_buf) + 1); + } + + out->system_content = sys_buf; + out->user_content = usr_buf; + out->assistant_content = ast_buf; + + return 0; +} + +void context_roles_free(context_roles_t* roles) { + if (!roles) return; + free(roles->system_content); + free(roles->user_content); + free(roles->assistant_content); + memset(roles, 0, sizeof(*roles)); +} diff --git a/src/context_roles.h b/src/context_roles.h new file mode 100644 index 0000000..754868c --- /dev/null +++ b/src/context_roles.h @@ -0,0 +1,13 @@ +#ifndef DIDACTYL_CONTEXT_ROLES_H +#define DIDACTYL_CONTEXT_ROLES_H + +typedef struct { + char* system_content; + char* user_content; + char* assistant_content; +} context_roles_t; + +int context_roles_split(const char* markdown, context_roles_t* out); +void context_roles_free(context_roles_t* roles); + +#endif diff --git a/src/json_to_markdown.c b/src/json_to_markdown.c new file mode 100644 index 0000000..ba795b9 --- /dev/null +++ b/src/json_to_markdown.c @@ -0,0 +1,142 @@ +#define _POSIX_C_SOURCE 200809L + +#include "json_to_markdown.h" +#include "cjson/cJSON.h" +#include +#include +#include + +static int append_text(char** buf, size_t* cap, size_t* used, const char* s) { + if (!buf || !cap || !used || !s) return -1; + size_t n = strlen(s); + if (*used + n + 1U > *cap) { + size_t next = *cap; + while (*used + n + 1U > next) { + next = (next == 0U) ? 256U : (next * 2U); + } + char* grown = (char*)realloc(*buf, next); + if (!grown) return -1; + *buf = grown; + *cap = next; + } + memcpy(*buf + *used, s, n); + *used += n; + (*buf)[*used] = '\0'; + return 0; +} + +static void print_indent(int indent_level, char** buf, size_t* cap, size_t* used) { + for (int i = 0; i < indent_level; i++) { + append_text(buf, cap, used, " "); + } +} + +static void render_json_node(cJSON* node, int indent_level, int is_list_item, char** buf, size_t* cap, size_t* used, int max_array_items) { + if (!node) return; + + if (cJSON_IsString(node)) { + if (is_list_item) { + print_indent(indent_level, buf, cap, used); + append_text(buf, cap, used, "- "); + } + append_text(buf, cap, used, node->valuestring ? node->valuestring : ""); + append_text(buf, cap, used, "\n"); + } else if (cJSON_IsNumber(node)) { + if (is_list_item) { + print_indent(indent_level, buf, cap, used); + append_text(buf, cap, used, "- "); + } + char num_str[64]; + if (node->valuedouble == (double)node->valueint) { + snprintf(num_str, sizeof(num_str), "%d", node->valueint); + } else { + snprintf(num_str, sizeof(num_str), "%f", node->valuedouble); + } + append_text(buf, cap, used, num_str); + append_text(buf, cap, used, "\n"); + } else if (cJSON_IsBool(node)) { + if (is_list_item) { + print_indent(indent_level, buf, cap, used); + append_text(buf, cap, used, "- "); + } + append_text(buf, cap, used, cJSON_IsTrue(node) ? "true\n" : "false\n"); + } else if (cJSON_IsNull(node)) { + if (is_list_item) { + print_indent(indent_level, buf, cap, used); + append_text(buf, cap, used, "- null\n"); + } + } else if (cJSON_IsObject(node)) { + cJSON* child = node->child; + int first = 1; + while (child) { + if (is_list_item && first) { + print_indent(indent_level, buf, cap, used); + append_text(buf, cap, used, "- **"); + first = 0; + } else { + print_indent(indent_level + (is_list_item ? 1 : 0), buf, cap, used); + append_text(buf, cap, used, "- **"); + } + append_text(buf, cap, used, child->string ? child->string : "unknown"); + append_text(buf, cap, used, "**: "); + + if (cJSON_IsObject(child) || cJSON_IsArray(child)) { + append_text(buf, cap, used, "\n"); + render_json_node(child, indent_level + (is_list_item ? 2 : 1), 0, buf, cap, used, max_array_items); + } else { + render_json_node(child, 0, 0, buf, cap, used, max_array_items); + } + child = child->next; + } + } else if (cJSON_IsArray(node)) { + int count = cJSON_GetArraySize(node); + int limit = (max_array_items > 0 && count > max_array_items) ? max_array_items : count; + for (int i = 0; i < limit; i++) { + cJSON* item = cJSON_GetArrayItem(node, i); + render_json_node(item, indent_level, 1, buf, cap, used, max_array_items); + } + if (count > limit) { + print_indent(indent_level, buf, cap, used); + char trunc_msg[64]; + snprintf(trunc_msg, sizeof(trunc_msg), "- *(... and %d more items)*\n", count - limit); + append_text(buf, cap, used, trunc_msg); + } + } +} + +char* json_to_markdown(const char* json_string, int max_array_items) { + if (!json_string) return NULL; + + cJSON* root = cJSON_Parse(json_string); + if (!root) { + // If it's not valid JSON, just return a copy of the string + return strdup(json_string); + } + + // If it's a plain string, return it without markdown formatting + if (cJSON_IsString(root)) { + char* out = strdup(root->valuestring ? root->valuestring : ""); + cJSON_Delete(root); + return out; + } + + size_t cap = 1024; + size_t used = 0; + char* buf = (char*)malloc(cap); + if (!buf) { + cJSON_Delete(root); + return NULL; + } + buf[0] = '\0'; + + render_json_node(root, 0, 0, &buf, &cap, &used, max_array_items); + + cJSON_Delete(root); + + // Trim trailing newline if present + if (used > 0 && buf[used - 1] == '\n') { + buf[used - 1] = '\0'; + } + + return buf; +} diff --git a/src/json_to_markdown.h b/src/json_to_markdown.h new file mode 100644 index 0000000..4919d4f --- /dev/null +++ b/src/json_to_markdown.h @@ -0,0 +1,6 @@ +#ifndef DIDACTYL_JSON_TO_MARKDOWN_H +#define DIDACTYL_JSON_TO_MARKDOWN_H + +char* json_to_markdown(const char* json_string, int max_array_items); + +#endif diff --git a/src/main.c b/src/main.c index dc91cf7..15fd70d 100644 --- a/src/main.c +++ b/src/main.c @@ -107,6 +107,8 @@ static void print_usage(const char* prog) { " Enable HTTP API and bind address (example: 127.0.0.1).\n" " --debug <0-5>\n" " Log level (0=fatal, 1=error, 2=warn, 3=info, 4=debug, 5=trace).\n" + " --context-debug \n" + " Context snapshot mode (off=none, init=initial only, full=initial+every turn).\n" " --dump-schemas\n" " Print tool schemas JSON and exit.\n" " --test-tool \n" @@ -115,6 +117,8 @@ static void print_usage(const char* prog) { "Environment:\n" " DIDACTYL_NSEC\n" " Fallback private key when --nsec is not provided.\n" + " DIDACTYL_CONTEXT_DEBUG\n" + " Context snapshot mode override: off, init, or full.\n" "\n" "Examples:\n" " 1) Genesis-based startup\n" @@ -931,6 +935,7 @@ int main(int argc, char** argv) { int dump_schemas = 0; const char* test_tool_name = NULL; const char* test_tool_args = "{}"; + const char* context_debug_mode = "off"; didactyl_config_t cfg; memset(&cfg, 0, sizeof(cfg)); @@ -995,6 +1000,8 @@ int main(int argc, char** argv) { api_port_override = atoi(argv[++i]); } else if (strcmp(argv[i], "--api-bind") == 0 && i + 1 < argc) { api_bind_override = argv[++i]; + } else if (strcmp(argv[i], "--context-debug") == 0 && i + 1 < argc) { + context_debug_mode = argv[++i]; } else if (strcmp(argv[i], "--dump-schemas") == 0) { dump_schemas = 1; } else if (strcmp(argv[i], "--test-tool") == 0 && i + 2 < argc) { @@ -1057,6 +1064,22 @@ int main(int argc, char** argv) { DEBUG_INFO("[didactyl] startup phase: admin override applied from --admin (%.16s...)", cfg.admin.pubkey); } + { + const char* env_context_debug = getenv("DIDACTYL_CONTEXT_DEBUG"); + if (env_context_debug && env_context_debug[0] != '\0' && + strcmp(context_debug_mode, "off") == 0) { + context_debug_mode = env_context_debug; + } + + if (agent_set_context_debug_mode(context_debug_mode) != 0) { + fprintf(stderr, "Invalid context debug mode '%s' (expected off, init, or full)\n", context_debug_mode); + config_free(&cfg); + nostr_cleanup(); + return 1; + } + DEBUG_INFO("[didactyl] context debug mode: %s", context_debug_mode); + } + if (config_ensure_startup_skill_adoption(&cfg) != 0) { fprintf(stderr, "Failed to synthesize startup skill adoption events\n"); config_free(&cfg); diff --git a/src/main.h b/src/main.h index 48ecfb2..f662162 100644 --- a/src/main.h +++ b/src/main.h @@ -12,8 +12,8 @@ // Using DIDACTYL_ prefix to avoid conflicts with nostr_core_lib VERSION macros #define DIDACTYL_VERSION_MAJOR 0 #define DIDACTYL_VERSION_MINOR 2 -#define DIDACTYL_VERSION_PATCH 20 -#define DIDACTYL_VERSION "v0.2.20" +#define DIDACTYL_VERSION_PATCH 21 +#define DIDACTYL_VERSION "v0.2.21" // Agent metadata #define DIDACTYL_NAME "Didactyl" diff --git a/src/nostr_handler.c b/src/nostr_handler.c index 6abbb53..0f9dc80 100644 --- a/src/nostr_handler.c +++ b/src/nostr_handler.c @@ -3902,6 +3902,92 @@ char* nostr_handler_get_dm_history_json(const char* peer_pubkey_hex, int limit) return out ? out : strdup("[]"); } +char* nostr_handler_get_dm_history_markdown(const char* peer_pubkey_hex, int limit) { + if (!peer_pubkey_hex || strlen(peer_pubkey_hex) != 64U) { + return strdup(""); + } + + int max_take = limit > 0 ? limit : 64; + if (max_take > DM_HISTORY_RING_SIZE) { + max_take = DM_HISTORY_RING_SIZE; + } + + size_t cap = 1024; + size_t used = 0; + char* buf = (char*)malloc(cap); + if (!buf) return NULL; + buf[0] = '\0'; + + pthread_mutex_lock(&g_dm_history_mutex); + + int total = g_dm_history_count; + int matches = 0; + for (int i = 0; i < total; i++) { + int idx = (g_dm_history_count < DM_HISTORY_RING_SIZE) + ? i + : (g_dm_history_next + i) % DM_HISTORY_RING_SIZE; + dm_history_entry_t* entry = &g_dm_history_ring[idx]; + if (entry->peer_pubkey_hex[0] == '\0' || !entry->content) { + continue; + } + if (strcmp(entry->peer_pubkey_hex, peer_pubkey_hex) == 0) { + matches++; + } + } + + int skip = matches > max_take ? (matches - max_take) : 0; + int seen = 0; + for (int i = 0; i < total; i++) { + int idx = (g_dm_history_count < DM_HISTORY_RING_SIZE) + ? i + : (g_dm_history_next + i) % DM_HISTORY_RING_SIZE; + dm_history_entry_t* entry = &g_dm_history_ring[idx]; + if (entry->peer_pubkey_hex[0] == '\0' || !entry->content) { + continue; + } + if (strcmp(entry->peer_pubkey_hex, peer_pubkey_hex) != 0) { + continue; + } + if (seen++ < skip) { + continue; + } + + const char* role_label = "**You**"; + if (entry->role == DIDACTYL_DM_HISTORY_USER) { + role_label = "**Admin**"; + } else if (entry->role == DIDACTYL_DM_HISTORY_TOOL_REQUEST) { + role_label = "**Admin (Tool Request)**"; + } else if (entry->role == DIDACTYL_DM_HISTORY_TOOL_RESPONSE) { + role_label = "**You (Tool Response)**"; + } + + size_t need = strlen("- ") + strlen(role_label) + strlen(": ") + strlen(entry->content) + 2U; + if (used + need > cap) { + size_t next = cap; + while (used + need > next) next *= 2U; + char* grown = (char*)realloc(buf, next); + if (!grown) { + pthread_mutex_unlock(&g_dm_history_mutex); + free(buf); + return NULL; + } + buf = grown; + cap = next; + } + + snprintf(buf + used, cap - used, "- %s: %s\n", role_label, entry->content); + used += strlen(buf + used); + } + + pthread_mutex_unlock(&g_dm_history_mutex); + + if (used > 0 && buf[used - 1] == '\n') { + buf[used - 1] = '\0'; + } + + return buf; +} + char* nostr_handler_get_admin_kind0_context(void) { if (!g_cfg || !g_cfg->admin_context.enabled || !g_cfg->admin_context.track_kind_0) { return NULL; diff --git a/src/nostr_handler.h b/src/nostr_handler.h index 25faa2a..009c80b 100644 --- a/src/nostr_handler.h +++ b/src/nostr_handler.h @@ -102,6 +102,7 @@ int nostr_handler_dm_history_remember(const char* peer_pubkey_hex, const char* content, didactyl_dm_history_role_t role); char* nostr_handler_get_dm_history_json(const char* peer_pubkey_hex, int limit); +char* nostr_handler_get_dm_history_markdown(const char* peer_pubkey_hex, int limit); void nostr_handler_cleanup(void); #endif \ No newline at end of file diff --git a/src/prompt_template.c b/src/prompt_template.c index 2c94e56..8fdaf20 100644 --- a/src/prompt_template.c +++ b/src/prompt_template.c @@ -2,6 +2,7 @@ #include "prompt_template.h" #include "cjson/cJSON.h" +#include "json_to_markdown.h" #include #include @@ -177,9 +178,15 @@ char* prompt_template_resolve_inline_variables(const char* tpl, tools_context_t* if (!success || !cJSON_IsBool(success) || cJSON_IsTrue(success)) { cJSON* content = cJSON_GetObjectItemCaseSensitive(root, "content"); if (content && cJSON_IsString(content) && content->valuestring) { - replacement_owned = strdup(content->valuestring); + char* md = json_to_markdown(content->valuestring, 8); + replacement_owned = md ? md : strdup(content->valuestring); } else if (content) { - replacement_owned = cJSON_PrintUnformatted(content); + char* raw = cJSON_PrintUnformatted(content); + if (raw) { + char* md = json_to_markdown(raw, 8); + replacement_owned = md ? md : raw; + if (md) free(raw); + } } } } diff --git a/src/tools/tool_agent.c b/src/tools/tool_agent.c index fa1b6a6..e1a4d52 100644 --- a/src/tools/tool_agent.c +++ b/src/tools/tool_agent.c @@ -410,7 +410,7 @@ char* execute_nostr_dm_history(tools_context_t* ctx, const char* args_json) { continue; } - const char* prefix = (strcmp(role->valuestring, "user") == 0) ? "User: " : "Assistant: "; + const char* prefix = (strcmp(role->valuestring, "user") == 0) ? "- **Admin**: " : "- **You**: "; if (append_text_local(&rendered, &cap, &used, prefix) != 0 || append_text_local(&rendered, &cap, &used, content->valuestring) != 0 || append_text_local(&rendered, &cap, &used, "\n") != 0) { diff --git a/test_pool b/test_pool new file mode 100755 index 0000000000000000000000000000000000000000..6f48606b9e28f927372abb28bcf1502f41109fdc GIT binary patch literal 188760 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%0Dsg7trdj&fvS5C3PH5gOx71v5`KViN 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 z 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

un`DV0>s$s7$fbTj)^qdC%I`H{xgufU3UEs&} z6Mh-^$G{JS?D-S$W79=`0pwo=Klm}>2L;UQ;JfY@LgSx(XyR0}&taCQ`Tigj08XwU ze=hNLv`!6T-k~}sUPHbQ`bQF?ST_QL;LRh#{~C(!13$W1_yO?0sGi(pAPuP=wPav{x4I&6CpbcVZDa9nPK&4s*Il`ej2|$ z7@xzDySCS-Q0^e+wa%mETA$E(i-8~CU)sfcM!wcrUhac`zY6&V#FH9VY@kL}Yu9a% z?^-VUS3}vu;M;#B{8Qjxs8Q~lkRN$UF81y#~e?+iB zxL*c&vqkjScU#l5Zp%;1unzJg2SXkr*Mbkn?YDw&K2+rAK>qtR^pDoyUsF8~i5}iZ z@wGq2-PP=Wg5}9Re-gQMoqTx4-*JdFIvd4@)6$CX2l8<33CLw8=9{=Q=3XbVI%YPCd2B|(kKQH(+>_Y`e(*np*Zz8}#=PU-bFbDfvn)^J>La43 z3H6x`z6f9K94PYlL4G6n@lN42PIwgjXed6}RzpAk zraSwS2FUZd3}1aVPLTWH->Gzj!Sd~)`NF^Xe&FN-jD9|$d0wLlRn4A9SUoCL#?L^1 z#(8Jxa&gj&9DQX`SEoF__2dTkFNW}%45O5Ld4C8>OV>3?eA05vu7<2 zp#W@y{LmR9Z+}ado}W(}v})~r7xE1#mzG6)O|^btsu>K$W5+4a2Q_rPXaCZ^1pF`` zu+TMR|I+V_SRRQ0++oLmfWYkqHRQjb`adlCzXSPe!AB4WUIhLD@MDNWTEIV7L;stQ zAIE&u@t>(t&pKN2dAtn`OMTRDH&`Bp1>m6?aZVfbG>78Dv%t5{6#WlESFVQs0mzSk zNaRn2{08tNM+yHJ_{}x+j6!~Fs>nYM`O~IVA3tx^&@s-6+Mff=My#bTuyNp z+q3y~5!BeA6?z6nM32t9WDPz2kRQBI990Dg<|q4EC* z)gSURZ(1Ii1F$DeI&Rk};tjRuq2Rl)U(mR;5q$GGQqOy!YnkN(ig6pS!LL*O*r!l8 z*=taF#Nj>QPv`x`WLliZKmZ=TL` zXIXxto|B2^bv(RJSpq$SSTEEMN5MD1KR*kV{ou#06g%s>`7PCx5&l#G^APw5{3N*x zd;Ls#*kKRwf3!Rj0obMT@H1-vL+tqgou|Rh6b9IHft~|=!+n$Hex{vkrOW3jFA~q^3E_S8Amj(WA`13`o_oOO zL*xGkR?kfHcIaHSO%twzYVapmp2k~` z6li|~n{uC4L;gI-4>yTCwtc1mz9B6<~z^A_|ppDg-+kF2-S2cXsLGuiTFhmb!%68ylKq6co#ECk;Gf2H&8%o_T$ zs=r(G>_UC60^i&wd;|{nmKu77AwPh9ke+)#4L*3IocOxHJc77+ zAM(v-Nqzo;2QzHEP4VH+g}1+DMbAzI9|_IVB{kynvmtM=ULObjYir0~3HgT5dUO}~ z_Mb|9^jzf`)gRiAyaK)s<5vCQxaxs@)E^dW?Bk}<7rr?DX*gTTg}XHM;OnqYuSdI1 z0pEp<@@e2ZEssP1dTQ_&LeBv9-R;P84fqknT}Ohy4ZK0z`zrVm@DZ#FI&QatA4A;o zJIL<_KYX{e*ZwXlJ==Gt*kK6%O~=(N@WYtD`nm84;2Y5ITF-XydCV{MC+CA7!?@D; z*#qDHu+(Qy0dphq5!5*d4?}(k``kf1d)@NwkH3NZdzKGsmiwuPRre+KG{y-}D1!O%1{4mZXhQO}?-;Q|f67cU$udd&L{2=se{ICwZL0sWM&vz}4L;xO# z{K#^t=V_DW;j_wz=JhM!2OkspHPH1Q_@U>7r)g!cy2E9h3|=ODEBxCDmWNOP&LBP# zNb7Bv>Vbd5a&C&?^OzSD7TW8w8hUPnd>76!G`@NU{J_Vhp1M!jW_hhe8Q+2Y`1eE} zUsEvC=wPJU`qEuvKXp_M`A=J(&cR&K5A&IGz_-Jn&^Fv&7lCg;e5m`=jo=4DapUur zFSkeU-h%wdVN$Ms-(y~l@xRBvN&mKoKKC&l@q*u2pEHgVjinqv+HHK-^PPMym#&YS zZfA|>xH->tJlD;dY;KK{@k}mjITOznvp(mk&qoV>eS>|_=VpD=>m=M{w3zmtf}4)^ zdxlE!{9-amIumZe?M`{VTX6i06Hn)|u1Ccua!z+T7mKEygr6&TPPDj|3drQsuJ0yD zcU1`|naZY|XrU18cc|P#zeyIN8P`b^GnszM5=c2D=~sxxPwQB@f+|Fnrv~=8Yv+MYz6{- z(R2~Q_3>QR^YevVo|=QU_T+NCo{1(BHjm@yprV?rTPWlTrq3;Ssa%#+*!nxIE?cNR zor_1)(R3>6p%GFrWQzH8DjxM+>W+9&R30xRGe-**I*NWO?U8M~p6Kj(O^#oPX1!#t zkTJ`cn%6ixNKw*wTs#?zj1ZeM+qm&WkJA(NdYoi28~3TgPCS?Ich;I%)N`BWI`JN| zx>?ZHK4-pj*2-0_%iB6SslN+cMH(?W0L9qg$bMNeFDWyhFS><($Lr6=X{5!9-A+2! zO`~-J9goI)+=QWFl7Ny}zf&xvO~K9N`n0fQA(wIT#aKE;70%_|Lev%lnTZd$tK-E& zfrgypr)a#Z7j*N)?SuE6c^DuyB%mMd2&kWJ+q8s}46V{W`>&TO`6eTR@r68F~{a4Ls`DGm>ZVY4)^{FI!CWW2JR%hJ{FDB9w#a zg`H=U`CT{kNR+jCodt4)p|*2}`|etrO}3VJoR9S;lCzzjOf>F@w=p!hvx#E9dCBs& z1q+?o^nKxZ5GHfYo$ZjfOVRjsY1#0592&Xen~QOueKp*iEu+Ia_f;;rcCv&^c8&Y} zd6!&oI=6;q0XfoADRU~ykafZqK;4{9yV=sbgPY_f#&~vRNS2h8t4d?kdX7?NsaH!P zLEY1tgM}x|*fsbCSv@Bh7na~iBkH4o9vXh+&*Oa+%AB^9q$fe1k-TLBqVYn1o)&G} z@uHhbv@348XSma}XBKv_!Q(xZ2AA%lPVTT{bhQl~L@QUxp@~w6#~TxA$a9E6HOj{G{S_82Nq7`c2aA*kQll)b zv9>R5R8$&)(irP!L|UCOEmOh=bI23Wq6P_h`)6&}YKLEf2h`+fDYc z1GdWAA!S!|IVE_&`fJ>1uS3z3>b6m7xo;3D=u=&6ozfg(CFOC$&Ws&dGHm8x_XIrc z(kS}dny6esUC-DRAPqdkUO&_=|2 zsJAG_rg>GGCKH$Fg%n_SP>`1LDN-^V=ml0ZUW}xq%GF)QoIGjsBxN^O)Q{;@jMibF zws;PY|5CrtnX45mS(n59l78Noa6`(`LuJ*Zb9pEfQhhurxJzUufeu>FRunTaw@~s9 zl7~Z9HQ6q50#`6te&w^ViC2EY>R20Po@pd2Ph!Ld}H z!a&-=au7-jhAvpLl-U4O93D-P;B;)9%aJ^9UMeal<+bxpa)I4!me!R}R8w{rQV)5| z6Gql>T_7tw7=`H}_a&Ljqt%lUMFYwXD%!UN)dzj0x=3<)@m`K%c-vEIOXG>?ur_aT zG)+5#pdnjxw6St!NLA!2kHrqxmwsOArYY`6_P|_d%Zfv)96;3FK_i#-(YWD(SL(ge z@bPG~9O?sUaUd4q5DiK4tWKInW;V`;2`P$X=FfLL+UjP@;oXU)k%V~O30M`QIcZml zlDWzeVW8OZ))fjR#hlnieP88WhKHyHaj%i zY58R*LWWRpEAxQotKB;D&V@q*8%W1OAvE_Axuoi=a=-w*VVz*h@D=aHsPti~0)?`Di4w_On%-q|yJ}88z7oY0%(0U*1&(gV4 zMGrbo=jjWvITI5~(4sD{!ljbqFZleVEi3$l;>*L1t6tPr1q_s+gSkRZ19@JpDt7Xs zk*vU)0yaqUX1Jo}B3X6=6>Fj$c40Fwr@an2JG(*$y{ojFrkkDirD+Cp=&93$UbStt zE2LwiOBImACL3L{;oWRy1YT+(e5pz1i+*{xRx$srl}i;0x41m4g3Xl)B%TOW3Yfp# zLFp)+kfIKJvL#!K;4}*Cbpsmc?5E^gxRdEv)NZuxEW>?ySq`lKLDh@XxoE{4)mqc+ z3CeZIA*O`#72V&Lqy;zYmt9!daBbd-BI%)R4z;Nw6D(+Z2<24x=isQWSS8~$ zxQQ_V2a0s?BmqWstILuKTk*^{bl9sp+tn4w7_|;TL}^&csu3OrGR%s3J}|KRu+WBq z-J~wpx}wU#h*%-er8v5axu)o)UANlC16#y+S`Mm2h4nZU=YHbJClo+OlBGlRU>{I$ zMn{U|5Zq|_C;)w~JM)RI-X6=+VFmAcX#N*m-Ao3W#QO;>fPB7+F5y0%?_{!cY?@8Q z#qG=74Gub4{a3ZniIsi$uY7Ls6cz>PMuQKP$=}dPQ&oqu)CxK_m-DQ^E>ZMp<+MJ2q2hpx8p~UhQe!0(ZQFVG9_*|ou3z5ZRmN13 zC)C@N-7fLc)vM(b2v{gEaKJ(Kpo0nI_P03z_h0+WOm*yOUZT{Nrtg!yC}!H;xn^+@*MR!-ofaWPe_olymHby zLW#h>MKm*A-lS1;WKFSEfDVb^#bPwuxMFcCuXGmSv~`}&8x4F^Mxh3OP}UixP`X+z zaEv3n;!Lq}DG*ahOWNp&a;g2$iHf9ZY)cQj8tn6sWO0@WUAs6R3-Q*#~!_cX{6e9X;z)puwAQ7qY zFr@{JyZfJXwgfEnA)ehN%jZ?)T{9Y9u^`I?w;}(ULdp-H%1gx(6lC$|m05CN4oB}M zDZ)XY%Z;WGl1@ImLyZl#&aP-t`ZUWn7Hza79mUArmlsv~G>e9{{cO0xI99Sed1W`G zd#?%Qlm%@prkx~#nC#SM&*5Q4r~0K4KIbIZsZ|40s%sn^`A!afO1{(7d;J+d8Y3hv z7JKwAo1+h0yR*f5{w$Z``hHViqGCk~caBdbOuaq2$r~%kpM;wFM1Pi)3epU)PiS27 z$GH^=hn}$&IRozUX`eOH#|!mzTi>0d8|yLa`4eyw%GtB_dY3+MwV#0ZBuI&-P`{Q1 zZO8K`{8o{C6mD#Oy=NU^raUbr#c(@0}<{LYnn^6iV~=xA;3vnm>>Hibts+5MMVfu`P7R-=NU^Uq^mL`npWT zCEvCvQ*Cs+o8FbB`G=8T@g#`q_oc%1@1W=O?e|jo`hGFRQDornt#jF$pT7QQug}q6 zE>g>Xg#O(KUyAyEGGRrB-ZRGOe|{I3=GXUsDLxKit-s1C^7one-C~;7_lqfxAV1fi z>#6kzZ7!x8?i0AG__nT??nqS+0Ir1-nLVaJF;uya0#`*cXPT~C55yy7be-~iJ zD8bj5{abnuo1~& zqbI@q`tLjy-_b9ILPK*&lib`z5X`UtUQKbIkbHRl+=u+yfBNr{71wIVKu;v3Z&N70 zz6U|^p}i|Jgv+HVnhXj;qqT5ek%QsX+ZgN`R zH!<*4sXt!3Q<@)IZymokvkv-S%OAx14F|80{7YGB;L`kx`%>m$etkdU4&>+e^8_!= zr^sJt3+C7F{SRF$8OMW}={^!l^VjuRm)2kZy~5yilK1%ft z7t8V$#84CM^I??V^KqLr^mRWIqyM$t>i>9tYTZRr{vPtEXgxGk0WDW$`TOV9@;AOH M`Hu-@2qn$`04rh>+yDRo literal 0 HcmV?d00001 diff --git a/test_pool.c b/test_pool.c new file mode 100644 index 0000000..abc3187 --- /dev/null +++ b/test_pool.c @@ -0,0 +1,267 @@ +#define _POSIX_C_SOURCE 200809L + +#include +#include +#include +#include + +#include "./nostr_core_lib/cjson/cJSON.h" +#include "./nostr_core_lib/nostr_core/nostr_core.h" + +static const char* k_default_relays[] = { + "wss://relay.damus.io", + "wss://relay.primal.net", + "wss://relay.laantungir.net" +}; + +typedef struct { + int eose_received; + int events_received; + int kind3_events; + int max_p_tags; +} test_ctx_t; + +static volatile int g_ws_eose_seen = 0; + +static long long now_ms(void) { + struct timespec ts; + if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0) { + return 0; + } + return ((long long)ts.tv_sec * 1000LL) + (ts.tv_nsec / 1000000LL); +} + +static int count_tag(cJSON* tags, const char* key) { + if (!tags || !cJSON_IsArray(tags) || !key) { + return 0; + } + + int n = cJSON_GetArraySize(tags); + int count = 0; + for (int i = 0; i < n; i++) { + cJSON* tag = cJSON_GetArrayItem(tags, i); + if (!tag || !cJSON_IsArray(tag) || cJSON_GetArraySize(tag) < 1) { + continue; + } + + cJSON* k = cJSON_GetArrayItem(tag, 0); + if (k && cJSON_IsString(k) && k->valuestring && strcmp(k->valuestring, key) == 0) { + count++; + } + } + return count; +} + +static void on_event(cJSON* event, const char* relay_url, void* user_data) { + test_ctx_t* ctx = (test_ctx_t*)user_data; + if (!ctx || !event || !cJSON_IsObject(event)) { + return; + } + + ctx->events_received++; + + cJSON* kind = cJSON_GetObjectItemCaseSensitive(event, "kind"); + cJSON* id = cJSON_GetObjectItemCaseSensitive(event, "id"); + cJSON* pubkey = cJSON_GetObjectItemCaseSensitive(event, "pubkey"); + cJSON* created_at = cJSON_GetObjectItemCaseSensitive(event, "created_at"); + cJSON* content = cJSON_GetObjectItemCaseSensitive(event, "content"); + cJSON* tags = cJSON_GetObjectItemCaseSensitive(event, "tags"); + + int kind_val = (kind && cJSON_IsNumber(kind)) ? (int)kind->valuedouble : -1; + int p_tags = count_tag(tags, "p"); + int r_tags = count_tag(tags, "r"); + int tag_count = (tags && cJSON_IsArray(tags)) ? cJSON_GetArraySize(tags) : 0; + int content_len = (content && cJSON_IsString(content) && content->valuestring) ? (int)strlen(content->valuestring) : 0; + + if (p_tags > ctx->max_p_tags) { + ctx->max_p_tags = p_tags; + } + if (kind_val == 3) { + ctx->kind3_events++; + } + + printf("[EVENT] relay=%s kind=%d created_at=%lld tags=%d p_tags=%d r_tags=%d content_len=%d id=%.16s pubkey=%.16s\n", + relay_url ? relay_url : "", + kind_val, + (created_at && cJSON_IsNumber(created_at)) ? (long long)created_at->valuedouble : -1LL, + tag_count, + p_tags, + r_tags, + content_len, + (id && cJSON_IsString(id) && id->valuestring) ? id->valuestring : "", + (pubkey && cJSON_IsString(pubkey) && pubkey->valuestring) ? pubkey->valuestring : ""); +} + +static void on_eose(cJSON** events, int event_count, void* user_data) { + (void)events; + test_ctx_t* ctx = (test_ctx_t*)user_data; + if (!ctx) { + return; + } + + ctx->eose_received = 1; + printf("[EOSE] received for subscription (event_count=%d)\n", event_count); +} + +static void log_callback(int level, const char* component, const char* message, void* user_data) { + (void)user_data; + const char* lvl = "?"; + switch (level) { + case NOSTR_LOG_LEVEL_ERROR: lvl = "ERROR"; break; + case NOSTR_LOG_LEVEL_WARN: lvl = "WARN"; break; + case NOSTR_LOG_LEVEL_INFO: lvl = "INFO"; break; + case NOSTR_LOG_LEVEL_DEBUG: lvl = "DEBUG"; break; + case NOSTR_LOG_LEVEL_TRACE: lvl = "TRACE"; break; + default: break; + } + + if (!component) component = "core"; + if (!message) message = ""; + + if (strcmp(component, "websocket") == 0) { + printf("[NOSTR %s] [%s] %s\n", lvl, component, message); + if (strstr(message, "[\"EOSE\"") != NULL) { + g_ws_eose_seen = 1; + } + } +} + +int main(int argc, char** argv) { + const char* npub = "npub13lm5wf8dvsdnc2894pkhch9uf8phvw9varrv8zf4sc885hhdmc8q6lx7ks"; + int timeout_ms = 15000; + + if (argc >= 2 && argv[1] && argv[1][0] != '\0') { + npub = argv[1]; + } + if (argc >= 3) { + timeout_ms = atoi(argv[2]); + if (timeout_ms <= 0) { + timeout_ms = 15000; + } + } + + unsigned char pubkey[32] = {0}; + char pubkey_hex[65] = {0}; + + if (nostr_decode_npub(npub, pubkey) != 0) { + fprintf(stderr, "Failed to decode npub: %s\n", npub); + return 1; + } + nostr_bytes_to_hex(pubkey, 32, pubkey_hex); + + if (nostr_crypto_init() != 0) { + fprintf(stderr, "nostr_crypto_init failed\n"); + return 1; + } + + nostr_set_log_callback(log_callback, NULL); + nostr_set_log_level(NOSTR_LOG_LEVEL_TRACE); + + nostr_pool_reconnect_config_t* reconnect = nostr_pool_reconnect_config_default(); + if (!reconnect) { + fprintf(stderr, "nostr_pool_reconnect_config_default failed\n"); + nostr_crypto_cleanup(); + return 1; + } + + reconnect->enable_auto_reconnect = 1; + reconnect->ping_interval_seconds = 20; + reconnect->pong_timeout_seconds = 10; + + nostr_relay_pool_t* pool = nostr_relay_pool_create(reconnect); + if (!pool) { + fprintf(stderr, "nostr_relay_pool_create failed\n"); + nostr_crypto_cleanup(); + return 1; + } + + const int relay_count = (int)(sizeof(k_default_relays) / sizeof(k_default_relays[0])); + for (int i = 0; i < relay_count; i++) { + if (nostr_relay_pool_add_relay(pool, k_default_relays[i]) != 0) { + fprintf(stderr, "Failed to add relay: %s\n", k_default_relays[i]); + } + } + + cJSON* filter = cJSON_CreateObject(); + cJSON* kinds = cJSON_CreateArray(); + cJSON* authors = cJSON_CreateArray(); + if (!filter || !kinds || !authors) { + fprintf(stderr, "Failed to build filter JSON\n"); + cJSON_Delete(filter); + cJSON_Delete(kinds); + cJSON_Delete(authors); + nostr_relay_pool_destroy(pool); + nostr_crypto_cleanup(); + return 1; + } + + cJSON_AddItemToArray(kinds, cJSON_CreateNumber(3)); + cJSON_AddItemToObject(filter, "kinds", kinds); + cJSON_AddItemToArray(authors, cJSON_CreateString(pubkey_hex)); + cJSON_AddItemToObject(filter, "authors", authors); + cJSON_AddNumberToObject(filter, "limit", 16); + + test_ctx_t ctx; + memset(&ctx, 0, sizeof(ctx)); + + printf("Target npub: %s\n", npub); + printf("Target hex : %s\n", pubkey_hex); + printf("Relays (%d):\n", relay_count); + for (int i = 0; i < relay_count; i++) { + printf(" - %s\n", k_default_relays[i]); + } + printf("Subscribing to kind=3 with timeout=%d ms\n", timeout_ms); + + nostr_pool_subscription_t* sub = nostr_relay_pool_subscribe( + pool, + k_default_relays, + relay_count, + filter, + on_event, + on_eose, + &ctx, + 0, + 0, + NOSTR_POOL_EOSE_FIRST, + 12, + 20 + ); + + if (!sub) { + fprintf(stderr, "nostr_relay_pool_subscribe failed\n"); + cJSON_Delete(filter); + nostr_relay_pool_destroy(pool); + nostr_crypto_cleanup(); + return 1; + } + + long long started = now_ms(); + while (!ctx.eose_received && !g_ws_eose_seen) { + long long elapsed = now_ms() - started; + if (elapsed >= timeout_ms) { + break; + } + (void)nostr_relay_pool_poll(pool, 100); + } + + printf("\nSummary:\n"); + printf(" EOSE callback : %s\n", ctx.eose_received ? "yes" : "no"); + printf(" Websocket EOSE: %s\n", g_ws_eose_seen ? "yes" : "no"); + printf(" Events seen : %d\n", ctx.events_received); + printf(" Kind-3 events : %d\n", ctx.kind3_events); + printf(" Max p-tags : %d\n", ctx.max_p_tags); + + if (!ctx.eose_received && g_ws_eose_seen) { + fprintf(stderr, "NOTE: Relay sent EOSE on websocket but subscription callback did not fire (pool EOSE behavior mismatch).\n"); + } + if (!ctx.eose_received && !g_ws_eose_seen) { + fprintf(stderr, "Timed out waiting for EOSE after %d ms\n", timeout_ms); + } + + (void)nostr_pool_subscription_close(sub); + cJSON_Delete(filter); + nostr_relay_pool_destroy(pool); + nostr_crypto_cleanup(); + + return (ctx.eose_received || g_ws_eose_seen) ? 0 : 2; +} diff --git a/tests/configs/test_genesis.jsonc b/tests/configs/test_genesis.jsonc index 105941a..0215462 100644 --- a/tests/configs/test_genesis.jsonc +++ b/tests/configs/test_genesis.jsonc @@ -9,12 +9,12 @@ }, "dm_protocol": "nip04", "llm": { - "provider": "openai", - "api_key": "sk-REPLACE_WITH_API_KEY", + "provider": "https://api.ppq.ai", + "api_key": "sk-SBI2Pga0J6n8jpPorHbRHC", "model": "claude-haiku-4.5", - "base_url": "https://api.anthropic.com/v1", - "max_tokens": 512, - "temperature": 0.3 + "base_url": "https://api.ppq.ai", + "max_tokens": 10000, + "temperature": 0.7 }, "api": { "enabled": true, diff --git a/tests/harness/__pycache__/agent_process.cpython-313.pyc b/tests/harness/__pycache__/agent_process.cpython-313.pyc index 79284a81c31bfadf81c27de28a9d9b0b929f8792..6bcf5b8e61a6c1794029d565c128d00b57986b40 100644 GIT binary patch delta 1688 zcmah}TWl0n7(QoqXXmorOWQ5o-R`oNvUC@1mqjYKQZAtdTAI=>3zaR|EZu1toX#|7 zrbUYgAz~U5la>=nArZC#Ee4I3)d!4{m`F@~f|y_#)EG6!C)1{VFg_U1+15*V@qhTv z|9|uS|NQ5lGiRaa++pXU!yy7CY z3vwYpV7#a#@u$}Pb{~1Kev!aAfKzOwlwxNgjFcYP)V|Kz&uv^5I=aPcOC`}qE< zG?(U+Owyt<8KxTSh5J~1mrrm)T1bm&JN}Ii!U|j=1S6I>$)uJ)3qZz_cCf@xb6fie zGe$foe!#90o!}WpwPn~|7mnDf@eV<(=>b6H`i8baB4hyQA{}JixWlpfY}%FKnh8a% z8ti+T2>A@`tyyONi`m-0qYU3mH~EC6-OZ#twI|mw=xPGM4#=8bSwAEFv%PfyM{QN4 zLnkX_gx=svzZ~wa5)!meKau@kGXlOQ_&4r_UHo$G3|s>uAWNcU<3OH_DGWknvb5kJ z*lr1dL71q-zY0yF$>{nx+o%{St`TDaL}!7#1jdCCxv)LnUwA5kI1(pZxJBI2BI+ZG zY$&oS=_Z3DlOL4Tf^100wB!o~ctTa8976H zUQ$gftz*JrlP$+OJsII}7=I=1=bDMHZ-CfuZ+6c)DknEhbkB2Ek2t|ERp35*_$&{6 z!Rz)b_Q^BF-9=;e^vvnv@co*N6YamaswVa6)YMqfgI}Htg^Qlq;7qXSx*uFWDb59= z-vk;T1R5U(l9PMpY9n9QHa_x(zYT`2XRl^&1>QFv23w}#oHuZtzrx?FF80hGnK^Q+ z=f=?+p@-hoq6MtpzQBRtdfS2sDt$lTOZM2-B^LPO3-!Pgm|q9no(cQ!OEKVyEduE9 zJ$8HNEoA*euOvIVQ`-iZdq8Y+vG<&?%~e{kU$(#wJmJd1UHE6$8F;94+#R(-wRFK# z!8RTvIVeXU0Zm>~2eg8$4kfH;Gv&8XND(kCk~)UE@Lk_+cod)X?}p9zU4N{CoO@te zM>Iu28vfoN2=x*oA)q>%c!~n~ZGdSTP-ICpP#^Zyt%EAwQylc?>GF`8S5P;l4^rBCDlZ#|emT>5NFJ87QKRjJ5gADZ)Ii9$nUy!= z`cNF+E*%KDVbyEoL4rY-$b#s#fu^t;Zi1iSH^Lvn^Eegt1jY$%*6bQ0Jsw2bfTZh9 zvYyK;@|biNpNrgt(>NA=7JiQVqcgT?TH^}ItH!J98{ikE*7_M3DbMjAAOkG#r98zG zG-1o7SUda@?~T3F_9ltP1x414tZtxVM4O-rda@CHW?GMHnnGGG3wd2Kva)JW-N}D- zc=Ot5I~|_M4k}vSD2vf7Arf$iAh9(&S86QNEA<@RBZjPzu`U-SEH19S(9}T^&`}B+ zh4P@vBYT~ObU@2pUZdU(oNibTUHFxT=hz_YS!%^UHf)KzO)-}n95qIfl*=J<0Ryy_ z!Zr%!Z%98J>cOq?0~_9>ZkR%lfXTNEYlTroYDFKDklu6p4g%z0L--wt{se@__)dJa Vf2Rd{=K)3XqcHXbhPvfCe*tGPlWG6} delta 1207 zcmY*YO>7%Q6rR~1d%bHrv9oC%$4+b~ZoE;OMk0o`3KY3Y(%1z;EK?vw#)+M1g}a;1 zt|>~DDnhCVP(-XJRV^qakt*dtTPQ9<;)M8-IF=hS;=~DYAdz#3;KIx%ATcX_^WOK~ z`*!xz?8-~KFZx?PpN!}V#J|^5N9+C&zFr>~$?-W?)=k_olt+0XCuKdv2VeD^c+(#VlfnS74B}8l1dlXg> zOo{Qvl(>y?tg-Lv$M^(9r56;57I}nl>n4V)(xu=@&fp4M2ReTpA%pKhGXj|Y$ngX! z@Od|Vq`c>}U8N>!<9$|kHDiya$=Iz zghjJjB?~NcS$4^;SxtVSVmCcwZ_QN}ZQV3TmKHS+>82Yo85mCYTve|aHkpE0bWoV5 z6to9bn2m<9*;tNVigb_QU(yD`G%Y_m08u z+UdTwrd%w}*X$ap6pMuZuZSFD;F!*?Gns_!;DwXhEQ~Q=_BEx^%VxP&tvo~S)0FvY ijZ&cZ6ysk};y3tluqSMDIJAQpws^!TyBrH0k$(Wv^BEri diff --git a/tests/harness/agent_process.py b/tests/harness/agent_process.py index f0f5597..085b44f 100644 --- a/tests/harness/agent_process.py +++ b/tests/harness/agent_process.py @@ -1,6 +1,7 @@ from __future__ import annotations import os +import re import signal import subprocess import time @@ -54,6 +55,7 @@ class AgentProcess: commands = [ ["lsof", "-t", f"-iTCP:{self.api_port}", "-sTCP:LISTEN"], ["fuser", f"{self.api_port}/tcp"], + ["ss", "-ltnp"], ] for cmd in commands: @@ -61,7 +63,18 @@ class AgentProcess: proc = subprocess.run(cmd, capture_output=True, text=True, check=False) except FileNotFoundError: continue + output = f"{proc.stdout}\n{proc.stderr}" + + if cmd and cmd[0] == "ss": + port_token = f":{self.api_port}" + for line in output.splitlines(): + if port_token not in line: + continue + for m in re.finditer(r"pid=(\d+)", line): + pids.add(int(m.group(1))) + continue + for token in output.split(): if token.isdigit(): pids.add(int(token)) diff --git a/tests/results/20260404T135221Z/agent_debug.log b/tests/results/20260404T135221Z/agent_debug.log new file mode 100644 index 0000000..057363d --- /dev/null +++ b/tests/results/20260404T135221Z/agent_debug.log @@ -0,0 +1,390 @@ +[2026-04-04 10:04:51] [INFO ] [main.c:1016] Didactyl v0.2.20 starting +[2026-04-04 10:04:51] [INFO ] [nostr_handler.c:2207] [didactyl] initializing relay pool with 2 relays +[2026-04-04 10:04:51] [INFO ] [nostr_handler.c:2231] [didactyl] added relay: wss://relay.damus.io +[2026-04-04 10:04:51] [INFO ] [nostr_handler.c:2231] [didactyl] added relay: wss://relay.primal.net +[2026-04-04 10:04:51] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_1_1775311491", { + "kinds": [10000], + "authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"], + "limit": 1 + }] +[2026-04-04 10:04:52] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_1_1775311491", { + "kinds": [10000], + "authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"], + "limit": 1 + }] +[2026-04-04 10:04:52] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_1_1775311491"] +[2026-04-04 10:04:52] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_1_1775311491"] +[2026-04-04 10:04:52] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_1_1775311491"] +[2026-04-04 10:04:52] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_1_1775311491"] +[2026-04-04 10:04:52] [INFO ] [main.c:248] [didactyl] startup checklist [01] Relay connectivity: begin +[2026-04-04 10:04:52] [INFO ] [main.c:255] [didactyl] startup checklist [01] Relay connectivity: ok (connected relays: 2/2) +[2026-04-04 10:04:52] [INFO ] [main.c:248] [didactyl] startup checklist [02] Recover runtime config from Nostr: begin +[2026-04-04 10:04:52] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_2_1775311492", { + "kinds": [30078], + "authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"], + "#d": ["agent_config"], + "limit": 1 + }] +[2026-04-04 10:04:52] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_2_1775311492", { + "kinds": [30078], + "authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"], + "#d": ["agent_config"], + "limit": 1 + }] +[2026-04-04 10:04:52] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 10:04:52] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_2_1775311492"] +[2026-04-04 10:04:52] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_2_1775311492"] +[2026-04-04 10:04:52] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_2_1775311492"] +[2026-04-04 10:04:52] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_2_1775311492"] +[2026-04-04 10:04:52] [INFO ] [main.c:258] [didactyl] startup checklist [02] Recover runtime config from Nostr: ok +[2026-04-04 10:04:52] [INFO ] [main.c:248] [didactyl] startup checklist [03] Validate LLM config: begin +[2026-04-04 10:04:52] [INFO ] [main.c:258] [didactyl] startup checklist [03] Validate LLM config: ok +[2026-04-04 10:04:52] [INFO ] [main.c:248] [didactyl] startup checklist [04] Validate admin config: begin +[2026-04-04 10:04:52] [INFO ] [main.c:258] [didactyl] startup checklist [04] Validate admin config: ok +[2026-04-04 10:04:52] [INFO ] [main.c:248] [didactyl] startup checklist [05] Initialize LLM client: begin +[2026-04-04 10:04:52] [INFO ] [main.c:258] [didactyl] startup checklist [05] Initialize LLM client: ok +[2026-04-04 10:04:52] [INFO ] [main.c:248] [didactyl] startup checklist [06] Detect first run via kind 10002: begin +[2026-04-04 10:04:52] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_3_1775311492", { + "kinds": [10002], + "authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"], + "limit": 1 + }] +[2026-04-04 10:04:52] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_3_1775311492", { + "kinds": [10002], + "authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"], + "limit": 1 + }] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_3_1775311492"] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_3_1775311492"] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_3_1775311492"] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_3_1775311492"] +[2026-04-04 10:04:53] [INFO ] [main.c:1245] [didactyl] startup phase: first-run detection via kind 10002 => subsequent-run +[2026-04-04 10:04:53] [INFO ] [main.c:255] [didactyl] startup checklist [06] Detect first run via kind 10002: ok (subsequent-run) +[2026-04-04 10:04:53] [INFO ] [main.c:248] [didactyl] startup checklist [07] Reconcile/persist startup state: begin +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_4_1775311493", { + "kinds": [1], + "authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"], + "limit": 1 + }] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_4_1775311493", { + "kinds": [1], + "authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"], + "limit": 1 + }] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_4_1775311493"] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_4_1775311493"] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_4_1775311493"] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_4_1775311493"] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] SEND +[2026-04-04 10:04:53] [INFO ] [nostr_handler.c:2949] [didactyl] kind 0 event published to wss://relay.damus.io (async, reason=startup_reconcile) +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] SEND +[2026-04-04 10:04:53] [INFO ] [nostr_handler.c:2949] [didactyl] kind 10002 event published to wss://relay.damus.io (async, reason=startup_reconcile) +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] SEND +[2026-04-04 10:04:53] [INFO ] [nostr_handler.c:2949] [didactyl] kind 31124 event published to wss://relay.damus.io (async, reason=startup_reconcile) +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] SEND +[2026-04-04 10:04:53] [INFO ] [nostr_handler.c:2949] [didactyl] kind 10123 event published to wss://relay.damus.io (async, reason=startup_reconcile) +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] SEND +[2026-04-04 10:04:53] [INFO ] [nostr_handler.c:2949] [didactyl] kind 0 event published to wss://relay.primal.net (async, reason=startup_reconcile) +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] SEND +[2026-04-04 10:04:53] [INFO ] [nostr_handler.c:2949] [didactyl] kind 10002 event published to wss://relay.primal.net (async, reason=startup_reconcile) +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] SEND +[2026-04-04 10:04:53] [INFO ] [nostr_handler.c:2949] [didactyl] kind 31124 event published to wss://relay.primal.net (async, reason=startup_reconcile) +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] SEND +[2026-04-04 10:04:53] [INFO ] [nostr_handler.c:2949] [didactyl] kind 10123 event published to wss://relay.primal.net (async, reason=startup_reconcile) +[2026-04-04 10:04:53] [INFO ] [nostr_handler.c:3285] [didactyl] startup publish detail: kind=0 (profile) -> wss://relay.damus.io, wss://relay.primal.net +[2026-04-04 10:04:53] [INFO ] [nostr_handler.c:3285] [didactyl] startup publish detail: kind=10002 (relay_list) -> wss://relay.damus.io, wss://relay.primal.net +[2026-04-04 10:04:53] [INFO ] [nostr_handler.c:3285] [didactyl] startup publish detail: kind=31124 (skill) -> wss://relay.damus.io, wss://relay.primal.net +[2026-04-04 10:04:53] [INFO ] [nostr_handler.c:3285] [didactyl] startup publish detail: kind=10123 (adoption_list) -> wss://relay.damus.io, wss://relay.primal.net +[2026-04-04 10:04:53] [INFO ] [nostr_handler.c:3297] [didactyl] startup publish summary: 4/4 event(s) published to at least one relay; relays used=2/2 +[2026-04-04 10:04:53] [INFO ] [nostr_handler.c:906] [didactyl] publish kind event target relays (2): +[2026-04-04 10:04:53] [INFO ] [nostr_handler.c:908] [didactyl] -> wss://relay.damus.io +[2026-04-04 10:04:53] [INFO ] [nostr_handler.c:908] [didactyl] -> wss://relay.primal.net +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] SEND +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] SEND +[2026-04-04 10:04:53] [INFO ] [nostr_handler.c:2949] [didactyl] kind 30078 event published to wss://relay.damus.io (async, reason=manual_publish) +[2026-04-04 10:04:53] [INFO ] [nostr_handler.c:2949] [didactyl] kind 30078 event published to wss://relay.primal.net (async, reason=manual_publish) +[2026-04-04 10:04:53] [INFO ] [nostr_handler.c:3012] [didactyl] published kind 30078 event via 2 connected relay(s) +[2026-04-04 10:04:53] [INFO ] [nostr_handler.c:906] [didactyl] publish kind event target relays (2): +[2026-04-04 10:04:53] [INFO ] [nostr_handler.c:908] [didactyl] -> wss://relay.damus.io +[2026-04-04 10:04:53] [INFO ] [nostr_handler.c:908] [didactyl] -> wss://relay.primal.net +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] SEND +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] SEND +[2026-04-04 10:04:53] [INFO ] [nostr_handler.c:2949] [didactyl] kind 30078 event published to wss://relay.damus.io (async, reason=manual_publish) +[2026-04-04 10:04:53] [INFO ] [nostr_handler.c:2949] [didactyl] kind 30078 event published to wss://relay.primal.net (async, reason=manual_publish) +[2026-04-04 10:04:53] [INFO ] [nostr_handler.c:3012] [didactyl] published kind 30078 event via 2 connected relay(s) +[2026-04-04 10:04:53] [INFO ] [main.c:1267] [didactyl] startup phase: persisted encrypted runtime config to Nostr +[2026-04-04 10:04:53] [INFO ] [main.c:258] [didactyl] startup checklist [07] Reconcile/persist startup state: ok +[2026-04-04 10:04:53] [INFO ] [main.c:248] [didactyl] startup checklist [08] Initialize agent: begin +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_5_1775311493", { + "kinds": [30078], + "authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"], + "#d": ["memory"], + "limit": 1 + }] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_5_1775311493", { + "kinds": [30078], + "authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"], + "#d": ["memory"], + "limit": 1 + }] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["OK","98b892b33ce3731cbeb82d791f5e41c7deade4bb61756ed600a14f3aeaa7d45e",true,"duplicate: have this event"] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["OK","98b892b33ce3731cbeb82d791f5e41c7deade4bb61756ed600a14f3aeaa7d45e",true,"duplicate: have this event"] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["OK","7fb5e7e09092254b700df2e7a8473fdca42ee09e3a32b86d262ad2403807f920",true,"duplicate: have this event"] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["OK","7fb5e7e09092254b700df2e7a8473fdca42ee09e3a32b86d262ad2403807f920",true,"duplicate: have this event"] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["OK","b3095dd390e3a08ff24404195292ed302711334687a7de5ca0b433d9fe66a92e",true,"duplicate: have this event"] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["OK","b3095dd390e3a08ff24404195292ed302711334687a7de5ca0b433d9fe66a92e",true,"duplicate: have this event"] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["OK","952130858723506c1eac58e94f253c8b35e137c056dcedf0619ab63348f43caa",true,"duplicate: have this event"] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["OK","952130858723506c1eac58e94f253c8b35e137c056dcedf0619ab63348f43caa",true,"duplicate: have this event"] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["OK","c27bd1317e1c716b8450c4302b42214397fd03d2620d39b8f28d3fe765b446d7",false,"rate-limited: you are noting too much"] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_5_1775311493"] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["OK","8842d5de102138e7f7bc72819624a5735360d575e1cd136cc69902b38544a717",false,"rate-limited: you are noting too much"] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["OK","8842d5de102138e7f7bc72819624a5735360d575e1cd136cc69902b38544a717",true,""] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_5_1775311493"] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["OK","c27bd1317e1c716b8450c4302b42214397fd03d2620d39b8f28d3fe765b446d7",true,""] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_5_1775311493"] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_5_1775311493"] +[2026-04-04 10:04:53] [INFO ] [main.c:258] [didactyl] startup checklist [08] Initialize agent: ok +[2026-04-04 10:04:53] [INFO ] [main.c:248] [didactyl] startup checklist [09] Initialize trigger manager: begin +[2026-04-04 10:04:53] [INFO ] [trigger_manager.c:814] [didactyl] trigger manager initialized (capacity=16) +[2026-04-04 10:04:53] [INFO ] [main.c:258] [didactyl] startup checklist [09] Initialize trigger manager: ok +[2026-04-04 10:04:53] [INFO ] [main.c:1308] [didactyl] startup phase: deferred trigger load will run after self-skill EOSE +[2026-04-04 10:04:53] [INFO ] [main.c:248] [didactyl] startup checklist [10] Load startup triggers: begin +[2026-04-04 10:04:53] [INFO ] [main.c:1311] [didactyl] startup phase: startup-config trigger load begin +[2026-04-04 10:04:53] [INFO ] [trigger_manager.c:1138] [didactyl] trigger added d_tag=identity_and_rules action=0 enabled=1 +[2026-04-04 10:04:53] [INFO ] [trigger_manager.c:1021] [didactyl] startup trigger registered d_tag=identity_and_rules action=llm enabled=1 +[2026-04-04 10:04:53] [INFO ] [trigger_manager.c:1029] [didactyl] trigger manager loaded 1 trigger(s) from startup config (considered=1) +[2026-04-04 10:04:53] [INFO ] [main.c:1315] [didactyl] startup phase: startup-config trigger load end +[2026-04-04 10:04:53] [INFO ] [main.c:258] [didactyl] startup checklist [10] Load startup triggers: ok +[2026-04-04 10:04:53] [INFO ] [main.c:248] [didactyl] startup checklist [11] Discover self relay list via kind 10002: begin +[2026-04-04 10:04:53] [INFO ] [main.c:327] [didactyl] kind10002 query begin: timeout_ms=5000 author=1eb171136dbb1ba6... relay_count=2 +[2026-04-04 10:04:53] [INFO ] [main.c:334] [didactyl] kind10002 query relay snapshot: {"relay_count":2,"connected_count":2,"relays":[{"url":"wss://relay.damus.io","status":"connected","events_received":0,"events_published":6,"events_published_ok":4,"events_published_failed":2,"ping_latency_current":0,"ping_latency_avg":0,"query_latency_avg":0,"publish_latency_avg":0,"connection_uptime_start":1775311491,"last_event_time":1775311493,"last_publish_error":"rate-limited: you are noting too much"},{"url":"wss://relay.primal.net","status":"connected","events_received":3,"events_published":6,"events_published_ok":6,"events_published_failed":0,"ping_latency_current":0,"ping_latency_avg":0,"query_latency_avg":0,"publish_latency_avg":0,"connection_uptime_start":1775311491,"last_event_time":1775311493}]} +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_6_1775311493", { + "kinds": [10002], + "authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"], + "limit": 16 + }] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_6_1775311493", { + "kinds": [10002], + "authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"], + "limit": 16 + }] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_6_1775311493"] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_6_1775311493"] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_6_1775311493"] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_6_1775311493"] +[2026-04-04 10:04:53] [INFO ] [main.c:354] [didactyl] kind10002 query received event_count=1 +[2026-04-04 10:04:53] [INFO ] [main.c:451] [didactyl] kind10002 query extracted relays=["wss://relay.damus.io","wss://relay.primal.net"] +[2026-04-04 10:04:53] [INFO ] [main.c:556] [didactyl] kind10002 wait success: relay_count=2 +[2026-04-04 10:04:53] [INFO ] [main.c:558] [didactyl] kind10002 relay[0]=wss://relay.damus.io +[2026-04-04 10:04:53] [INFO ] [main.c:558] [didactyl] kind10002 relay[1]=wss://relay.primal.net +[2026-04-04 10:04:53] [INFO ] [main.c:562] [didactyl] kind10002 wait success relay snapshot: {"relay_count":2,"connected_count":2,"relays":[{"url":"wss://relay.damus.io","status":"connected","events_received":0,"events_published":6,"events_published_ok":4,"events_published_failed":2,"ping_latency_current":0,"ping_latency_avg":0,"query_latency_avg":0,"publish_latency_avg":0,"connection_uptime_start":1775311491,"last_event_time":1775311493,"last_publish_error":"rate-limited: you are noting too much"},{"url":"wss://relay.primal.net","status":"connected","events_received":4,"events_published":6,"events_published_ok":6,"events_published_failed":0,"ping_latency_current":0,"ping_latency_avg":0,"query_latency_avg":0,"publish_latency_avg":0,"connection_uptime_start":1775311491,"last_event_time":1775311493}]} +[2026-04-04 10:04:53] [INFO ] [main.c:255] [didactyl] startup checklist [11] Discover self relay list via kind 10002: ok (kind10002=2 added=0 connected=2/2) +[2026-04-04 10:04:53] [INFO ] [main.c:248] [didactyl] startup checklist [12] Subscribe admin context: begin +[2026-04-04 10:04:53] [INFO ] [main.c:1366] [didactyl] startup phase: subscribe admin context begin +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_7_1775311493", { + "kinds": [0, 3, 10002], + "authors": ["254d7a7f68b4443f5430564eb3a726f09aea57d27e75ab2b8ef57176f9ca3dac"], + "limit": 32 + }] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_7_1775311493", { + "kinds": [0, 3, 10002], + "authors": ["254d7a7f68b4443f5430564eb3a726f09aea57d27e75ab2b8ef57176f9ca3dac"], + "limit": 32 + }] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_8_1775311493", { + "kinds": [1], + "authors": ["254d7a7f68b4443f5430564eb3a726f09aea57d27e75ab2b8ef57176f9ca3dac"], + "limit": 10 + }] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_8_1775311493", { + "kinds": [1], + "authors": ["254d7a7f68b4443f5430564eb3a726f09aea57d27e75ab2b8ef57176f9ca3dac"], + "limit": 10 + }] +[2026-04-04 10:04:53] [INFO ] [nostr_handler.c:2352] [didactyl] admin context subscriptions active for admin 254d7a7f68b4443f... +[2026-04-04 10:04:53] [INFO ] [main.c:1370] [didactyl] startup phase: subscribe admin context end +[2026-04-04 10:04:53] [INFO ] [main.c:258] [didactyl] startup checklist [12] Subscribe admin context: ok +[2026-04-04 10:04:53] [INFO ] [main.c:248] [didactyl] startup checklist [13] Subscribe agent self context: begin +[2026-04-04 10:04:53] [INFO ] [main.c:1374] [didactyl] startup phase: subscribe agent self context begin +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_9_1775311493", { + "kinds": [0, 3, 10002], + "authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"], + "limit": 64 + }] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_9_1775311493", { + "kinds": [0, 3, 10002], + "authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"], + "limit": 64 + }] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_10_1775311493", { + "kinds": [1], + "authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"], + "limit": 10 + }] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_10_1775311493", { + "kinds": [1], + "authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"], + "limit": 10 + }] +[2026-04-04 10:04:53] [INFO ] [nostr_handler.c:2440] [didactyl] agent self-context subscriptions active for pubkey 1eb171136dbb1ba6... +[2026-04-04 10:04:53] [INFO ] [main.c:1378] [didactyl] startup phase: subscribe agent self context end +[2026-04-04 10:04:53] [INFO ] [main.c:258] [didactyl] startup checklist [13] Subscribe agent self context: ok +[2026-04-04 10:04:53] [INFO ] [main.c:248] [didactyl] startup checklist [14] Subscribe self-skill cache: begin +[2026-04-04 10:04:53] [INFO ] [main.c:1382] [didactyl] startup phase: subscribe self skill cache begin +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_11_1775311493", { + "kinds": [31123, 31124, 10123], + "authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"], + "limit": 300 + }] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_11_1775311493", { + "kinds": [31123, 31124, 10123], + "authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"], + "limit": 300 + }] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_12_1775311493", { + "kinds": [31123], + "authors": ["254d7a7f68b4443f5430564eb3a726f09aea57d27e75ab2b8ef57176f9ca3dac"], + "limit": 150 + }] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_12_1775311493", { + "kinds": [31123], + "authors": ["254d7a7f68b4443f5430564eb3a726f09aea57d27e75ab2b8ef57176f9ca3dac"], + "limit": 150 + }] +[2026-04-04 10:04:53] [INFO ] [nostr_handler.c:2532] [didactyl] self/admin skill subscriptions active (self=1eb171136dbb1ba6..., admin=254d7a7f68b4443f...) +[2026-04-04 10:04:54] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_7_1775311493"] +[2026-04-04 10:04:54] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_7_1775311493"] +[2026-04-04 10:04:54] [TRACE] [nostr_handler.c:1718] [didactyl] DEBUG on_eose called: event_count=0 +[2026-04-04 10:04:54] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_8_1775311493"] +[2026-04-04 10:04:54] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_8_1775311493"] +[2026-04-04 10:04:54] [TRACE] [nostr_handler.c:1718] [didactyl] DEBUG on_eose called: event_count=0 +[2026-04-04 10:04:54] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 10:04:54] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 10:04:54] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 10:04:54] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 10:04:54] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_9_1775311493"] +[2026-04-04 10:04:54] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_9_1775311493"] +[2026-04-04 10:04:54] [TRACE] [nostr_handler.c:1718] [didactyl] DEBUG on_eose called: event_count=0 +[2026-04-04 10:04:54] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_10_1775311493"] +[2026-04-04 10:04:54] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_10_1775311493"] +[2026-04-04 10:04:54] [TRACE] [nostr_handler.c:1718] [didactyl] DEBUG on_eose called: event_count=0 +[2026-04-04 10:04:54] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 10:04:54] [INFO ] [nostr_handler.c:2151] [didactyl] self-skill cache received kind=31124 d_tag=identity_and_rules id=b3095dd390e3a08ff24404195292ed302711334687a7de5ca0b433d9fe66a92e created_at=1775311493 via wss://relay.damus.io +[2026-04-04 10:04:54] [INFO ] [trigger_manager.c:1364] [didactyl] trigger updated d_tag=identity_and_rules action=0 enabled=1 +[2026-04-04 10:04:54] [INFO ] [nostr_handler.c:1207] [didactyl] live self-skill trigger registered d_tag=identity_and_rules action=llm enabled=1 +[2026-04-04 10:04:54] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 10:04:54] [INFO ] [nostr_handler.c:2151] [didactyl] self-skill cache received kind=31124 d_tag=identity_and_rules id=b3095dd390e3a08ff24404195292ed302711334687a7de5ca0b433d9fe66a92e created_at=1775311493 via wss://relay.primal.net +[2026-04-04 10:04:54] [INFO ] [trigger_manager.c:1364] [didactyl] trigger updated d_tag=identity_and_rules action=0 enabled=1 +[2026-04-04 10:04:54] [INFO ] [nostr_handler.c:1207] [didactyl] live self-skill trigger registered d_tag=identity_and_rules action=llm enabled=1 +[2026-04-04 10:04:54] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 10:04:54] [INFO ] [nostr_handler.c:2151] [didactyl] self-skill cache received kind=10123 d_tag=- id=952130858723506c1eac58e94f253c8b35e137c056dcedf0619ab63348f43caa created_at=1775311493 via wss://relay.damus.io +[2026-04-04 10:04:54] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 10:04:54] [INFO ] [nostr_handler.c:2151] [didactyl] self-skill cache received kind=10123 d_tag=- id=952130858723506c1eac58e94f253c8b35e137c056dcedf0619ab63348f43caa created_at=1775311493 via wss://relay.primal.net +[2026-04-04 10:04:54] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_11_1775311493"] +[2026-04-04 10:04:54] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_11_1775311493"] +[2026-04-04 10:04:54] [INFO ] [nostr_handler.c:1725] [didactyl] self-skill EOSE received: cached skill events=0 +[2026-04-04 10:04:54] [INFO ] [main.c:44] [didactyl] deferred trigger load begin after self-skill EOSE (event_count=0) +[2026-04-04 10:04:54] [INFO ] [trigger_manager.c:1364] [didactyl] trigger updated d_tag=identity_and_rules action=0 enabled=1 +[2026-04-04 10:04:54] [INFO ] [trigger_manager.c:918] [didactyl] trigger manager loaded 1 trigger(s) from self skills (considered=1) +[2026-04-04 10:04:54] [INFO ] [main.c:54] [didactyl] deferred trigger load status: {"success":true,"count":1,"active":1,"triggers":[{"skill_d_tag":"identity_and_rules","filter_json":"{\"from\":\"admin\"}","type":"dm","action":"llm","enabled":true,"subscribed":false,"llm":"","tools":"","has_max_tokens":false,"has_temperature":false,"has_seed":false,"last_fired":0,"last_seen_created_at":0}]} +[2026-04-04 10:04:54] [INFO ] [nostr_handler.c:2757] [didactyl] self-skill cache event: kind=31124 d_tag=identity_and_rules id=b3095dd390e3a08ff24404195292ed302711334687a7de5ca0b433d9fe66a92e created_at=1775311493 +[2026-04-04 10:04:54] [INFO ] [nostr_handler.c:2757] [didactyl] self-skill cache event: kind=10123 d_tag=- id=952130858723506c1eac58e94f253c8b35e137c056dcedf0619ab63348f43caa created_at=1775311493 +[2026-04-04 10:04:54] [INFO ] [main.c:1449] [didactyl] startup phase: subscribe self skill cache end +[2026-04-04 10:04:54] [INFO ] [main.c:255] [didactyl] startup checklist [14] Subscribe self-skill cache: ok (skills=1 adoptions=1) +[2026-04-04 10:04:54] [INFO ] [main.c:248] [didactyl] startup checklist [15] Subscribe DMs: begin +[2026-04-04 10:04:54] [INFO ] [main.c:1453] [didactyl] startup phase: subscribe DMs begin +[2026-04-04 10:04:54] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_13_1775311494", { + "kinds": [4], + "#p": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"], + "since": 1775311491, + "limit": 100 + }] +[2026-04-04 10:04:54] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_13_1775311494", { + "kinds": [4], + "#p": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"], + "since": 1775311491, + "limit": 100 + }] +[2026-04-04 10:04:54] [INFO ] [nostr_handler.c:2643] [didactyl] DM subscription active for pubkey 1eb171136dbb1ba6... +[2026-04-04 10:04:54] [TRACE] [nostr_handler.c:2644] [didactyl] DEBUG DM subscription g_start_time=1775311491 now=1775311494 delta=3 relay_count=2 +[2026-04-04 10:04:54] [INFO ] [main.c:1468] [didactyl] startup phase: subscribe DMs end +[2026-04-04 10:04:54] [INFO ] [main.c:258] [didactyl] startup checklist [15] Subscribe DMs: ok +[2026-04-04 10:04:54] [INFO ] [main.c:248] [didactyl] startup checklist [16] Subscribe wallet events: begin +[2026-04-04 10:04:54] [INFO ] [main.c:1472] [didactyl] startup phase: subscribe wallet events begin +[2026-04-04 10:04:54] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_14_1775311494", { + "kinds": [17375, 7375, 5], + "authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"], + "since": 1775311491, + "limit": 500 + }] +[2026-04-04 10:04:54] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_14_1775311494", { + "kinds": [17375, 7375, 5], + "authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"], + "since": 1775311491, + "limit": 500 + }] +[2026-04-04 10:04:54] [INFO ] [nostr_handler.c:2696] [didactyl] wallet event subscription active for pubkey 1eb171136dbb1ba6... +[2026-04-04 10:04:54] [INFO ] [main.c:1476] [didactyl] startup phase: subscribe wallet events end +[2026-04-04 10:04:54] [INFO ] [main.c:258] [didactyl] startup checklist [16] Subscribe wallet events: ok +[2026-04-04 10:04:54] [INFO ] [main.c:248] [didactyl] startup checklist [17] Initialize cashu wallet: begin +[2026-04-04 10:04:54] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_15_1775311494", { + "kinds": [17375, 7375], + "authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"] + }] +[2026-04-04 10:04:54] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_15_1775311494", { + "kinds": [17375, 7375], + "authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"] + }] +[2026-04-04 10:04:54] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_12_1775311493"] +[2026-04-04 10:04:54] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_12_1775311493"] +[2026-04-04 10:04:54] [TRACE] [nostr_handler.c:1718] [didactyl] DEBUG on_eose called: event_count=0 +[2026-04-04 10:04:54] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_13_1775311494"] +[2026-04-04 10:04:54] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_13_1775311494"] +[2026-04-04 10:04:54] [TRACE] [nostr_handler.c:1718] [didactyl] DEBUG on_eose called: event_count=0 +[2026-04-04 10:04:54] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_14_1775311494"] +[2026-04-04 10:04:54] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_14_1775311494"] +[2026-04-04 10:04:54] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_15_1775311494"] +[2026-04-04 10:04:54] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_15_1775311494"] +[2026-04-04 10:04:54] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_15_1775311494"] +[2026-04-04 10:04:54] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_15_1775311494"] +[2026-04-04 10:04:54] [WARN ] [main.c:1492] [didactyl] startup phase: cashu wallet load/create deferred to first tool call +[2026-04-04 10:04:54] [INFO ] [main.c:255] [didactyl] startup checklist [17] Initialize cashu wallet: ok (initialized; load/create deferred) +[2026-04-04 10:04:54] [WARN ] [main.c:1528] [didactyl] HTTP API disabled due to bind/init failure; agent will continue without local API +[2026-04-04 10:04:54] [TRACE] [nostr_handler.c:1499] [didactyl] sending plaintext DM content: Didactyl Test Agent has started up and is online at 2026-04-04 10:04:54 (version v0.2.20, connected relays: 2/2). +[2026-04-04 10:04:54] [TRACE] [nostr_handler.c:1491] [didactyl] sending encrypted DM event: {"pubkey":"1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc","created_at":1775311494,"kind":4,"tags":[["p","254d7a7f68b4443f5430564eb3a726f09aea57d27e75ab2b8ef57176f9ca3dac"]],"content":"L7Q6tbtXVWYnJZFZkkVvVfG/+6NQa9V8UMMK2tICfBTaG9V2MfwaKg9cVmC5BkmDl44QuJo7HFMQWHhj9NRlM1D2mP8JVTFndXdVCM5KU6tslhRfrRpjRFhfkGd4+0/lpmhVSq7wUP3f7oLOAvQaH66mDA7bIebr5BIss/4rSFE=?iv=fOZr9Tl0qw3NBGqRXOJPAA==","id":"3edc6798888fa063a724c277ed553f35fcf339aa62a74b039edf35907bc4da9e","sig":"0fc1537a1729128950d0cdffee17822186b7fc1fc1b4c29cf1e23b8979ae7264b1e702600bfa6c9778916b316e2b8b1050a113c815ba8a890ad455b2d2947bdc"} +[2026-04-04 10:04:54] [INFO ] [nostr_handler.c:906] [didactyl] publish DM target relays (2): +[2026-04-04 10:04:54] [INFO ] [nostr_handler.c:908] [didactyl] -> wss://relay.damus.io +[2026-04-04 10:04:54] [INFO ] [nostr_handler.c:908] [didactyl] -> wss://relay.primal.net +[2026-04-04 10:04:54] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] SEND +[2026-04-04 10:04:54] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] SEND +[2026-04-04 10:04:54] [INFO ] [nostr_handler.c:2843] [didactyl] kind 4 event published to wss://relay.damus.io (async) +[2026-04-04 10:04:54] [INFO ] [nostr_handler.c:2843] [didactyl] kind 4 event published to wss://relay.primal.net (async) +[2026-04-04 10:04:54] [INFO ] [nostr_handler.c:2852] [didactyl] sent DM 3edc6798888fa063... to 254d7a7f68b4443f... via 2 connected relay(s) +[2026-04-04 10:04:54] [INFO ] [main.c:255] [didactyl] startup checklist [18] READY: ok (agent online; entering main poll loop) +[2026-04-04 10:04:54] [INFO ] [main.c:1571] [didactyl] entering main poll loop +[2026-04-04 10:04:54] [INFO ] [main.c:1572] [didactyl] running with pubkey 1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc +[2026-04-04 10:04:54] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["OK","3edc6798888fa063a724c277ed553f35fcf339aa62a74b039edf35907bc4da9e",true,""] +[2026-04-04 10:04:54] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["OK","3edc6798888fa063a724c277ed553f35fcf339aa62a74b039edf35907bc4da9e",false,"rate-limited: you are noting too much"] +[2026-04-04 10:05:36] [INFO ] [main.c:1584] [didactyl] shutting down +[2026-04-04 10:05:36] [INFO ] [trigger_manager.c:1602] [didactyl] trigger manager cleaned up +[2026-04-04 10:05:36] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_7_1775311493"] +[2026-04-04 10:05:36] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_7_1775311493"] +[2026-04-04 10:05:36] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_8_1775311493"] +[2026-04-04 10:05:36] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_8_1775311493"] +[2026-04-04 10:05:36] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_9_1775311493"] +[2026-04-04 10:05:36] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_9_1775311493"] +[2026-04-04 10:05:36] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_10_1775311493"] +[2026-04-04 10:05:36] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_10_1775311493"] +[2026-04-04 10:05:36] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_11_1775311493"] +[2026-04-04 10:05:36] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_11_1775311493"] +[2026-04-04 10:05:36] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_12_1775311493"] +[2026-04-04 10:05:36] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_12_1775311493"] +[2026-04-04 10:05:36] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_13_1775311494"] +[2026-04-04 10:05:36] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_13_1775311494"] +[2026-04-04 10:05:36] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_14_1775311494"] +[2026-04-04 10:05:36] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_14_1775311494"] diff --git a/tests/results/20260404T135221Z/runtime_test_genesis.jsonc b/tests/results/20260404T135221Z/runtime_test_genesis.jsonc new file mode 100644 index 0000000..8aa613f --- /dev/null +++ b/tests/results/20260404T135221Z/runtime_test_genesis.jsonc @@ -0,0 +1,53 @@ +{ + // TEST CONFIGURATION + // Use disposable keys/accounts only. + "key": { + "nsec": "d3011e59954403fb84729ab0e9fb239ba0f0238b3f353dfb5c08a32f4b3d21a1" + }, + "admin": { + "pubkey": "254d7a7f68b4443f5430564eb3a726f09aea57d27e75ab2b8ef57176f9ca3dac" + }, + "dm_protocol": "nip04", + "llm": { + "provider": "https://api.ppq.ai", + "api_key": "sk-SBI2Pga0J6n8jpPorHbRHC", + "model": "claude-haiku-4.5", + "base_url": "https://api.ppq.ai", + "max_tokens": 10000, + "temperature": 0.7 + }, + "api": { + "enabled": true, + "port": 8485, + "bind_address": "127.0.0.1" + }, + "startup_events": [ + { + "kind": 0, + "content_fields": { + "name": "Didactyl Test Agent", + "about": "Automated test instance" + }, + "tags": [] + }, + { + "kind": 10002, + "content": "", + "tags": [ + ["r", "wss://relay.damus.io"], + ["r", "wss://relay.primal.net"] + ] + }, + { + "kind": 31124, + "content": "# Test Agent\n\nYou are a test agent. Respond to requests and use tools when needed.", + "tags": [ + ["d", "identity_and_rules"], + ["app", "didactyl"], + ["scope", "private"], + ["trigger", "dm"], + ["filter", "{\"from\":\"admin\"}"] + ] + } + ] +} diff --git a/tests/results/20260404T135328Z/agent_debug.log b/tests/results/20260404T135328Z/agent_debug.log new file mode 100644 index 0000000..1ede16d --- /dev/null +++ b/tests/results/20260404T135328Z/agent_debug.log @@ -0,0 +1,574 @@ +[2026-04-04 09:53:31] [INFO ] [main.c:1016] Didactyl v0.2.20 starting +[2026-04-04 09:53:31] [INFO ] [nostr_handler.c:2207] [didactyl] initializing relay pool with 2 relays +[2026-04-04 09:53:31] [INFO ] [nostr_handler.c:2231] [didactyl] added relay: wss://relay.damus.io +[2026-04-04 09:53:31] [INFO ] [nostr_handler.c:2231] [didactyl] added relay: wss://relay.primal.net +[2026-04-04 09:53:31] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_1_1775310811", { + "kinds": [10000], + "authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"], + "limit": 1 + }] +[2026-04-04 09:53:31] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_1_1775310811", { + "kinds": [10000], + "authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"], + "limit": 1 + }] +[2026-04-04 09:53:31] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_1_1775310811"] +[2026-04-04 09:53:32] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_1_1775310811"] +[2026-04-04 09:53:32] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_1_1775310811"] +[2026-04-04 09:53:32] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_1_1775310811"] +[2026-04-04 09:53:32] [INFO ] [main.c:248] [didactyl] startup checklist [01] Relay connectivity: begin +[2026-04-04 09:53:32] [INFO ] [main.c:255] [didactyl] startup checklist [01] Relay connectivity: ok (connected relays: 2/2) +[2026-04-04 09:53:32] [INFO ] [main.c:248] [didactyl] startup checklist [02] Recover runtime config from Nostr: begin +[2026-04-04 09:53:32] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_2_1775310812", { + "kinds": [30078], + "authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"], + "#d": ["agent_config"], + "limit": 1 + }] +[2026-04-04 09:53:32] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_2_1775310812", { + "kinds": [30078], + "authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"], + "#d": ["agent_config"], + "limit": 1 + }] +[2026-04-04 09:53:32] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 09:53:32] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_2_1775310812"] +[2026-04-04 09:53:32] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_2_1775310812"] +[2026-04-04 09:53:32] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_2_1775310812"] +[2026-04-04 09:53:32] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_2_1775310812"] +[2026-04-04 09:53:32] [INFO ] [main.c:258] [didactyl] startup checklist [02] Recover runtime config from Nostr: ok +[2026-04-04 09:53:32] [INFO ] [main.c:248] [didactyl] startup checklist [03] Validate LLM config: begin +[2026-04-04 09:53:32] [INFO ] [main.c:258] [didactyl] startup checklist [03] Validate LLM config: ok +[2026-04-04 09:53:32] [INFO ] [main.c:248] [didactyl] startup checklist [04] Validate admin config: begin +[2026-04-04 09:53:32] [INFO ] [main.c:258] [didactyl] startup checklist [04] Validate admin config: ok +[2026-04-04 09:53:32] [INFO ] [main.c:248] [didactyl] startup checklist [05] Initialize LLM client: begin +[2026-04-04 09:53:32] [INFO ] [main.c:258] [didactyl] startup checklist [05] Initialize LLM client: ok +[2026-04-04 09:53:32] [INFO ] [main.c:248] [didactyl] startup checklist [06] Detect first run via kind 10002: begin +[2026-04-04 09:53:32] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_3_1775310812", { + "kinds": [10002], + "authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"], + "limit": 1 + }] +[2026-04-04 09:53:32] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_3_1775310812", { + "kinds": [10002], + "authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"], + "limit": 1 + }] +[2026-04-04 09:53:32] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 09:53:32] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 09:53:32] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_3_1775310812"] +[2026-04-04 09:53:32] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_3_1775310812"] +[2026-04-04 09:53:32] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_3_1775310812"] +[2026-04-04 09:53:32] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_3_1775310812"] +[2026-04-04 09:53:32] [INFO ] [main.c:1245] [didactyl] startup phase: first-run detection via kind 10002 => subsequent-run +[2026-04-04 09:53:32] [INFO ] [main.c:255] [didactyl] startup checklist [06] Detect first run via kind 10002: ok (subsequent-run) +[2026-04-04 09:53:32] [INFO ] [main.c:248] [didactyl] startup checklist [07] Reconcile/persist startup state: begin +[2026-04-04 09:53:32] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_4_1775310812", { + "kinds": [1], + "authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"], + "limit": 1 + }] +[2026-04-04 09:53:32] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_4_1775310812", { + "kinds": [1], + "authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"], + "limit": 1 + }] +[2026-04-04 09:53:32] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_4_1775310812"] +[2026-04-04 09:53:32] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_4_1775310812"] +[2026-04-04 09:53:32] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_4_1775310812"] +[2026-04-04 09:53:32] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_4_1775310812"] +[2026-04-04 09:53:32] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] SEND +[2026-04-04 09:53:32] [INFO ] [nostr_handler.c:2949] [didactyl] kind 0 event published to wss://relay.damus.io (async, reason=startup_reconcile) +[2026-04-04 09:53:32] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] SEND +[2026-04-04 09:53:32] [INFO ] [nostr_handler.c:2949] [didactyl] kind 10002 event published to wss://relay.damus.io (async, reason=startup_reconcile) +[2026-04-04 09:53:32] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] SEND +[2026-04-04 09:53:32] [INFO ] [nostr_handler.c:2949] [didactyl] kind 31124 event published to wss://relay.damus.io (async, reason=startup_reconcile) +[2026-04-04 09:53:32] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] SEND +[2026-04-04 09:53:32] [INFO ] [nostr_handler.c:2949] [didactyl] kind 10123 event published to wss://relay.damus.io (async, reason=startup_reconcile) +[2026-04-04 09:53:32] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] SEND +[2026-04-04 09:53:32] [INFO ] [nostr_handler.c:2949] [didactyl] kind 0 event published to wss://relay.primal.net (async, reason=startup_reconcile) +[2026-04-04 09:53:32] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] SEND +[2026-04-04 09:53:32] [INFO ] [nostr_handler.c:2949] [didactyl] kind 10002 event published to wss://relay.primal.net (async, reason=startup_reconcile) +[2026-04-04 09:53:32] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] SEND +[2026-04-04 09:53:32] [INFO ] [nostr_handler.c:2949] [didactyl] kind 31124 event published to wss://relay.primal.net (async, reason=startup_reconcile) +[2026-04-04 09:53:32] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] SEND +[2026-04-04 09:53:32] [INFO ] [nostr_handler.c:2949] [didactyl] kind 10123 event published to wss://relay.primal.net (async, reason=startup_reconcile) +[2026-04-04 09:53:32] [INFO ] [nostr_handler.c:3285] [didactyl] startup publish detail: kind=0 (profile) -> wss://relay.damus.io, wss://relay.primal.net +[2026-04-04 09:53:32] [INFO ] [nostr_handler.c:3285] [didactyl] startup publish detail: kind=10002 (relay_list) -> wss://relay.damus.io, wss://relay.primal.net +[2026-04-04 09:53:32] [INFO ] [nostr_handler.c:3285] [didactyl] startup publish detail: kind=31124 (skill) -> wss://relay.damus.io, wss://relay.primal.net +[2026-04-04 09:53:32] [INFO ] [nostr_handler.c:3285] [didactyl] startup publish detail: kind=10123 (adoption_list) -> wss://relay.damus.io, wss://relay.primal.net +[2026-04-04 09:53:32] [INFO ] [nostr_handler.c:3297] [didactyl] startup publish summary: 4/4 event(s) published to at least one relay; relays used=2/2 +[2026-04-04 09:53:32] [INFO ] [nostr_handler.c:906] [didactyl] publish kind event target relays (2): +[2026-04-04 09:53:32] [INFO ] [nostr_handler.c:908] [didactyl] -> wss://relay.damus.io +[2026-04-04 09:53:32] [INFO ] [nostr_handler.c:908] [didactyl] -> wss://relay.primal.net +[2026-04-04 09:53:32] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] SEND +[2026-04-04 09:53:32] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] SEND +[2026-04-04 09:53:32] [INFO ] [nostr_handler.c:2949] [didactyl] kind 30078 event published to wss://relay.damus.io (async, reason=manual_publish) +[2026-04-04 09:53:32] [INFO ] [nostr_handler.c:2949] [didactyl] kind 30078 event published to wss://relay.primal.net (async, reason=manual_publish) +[2026-04-04 09:53:32] [INFO ] [nostr_handler.c:3012] [didactyl] published kind 30078 event via 2 connected relay(s) +[2026-04-04 09:53:32] [INFO ] [nostr_handler.c:906] [didactyl] publish kind event target relays (2): +[2026-04-04 09:53:32] [INFO ] [nostr_handler.c:908] [didactyl] -> wss://relay.damus.io +[2026-04-04 09:53:32] [INFO ] [nostr_handler.c:908] [didactyl] -> wss://relay.primal.net +[2026-04-04 09:53:32] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] SEND +[2026-04-04 09:53:32] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] SEND +[2026-04-04 09:53:32] [INFO ] [nostr_handler.c:2949] [didactyl] kind 30078 event published to wss://relay.damus.io (async, reason=manual_publish) +[2026-04-04 09:53:32] [INFO ] [nostr_handler.c:2949] [didactyl] kind 30078 event published to wss://relay.primal.net (async, reason=manual_publish) +[2026-04-04 09:53:32] [INFO ] [nostr_handler.c:3012] [didactyl] published kind 30078 event via 2 connected relay(s) +[2026-04-04 09:53:32] [INFO ] [main.c:1267] [didactyl] startup phase: persisted encrypted runtime config to Nostr +[2026-04-04 09:53:32] [INFO ] [main.c:258] [didactyl] startup checklist [07] Reconcile/persist startup state: ok +[2026-04-04 09:53:32] [INFO ] [main.c:248] [didactyl] startup checklist [08] Initialize agent: begin +[2026-04-04 09:53:32] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_5_1775310812", { + "kinds": [30078], + "authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"], + "#d": ["memory"], + "limit": 1 + }] +[2026-04-04 09:53:32] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_5_1775310812", { + "kinds": [30078], + "authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"], + "#d": ["memory"], + "limit": 1 + }] +[2026-04-04 09:53:33] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["OK","62db0aa31438a53f9811b70aa5de7d8b5e07f9327a1df1a1f28d1c56f2041cdd",true,""] +[2026-04-04 09:53:33] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["OK","62db0aa31438a53f9811b70aa5de7d8b5e07f9327a1df1a1f28d1c56f2041cdd",true,""] +[2026-04-04 09:53:33] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["OK","b2c46547046ec744abd08f147d49c1d218011fef013e6effc94fde7203db60d8",true,""] +[2026-04-04 09:53:33] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["OK","b2c46547046ec744abd08f147d49c1d218011fef013e6effc94fde7203db60d8",true,""] +[2026-04-04 09:53:33] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["OK","8e6842039e11bfabaa9ba7e2c9e94c9a75a87b2b2a8ea3f8c9ea4a31a8bbbbf8",true,""] +[2026-04-04 09:53:33] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["OK","8e6842039e11bfabaa9ba7e2c9e94c9a75a87b2b2a8ea3f8c9ea4a31a8bbbbf8",false,"rate-limited: you are noting too much"] +[2026-04-04 09:53:33] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["OK","912179848fa53e52e842e13b124c8e49d3015ef48b15a079ea4bd687b6251fc4",true,""] +[2026-04-04 09:53:33] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["OK","912179848fa53e52e842e13b124c8e49d3015ef48b15a079ea4bd687b6251fc4",true,""] +[2026-04-04 09:53:33] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_5_1775310812"] +[2026-04-04 09:53:33] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_5_1775310812"] +[2026-04-04 09:53:33] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["OK","07e867604bb4b8bd8a8925cdc485f437c7e6463a3cf039d3f3b1af42f4f6e95e",true,""] +[2026-04-04 09:53:33] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_5_1775310812"] +[2026-04-04 09:53:33] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_5_1775310812"] +[2026-04-04 09:53:33] [INFO ] [main.c:258] [didactyl] startup checklist [08] Initialize agent: ok +[2026-04-04 09:53:33] [INFO ] [main.c:248] [didactyl] startup checklist [09] Initialize trigger manager: begin +[2026-04-04 09:53:33] [INFO ] [trigger_manager.c:814] [didactyl] trigger manager initialized (capacity=16) +[2026-04-04 09:53:33] [INFO ] [main.c:258] [didactyl] startup checklist [09] Initialize trigger manager: ok +[2026-04-04 09:53:33] [INFO ] [main.c:1308] [didactyl] startup phase: deferred trigger load will run after self-skill EOSE +[2026-04-04 09:53:33] [INFO ] [main.c:248] [didactyl] startup checklist [10] Load startup triggers: begin +[2026-04-04 09:53:33] [INFO ] [main.c:1311] [didactyl] startup phase: startup-config trigger load begin +[2026-04-04 09:53:33] [INFO ] [trigger_manager.c:1138] [didactyl] trigger added d_tag=identity_and_rules action=0 enabled=1 +[2026-04-04 09:53:33] [INFO ] [trigger_manager.c:1021] [didactyl] startup trigger registered d_tag=identity_and_rules action=llm enabled=1 +[2026-04-04 09:53:33] [INFO ] [trigger_manager.c:1029] [didactyl] trigger manager loaded 1 trigger(s) from startup config (considered=1) +[2026-04-04 09:53:33] [INFO ] [main.c:1315] [didactyl] startup phase: startup-config trigger load end +[2026-04-04 09:53:33] [INFO ] [main.c:258] [didactyl] startup checklist [10] Load startup triggers: ok +[2026-04-04 09:53:33] [INFO ] [main.c:248] [didactyl] startup checklist [11] Discover self relay list via kind 10002: begin +[2026-04-04 09:53:33] [INFO ] [main.c:327] [didactyl] kind10002 query begin: timeout_ms=5000 author=1eb171136dbb1ba6... relay_count=2 +[2026-04-04 09:53:33] [INFO ] [main.c:334] [didactyl] kind10002 query relay snapshot: {"relay_count":2,"connected_count":2,"relays":[{"url":"wss://relay.damus.io","status":"connected","events_received":0,"events_published":6,"events_published_ok":3,"events_published_failed":1,"ping_latency_current":0,"ping_latency_avg":0,"query_latency_avg":0,"publish_latency_avg":0,"connection_uptime_start":1775310811,"last_event_time":1775310813,"last_publish_error":"rate-limited: you are noting too much"},{"url":"wss://relay.primal.net","status":"connected","events_received":2,"events_published":6,"events_published_ok":5,"events_published_failed":0,"ping_latency_current":0,"ping_latency_avg":0,"query_latency_avg":0,"publish_latency_avg":0,"connection_uptime_start":1775310811,"last_event_time":1775310813}]} +[2026-04-04 09:53:33] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_6_1775310813", { + "kinds": [10002], + "authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"], + "limit": 16 + }] +[2026-04-04 09:53:33] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_6_1775310813", { + "kinds": [10002], + "authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"], + "limit": 16 + }] +[2026-04-04 09:53:33] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["OK","b4ed5a24cfff8247076e5d3320511fee0a0546ec0000974382422e8f82e8ecb7",false,"rate-limited: you are noting too much"] +[2026-04-04 09:53:33] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["OK","b4ed5a24cfff8247076e5d3320511fee0a0546ec0000974382422e8f82e8ecb7",true,""] +[2026-04-04 09:53:33] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["OK","07e867604bb4b8bd8a8925cdc485f437c7e6463a3cf039d3f3b1af42f4f6e95e",false,"rate-limited: you are noting too much"] +[2026-04-04 09:53:33] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 09:53:33] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 09:53:33] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_6_1775310813"] +[2026-04-04 09:53:33] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_6_1775310813"] +[2026-04-04 09:53:33] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_6_1775310813"] +[2026-04-04 09:53:33] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_6_1775310813"] +[2026-04-04 09:53:33] [INFO ] [main.c:354] [didactyl] kind10002 query received event_count=1 +[2026-04-04 09:53:33] [INFO ] [main.c:451] [didactyl] kind10002 query extracted relays=["wss://relay.damus.io","wss://relay.primal.net"] +[2026-04-04 09:53:33] [INFO ] [main.c:556] [didactyl] kind10002 wait success: relay_count=2 +[2026-04-04 09:53:33] [INFO ] [main.c:558] [didactyl] kind10002 relay[0]=wss://relay.damus.io +[2026-04-04 09:53:33] [INFO ] [main.c:558] [didactyl] kind10002 relay[1]=wss://relay.primal.net +[2026-04-04 09:53:33] [INFO ] [main.c:562] [didactyl] kind10002 wait success relay snapshot: {"relay_count":2,"connected_count":2,"relays":[{"url":"wss://relay.damus.io","status":"connected","events_received":1,"events_published":6,"events_published_ok":3,"events_published_failed":3,"ping_latency_current":0,"ping_latency_avg":0,"query_latency_avg":0,"publish_latency_avg":0,"connection_uptime_start":1775310811,"last_event_time":1775310813,"last_publish_error":"rate-limited: you are noting too much"},{"url":"wss://relay.primal.net","status":"connected","events_received":2,"events_published":6,"events_published_ok":6,"events_published_failed":0,"ping_latency_current":0,"ping_latency_avg":0,"query_latency_avg":0,"publish_latency_avg":0,"connection_uptime_start":1775310811,"last_event_time":1775310813}]} +[2026-04-04 09:53:33] [INFO ] [main.c:255] [didactyl] startup checklist [11] Discover self relay list via kind 10002: ok (kind10002=2 added=0 connected=2/2) +[2026-04-04 09:53:33] [INFO ] [main.c:248] [didactyl] startup checklist [12] Subscribe admin context: begin +[2026-04-04 09:53:33] [INFO ] [main.c:1366] [didactyl] startup phase: subscribe admin context begin +[2026-04-04 09:53:33] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_7_1775310813", { + "kinds": [0, 3, 10002], + "authors": ["254d7a7f68b4443f5430564eb3a726f09aea57d27e75ab2b8ef57176f9ca3dac"], + "limit": 32 + }] +[2026-04-04 09:53:33] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_7_1775310813", { + "kinds": [0, 3, 10002], + "authors": ["254d7a7f68b4443f5430564eb3a726f09aea57d27e75ab2b8ef57176f9ca3dac"], + "limit": 32 + }] +[2026-04-04 09:53:33] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_8_1775310813", { + "kinds": [1], + "authors": ["254d7a7f68b4443f5430564eb3a726f09aea57d27e75ab2b8ef57176f9ca3dac"], + "limit": 10 + }] +[2026-04-04 09:53:33] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_8_1775310813", { + "kinds": [1], + "authors": ["254d7a7f68b4443f5430564eb3a726f09aea57d27e75ab2b8ef57176f9ca3dac"], + "limit": 10 + }] +[2026-04-04 09:53:33] [INFO ] [nostr_handler.c:2352] [didactyl] admin context subscriptions active for admin 254d7a7f68b4443f... +[2026-04-04 09:53:33] [INFO ] [main.c:1370] [didactyl] startup phase: subscribe admin context end +[2026-04-04 09:53:33] [INFO ] [main.c:258] [didactyl] startup checklist [12] Subscribe admin context: ok +[2026-04-04 09:53:33] [INFO ] [main.c:248] [didactyl] startup checklist [13] Subscribe agent self context: begin +[2026-04-04 09:53:33] [INFO ] [main.c:1374] [didactyl] startup phase: subscribe agent self context begin +[2026-04-04 09:53:33] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_9_1775310813", { + "kinds": [0, 3, 10002], + "authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"], + "limit": 64 + }] +[2026-04-04 09:53:33] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_9_1775310813", { + "kinds": [0, 3, 10002], + "authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"], + "limit": 64 + }] +[2026-04-04 09:53:33] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_10_1775310813", { + "kinds": [1], + "authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"], + "limit": 10 + }] +[2026-04-04 09:53:33] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_10_1775310813", { + "kinds": [1], + "authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"], + "limit": 10 + }] +[2026-04-04 09:53:33] [INFO ] [nostr_handler.c:2440] [didactyl] agent self-context subscriptions active for pubkey 1eb171136dbb1ba6... +[2026-04-04 09:53:33] [INFO ] [main.c:1378] [didactyl] startup phase: subscribe agent self context end +[2026-04-04 09:53:33] [INFO ] [main.c:258] [didactyl] startup checklist [13] Subscribe agent self context: ok +[2026-04-04 09:53:33] [INFO ] [main.c:248] [didactyl] startup checklist [14] Subscribe self-skill cache: begin +[2026-04-04 09:53:33] [INFO ] [main.c:1382] [didactyl] startup phase: subscribe self skill cache begin +[2026-04-04 09:53:33] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_11_1775310813", { + "kinds": [31123, 31124, 10123], + "authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"], + "limit": 300 + }] +[2026-04-04 09:53:33] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_11_1775310813", { + "kinds": [31123, 31124, 10123], + "authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"], + "limit": 300 + }] +[2026-04-04 09:53:33] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_12_1775310813", { + "kinds": [31123], + "authors": ["254d7a7f68b4443f5430564eb3a726f09aea57d27e75ab2b8ef57176f9ca3dac"], + "limit": 150 + }] +[2026-04-04 09:53:33] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_12_1775310813", { + "kinds": [31123], + "authors": ["254d7a7f68b4443f5430564eb3a726f09aea57d27e75ab2b8ef57176f9ca3dac"], + "limit": 150 + }] +[2026-04-04 09:53:33] [INFO ] [nostr_handler.c:2532] [didactyl] self/admin skill subscriptions active (self=1eb171136dbb1ba6..., admin=254d7a7f68b4443f...) +[2026-04-04 09:53:33] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_7_1775310813"] +[2026-04-04 09:53:33] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_8_1775310813"] +[2026-04-04 09:53:33] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 09:53:33] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 09:53:33] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_9_1775310813"] +[2026-04-04 09:53:33] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_10_1775310813"] +[2026-04-04 09:53:33] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 09:53:33] [INFO ] [nostr_handler.c:2151] [didactyl] self-skill cache received kind=31124 d_tag=identity_and_rules id=9f094c21eade42f8c2f23fbce8127d13e6f0baacef8ead7afca66dd54d2033a7 created_at=1775310813 via wss://relay.primal.net +[2026-04-04 09:53:33] [INFO ] [trigger_manager.c:1364] [didactyl] trigger updated d_tag=identity_and_rules action=0 enabled=1 +[2026-04-04 09:53:33] [INFO ] [nostr_handler.c:1207] [didactyl] live self-skill trigger registered d_tag=identity_and_rules action=llm enabled=1 +[2026-04-04 09:53:33] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 09:53:33] [INFO ] [nostr_handler.c:2151] [didactyl] self-skill cache received kind=10123 d_tag=- id=62107bfc20c3b449541878519bed40ebf94c21d49a1a9bb1da55c3f2d035fa7d created_at=1775310813 via wss://relay.primal.net +[2026-04-04 09:53:33] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_11_1775310813"] +[2026-04-04 09:53:33] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_7_1775310813"] +[2026-04-04 09:53:33] [TRACE] [nostr_handler.c:1718] [didactyl] DEBUG on_eose called: event_count=0 +[2026-04-04 09:53:33] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_12_1775310813"] +[2026-04-04 09:53:33] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_8_1775310813"] +[2026-04-04 09:53:33] [TRACE] [nostr_handler.c:1718] [didactyl] DEBUG on_eose called: event_count=0 +[2026-04-04 09:53:33] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 09:53:34] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 09:53:34] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_9_1775310813"] +[2026-04-04 09:53:34] [TRACE] [nostr_handler.c:1718] [didactyl] DEBUG on_eose called: event_count=0 +[2026-04-04 09:53:34] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_10_1775310813"] +[2026-04-04 09:53:34] [TRACE] [nostr_handler.c:1718] [didactyl] DEBUG on_eose called: event_count=0 +[2026-04-04 09:53:34] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 09:53:34] [INFO ] [nostr_handler.c:2151] [didactyl] self-skill cache received kind=31124 d_tag=identity_and_rules id=912179848fa53e52e842e13b124c8e49d3015ef48b15a079ea4bd687b6251fc4 created_at=1775310812 via wss://relay.damus.io +[2026-04-04 09:53:34] [INFO ] [trigger_manager.c:1364] [didactyl] trigger updated d_tag=identity_and_rules action=0 enabled=1 +[2026-04-04 09:53:34] [INFO ] [nostr_handler.c:1207] [didactyl] live self-skill trigger registered d_tag=identity_and_rules action=llm enabled=1 +[2026-04-04 09:53:34] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 09:53:34] [INFO ] [nostr_handler.c:2151] [didactyl] self-skill cache received kind=10123 d_tag=- id=e54b9c4384ee5e214a337f153c9cb7ecc61b46b77bce99851c2b80166cc3dd6d created_at=1775310771 via wss://relay.damus.io +[2026-04-04 09:53:34] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_11_1775310813"] +[2026-04-04 09:53:34] [INFO ] [nostr_handler.c:1725] [didactyl] self-skill EOSE received: cached skill events=0 +[2026-04-04 09:53:34] [INFO ] [main.c:44] [didactyl] deferred trigger load begin after self-skill EOSE (event_count=0) +[2026-04-04 09:53:34] [INFO ] [trigger_manager.c:1364] [didactyl] trigger updated d_tag=identity_and_rules action=0 enabled=1 +[2026-04-04 09:53:34] [INFO ] [trigger_manager.c:918] [didactyl] trigger manager loaded 1 trigger(s) from self skills (considered=1) +[2026-04-04 09:53:34] [INFO ] [main.c:54] [didactyl] deferred trigger load status: {"success":true,"count":1,"active":1,"triggers":[{"skill_d_tag":"identity_and_rules","filter_json":"{\"from\":\"admin\"}","type":"dm","action":"llm","enabled":true,"subscribed":false,"llm":"","tools":"","has_max_tokens":false,"has_temperature":false,"has_seed":false,"last_fired":0,"last_seen_created_at":0}]} +[2026-04-04 09:53:34] [INFO ] [nostr_handler.c:2757] [didactyl] self-skill cache event: kind=31124 d_tag=identity_and_rules id=9f094c21eade42f8c2f23fbce8127d13e6f0baacef8ead7afca66dd54d2033a7 created_at=1775310813 +[2026-04-04 09:53:34] [INFO ] [nostr_handler.c:2757] [didactyl] self-skill cache event: kind=10123 d_tag=- id=62107bfc20c3b449541878519bed40ebf94c21d49a1a9bb1da55c3f2d035fa7d created_at=1775310813 +[2026-04-04 09:53:34] [INFO ] [main.c:1449] [didactyl] startup phase: subscribe self skill cache end +[2026-04-04 09:53:34] [INFO ] [main.c:255] [didactyl] startup checklist [14] Subscribe self-skill cache: ok (skills=1 adoptions=1) +[2026-04-04 09:53:34] [INFO ] [main.c:248] [didactyl] startup checklist [15] Subscribe DMs: begin +[2026-04-04 09:53:34] [INFO ] [main.c:1453] [didactyl] startup phase: subscribe DMs begin +[2026-04-04 09:53:34] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_13_1775310814", { + "kinds": [4], + "#p": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"], + "since": 1775310811, + "limit": 100 + }] +[2026-04-04 09:53:34] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_13_1775310814", { + "kinds": [4], + "#p": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"], + "since": 1775310811, + "limit": 100 + }] +[2026-04-04 09:53:34] [INFO ] [nostr_handler.c:2643] [didactyl] DM subscription active for pubkey 1eb171136dbb1ba6... +[2026-04-04 09:53:34] [TRACE] [nostr_handler.c:2644] [didactyl] DEBUG DM subscription g_start_time=1775310811 now=1775310814 delta=3 relay_count=2 +[2026-04-04 09:53:34] [INFO ] [main.c:1468] [didactyl] startup phase: subscribe DMs end +[2026-04-04 09:53:34] [INFO ] [main.c:258] [didactyl] startup checklist [15] Subscribe DMs: ok +[2026-04-04 09:53:34] [INFO ] [main.c:248] [didactyl] startup checklist [16] Subscribe wallet events: begin +[2026-04-04 09:53:34] [INFO ] [main.c:1472] [didactyl] startup phase: subscribe wallet events begin +[2026-04-04 09:53:34] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_14_1775310814", { + "kinds": [17375, 7375, 5], + "authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"], + "since": 1775310811, + "limit": 500 + }] +[2026-04-04 09:53:34] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_14_1775310814", { + "kinds": [17375, 7375, 5], + "authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"], + "since": 1775310811, + "limit": 500 + }] +[2026-04-04 09:53:34] [INFO ] [nostr_handler.c:2696] [didactyl] wallet event subscription active for pubkey 1eb171136dbb1ba6... +[2026-04-04 09:53:34] [INFO ] [main.c:1476] [didactyl] startup phase: subscribe wallet events end +[2026-04-04 09:53:34] [INFO ] [main.c:258] [didactyl] startup checklist [16] Subscribe wallet events: ok +[2026-04-04 09:53:34] [INFO ] [main.c:248] [didactyl] startup checklist [17] Initialize cashu wallet: begin +[2026-04-04 09:53:34] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_15_1775310814", { + "kinds": [17375, 7375], + "authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"] + }] +[2026-04-04 09:53:34] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_15_1775310814", { + "kinds": [17375, 7375], + "authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"] + }] +[2026-04-04 09:53:34] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_12_1775310813"] +[2026-04-04 09:53:34] [TRACE] [nostr_handler.c:1718] [didactyl] DEBUG on_eose called: event_count=0 +[2026-04-04 09:53:34] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 09:53:34] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_13_1775310814"] +[2026-04-04 09:53:34] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 09:53:34] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_14_1775310814"] +[2026-04-04 09:53:34] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_13_1775310814"] +[2026-04-04 09:53:34] [TRACE] [nostr_handler.c:1718] [didactyl] DEBUG on_eose called: event_count=0 +[2026-04-04 09:53:34] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_15_1775310814"] +[2026-04-04 09:53:34] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_14_1775310814"] +[2026-04-04 09:53:34] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_15_1775310814"] +[2026-04-04 09:53:34] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_15_1775310814"] +[2026-04-04 09:53:34] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_15_1775310814"] +[2026-04-04 09:53:34] [WARN ] [main.c:1492] [didactyl] startup phase: cashu wallet load/create deferred to first tool call +[2026-04-04 09:53:34] [INFO ] [main.c:255] [didactyl] startup checklist [17] Initialize cashu wallet: ok (initialized; load/create deferred) +[2026-04-04 09:53:34] [WARN ] [main.c:1528] [didactyl] HTTP API disabled due to bind/init failure; agent will continue without local API +[2026-04-04 09:53:34] [TRACE] [nostr_handler.c:1499] [didactyl] sending plaintext DM content: Didactyl Test Agent has started up and is online at 2026-04-04 09:53:34 (version v0.2.20, connected relays: 2/2). +[2026-04-04 09:53:34] [TRACE] [nostr_handler.c:1491] [didactyl] sending encrypted DM event: {"pubkey":"1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc","created_at":1775310814,"kind":4,"tags":[["p","254d7a7f68b4443f5430564eb3a726f09aea57d27e75ab2b8ef57176f9ca3dac"]],"content":"2IdS5q7P2xG6npiwo8XNCheTJ0ORjlriZhvvgByCrOrcY7U4roaoXA0AIJXM9CQxWLSkrWaXi0h1DTSOPZVCo5SqhQHEW4phQRWSz/bKnTjd3v8FTbCqO/JG1ghELrtmc3wED7CkEudSYgIEn46YYiYtomq/ulvkx7W6cm38tRM=?iv=jGIH5/frLMaWsdBfOKb0Gw==","id":"67ea8af90c4f8e8f1d9267beea62ce3c85c38493e386d22019b8a51b430782ba","sig":"1d6589f39630ad5e278b6c2a4b550903f7f09a0e5f5c4a42257a36e718c10ae9d959db9c73d72a3ab597baeac41076a71c7dfc3a87e6ba234e98946a6205dbef"} +[2026-04-04 09:53:34] [INFO ] [nostr_handler.c:906] [didactyl] publish DM target relays (2): +[2026-04-04 09:53:34] [INFO ] [nostr_handler.c:908] [didactyl] -> wss://relay.damus.io +[2026-04-04 09:53:34] [INFO ] [nostr_handler.c:908] [didactyl] -> wss://relay.primal.net +[2026-04-04 09:53:34] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] SEND +[2026-04-04 09:53:34] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] SEND +[2026-04-04 09:53:34] [INFO ] [nostr_handler.c:2843] [didactyl] kind 4 event published to wss://relay.damus.io (async) +[2026-04-04 09:53:34] [INFO ] [nostr_handler.c:2843] [didactyl] kind 4 event published to wss://relay.primal.net (async) +[2026-04-04 09:53:34] [INFO ] [nostr_handler.c:2852] [didactyl] sent DM 67ea8af90c4f8e8f... to 254d7a7f68b4443f... via 2 connected relay(s) +[2026-04-04 09:53:34] [INFO ] [main.c:255] [didactyl] startup checklist [18] READY: ok (agent online; entering main poll loop) +[2026-04-04 09:53:34] [INFO ] [main.c:1571] [didactyl] entering main poll loop +[2026-04-04 09:53:34] [INFO ] [main.c:1572] [didactyl] running with pubkey 1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc +[2026-04-04 09:53:34] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["OK","67ea8af90c4f8e8f1d9267beea62ce3c85c38493e386d22019b8a51b430782ba",false,"rate-limited: you are noting too much"] +[2026-04-04 09:53:34] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["OK","67ea8af90c4f8e8f1d9267beea62ce3c85c38493e386d22019b8a51b430782ba",true,""] +[2026-04-04 09:53:37] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 09:53:37] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 09:53:37] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 09:53:37] [INFO ] [nostr_handler.c:2151] [didactyl] self-skill cache received kind=31124 d_tag=identity_and_rules id=9153008526c935129f5e6aad28d37b8cf73eade2cac7a581ea19bb31ddfb77fd created_at=1775310817 via wss://relay.primal.net +[2026-04-04 09:53:37] [INFO ] [trigger_manager.c:1364] [didactyl] trigger updated d_tag=identity_and_rules action=0 enabled=1 +[2026-04-04 09:53:37] [INFO ] [nostr_handler.c:1207] [didactyl] live self-skill trigger registered d_tag=identity_and_rules action=llm enabled=1 +[2026-04-04 09:53:37] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 09:53:37] [INFO ] [nostr_handler.c:2151] [didactyl] self-skill cache received kind=10123 d_tag=- id=e3c57af82d9726d819e56625794a1887914536576cc80c6dd9b5795dc533699b created_at=1775310817 via wss://relay.primal.net +[2026-04-04 09:53:38] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 09:53:42] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 09:53:42] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 09:53:42] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 09:53:42] [INFO ] [nostr_handler.c:2151] [didactyl] self-skill cache received kind=31124 d_tag=identity_and_rules id=6136e4b953dff6c275673c984419e3732528d588de5eedd8d492123638d4a459 created_at=1775310821 via wss://relay.primal.net +[2026-04-04 09:53:42] [INFO ] [trigger_manager.c:1364] [didactyl] trigger updated d_tag=identity_and_rules action=0 enabled=1 +[2026-04-04 09:53:42] [INFO ] [nostr_handler.c:1207] [didactyl] live self-skill trigger registered d_tag=identity_and_rules action=llm enabled=1 +[2026-04-04 09:53:42] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 09:53:42] [INFO ] [nostr_handler.c:2151] [didactyl] self-skill cache received kind=10123 d_tag=- id=a42e4d656c81b6393d9ca684b2c4609d1268e8b41e6a6170ef33c8d25d448b8d created_at=1775310821 via wss://relay.primal.net +[2026-04-04 09:53:43] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 09:53:46] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 09:53:46] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 09:53:46] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 09:53:46] [INFO ] [nostr_handler.c:2151] [didactyl] self-skill cache received kind=31124 d_tag=identity_and_rules id=3e46947f0f9953c994de18c03873c8290528791d5e161489d3830288e758851e created_at=1775310826 via wss://relay.primal.net +[2026-04-04 09:53:46] [INFO ] [trigger_manager.c:1364] [didactyl] trigger updated d_tag=identity_and_rules action=0 enabled=1 +[2026-04-04 09:53:46] [INFO ] [nostr_handler.c:1207] [didactyl] live self-skill trigger registered d_tag=identity_and_rules action=llm enabled=1 +[2026-04-04 09:53:46] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 09:53:46] [INFO ] [nostr_handler.c:2151] [didactyl] self-skill cache received kind=10123 d_tag=- id=555277e0d773c2c05dfd59495698e0f2ae63b88a2c892716d34fbf731f90c93b created_at=1775310826 via wss://relay.primal.net +[2026-04-04 09:55:05] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 09:55:05] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 09:55:05] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 09:55:05] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 09:55:05] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 09:55:05] [INFO ] [nostr_handler.c:2151] [didactyl] self-skill cache received kind=31124 d_tag=identity_and_rules id=199821474ffaaedf3e7d90e8848fb95adfb3c547d25e62292f2ae01c6d15be26 created_at=1775310905 via wss://relay.damus.io +[2026-04-04 09:55:05] [INFO ] [trigger_manager.c:1364] [didactyl] trigger updated d_tag=identity_and_rules action=0 enabled=1 +[2026-04-04 09:55:05] [INFO ] [nostr_handler.c:1207] [didactyl] live self-skill trigger registered d_tag=identity_and_rules action=llm enabled=1 +[2026-04-04 09:55:05] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 09:55:05] [INFO ] [nostr_handler.c:2151] [didactyl] self-skill cache received kind=31124 d_tag=identity_and_rules id=199821474ffaaedf3e7d90e8848fb95adfb3c547d25e62292f2ae01c6d15be26 created_at=1775310905 via wss://relay.primal.net +[2026-04-04 09:55:05] [INFO ] [trigger_manager.c:1364] [didactyl] trigger updated d_tag=identity_and_rules action=0 enabled=1 +[2026-04-04 09:55:05] [INFO ] [nostr_handler.c:1207] [didactyl] live self-skill trigger registered d_tag=identity_and_rules action=llm enabled=1 +[2026-04-04 09:55:05] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 09:55:05] [INFO ] [nostr_handler.c:2151] [didactyl] self-skill cache received kind=10123 d_tag=- id=34ab5236e0848a5d27a6d98be6c3962919867267d861ae88d7887c186dc59d6f created_at=1775310905 via wss://relay.damus.io +[2026-04-04 09:55:05] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 09:55:05] [INFO ] [nostr_handler.c:2151] [didactyl] self-skill cache received kind=10123 d_tag=- id=34ab5236e0848a5d27a6d98be6c3962919867267d861ae88d7887c186dc59d6f created_at=1775310905 via wss://relay.primal.net +[2026-04-04 09:56:27] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 09:56:27] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 09:56:27] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 09:56:27] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 09:56:27] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 09:56:27] [INFO ] [nostr_handler.c:2151] [didactyl] self-skill cache received kind=10123 d_tag=- id=3593e19374c0110c7fc6d62dddb4d5ea5aa96d71a6b5a87df3f58e7e6044b721 created_at=1775310986 via wss://relay.damus.io +[2026-04-04 09:56:27] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 09:56:27] [INFO ] [nostr_handler.c:2151] [didactyl] self-skill cache received kind=10123 d_tag=- id=3593e19374c0110c7fc6d62dddb4d5ea5aa96d71a6b5a87df3f58e7e6044b721 created_at=1775310986 via wss://relay.primal.net +[2026-04-04 09:56:27] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 09:56:27] [INFO ] [nostr_handler.c:2151] [didactyl] self-skill cache received kind=31124 d_tag=identity_and_rules id=a099e3e10e33e4874c687dd7a6c19efc4cf96500c9190279e8f7a0a02ba655bb created_at=1775310986 via wss://relay.damus.io +[2026-04-04 09:56:27] [INFO ] [trigger_manager.c:1364] [didactyl] trigger updated d_tag=identity_and_rules action=0 enabled=1 +[2026-04-04 09:56:27] [INFO ] [nostr_handler.c:1207] [didactyl] live self-skill trigger registered d_tag=identity_and_rules action=llm enabled=1 +[2026-04-04 09:56:27] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 09:56:27] [INFO ] [nostr_handler.c:2151] [didactyl] self-skill cache received kind=31124 d_tag=identity_and_rules id=a099e3e10e33e4874c687dd7a6c19efc4cf96500c9190279e8f7a0a02ba655bb created_at=1775310986 via wss://relay.primal.net +[2026-04-04 09:56:27] [INFO ] [trigger_manager.c:1364] [didactyl] trigger updated d_tag=identity_and_rules action=0 enabled=1 +[2026-04-04 09:56:27] [INFO ] [nostr_handler.c:1207] [didactyl] live self-skill trigger registered d_tag=identity_and_rules action=llm enabled=1 +[2026-04-04 09:57:50] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 09:57:50] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 09:57:50] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 09:57:50] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 09:57:50] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 09:57:50] [INFO ] [nostr_handler.c:2151] [didactyl] self-skill cache received kind=31124 d_tag=identity_and_rules id=6b1239e90ef1eb0cc6d01d5e23e46a35075f8676fa9bdee1f27844da3bceebae created_at=1775311069 via wss://relay.damus.io +[2026-04-04 09:57:50] [INFO ] [trigger_manager.c:1364] [didactyl] trigger updated d_tag=identity_and_rules action=0 enabled=1 +[2026-04-04 09:57:50] [INFO ] [nostr_handler.c:1207] [didactyl] live self-skill trigger registered d_tag=identity_and_rules action=llm enabled=1 +[2026-04-04 09:57:50] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 09:57:50] [INFO ] [nostr_handler.c:2151] [didactyl] self-skill cache received kind=31124 d_tag=identity_and_rules id=6b1239e90ef1eb0cc6d01d5e23e46a35075f8676fa9bdee1f27844da3bceebae created_at=1775311069 via wss://relay.primal.net +[2026-04-04 09:57:50] [INFO ] [trigger_manager.c:1364] [didactyl] trigger updated d_tag=identity_and_rules action=0 enabled=1 +[2026-04-04 09:57:50] [INFO ] [nostr_handler.c:1207] [didactyl] live self-skill trigger registered d_tag=identity_and_rules action=llm enabled=1 +[2026-04-04 09:57:50] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 09:57:50] [INFO ] [nostr_handler.c:2151] [didactyl] self-skill cache received kind=10123 d_tag=- id=93a70c1d405d848e4c0d8e6b73b1e5ca9111c06549ddce551a275590846865ab created_at=1775311069 via wss://relay.damus.io +[2026-04-04 09:57:50] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 09:57:50] [INFO ] [nostr_handler.c:2151] [didactyl] self-skill cache received kind=10123 d_tag=- id=93a70c1d405d848e4c0d8e6b73b1e5ca9111c06549ddce551a275590846865ab created_at=1775311069 via wss://relay.primal.net +[2026-04-04 09:59:10] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 09:59:10] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 09:59:10] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 09:59:10] [INFO ] [nostr_handler.c:2151] [didactyl] self-skill cache received kind=10123 d_tag=- id=20ee7cfd49445a14a8163d37cc2113989f08db64d5bd97b6635e7b71d5067397 created_at=1775311149 via wss://relay.damus.io +[2026-04-04 09:59:10] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 09:59:10] [INFO ] [nostr_handler.c:2151] [didactyl] self-skill cache received kind=10123 d_tag=- id=20ee7cfd49445a14a8163d37cc2113989f08db64d5bd97b6635e7b71d5067397 created_at=1775311149 via wss://relay.primal.net +[2026-04-04 09:59:10] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 09:59:10] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 09:59:10] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 09:59:10] [INFO ] [nostr_handler.c:2151] [didactyl] self-skill cache received kind=31124 d_tag=identity_and_rules id=a1da473de1325a5a6d0b64d7f4435ac61ae8ebde1a7a76a3abb27746529e0414 created_at=1775311149 via wss://relay.damus.io +[2026-04-04 09:59:10] [INFO ] [trigger_manager.c:1364] [didactyl] trigger updated d_tag=identity_and_rules action=0 enabled=1 +[2026-04-04 09:59:10] [INFO ] [nostr_handler.c:1207] [didactyl] live self-skill trigger registered d_tag=identity_and_rules action=llm enabled=1 +[2026-04-04 09:59:10] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 09:59:10] [INFO ] [nostr_handler.c:2151] [didactyl] self-skill cache received kind=31124 d_tag=identity_and_rules id=a1da473de1325a5a6d0b64d7f4435ac61ae8ebde1a7a76a3abb27746529e0414 created_at=1775311149 via wss://relay.primal.net +[2026-04-04 09:59:10] [INFO ] [trigger_manager.c:1364] [didactyl] trigger updated d_tag=identity_and_rules action=0 enabled=1 +[2026-04-04 09:59:10] [INFO ] [nostr_handler.c:1207] [didactyl] live self-skill trigger registered d_tag=identity_and_rules action=llm enabled=1 +[2026-04-04 10:00:31] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 10:00:31] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 10:00:31] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 10:00:31] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 10:00:31] [INFO ] [nostr_handler.c:2151] [didactyl] self-skill cache received kind=10123 d_tag=- id=23e119df15e5dab7da6052bef17b973eb732d64f96f0cb81bae55c7eafe0cf84 created_at=1775311231 via wss://relay.damus.io +[2026-04-04 10:00:31] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 10:00:31] [INFO ] [nostr_handler.c:2151] [didactyl] self-skill cache received kind=10123 d_tag=- id=23e119df15e5dab7da6052bef17b973eb732d64f96f0cb81bae55c7eafe0cf84 created_at=1775311231 via wss://relay.primal.net +[2026-04-04 10:00:31] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 10:00:31] [INFO ] [nostr_handler.c:2151] [didactyl] self-skill cache received kind=31124 d_tag=identity_and_rules id=af0e63a9aa9b8dcdec1705ef2b0e6eb183a760563916ac1abe4ccd4b3ff31b9e created_at=1775311231 via wss://relay.damus.io +[2026-04-04 10:00:31] [INFO ] [trigger_manager.c:1364] [didactyl] trigger updated d_tag=identity_and_rules action=0 enabled=1 +[2026-04-04 10:00:31] [INFO ] [nostr_handler.c:1207] [didactyl] live self-skill trigger registered d_tag=identity_and_rules action=llm enabled=1 +[2026-04-04 10:00:31] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 10:00:31] [INFO ] [nostr_handler.c:2151] [didactyl] self-skill cache received kind=31124 d_tag=identity_and_rules id=af0e63a9aa9b8dcdec1705ef2b0e6eb183a760563916ac1abe4ccd4b3ff31b9e created_at=1775311231 via wss://relay.primal.net +[2026-04-04 10:00:31] [INFO ] [trigger_manager.c:1364] [didactyl] trigger updated d_tag=identity_and_rules action=0 enabled=1 +[2026-04-04 10:00:31] [INFO ] [nostr_handler.c:1207] [didactyl] live self-skill trigger registered d_tag=identity_and_rules action=llm enabled=1 +[2026-04-04 10:00:31] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 10:01:54] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 10:01:54] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 10:01:54] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 10:01:54] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 10:01:54] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 10:01:54] [INFO ] [nostr_handler.c:2151] [didactyl] self-skill cache received kind=10123 d_tag=- id=b08288f7f0691347979d73d8224d59ab518b4c4903d467261c6f54a4ac6438a6 created_at=1775311314 via wss://relay.damus.io +[2026-04-04 10:01:55] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 10:01:55] [INFO ] [nostr_handler.c:2151] [didactyl] self-skill cache received kind=31124 d_tag=identity_and_rules id=e2da3b48d758a294257443571dbfaede7befff218e0a7ed633f8ae8d90d44b11 created_at=1775311314 via wss://relay.damus.io +[2026-04-04 10:01:55] [INFO ] [trigger_manager.c:1364] [didactyl] trigger updated d_tag=identity_and_rules action=0 enabled=1 +[2026-04-04 10:01:55] [INFO ] [nostr_handler.c:1207] [didactyl] live self-skill trigger registered d_tag=identity_and_rules action=llm enabled=1 +[2026-04-04 10:01:55] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 10:01:55] [INFO ] [nostr_handler.c:2151] [didactyl] self-skill cache received kind=10123 d_tag=- id=b08288f7f0691347979d73d8224d59ab518b4c4903d467261c6f54a4ac6438a6 created_at=1775311314 via wss://relay.primal.net +[2026-04-04 10:01:55] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 10:01:55] [INFO ] [nostr_handler.c:2151] [didactyl] self-skill cache received kind=31124 d_tag=identity_and_rules id=e2da3b48d758a294257443571dbfaede7befff218e0a7ed633f8ae8d90d44b11 created_at=1775311314 via wss://relay.primal.net +[2026-04-04 10:01:55] [INFO ] [trigger_manager.c:1364] [didactyl] trigger updated d_tag=identity_and_rules action=0 enabled=1 +[2026-04-04 10:01:55] [INFO ] [nostr_handler.c:1207] [didactyl] live self-skill trigger registered d_tag=identity_and_rules action=llm enabled=1 +[2026-04-04 10:02:42] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 10:02:42] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 10:02:42] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 10:02:42] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 10:02:42] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 10:02:42] [INFO ] [nostr_handler.c:2151] [didactyl] self-skill cache received kind=31124 d_tag=identity_and_rules id=b1e317e969767bd23746d85d16932a907d45c49911ec2d954f45253a0afc8c45 created_at=1775311361 via wss://relay.damus.io +[2026-04-04 10:02:42] [INFO ] [trigger_manager.c:1364] [didactyl] trigger updated d_tag=identity_and_rules action=0 enabled=1 +[2026-04-04 10:02:42] [INFO ] [nostr_handler.c:1207] [didactyl] live self-skill trigger registered d_tag=identity_and_rules action=llm enabled=1 +[2026-04-04 10:02:42] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 10:02:42] [INFO ] [nostr_handler.c:2151] [didactyl] self-skill cache received kind=10123 d_tag=- id=4ce55cdf481bdc52128adcf546c526819e910e6e429510a67b16aee6ab8a27ce created_at=1775311361 via wss://relay.primal.net +[2026-04-04 10:02:42] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 10:02:42] [INFO ] [nostr_handler.c:2151] [didactyl] self-skill cache received kind=31124 d_tag=identity_and_rules id=b1e317e969767bd23746d85d16932a907d45c49911ec2d954f45253a0afc8c45 created_at=1775311361 via wss://relay.primal.net +[2026-04-04 10:02:42] [INFO ] [trigger_manager.c:1364] [didactyl] trigger updated d_tag=identity_and_rules action=0 enabled=1 +[2026-04-04 10:02:42] [INFO ] [nostr_handler.c:1207] [didactyl] live self-skill trigger registered d_tag=identity_and_rules action=llm enabled=1 +[2026-04-04 10:02:42] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 10:02:42] [INFO ] [nostr_handler.c:2151] [didactyl] self-skill cache received kind=31124 d_tag=identity_and_rules id=1de0c656f13e494782235710020ec1863236f385ea9bbde63973e32fac22e96e created_at=1775311362 via wss://relay.primal.net +[2026-04-04 10:02:42] [INFO ] [trigger_manager.c:1364] [didactyl] trigger updated d_tag=identity_and_rules action=0 enabled=1 +[2026-04-04 10:02:42] [INFO ] [nostr_handler.c:1207] [didactyl] live self-skill trigger registered d_tag=identity_and_rules action=llm enabled=1 +[2026-04-04 10:02:42] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 10:02:42] [INFO ] [nostr_handler.c:2151] [didactyl] self-skill cache received kind=10123 d_tag=- id=5e118f362ee720887516af8389a895327771a32d97be51d179061cc959856306 created_at=1775311362 via wss://relay.primal.net +[2026-04-04 10:02:42] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 10:02:42] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 10:02:43] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 10:03:47] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 10:03:47] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 10:03:47] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 10:03:47] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 10:03:47] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 10:03:47] [INFO ] [nostr_handler.c:2151] [didactyl] self-skill cache received kind=10123 d_tag=- id=91a98af6794b4cda3e23918a72596ea072432a3bf61d0d2f175266555f69ad2c created_at=1775311427 via wss://relay.damus.io +[2026-04-04 10:03:47] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 10:03:47] [INFO ] [nostr_handler.c:2151] [didactyl] self-skill cache received kind=10123 d_tag=- id=91a98af6794b4cda3e23918a72596ea072432a3bf61d0d2f175266555f69ad2c created_at=1775311427 via wss://relay.primal.net +[2026-04-04 10:03:47] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 10:03:47] [INFO ] [nostr_handler.c:2151] [didactyl] self-skill cache received kind=31124 d_tag=identity_and_rules id=a101386a4d04cfe90e3278a442f7d8a5666b822551679be9e1da7ee3b50e66c0 created_at=1775311427 via wss://relay.damus.io +[2026-04-04 10:03:47] [INFO ] [trigger_manager.c:1364] [didactyl] trigger updated d_tag=identity_and_rules action=0 enabled=1 +[2026-04-04 10:03:47] [INFO ] [nostr_handler.c:1207] [didactyl] live self-skill trigger registered d_tag=identity_and_rules action=llm enabled=1 +[2026-04-04 10:03:47] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 10:03:47] [INFO ] [nostr_handler.c:2151] [didactyl] self-skill cache received kind=31124 d_tag=identity_and_rules id=a101386a4d04cfe90e3278a442f7d8a5666b822551679be9e1da7ee3b50e66c0 created_at=1775311427 via wss://relay.primal.net +[2026-04-04 10:03:47] [INFO ] [trigger_manager.c:1364] [didactyl] trigger updated d_tag=identity_and_rules action=0 enabled=1 +[2026-04-04 10:03:47] [INFO ] [nostr_handler.c:1207] [didactyl] live self-skill trigger registered d_tag=identity_and_rules action=llm enabled=1 +[2026-04-04 10:03:54] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 10:03:54] [TRACE] [nostr_handler.c:1505] [didactyl] DEBUG on_event ENTRY from wss://relay.primal.net (event=0x75dedd26ac00 g_cfg=0x7fff11e75dd0 g_dm_callback=set) +[2026-04-04 10:03:54] [TRACE] [nostr_handler.c:1539] [didactyl] DEBUG on_event: kind=4 id=d11595fbb84cc4e0... from=1eb171136dbb1ba6... via wss://relay.primal.net +[2026-04-04 10:03:54] [TRACE] [nostr_handler.c:1491] [didactyl] received encrypted DM event: {"content":"vU6HDxbHyDPXuQc6QBb6mKaxpT/v84wpTSRnDxIKB9HUtoMXbkmSmEWOE84JW8dm?iv=COBkis+eKnjifb3Qs2dSCg==","created_at":1775311434,"id":"d11595fbb84cc4e0d86c0791c1fc2fb0cd3c00f0556fa80529a6b2004b4d8eee","kind":4,"pubkey":"1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc","sig":"7db392af3ada4cba8fb8d864629f503e864797cb7d76577e573ece9127e828bd82cc4b6366a2ed97dec524be412a32b9389f46f678c72dcaac6f66d3f1e206d3","tags":[["p","1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"]]} +[2026-04-04 10:03:54] [TRACE] [nostr_handler.c:1499] [didactyl] received decrypted DM content: This is a test DM from me to myself! +[2026-04-04 10:03:54] [TRACE] [nostr_handler.c:1664] [didactyl] DEBUG on_event: ignoring self-sent DM 1eb171136dbb1ba6... via wss://relay.primal.net +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 10:04:53] [INFO ] [nostr_handler.c:2151] [didactyl] self-skill cache received kind=10123 d_tag=- id=952130858723506c1eac58e94f253c8b35e137c056dcedf0619ab63348f43caa created_at=1775311493 via wss://relay.damus.io +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 10:04:53] [INFO ] [nostr_handler.c:2151] [didactyl] self-skill cache received kind=10123 d_tag=- id=952130858723506c1eac58e94f253c8b35e137c056dcedf0619ab63348f43caa created_at=1775311493 via wss://relay.primal.net +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 10:04:53] [INFO ] [nostr_handler.c:2151] [didactyl] self-skill cache received kind=31124 d_tag=identity_and_rules id=b3095dd390e3a08ff24404195292ed302711334687a7de5ca0b433d9fe66a92e created_at=1775311493 via wss://relay.damus.io +[2026-04-04 10:04:53] [INFO ] [trigger_manager.c:1364] [didactyl] trigger updated d_tag=identity_and_rules action=0 enabled=1 +[2026-04-04 10:04:53] [INFO ] [nostr_handler.c:1207] [didactyl] live self-skill trigger registered d_tag=identity_and_rules action=llm enabled=1 +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 10:04:53] [INFO ] [nostr_handler.c:2151] [didactyl] self-skill cache received kind=31124 d_tag=identity_and_rules id=b3095dd390e3a08ff24404195292ed302711334687a7de5ca0b433d9fe66a92e created_at=1775311493 via wss://relay.primal.net +[2026-04-04 10:04:53] [INFO ] [trigger_manager.c:1364] [didactyl] trigger updated d_tag=identity_and_rules action=0 enabled=1 +[2026-04-04 10:04:53] [INFO ] [nostr_handler.c:1207] [didactyl] live self-skill trigger registered d_tag=identity_and_rules action=llm enabled=1 +[2026-04-04 10:05:36] [INFO ] [main.c:1584] [didactyl] shutting down +[2026-04-04 10:05:36] [INFO ] [trigger_manager.c:1602] [didactyl] trigger manager cleaned up +[2026-04-04 10:05:36] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_7_1775310813"] +[2026-04-04 10:05:36] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_7_1775310813"] +[2026-04-04 10:05:36] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_8_1775310813"] +[2026-04-04 10:05:36] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_8_1775310813"] +[2026-04-04 10:05:36] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_9_1775310813"] +[2026-04-04 10:05:36] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_9_1775310813"] +[2026-04-04 10:05:36] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_10_1775310813"] +[2026-04-04 10:05:36] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_10_1775310813"] +[2026-04-04 10:05:36] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_11_1775310813"] +[2026-04-04 10:05:36] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_11_1775310813"] +[2026-04-04 10:05:36] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_12_1775310813"] +[2026-04-04 10:05:36] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_12_1775310813"] +[2026-04-04 10:05:36] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_13_1775310814"] +[2026-04-04 10:05:36] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_13_1775310814"] +[2026-04-04 10:05:36] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_14_1775310814"] +[2026-04-04 10:05:36] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_14_1775310814"] diff --git a/tests/results/20260404T135328Z/runtime_test_genesis.jsonc b/tests/results/20260404T135328Z/runtime_test_genesis.jsonc new file mode 100644 index 0000000..8aa613f --- /dev/null +++ b/tests/results/20260404T135328Z/runtime_test_genesis.jsonc @@ -0,0 +1,53 @@ +{ + // TEST CONFIGURATION + // Use disposable keys/accounts only. + "key": { + "nsec": "d3011e59954403fb84729ab0e9fb239ba0f0238b3f353dfb5c08a32f4b3d21a1" + }, + "admin": { + "pubkey": "254d7a7f68b4443f5430564eb3a726f09aea57d27e75ab2b8ef57176f9ca3dac" + }, + "dm_protocol": "nip04", + "llm": { + "provider": "https://api.ppq.ai", + "api_key": "sk-SBI2Pga0J6n8jpPorHbRHC", + "model": "claude-haiku-4.5", + "base_url": "https://api.ppq.ai", + "max_tokens": 10000, + "temperature": 0.7 + }, + "api": { + "enabled": true, + "port": 8485, + "bind_address": "127.0.0.1" + }, + "startup_events": [ + { + "kind": 0, + "content_fields": { + "name": "Didactyl Test Agent", + "about": "Automated test instance" + }, + "tags": [] + }, + { + "kind": 10002, + "content": "", + "tags": [ + ["r", "wss://relay.damus.io"], + ["r", "wss://relay.primal.net"] + ] + }, + { + "kind": 31124, + "content": "# Test Agent\n\nYou are a test agent. Respond to requests and use tools when needed.", + "tags": [ + ["d", "identity_and_rules"], + ["app", "didactyl"], + ["scope", "private"], + ["trigger", "dm"], + ["filter", "{\"from\":\"admin\"}"] + ] + } + ] +} diff --git a/tests/results/20260404T140236Z/agent_debug.log b/tests/results/20260404T140236Z/agent_debug.log new file mode 100644 index 0000000..9b3aa70 --- /dev/null +++ b/tests/results/20260404T140236Z/agent_debug.log @@ -0,0 +1,395 @@ +[2026-04-04 10:04:51] [INFO ] [main.c:1016] Didactyl v0.2.20 starting +[2026-04-04 10:04:51] [INFO ] [nostr_handler.c:2207] [didactyl] initializing relay pool with 2 relays +[2026-04-04 10:04:51] [INFO ] [nostr_handler.c:2231] [didactyl] added relay: wss://relay.damus.io +[2026-04-04 10:04:51] [INFO ] [nostr_handler.c:2231] [didactyl] added relay: wss://relay.primal.net +[2026-04-04 10:04:51] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_1_1775311491", { + "kinds": [10000], + "authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"], + "limit": 1 + }] +[2026-04-04 10:04:52] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_1_1775311491", { + "kinds": [10000], + "authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"], + "limit": 1 + }] +[2026-04-04 10:04:52] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_1_1775311491"] +[2026-04-04 10:04:52] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_1_1775311491"] +[2026-04-04 10:04:52] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_1_1775311491"] +[2026-04-04 10:04:52] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_1_1775311491"] +[2026-04-04 10:04:52] [INFO ] [main.c:248] [didactyl] startup checklist [01] Relay connectivity: begin +[2026-04-04 10:04:52] [INFO ] [main.c:255] [didactyl] startup checklist [01] Relay connectivity: ok (connected relays: 2/2) +[2026-04-04 10:04:52] [INFO ] [main.c:248] [didactyl] startup checklist [02] Recover runtime config from Nostr: begin +[2026-04-04 10:04:52] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_2_1775311492", { + "kinds": [30078], + "authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"], + "#d": ["agent_config"], + "limit": 1 + }] +[2026-04-04 10:04:52] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_2_1775311492", { + "kinds": [30078], + "authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"], + "#d": ["agent_config"], + "limit": 1 + }] +[2026-04-04 10:04:52] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 10:04:52] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_2_1775311492"] +[2026-04-04 10:04:52] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_2_1775311492"] +[2026-04-04 10:04:52] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_2_1775311492"] +[2026-04-04 10:04:52] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_2_1775311492"] +[2026-04-04 10:04:52] [INFO ] [main.c:258] [didactyl] startup checklist [02] Recover runtime config from Nostr: ok +[2026-04-04 10:04:52] [INFO ] [main.c:248] [didactyl] startup checklist [03] Validate LLM config: begin +[2026-04-04 10:04:52] [INFO ] [main.c:258] [didactyl] startup checklist [03] Validate LLM config: ok +[2026-04-04 10:04:52] [INFO ] [main.c:248] [didactyl] startup checklist [04] Validate admin config: begin +[2026-04-04 10:04:52] [INFO ] [main.c:258] [didactyl] startup checklist [04] Validate admin config: ok +[2026-04-04 10:04:52] [INFO ] [main.c:248] [didactyl] startup checklist [05] Initialize LLM client: begin +[2026-04-04 10:04:52] [INFO ] [main.c:258] [didactyl] startup checklist [05] Initialize LLM client: ok +[2026-04-04 10:04:52] [INFO ] [main.c:248] [didactyl] startup checklist [06] Detect first run via kind 10002: begin +[2026-04-04 10:04:52] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_3_1775311492", { + "kinds": [10002], + "authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"], + "limit": 1 + }] +[2026-04-04 10:04:52] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_3_1775311492", { + "kinds": [10002], + "authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"], + "limit": 1 + }] +[2026-04-04 10:04:52] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 10:04:52] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 10:04:52] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_3_1775311492"] +[2026-04-04 10:04:52] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_3_1775311492"] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_3_1775311492"] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_3_1775311492"] +[2026-04-04 10:04:53] [INFO ] [main.c:1245] [didactyl] startup phase: first-run detection via kind 10002 => subsequent-run +[2026-04-04 10:04:53] [INFO ] [main.c:255] [didactyl] startup checklist [06] Detect first run via kind 10002: ok (subsequent-run) +[2026-04-04 10:04:53] [INFO ] [main.c:248] [didactyl] startup checklist [07] Reconcile/persist startup state: begin +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_4_1775311493", { + "kinds": [1], + "authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"], + "limit": 1 + }] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_4_1775311493", { + "kinds": [1], + "authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"], + "limit": 1 + }] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_4_1775311493"] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_4_1775311493"] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_4_1775311493"] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_4_1775311493"] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] SEND +[2026-04-04 10:04:53] [INFO ] [nostr_handler.c:2949] [didactyl] kind 0 event published to wss://relay.damus.io (async, reason=startup_reconcile) +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] SEND +[2026-04-04 10:04:53] [INFO ] [nostr_handler.c:2949] [didactyl] kind 10002 event published to wss://relay.damus.io (async, reason=startup_reconcile) +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] SEND +[2026-04-04 10:04:53] [INFO ] [nostr_handler.c:2949] [didactyl] kind 31124 event published to wss://relay.damus.io (async, reason=startup_reconcile) +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] SEND +[2026-04-04 10:04:53] [INFO ] [nostr_handler.c:2949] [didactyl] kind 10123 event published to wss://relay.damus.io (async, reason=startup_reconcile) +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] SEND +[2026-04-04 10:04:53] [INFO ] [nostr_handler.c:2949] [didactyl] kind 0 event published to wss://relay.primal.net (async, reason=startup_reconcile) +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] SEND +[2026-04-04 10:04:53] [INFO ] [nostr_handler.c:2949] [didactyl] kind 10002 event published to wss://relay.primal.net (async, reason=startup_reconcile) +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] SEND +[2026-04-04 10:04:53] [INFO ] [nostr_handler.c:2949] [didactyl] kind 31124 event published to wss://relay.primal.net (async, reason=startup_reconcile) +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] SEND +[2026-04-04 10:04:53] [INFO ] [nostr_handler.c:2949] [didactyl] kind 10123 event published to wss://relay.primal.net (async, reason=startup_reconcile) +[2026-04-04 10:04:53] [INFO ] [nostr_handler.c:3285] [didactyl] startup publish detail: kind=0 (profile) -> wss://relay.damus.io, wss://relay.primal.net +[2026-04-04 10:04:53] [INFO ] [nostr_handler.c:3285] [didactyl] startup publish detail: kind=10002 (relay_list) -> wss://relay.damus.io, wss://relay.primal.net +[2026-04-04 10:04:53] [INFO ] [nostr_handler.c:3285] [didactyl] startup publish detail: kind=31124 (skill) -> wss://relay.damus.io, wss://relay.primal.net +[2026-04-04 10:04:53] [INFO ] [nostr_handler.c:3285] [didactyl] startup publish detail: kind=10123 (adoption_list) -> wss://relay.damus.io, wss://relay.primal.net +[2026-04-04 10:04:53] [INFO ] [nostr_handler.c:3297] [didactyl] startup publish summary: 4/4 event(s) published to at least one relay; relays used=2/2 +[2026-04-04 10:04:53] [INFO ] [nostr_handler.c:906] [didactyl] publish kind event target relays (2): +[2026-04-04 10:04:53] [INFO ] [nostr_handler.c:908] [didactyl] -> wss://relay.damus.io +[2026-04-04 10:04:53] [INFO ] [nostr_handler.c:908] [didactyl] -> wss://relay.primal.net +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] SEND +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] SEND +[2026-04-04 10:04:53] [INFO ] [nostr_handler.c:2949] [didactyl] kind 30078 event published to wss://relay.damus.io (async, reason=manual_publish) +[2026-04-04 10:04:53] [INFO ] [nostr_handler.c:2949] [didactyl] kind 30078 event published to wss://relay.primal.net (async, reason=manual_publish) +[2026-04-04 10:04:53] [INFO ] [nostr_handler.c:3012] [didactyl] published kind 30078 event via 2 connected relay(s) +[2026-04-04 10:04:53] [INFO ] [nostr_handler.c:906] [didactyl] publish kind event target relays (2): +[2026-04-04 10:04:53] [INFO ] [nostr_handler.c:908] [didactyl] -> wss://relay.damus.io +[2026-04-04 10:04:53] [INFO ] [nostr_handler.c:908] [didactyl] -> wss://relay.primal.net +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] SEND +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] SEND +[2026-04-04 10:04:53] [INFO ] [nostr_handler.c:2949] [didactyl] kind 30078 event published to wss://relay.damus.io (async, reason=manual_publish) +[2026-04-04 10:04:53] [INFO ] [nostr_handler.c:2949] [didactyl] kind 30078 event published to wss://relay.primal.net (async, reason=manual_publish) +[2026-04-04 10:04:53] [INFO ] [nostr_handler.c:3012] [didactyl] published kind 30078 event via 2 connected relay(s) +[2026-04-04 10:04:53] [INFO ] [main.c:1267] [didactyl] startup phase: persisted encrypted runtime config to Nostr +[2026-04-04 10:04:53] [INFO ] [main.c:258] [didactyl] startup checklist [07] Reconcile/persist startup state: ok +[2026-04-04 10:04:53] [INFO ] [main.c:248] [didactyl] startup checklist [08] Initialize agent: begin +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_5_1775311493", { + "kinds": [30078], + "authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"], + "#d": ["memory"], + "limit": 1 + }] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_5_1775311493", { + "kinds": [30078], + "authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"], + "#d": ["memory"], + "limit": 1 + }] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["OK","7fb5e7e09092254b700df2e7a8473fdca42ee09e3a32b86d262ad2403807f920",true,""] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["OK","7fb5e7e09092254b700df2e7a8473fdca42ee09e3a32b86d262ad2403807f920",true,""] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["OK","98b892b33ce3731cbeb82d791f5e41c7deade4bb61756ed600a14f3aeaa7d45e",true,""] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["OK","98b892b33ce3731cbeb82d791f5e41c7deade4bb61756ed600a14f3aeaa7d45e",true,""] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["OK","952130858723506c1eac58e94f253c8b35e137c056dcedf0619ab63348f43caa",true,""] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["OK","952130858723506c1eac58e94f253c8b35e137c056dcedf0619ab63348f43caa",true,""] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["OK","b3095dd390e3a08ff24404195292ed302711334687a7de5ca0b433d9fe66a92e",true,""] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["OK","b3095dd390e3a08ff24404195292ed302711334687a7de5ca0b433d9fe66a92e",true,""] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_5_1775311493"] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_5_1775311493"] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_5_1775311493"] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_5_1775311493"] +[2026-04-04 10:04:53] [INFO ] [main.c:258] [didactyl] startup checklist [08] Initialize agent: ok +[2026-04-04 10:04:53] [INFO ] [main.c:248] [didactyl] startup checklist [09] Initialize trigger manager: begin +[2026-04-04 10:04:53] [INFO ] [trigger_manager.c:814] [didactyl] trigger manager initialized (capacity=16) +[2026-04-04 10:04:53] [INFO ] [main.c:258] [didactyl] startup checklist [09] Initialize trigger manager: ok +[2026-04-04 10:04:53] [INFO ] [main.c:1308] [didactyl] startup phase: deferred trigger load will run after self-skill EOSE +[2026-04-04 10:04:53] [INFO ] [main.c:248] [didactyl] startup checklist [10] Load startup triggers: begin +[2026-04-04 10:04:53] [INFO ] [main.c:1311] [didactyl] startup phase: startup-config trigger load begin +[2026-04-04 10:04:53] [INFO ] [trigger_manager.c:1138] [didactyl] trigger added d_tag=identity_and_rules action=0 enabled=1 +[2026-04-04 10:04:53] [INFO ] [trigger_manager.c:1021] [didactyl] startup trigger registered d_tag=identity_and_rules action=llm enabled=1 +[2026-04-04 10:04:53] [INFO ] [trigger_manager.c:1029] [didactyl] trigger manager loaded 1 trigger(s) from startup config (considered=1) +[2026-04-04 10:04:53] [INFO ] [main.c:1315] [didactyl] startup phase: startup-config trigger load end +[2026-04-04 10:04:53] [INFO ] [main.c:258] [didactyl] startup checklist [10] Load startup triggers: ok +[2026-04-04 10:04:53] [INFO ] [main.c:248] [didactyl] startup checklist [11] Discover self relay list via kind 10002: begin +[2026-04-04 10:04:53] [INFO ] [main.c:327] [didactyl] kind10002 query begin: timeout_ms=5000 author=1eb171136dbb1ba6... relay_count=2 +[2026-04-04 10:04:53] [INFO ] [main.c:334] [didactyl] kind10002 query relay snapshot: {"relay_count":2,"connected_count":2,"relays":[{"url":"wss://relay.damus.io","status":"connected","events_received":0,"events_published":6,"events_published_ok":4,"events_published_failed":0,"ping_latency_current":0,"ping_latency_avg":0,"query_latency_avg":0,"publish_latency_avg":0,"connection_uptime_start":1775311491,"last_event_time":1775311493},{"url":"wss://relay.primal.net","status":"connected","events_received":3,"events_published":6,"events_published_ok":4,"events_published_failed":0,"ping_latency_current":0,"ping_latency_avg":0,"query_latency_avg":0,"publish_latency_avg":0,"connection_uptime_start":1775311491,"last_event_time":1775311493}]} +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_6_1775311493", { + "kinds": [10002], + "authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"], + "limit": 16 + }] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_6_1775311493", { + "kinds": [10002], + "authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"], + "limit": 16 + }] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["OK","e94ea27dc06a8f4f1e70e7e96d1d3e7f328016ff1bbbc3a50723268f14277fb1",false,"rate-limited: you are noting too much"] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["OK","cfe5aafd9e84b8a7c6caf1842ce51a502a156b5706ecddadaee73b106796bc6c",true,""] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["OK","cfe5aafd9e84b8a7c6caf1842ce51a502a156b5706ecddadaee73b106796bc6c",true,""] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["OK","e94ea27dc06a8f4f1e70e7e96d1d3e7f328016ff1bbbc3a50723268f14277fb1",true,""] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_6_1775311493"] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_6_1775311493"] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_6_1775311493"] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_6_1775311493"] +[2026-04-04 10:04:53] [INFO ] [main.c:354] [didactyl] kind10002 query received event_count=1 +[2026-04-04 10:04:53] [INFO ] [main.c:451] [didactyl] kind10002 query extracted relays=["wss://relay.damus.io","wss://relay.primal.net"] +[2026-04-04 10:04:53] [INFO ] [main.c:556] [didactyl] kind10002 wait success: relay_count=2 +[2026-04-04 10:04:53] [INFO ] [main.c:558] [didactyl] kind10002 relay[0]=wss://relay.damus.io +[2026-04-04 10:04:53] [INFO ] [main.c:558] [didactyl] kind10002 relay[1]=wss://relay.primal.net +[2026-04-04 10:04:53] [INFO ] [main.c:562] [didactyl] kind10002 wait success relay snapshot: {"relay_count":2,"connected_count":2,"relays":[{"url":"wss://relay.damus.io","status":"connected","events_received":0,"events_published":6,"events_published_ok":5,"events_published_failed":1,"ping_latency_current":0,"ping_latency_avg":0,"query_latency_avg":0,"publish_latency_avg":0,"connection_uptime_start":1775311491,"last_event_time":1775311493,"last_publish_error":"rate-limited: you are noting too much"},{"url":"wss://relay.primal.net","status":"connected","events_received":4,"events_published":6,"events_published_ok":6,"events_published_failed":0,"ping_latency_current":0,"ping_latency_avg":0,"query_latency_avg":0,"publish_latency_avg":0,"connection_uptime_start":1775311491,"last_event_time":1775311493}]} +[2026-04-04 10:04:53] [INFO ] [main.c:255] [didactyl] startup checklist [11] Discover self relay list via kind 10002: ok (kind10002=2 added=0 connected=2/2) +[2026-04-04 10:04:53] [INFO ] [main.c:248] [didactyl] startup checklist [12] Subscribe admin context: begin +[2026-04-04 10:04:53] [INFO ] [main.c:1366] [didactyl] startup phase: subscribe admin context begin +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_7_1775311493", { + "kinds": [0, 3, 10002], + "authors": ["254d7a7f68b4443f5430564eb3a726f09aea57d27e75ab2b8ef57176f9ca3dac"], + "limit": 32 + }] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_7_1775311493", { + "kinds": [0, 3, 10002], + "authors": ["254d7a7f68b4443f5430564eb3a726f09aea57d27e75ab2b8ef57176f9ca3dac"], + "limit": 32 + }] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_8_1775311493", { + "kinds": [1], + "authors": ["254d7a7f68b4443f5430564eb3a726f09aea57d27e75ab2b8ef57176f9ca3dac"], + "limit": 10 + }] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_8_1775311493", { + "kinds": [1], + "authors": ["254d7a7f68b4443f5430564eb3a726f09aea57d27e75ab2b8ef57176f9ca3dac"], + "limit": 10 + }] +[2026-04-04 10:04:53] [INFO ] [nostr_handler.c:2352] [didactyl] admin context subscriptions active for admin 254d7a7f68b4443f... +[2026-04-04 10:04:53] [INFO ] [main.c:1370] [didactyl] startup phase: subscribe admin context end +[2026-04-04 10:04:53] [INFO ] [main.c:258] [didactyl] startup checklist [12] Subscribe admin context: ok +[2026-04-04 10:04:53] [INFO ] [main.c:248] [didactyl] startup checklist [13] Subscribe agent self context: begin +[2026-04-04 10:04:53] [INFO ] [main.c:1374] [didactyl] startup phase: subscribe agent self context begin +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_9_1775311493", { + "kinds": [0, 3, 10002], + "authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"], + "limit": 64 + }] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_9_1775311493", { + "kinds": [0, 3, 10002], + "authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"], + "limit": 64 + }] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_10_1775311493", { + "kinds": [1], + "authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"], + "limit": 10 + }] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_10_1775311493", { + "kinds": [1], + "authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"], + "limit": 10 + }] +[2026-04-04 10:04:53] [INFO ] [nostr_handler.c:2440] [didactyl] agent self-context subscriptions active for pubkey 1eb171136dbb1ba6... +[2026-04-04 10:04:53] [INFO ] [main.c:1378] [didactyl] startup phase: subscribe agent self context end +[2026-04-04 10:04:53] [INFO ] [main.c:258] [didactyl] startup checklist [13] Subscribe agent self context: ok +[2026-04-04 10:04:53] [INFO ] [main.c:248] [didactyl] startup checklist [14] Subscribe self-skill cache: begin +[2026-04-04 10:04:53] [INFO ] [main.c:1382] [didactyl] startup phase: subscribe self skill cache begin +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_11_1775311493", { + "kinds": [31123, 31124, 10123], + "authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"], + "limit": 300 + }] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_11_1775311493", { + "kinds": [31123, 31124, 10123], + "authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"], + "limit": 300 + }] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_12_1775311493", { + "kinds": [31123], + "authors": ["254d7a7f68b4443f5430564eb3a726f09aea57d27e75ab2b8ef57176f9ca3dac"], + "limit": 150 + }] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_12_1775311493", { + "kinds": [31123], + "authors": ["254d7a7f68b4443f5430564eb3a726f09aea57d27e75ab2b8ef57176f9ca3dac"], + "limit": 150 + }] +[2026-04-04 10:04:53] [INFO ] [nostr_handler.c:2532] [didactyl] self/admin skill subscriptions active (self=1eb171136dbb1ba6..., admin=254d7a7f68b4443f...) +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_7_1775311493"] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_7_1775311493"] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:1718] [didactyl] DEBUG on_eose called: event_count=0 +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_8_1775311493"] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_8_1775311493"] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:1718] [didactyl] DEBUG on_eose called: event_count=0 +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_9_1775311493"] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_9_1775311493"] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:1718] [didactyl] DEBUG on_eose called: event_count=0 +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_10_1775311493"] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_10_1775311493"] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:1718] [didactyl] DEBUG on_eose called: event_count=0 +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 10:04:53] [INFO ] [nostr_handler.c:2151] [didactyl] self-skill cache received kind=31124 d_tag=identity_and_rules id=b3095dd390e3a08ff24404195292ed302711334687a7de5ca0b433d9fe66a92e created_at=1775311493 via wss://relay.damus.io +[2026-04-04 10:04:53] [INFO ] [trigger_manager.c:1364] [didactyl] trigger updated d_tag=identity_and_rules action=0 enabled=1 +[2026-04-04 10:04:53] [INFO ] [nostr_handler.c:1207] [didactyl] live self-skill trigger registered d_tag=identity_and_rules action=llm enabled=1 +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 10:04:53] [INFO ] [nostr_handler.c:2151] [didactyl] self-skill cache received kind=31124 d_tag=identity_and_rules id=b3095dd390e3a08ff24404195292ed302711334687a7de5ca0b433d9fe66a92e created_at=1775311493 via wss://relay.primal.net +[2026-04-04 10:04:53] [INFO ] [trigger_manager.c:1364] [didactyl] trigger updated d_tag=identity_and_rules action=0 enabled=1 +[2026-04-04 10:04:53] [INFO ] [nostr_handler.c:1207] [didactyl] live self-skill trigger registered d_tag=identity_and_rules action=llm enabled=1 +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 10:04:53] [INFO ] [nostr_handler.c:2151] [didactyl] self-skill cache received kind=10123 d_tag=- id=952130858723506c1eac58e94f253c8b35e137c056dcedf0619ab63348f43caa created_at=1775311493 via wss://relay.damus.io +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 10:04:53] [INFO ] [nostr_handler.c:2151] [didactyl] self-skill cache received kind=10123 d_tag=- id=952130858723506c1eac58e94f253c8b35e137c056dcedf0619ab63348f43caa created_at=1775311493 via wss://relay.primal.net +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_11_1775311493"] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_11_1775311493"] +[2026-04-04 10:04:53] [INFO ] [nostr_handler.c:1725] [didactyl] self-skill EOSE received: cached skill events=0 +[2026-04-04 10:04:53] [INFO ] [main.c:44] [didactyl] deferred trigger load begin after self-skill EOSE (event_count=0) +[2026-04-04 10:04:53] [INFO ] [trigger_manager.c:1364] [didactyl] trigger updated d_tag=identity_and_rules action=0 enabled=1 +[2026-04-04 10:04:53] [INFO ] [trigger_manager.c:918] [didactyl] trigger manager loaded 1 trigger(s) from self skills (considered=1) +[2026-04-04 10:04:53] [INFO ] [main.c:54] [didactyl] deferred trigger load status: {"success":true,"count":1,"active":1,"triggers":[{"skill_d_tag":"identity_and_rules","filter_json":"{\"from\":\"admin\"}","type":"dm","action":"llm","enabled":true,"subscribed":false,"llm":"","tools":"","has_max_tokens":false,"has_temperature":false,"has_seed":false,"last_fired":0,"last_seen_created_at":0}]} +[2026-04-04 10:04:53] [INFO ] [nostr_handler.c:2757] [didactyl] self-skill cache event: kind=31124 d_tag=identity_and_rules id=b3095dd390e3a08ff24404195292ed302711334687a7de5ca0b433d9fe66a92e created_at=1775311493 +[2026-04-04 10:04:53] [INFO ] [nostr_handler.c:2757] [didactyl] self-skill cache event: kind=10123 d_tag=- id=952130858723506c1eac58e94f253c8b35e137c056dcedf0619ab63348f43caa created_at=1775311493 +[2026-04-04 10:04:53] [INFO ] [main.c:1449] [didactyl] startup phase: subscribe self skill cache end +[2026-04-04 10:04:53] [INFO ] [main.c:255] [didactyl] startup checklist [14] Subscribe self-skill cache: ok (skills=1 adoptions=1) +[2026-04-04 10:04:53] [INFO ] [main.c:248] [didactyl] startup checklist [15] Subscribe DMs: begin +[2026-04-04 10:04:53] [INFO ] [main.c:1453] [didactyl] startup phase: subscribe DMs begin +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_13_1775311493", { + "kinds": [4], + "#p": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"], + "since": 1775311491, + "limit": 100 + }] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_13_1775311493", { + "kinds": [4], + "#p": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"], + "since": 1775311491, + "limit": 100 + }] +[2026-04-04 10:04:53] [INFO ] [nostr_handler.c:2643] [didactyl] DM subscription active for pubkey 1eb171136dbb1ba6... +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:2644] [didactyl] DEBUG DM subscription g_start_time=1775311491 now=1775311493 delta=2 relay_count=2 +[2026-04-04 10:04:53] [INFO ] [main.c:1468] [didactyl] startup phase: subscribe DMs end +[2026-04-04 10:04:53] [INFO ] [main.c:258] [didactyl] startup checklist [15] Subscribe DMs: ok +[2026-04-04 10:04:53] [INFO ] [main.c:248] [didactyl] startup checklist [16] Subscribe wallet events: begin +[2026-04-04 10:04:53] [INFO ] [main.c:1472] [didactyl] startup phase: subscribe wallet events begin +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_14_1775311493", { + "kinds": [17375, 7375, 5], + "authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"], + "since": 1775311491, + "limit": 500 + }] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_14_1775311493", { + "kinds": [17375, 7375, 5], + "authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"], + "since": 1775311491, + "limit": 500 + }] +[2026-04-04 10:04:53] [INFO ] [nostr_handler.c:2696] [didactyl] wallet event subscription active for pubkey 1eb171136dbb1ba6... +[2026-04-04 10:04:53] [INFO ] [main.c:1476] [didactyl] startup phase: subscribe wallet events end +[2026-04-04 10:04:53] [INFO ] [main.c:258] [didactyl] startup checklist [16] Subscribe wallet events: ok +[2026-04-04 10:04:53] [INFO ] [main.c:248] [didactyl] startup checklist [17] Initialize cashu wallet: begin +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_15_1775311493", { + "kinds": [17375, 7375], + "authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"] + }] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_15_1775311493", { + "kinds": [17375, 7375], + "authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"] + }] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_12_1775311493"] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_12_1775311493"] +[2026-04-04 10:04:53] [TRACE] [nostr_handler.c:1718] [didactyl] DEBUG on_eose called: event_count=0 +[2026-04-04 10:04:54] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_13_1775311493"] +[2026-04-04 10:04:54] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_13_1775311493"] +[2026-04-04 10:04:54] [TRACE] [nostr_handler.c:1718] [didactyl] DEBUG on_eose called: event_count=0 +[2026-04-04 10:04:54] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_14_1775311493"] +[2026-04-04 10:04:54] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_14_1775311493"] +[2026-04-04 10:04:54] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_15_1775311493"] +[2026-04-04 10:04:54] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_15_1775311493"] +[2026-04-04 10:04:54] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_15_1775311493"] +[2026-04-04 10:04:54] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_15_1775311493"] +[2026-04-04 10:04:54] [WARN ] [main.c:1492] [didactyl] startup phase: cashu wallet load/create deferred to first tool call +[2026-04-04 10:04:54] [INFO ] [main.c:255] [didactyl] startup checklist [17] Initialize cashu wallet: ok (initialized; load/create deferred) +[2026-04-04 10:04:54] [INFO ] [http_api.c:1570] [didactyl] http api listening on http://127.0.0.1:8485 +[2026-04-04 10:04:54] [INFO ] [main.c:1532] [didactyl] HTTP API listening at http://127.0.0.1:8485 +[2026-04-04 10:04:54] [INFO ] [main.c:1535] [didactyl] HTTP API endpoints: http://127.0.0.1:8485/api/context/current http://127.0.0.1:8485/api/context/parts +[2026-04-04 10:04:54] [TRACE] [nostr_handler.c:1499] [didactyl] sending plaintext DM content: Didactyl Test Agent has started up and is online at 2026-04-04 10:04:54 (version v0.2.20, connected relays: 2/2). +[2026-04-04 10:04:54] [TRACE] [nostr_handler.c:1491] [didactyl] sending encrypted DM event: {"pubkey":"1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc","created_at":1775311494,"kind":4,"tags":[["p","254d7a7f68b4443f5430564eb3a726f09aea57d27e75ab2b8ef57176f9ca3dac"]],"content":"jW17RpM/0GksuCPaI3kIdas95grzicaXf5xODF31xSY896PZQuQWRa9VFX3+TPRQuDmv2xLbRw32sox/BAhXasZZe2RFLKPjsHyFoeNBbQB4tRn9fGum/0djr/WL8nK+ppT64wLUPut/2vRFnM+sqv1pZ/nRQe/FFxy8K/VE1eM=?iv=g0c6X4mBxWM04miChtqXTA==","id":"73a9a8e4e3688d46cefb02643ead141fdb7397c632fdd9e6f336c0d31d9ad59c","sig":"08c739b14c9c8482b47dda28f82903eb60ef7fdfd6290b0804f8e4b1c5c7da59b9c82294773bc7394d0adba6474dac7530d36868e65c66f66b9c6e6bda1b6057"} +[2026-04-04 10:04:54] [INFO ] [nostr_handler.c:906] [didactyl] publish DM target relays (2): +[2026-04-04 10:04:54] [INFO ] [nostr_handler.c:908] [didactyl] -> wss://relay.damus.io +[2026-04-04 10:04:54] [INFO ] [nostr_handler.c:908] [didactyl] -> wss://relay.primal.net +[2026-04-04 10:04:54] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] SEND +[2026-04-04 10:04:54] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] SEND +[2026-04-04 10:04:54] [INFO ] [nostr_handler.c:2843] [didactyl] kind 4 event published to wss://relay.damus.io (async) +[2026-04-04 10:04:54] [INFO ] [nostr_handler.c:2843] [didactyl] kind 4 event published to wss://relay.primal.net (async) +[2026-04-04 10:04:54] [INFO ] [nostr_handler.c:2852] [didactyl] sent DM 73a9a8e4e3688d46... to 254d7a7f68b4443f... via 2 connected relay(s) +[2026-04-04 10:04:54] [INFO ] [main.c:255] [didactyl] startup checklist [18] READY: ok (agent online; entering main poll loop) +[2026-04-04 10:04:54] [INFO ] [main.c:1571] [didactyl] entering main poll loop +[2026-04-04 10:04:54] [INFO ] [main.c:1572] [didactyl] running with pubkey 1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc +[2026-04-04 10:04:54] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["OK","73a9a8e4e3688d46cefb02643ead141fdb7397c632fdd9e6f336c0d31d9ad59c",true,""] +[2026-04-04 10:04:54] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["OK","73a9a8e4e3688d46cefb02643ead141fdb7397c632fdd9e6f336c0d31d9ad59c",false,"rate-limited: you are noting too much"] +[2026-04-04 10:04:54] [INFO ] [llm.c:76] [didactyl] llm request: method=POST url=https://api.ppq.ai/chat/completions body_bytes=27553 body_preview={"model":"claude-haiku-4.5","max_tokens":10000,"temperature":0.7,"messages":[{"role":"system","content":"# Didactyl Agent\n\n## Test Agent\n\nYou are a test agent. Respond to requests and use tools when needed."},{"role":"user","content":"Look up your own Nostr profile"},{"role":"user","content":"Look up your own Nostr profile","_ts":1775311494}],"tools":[{"type":"function","function":{"name":"nostr_post","description":"Publish a Nostr event to connected relays","parameters":{"type":"object","properties":{"kind":{"type":"integer","description":"Nostr event kind number (for example 1 for note, 7 for reaction, 30023 for longform)"},"content":{"type":"string","description":"Event content/body text to publish"},"tags":{"type":"array","description":"Optional Nostr tags array, e.g. [[\"d\",\"my-skill\"],[\"t\",\"nostr\"]]","items":{"type":"array","items":{"type":"string"}}}},"required":["kind","content"]}}},{"type":"function","function":{"name":"nostr_query","description":"Query events from relays using a Nostr filter","parameters":{"type":"object","properties":{"filter":{"type":"object","description":"Nostr filter object (authors, kinds, ids, since, until, limit, and tag filters)"},"timeout_ms":{"type":"integer","description":"Optional query timeout in milliseconds"}},"required":["filter"]}}},{"type":"function","function":{"name":"local_shell_exec","description":"Execute a shell command and return stdout/stderr","parameters":{"type":"object","properties":{"command":{"type":"string","description":"Shell command to execute in the configured working directory"}},"required":["command"]}}},{"type":"function","function":{"name":"local_file_read","description":"Read a local file as text from the configured working directory","parameters":{"type":"object","properties":{"path":{"type":"string","description":"Path to file to read (relative to configured working directory)"},"max_bytes":{"type":"integer","description":"Optional maximum bytes to return"}},"required":["path"]}}},{"type":"function","function":{"name":"local_file_write","description":"Write text content to a local file in the configured working directory","parameters":{"type":"object","properties":{"path":{"type":"string","description":"Path to file to write (relative to configured working directory)"},"content":{"type":"string","description":"Text content to write"},"append":{"type":"boolean","description":"When true, append to file instead of overwriting"}},"required":["path","content"]}}},{"type":"function","function":{"name":"nostr_post_readme","description":"Publish README.md as kind 30023 with deterministic d tag readme.md","parameters":{"type":"object","properties":{},"required":[]}}},{"type":"function","function":{"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","description":"Event IDs to request deletion for","items":{"type":"string"}},"kinds":{"type":"array","description":"Optional kinds corresponding to event_ids entries","items":{"type":"integer"}},"reason":{"type":"string","description":"Optional human-readable reason"}},"required":["event_ids"]}}},{"type":"function","function":{"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","description":"Target event ID to react to"},"event_pubkey":{"type":"string","description":"Author pubkey of target event"},"event_kind":{"type":"integer","description":"Optional kind of target event"},"reaction":{"type":"string","description":"Reaction content such as +, -, ❤️, or emoji"}},"required":["event_id","event_pubkey"]}}},{"type":"function","function":{"name":"nostr_profile_get","description":"Look up a Nostr profile (kind 0 metadata) by pubkey","parameters":{"type":"object","properties":{"pubkey":{"type":"string","description":"Hex pubkey of the profile to fetch"}},"require... +[2026-04-04 10:04:58] [INFO ] [llm.c:76] [didactyl] llm request: method=POST url=https://api.ppq.ai/chat/completions body_bytes=28078 body_preview={"model":"claude-haiku-4.5","max_tokens":10000,"temperature":0.7,"messages":[{"role":"system","content":"# Didactyl Agent\n\n## Test Agent\n\nYou are a test agent. Respond to requests and use tools when needed."},{"role":"user","content":"Look up your own Nostr profile"},{"role":"user","content":"Look up your own Nostr profile","_ts":1775311494},{"role":"assistant","content":"I'll look up my own Nostr profile by first getting my pubkey and then fetching the profile metadata.","tool_calls":[{"id":"toolu_bdrk_0163qj8DFnDXcpoJdjkF4jYj","type":"function","function":{"name":"nostr_pubkey","arguments":"{}"}}]},{"role":"tool","tool_call_id":"toolu_bdrk_0163qj8DFnDXcpoJdjkF4jYj","content":"{\"success\":true,\"pubkey\":\"1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc\",\"content\":\"1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc\"}"}],"tools":[{"type":"function","function":{"name":"nostr_post","description":"Publish a Nostr event to connected relays","parameters":{"type":"object","properties":{"kind":{"type":"integer","description":"Nostr event kind number (for example 1 for note, 7 for reaction, 30023 for longform)"},"content":{"type":"string","description":"Event content/body text to publish"},"tags":{"type":"array","description":"Optional Nostr tags array, e.g. [[\"d\",\"my-skill\"],[\"t\",\"nostr\"]]","items":{"type":"array","items":{"type":"string"}}}},"required":["kind","content"]}}},{"type":"function","function":{"name":"nostr_query","description":"Query events from relays using a Nostr filter","parameters":{"type":"object","properties":{"filter":{"type":"object","description":"Nostr filter object (authors, kinds, ids, since, until, limit, and tag filters)"},"timeout_ms":{"type":"integer","description":"Optional query timeout in milliseconds"}},"required":["filter"]}}},{"type":"function","function":{"name":"local_shell_exec","description":"Execute a shell command and return stdout/stderr","parameters":{"type":"object","properties":{"command":{"type":"string","description":"Shell command to execute in the configured working directory"}},"required":["command"]}}},{"type":"function","function":{"name":"local_file_read","description":"Read a local file as text from the configured working directory","parameters":{"type":"object","properties":{"path":{"type":"string","description":"Path to file to read (relative to configured working directory)"},"max_bytes":{"type":"integer","description":"Optional maximum bytes to return"}},"required":["path"]}}},{"type":"function","function":{"name":"local_file_write","description":"Write text content to a local file in the configured working directory","parameters":{"type":"object","properties":{"path":{"type":"string","description":"Path to file to write (relative to configured working directory)"},"content":{"type":"string","description":"Text content to write"},"append":{"type":"boolean","description":"When true, append to file instead of overwriting"}},"required":["path","content"]}}},{"type":"function","function":{"name":"nostr_post_readme","description":"Publish README.md as kind 30023 with deterministic d tag readme.md","parameters":{"type":"object","properties":{},"required":[]}}},{"type":"function","function":{"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","description":"Event IDs to request deletion for","items":{"type":"string"}},"kinds":{"type":"array","description":"Optional kinds corresponding to event_ids entries","items":{"type":"integer"}},"reason":{"type":"string","description":"Optional human-readable reason"}},"required":["event_ids"]}}},{"type":"function","function":{"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","description":"Target event ID to react to"},"event_pubkey":{"type":"s... +[2026-04-04 10:05:11] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_16_1775311511", { + "kinds": [0], + "authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"], + "limit": 1 + }] +[2026-04-04 10:05:11] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_16_1775311511", { + "kinds": [0], + "authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"], + "limit": 1 + }] +[2026-04-04 10:05:11] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 10:05:11] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 10:05:11] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_16_1775311511"] +[2026-04-04 10:05:11] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_16_1775311511"] +[2026-04-04 10:05:11] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_16_1775311511"] +[2026-04-04 10:05:11] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_16_1775311511"] +[2026-04-04 10:05:11] [INFO ] [llm.c:76] [didactyl] llm request: method=POST url=https://api.ppq.ai/chat/completions body_bytes=29027 body_preview={"model":"claude-haiku-4.5","max_tokens":10000,"temperature":0.7,"messages":[{"role":"system","content":"# Didactyl Agent\n\n## Test Agent\n\nYou are a test agent. Respond to requests and use tools when needed."},{"role":"user","content":"Look up your own Nostr profile"},{"role":"user","content":"Look up your own Nostr profile","_ts":1775311494},{"role":"assistant","content":"I'll look up my own Nostr profile by first getting my pubkey and then fetching the profile metadata.","tool_calls":[{"id":"toolu_bdrk_0163qj8DFnDXcpoJdjkF4jYj","type":"function","function":{"name":"nostr_pubkey","arguments":"{}"}}]},{"role":"tool","tool_call_id":"toolu_bdrk_0163qj8DFnDXcpoJdjkF4jYj","content":"{\"success\":true,\"pubkey\":\"1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc\",\"content\":\"1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc\"}"},{"role":"assistant","content":"Now let me fetch my profile metadata:","tool_calls":[{"id":"toolu_bdrk_01SvmURpNtVuQN363Gvcgsrc","type":"function","function":{"name":"nostr_profile_get","arguments":"{\"pubkey\": \"1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc\"}"}}]},{"role":"tool","tool_call_id":"toolu_bdrk_01SvmURpNtVuQN363Gvcgsrc","content":"{\"success\":true,\"found\":true,\"event\":{\"content\":\"{\\\"name\\\":\\\"Didactyl Test Agent\\\",\\\"about\\\":\\\"Automated test instance\\\"}\",\"created_at\":1775311493,\"id\":\"98b892b33ce3731cbeb82d791f5e41c7deade4bb61756ed600a14f3aeaa7d45e\",\"kind\":0,\"pubkey\":\"1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc\",\"sig\":\"b9d083081f36b1be3c60066af6680050fbbdc42d766277f534fd096f81279e5b70f1083a309a3b96e7065bd3c89c4682cf89cc17968d29b59ac6e062c13109bd\",\"tags\":[]},\"profile\":{\"name\":\"Didactyl Test Agent\",\"about\":\"Automated test instance\"}}"}],"tools":[{"type":"function","function":{"name":"nostr_post","description":"Publish a Nostr event to connected relays","parameters":{"type":"object","properties":{"kind":{"type":"integer","description":"Nostr event kind number (for example 1 for note, 7 for reaction, 30023 for longform)"},"content":{"type":"string","description":"Event content/body text to publish"},"tags":{"type":"array","description":"Optional Nostr tags array, e.g. [[\"d\",\"my-skill\"],[\"t\",\"nostr\"]]","items":{"type":"array","items":{"type":"string"}}}},"required":["kind","content"]}}},{"type":"function","function":{"name":"nostr_query","description":"Query events from relays using a Nostr filter","parameters":{"type":"object","properties":{"filter":{"type":"object","description":"Nostr filter object (authors, kinds, ids, since, until, limit, and tag filters)"},"timeout_ms":{"type":"integer","description":"Optional query timeout in milliseconds"}},"required":["filter"]}}},{"type":"function","function":{"name":"local_shell_exec","description":"Execute a shell command and return stdout/stderr","parameters":{"type":"object","properties":{"command":{"type":"string","description":"Shell command to execute in the configured working directory"}},"required":["command"]}}},{"type":"function","function":{"name":"local_file_read","description":"Read a local file as text from the configured working directory","parameters":{"type":"object","properties":{"path":{"type":"string","description":"Path to file to read (relative to configured working directory)"},"max_bytes":{"type":"integer","description":"Optional maximum bytes to return"}},"required":["path"]}}},{"type":"function","function":{"name":"local_file_write","description":"Write text content to a local file in the configured working directory","parameters":{"type":"object","properties":{"path":{"type":"string","description":"Path to file to write (relative to configured working directory)"},"content":{"type":"string","description":"Text content to write"},"append":{"type":"boolean","description":"When true, append to file instead of overwriting"}},"required":["path","content"]}}},{"type":"function","function":{"name":"nost... +[2026-04-04 10:05:16] [INFO ] [llm.c:336] [didactyl] llm request sanitizer: removed 2 empty text message(s) before provider request +[2026-04-04 10:05:16] [INFO ] [llm.c:76] [didactyl] llm request: method=POST url=https://api.ppq.ai/chat/completions body_bytes=27418 body_preview={"model":"claude-haiku-4.5","max_tokens":10000,"temperature":0.7,"messages":[{"role":"system","content":"# Didactyl Agent\n\n## Test Agent\n\nYou are a test agent. Respond to requests and use tools when needed."}],"tools":[{"type":"function","function":{"name":"nostr_post","description":"Publish a Nostr event to connected relays","parameters":{"type":"object","properties":{"kind":{"type":"integer","description":"Nostr event kind number (for example 1 for note, 7 for reaction, 30023 for longform)"},"content":{"type":"string","description":"Event content/body text to publish"},"tags":{"type":"array","description":"Optional Nostr tags array, e.g. [[\"d\",\"my-skill\"],[\"t\",\"nostr\"]]","items":{"type":"array","items":{"type":"string"}}}},"required":["kind","content"]}}},{"type":"function","function":{"name":"nostr_query","description":"Query events from relays using a Nostr filter","parameters":{"type":"object","properties":{"filter":{"type":"object","description":"Nostr filter object (authors, kinds, ids, since, until, limit, and tag filters)"},"timeout_ms":{"type":"integer","description":"Optional query timeout in milliseconds"}},"required":["filter"]}}},{"type":"function","function":{"name":"local_shell_exec","description":"Execute a shell command and return stdout/stderr","parameters":{"type":"object","properties":{"command":{"type":"string","description":"Shell command to execute in the configured working directory"}},"required":["command"]}}},{"type":"function","function":{"name":"local_file_read","description":"Read a local file as text from the configured working directory","parameters":{"type":"object","properties":{"path":{"type":"string","description":"Path to file to read (relative to configured working directory)"},"max_bytes":{"type":"integer","description":"Optional maximum bytes to return"}},"required":["path"]}}},{"type":"function","function":{"name":"local_file_write","description":"Write text content to a local file in the configured working directory","parameters":{"type":"object","properties":{"path":{"type":"string","description":"Path to file to write (relative to configured working directory)"},"content":{"type":"string","description":"Text content to write"},"append":{"type":"boolean","description":"When true, append to file instead of overwriting"}},"required":["path","content"]}}},{"type":"function","function":{"name":"nostr_post_readme","description":"Publish README.md as kind 30023 with deterministic d tag readme.md","parameters":{"type":"object","properties":{},"required":[]}}},{"type":"function","function":{"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","description":"Event IDs to request deletion for","items":{"type":"string"}},"kinds":{"type":"array","description":"Optional kinds corresponding to event_ids entries","items":{"type":"integer"}},"reason":{"type":"string","description":"Optional human-readable reason"}},"required":["event_ids"]}}},{"type":"function","function":{"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","description":"Target event ID to react to"},"event_pubkey":{"type":"string","description":"Author pubkey of target event"},"event_kind":{"type":"integer","description":"Optional kind of target event"},"reaction":{"type":"string","description":"Reaction content such as +, -, ❤️, or emoji"}},"required":["event_id","event_pubkey"]}}},{"type":"function","function":{"name":"nostr_profile_get","description":"Look up a Nostr profile (kind 0 metadata) by pubkey","parameters":{"type":"object","properties":{"pubkey":{"type":"string","description":"Hex pubkey of the profile to fetch"}},"required":["pubkey"]}}},{"type":"function","function":{"name":"nostr_relay_status","description":"Get connection status and statistics for all... diff --git a/tests/results/20260404T140236Z/runtime_test_genesis.jsonc b/tests/results/20260404T140236Z/runtime_test_genesis.jsonc new file mode 100644 index 0000000..8aa613f --- /dev/null +++ b/tests/results/20260404T140236Z/runtime_test_genesis.jsonc @@ -0,0 +1,53 @@ +{ + // TEST CONFIGURATION + // Use disposable keys/accounts only. + "key": { + "nsec": "d3011e59954403fb84729ab0e9fb239ba0f0238b3f353dfb5c08a32f4b3d21a1" + }, + "admin": { + "pubkey": "254d7a7f68b4443f5430564eb3a726f09aea57d27e75ab2b8ef57176f9ca3dac" + }, + "dm_protocol": "nip04", + "llm": { + "provider": "https://api.ppq.ai", + "api_key": "sk-SBI2Pga0J6n8jpPorHbRHC", + "model": "claude-haiku-4.5", + "base_url": "https://api.ppq.ai", + "max_tokens": 10000, + "temperature": 0.7 + }, + "api": { + "enabled": true, + "port": 8485, + "bind_address": "127.0.0.1" + }, + "startup_events": [ + { + "kind": 0, + "content_fields": { + "name": "Didactyl Test Agent", + "about": "Automated test instance" + }, + "tags": [] + }, + { + "kind": 10002, + "content": "", + "tags": [ + ["r", "wss://relay.damus.io"], + ["r", "wss://relay.primal.net"] + ] + }, + { + "kind": 31124, + "content": "# Test Agent\n\nYou are a test agent. Respond to requests and use tools when needed.", + "tags": [ + ["d", "identity_and_rules"], + ["app", "didactyl"], + ["scope", "private"], + ["trigger", "dm"], + ["filter", "{\"from\":\"admin\"}"] + ] + } + ] +} diff --git a/tests/results/20260404T140545Z/agent_debug.log b/tests/results/20260404T140545Z/agent_debug.log new file mode 100644 index 0000000..c6ab66d --- /dev/null +++ b/tests/results/20260404T140545Z/agent_debug.log @@ -0,0 +1,396 @@ +[2026-04-04 10:29:17] [INFO ] [main.c:1016] Didactyl v0.2.20 starting +[2026-04-04 10:29:17] [INFO ] [nostr_handler.c:2207] [didactyl] initializing relay pool with 2 relays +[2026-04-04 10:29:17] [INFO ] [nostr_handler.c:2231] [didactyl] added relay: wss://relay.damus.io +[2026-04-04 10:29:17] [INFO ] [nostr_handler.c:2231] [didactyl] added relay: wss://relay.primal.net +[2026-04-04 10:29:18] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_1_1775312957", { + "kinds": [10000], + "authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"], + "limit": 1 + }] +[2026-04-04 10:29:18] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_1_1775312957", { + "kinds": [10000], + "authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"], + "limit": 1 + }] +[2026-04-04 10:29:18] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_1_1775312957"] +[2026-04-04 10:29:18] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_1_1775312957"] +[2026-04-04 10:29:18] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_1_1775312957"] +[2026-04-04 10:29:18] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_1_1775312957"] +[2026-04-04 10:29:18] [INFO ] [main.c:248] [didactyl] startup checklist [01] Relay connectivity: begin +[2026-04-04 10:29:18] [INFO ] [main.c:255] [didactyl] startup checklist [01] Relay connectivity: ok (connected relays: 2/2) +[2026-04-04 10:29:18] [INFO ] [main.c:248] [didactyl] startup checklist [02] Recover runtime config from Nostr: begin +[2026-04-04 10:29:18] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_2_1775312958", { + "kinds": [30078], + "authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"], + "#d": ["agent_config"], + "limit": 1 + }] +[2026-04-04 10:29:18] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_2_1775312958", { + "kinds": [30078], + "authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"], + "#d": ["agent_config"], + "limit": 1 + }] +[2026-04-04 10:29:19] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 10:29:19] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_2_1775312958"] +[2026-04-04 10:29:19] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 10:29:19] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_2_1775312958"] +[2026-04-04 10:29:19] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_2_1775312958"] +[2026-04-04 10:29:19] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_2_1775312958"] +[2026-04-04 10:29:19] [INFO ] [main.c:258] [didactyl] startup checklist [02] Recover runtime config from Nostr: ok +[2026-04-04 10:29:19] [INFO ] [main.c:248] [didactyl] startup checklist [03] Validate LLM config: begin +[2026-04-04 10:29:19] [INFO ] [main.c:258] [didactyl] startup checklist [03] Validate LLM config: ok +[2026-04-04 10:29:19] [INFO ] [main.c:248] [didactyl] startup checklist [04] Validate admin config: begin +[2026-04-04 10:29:19] [INFO ] [main.c:258] [didactyl] startup checklist [04] Validate admin config: ok +[2026-04-04 10:29:19] [INFO ] [main.c:248] [didactyl] startup checklist [05] Initialize LLM client: begin +[2026-04-04 10:29:19] [INFO ] [main.c:258] [didactyl] startup checklist [05] Initialize LLM client: ok +[2026-04-04 10:29:19] [INFO ] [main.c:248] [didactyl] startup checklist [06] Detect first run via kind 10002: begin +[2026-04-04 10:29:19] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_3_1775312959", { + "kinds": [10002], + "authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"], + "limit": 1 + }] +[2026-04-04 10:29:19] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_3_1775312959", { + "kinds": [10002], + "authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"], + "limit": 1 + }] +[2026-04-04 10:29:19] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 10:29:19] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 10:29:19] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_3_1775312959"] +[2026-04-04 10:29:19] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_3_1775312959"] +[2026-04-04 10:29:19] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_3_1775312959"] +[2026-04-04 10:29:19] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_3_1775312959"] +[2026-04-04 10:29:19] [INFO ] [main.c:1245] [didactyl] startup phase: first-run detection via kind 10002 => subsequent-run +[2026-04-04 10:29:19] [INFO ] [main.c:255] [didactyl] startup checklist [06] Detect first run via kind 10002: ok (subsequent-run) +[2026-04-04 10:29:19] [INFO ] [main.c:248] [didactyl] startup checklist [07] Reconcile/persist startup state: begin +[2026-04-04 10:29:19] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_4_1775312959", { + "kinds": [1], + "authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"], + "limit": 1 + }] +[2026-04-04 10:29:19] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_4_1775312959", { + "kinds": [1], + "authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"], + "limit": 1 + }] +[2026-04-04 10:29:20] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_4_1775312959"] +[2026-04-04 10:29:20] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_4_1775312959"] +[2026-04-04 10:29:20] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_4_1775312959"] +[2026-04-04 10:29:20] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_4_1775312959"] +[2026-04-04 10:29:20] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] SEND +[2026-04-04 10:29:20] [INFO ] [nostr_handler.c:2949] [didactyl] kind 0 event published to wss://relay.damus.io (async, reason=startup_reconcile) +[2026-04-04 10:29:20] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] SEND +[2026-04-04 10:29:20] [INFO ] [nostr_handler.c:2949] [didactyl] kind 10002 event published to wss://relay.damus.io (async, reason=startup_reconcile) +[2026-04-04 10:29:20] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] SEND +[2026-04-04 10:29:20] [INFO ] [nostr_handler.c:2949] [didactyl] kind 31124 event published to wss://relay.damus.io (async, reason=startup_reconcile) +[2026-04-04 10:29:20] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] SEND +[2026-04-04 10:29:20] [INFO ] [nostr_handler.c:2949] [didactyl] kind 10123 event published to wss://relay.damus.io (async, reason=startup_reconcile) +[2026-04-04 10:29:20] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] SEND +[2026-04-04 10:29:20] [INFO ] [nostr_handler.c:2949] [didactyl] kind 0 event published to wss://relay.primal.net (async, reason=startup_reconcile) +[2026-04-04 10:29:20] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] SEND +[2026-04-04 10:29:20] [INFO ] [nostr_handler.c:2949] [didactyl] kind 10002 event published to wss://relay.primal.net (async, reason=startup_reconcile) +[2026-04-04 10:29:20] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] SEND +[2026-04-04 10:29:20] [INFO ] [nostr_handler.c:2949] [didactyl] kind 31124 event published to wss://relay.primal.net (async, reason=startup_reconcile) +[2026-04-04 10:29:20] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] SEND +[2026-04-04 10:29:20] [INFO ] [nostr_handler.c:2949] [didactyl] kind 10123 event published to wss://relay.primal.net (async, reason=startup_reconcile) +[2026-04-04 10:29:20] [INFO ] [nostr_handler.c:3285] [didactyl] startup publish detail: kind=0 (profile) -> wss://relay.damus.io, wss://relay.primal.net +[2026-04-04 10:29:20] [INFO ] [nostr_handler.c:3285] [didactyl] startup publish detail: kind=10002 (relay_list) -> wss://relay.damus.io, wss://relay.primal.net +[2026-04-04 10:29:20] [INFO ] [nostr_handler.c:3285] [didactyl] startup publish detail: kind=31124 (skill) -> wss://relay.damus.io, wss://relay.primal.net +[2026-04-04 10:29:20] [INFO ] [nostr_handler.c:3285] [didactyl] startup publish detail: kind=10123 (adoption_list) -> wss://relay.damus.io, wss://relay.primal.net +[2026-04-04 10:29:20] [INFO ] [nostr_handler.c:3297] [didactyl] startup publish summary: 4/4 event(s) published to at least one relay; relays used=2/2 +[2026-04-04 10:29:20] [INFO ] [nostr_handler.c:906] [didactyl] publish kind event target relays (2): +[2026-04-04 10:29:20] [INFO ] [nostr_handler.c:908] [didactyl] -> wss://relay.damus.io +[2026-04-04 10:29:20] [INFO ] [nostr_handler.c:908] [didactyl] -> wss://relay.primal.net +[2026-04-04 10:29:20] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] SEND +[2026-04-04 10:29:20] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] SEND +[2026-04-04 10:29:20] [INFO ] [nostr_handler.c:2949] [didactyl] kind 30078 event published to wss://relay.damus.io (async, reason=manual_publish) +[2026-04-04 10:29:20] [INFO ] [nostr_handler.c:2949] [didactyl] kind 30078 event published to wss://relay.primal.net (async, reason=manual_publish) +[2026-04-04 10:29:20] [INFO ] [nostr_handler.c:3012] [didactyl] published kind 30078 event via 2 connected relay(s) +[2026-04-04 10:29:20] [INFO ] [nostr_handler.c:906] [didactyl] publish kind event target relays (2): +[2026-04-04 10:29:20] [INFO ] [nostr_handler.c:908] [didactyl] -> wss://relay.damus.io +[2026-04-04 10:29:20] [INFO ] [nostr_handler.c:908] [didactyl] -> wss://relay.primal.net +[2026-04-04 10:29:20] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] SEND +[2026-04-04 10:29:20] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] SEND +[2026-04-04 10:29:20] [INFO ] [nostr_handler.c:2949] [didactyl] kind 30078 event published to wss://relay.damus.io (async, reason=manual_publish) +[2026-04-04 10:29:20] [INFO ] [nostr_handler.c:2949] [didactyl] kind 30078 event published to wss://relay.primal.net (async, reason=manual_publish) +[2026-04-04 10:29:20] [INFO ] [nostr_handler.c:3012] [didactyl] published kind 30078 event via 2 connected relay(s) +[2026-04-04 10:29:20] [INFO ] [main.c:1267] [didactyl] startup phase: persisted encrypted runtime config to Nostr +[2026-04-04 10:29:20] [INFO ] [main.c:258] [didactyl] startup checklist [07] Reconcile/persist startup state: ok +[2026-04-04 10:29:20] [INFO ] [main.c:248] [didactyl] startup checklist [08] Initialize agent: begin +[2026-04-04 10:29:20] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_5_1775312960", { + "kinds": [30078], + "authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"], + "#d": ["memory"], + "limit": 1 + }] +[2026-04-04 10:29:20] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_5_1775312960", { + "kinds": [30078], + "authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"], + "#d": ["memory"], + "limit": 1 + }] +[2026-04-04 10:29:20] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["OK","11bc4eb1ff5e51d8df325c1f16eab167535bd04f4768129437098aef02917a0c",true,""] +[2026-04-04 10:29:20] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["OK","5e774283a8db088821e3c20d7f5ec2dbc64e9ca32d5342ce31f699205e16b22c",true,""] +[2026-04-04 10:29:20] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["OK","5e774283a8db088821e3c20d7f5ec2dbc64e9ca32d5342ce31f699205e16b22c",true,""] +[2026-04-04 10:29:20] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["OK","e863ae73ffdb98391429ecbb36d4ad931ca994804cd673a310ed91f94806a795",true,""] +[2026-04-04 10:29:20] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["OK","9f540b534baeaa37d5bdfd1b3ef6ef1f9b65b8f00236ad28d63e098b56e56c35",true,""] +[2026-04-04 10:29:20] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["OK","11bc4eb1ff5e51d8df325c1f16eab167535bd04f4768129437098aef02917a0c",true,""] +[2026-04-04 10:29:20] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["OK","e863ae73ffdb98391429ecbb36d4ad931ca994804cd673a310ed91f94806a795",true,""] +[2026-04-04 10:29:20] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["OK","9f540b534baeaa37d5bdfd1b3ef6ef1f9b65b8f00236ad28d63e098b56e56c35",true,""] +[2026-04-04 10:29:20] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 10:29:20] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_5_1775312960"] +[2026-04-04 10:29:20] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_5_1775312960"] +[2026-04-04 10:29:20] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_5_1775312960"] +[2026-04-04 10:29:20] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_5_1775312960"] +[2026-04-04 10:29:20] [INFO ] [main.c:258] [didactyl] startup checklist [08] Initialize agent: ok +[2026-04-04 10:29:20] [INFO ] [main.c:248] [didactyl] startup checklist [09] Initialize trigger manager: begin +[2026-04-04 10:29:20] [INFO ] [trigger_manager.c:814] [didactyl] trigger manager initialized (capacity=16) +[2026-04-04 10:29:20] [INFO ] [main.c:258] [didactyl] startup checklist [09] Initialize trigger manager: ok +[2026-04-04 10:29:20] [INFO ] [main.c:1308] [didactyl] startup phase: deferred trigger load will run after self-skill EOSE +[2026-04-04 10:29:20] [INFO ] [main.c:248] [didactyl] startup checklist [10] Load startup triggers: begin +[2026-04-04 10:29:20] [INFO ] [main.c:1311] [didactyl] startup phase: startup-config trigger load begin +[2026-04-04 10:29:20] [INFO ] [trigger_manager.c:1138] [didactyl] trigger added d_tag=identity_and_rules action=0 enabled=1 +[2026-04-04 10:29:20] [INFO ] [trigger_manager.c:1021] [didactyl] startup trigger registered d_tag=identity_and_rules action=llm enabled=1 +[2026-04-04 10:29:20] [INFO ] [trigger_manager.c:1029] [didactyl] trigger manager loaded 1 trigger(s) from startup config (considered=1) +[2026-04-04 10:29:20] [INFO ] [main.c:1315] [didactyl] startup phase: startup-config trigger load end +[2026-04-04 10:29:20] [INFO ] [main.c:258] [didactyl] startup checklist [10] Load startup triggers: ok +[2026-04-04 10:29:20] [INFO ] [main.c:248] [didactyl] startup checklist [11] Discover self relay list via kind 10002: begin +[2026-04-04 10:29:20] [INFO ] [main.c:327] [didactyl] kind10002 query begin: timeout_ms=5000 author=1eb171136dbb1ba6... relay_count=2 +[2026-04-04 10:29:20] [INFO ] [main.c:334] [didactyl] kind10002 query relay snapshot: {"relay_count":2,"connected_count":2,"relays":[{"url":"wss://relay.damus.io","status":"connected","events_received":1,"events_published":6,"events_published_ok":4,"events_published_failed":0,"ping_latency_current":0,"ping_latency_avg":0,"query_latency_avg":0,"publish_latency_avg":0,"connection_uptime_start":1775312957,"last_event_time":1775312960},{"url":"wss://relay.primal.net","status":"connected","events_received":3,"events_published":6,"events_published_ok":4,"events_published_failed":0,"ping_latency_current":0,"ping_latency_avg":0,"query_latency_avg":0,"publish_latency_avg":0,"connection_uptime_start":1775312957,"last_event_time":1775312960}]} +[2026-04-04 10:29:20] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_6_1775312960", { + "kinds": [10002], + "authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"], + "limit": 16 + }] +[2026-04-04 10:29:20] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_6_1775312960", { + "kinds": [10002], + "authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"], + "limit": 16 + }] +[2026-04-04 10:29:20] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["OK","86a6101b5cd0fc5e04cd22ad7c897cecd8b4a6a827180b1a18d1da10c31ce9bf",false,"rate-limited: you are noting too much"] +[2026-04-04 10:29:20] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["OK","59d0b381dcf93eb42a65bc1bbc31e54b82ac20bb3b2dd654b08d3780da41ee07",true,""] +[2026-04-04 10:29:20] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["OK","59d0b381dcf93eb42a65bc1bbc31e54b82ac20bb3b2dd654b08d3780da41ee07",true,""] +[2026-04-04 10:29:20] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["OK","86a6101b5cd0fc5e04cd22ad7c897cecd8b4a6a827180b1a18d1da10c31ce9bf",true,""] +[2026-04-04 10:29:20] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 10:29:20] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 10:29:20] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_6_1775312960"] +[2026-04-04 10:29:20] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_6_1775312960"] +[2026-04-04 10:29:20] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_6_1775312960"] +[2026-04-04 10:29:20] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_6_1775312960"] +[2026-04-04 10:29:20] [INFO ] [main.c:354] [didactyl] kind10002 query received event_count=1 +[2026-04-04 10:29:20] [INFO ] [main.c:451] [didactyl] kind10002 query extracted relays=["wss://relay.damus.io","wss://relay.primal.net"] +[2026-04-04 10:29:20] [INFO ] [main.c:556] [didactyl] kind10002 wait success: relay_count=2 +[2026-04-04 10:29:20] [INFO ] [main.c:558] [didactyl] kind10002 relay[0]=wss://relay.damus.io +[2026-04-04 10:29:20] [INFO ] [main.c:558] [didactyl] kind10002 relay[1]=wss://relay.primal.net +[2026-04-04 10:29:20] [INFO ] [main.c:562] [didactyl] kind10002 wait success relay snapshot: {"relay_count":2,"connected_count":2,"relays":[{"url":"wss://relay.damus.io","status":"connected","events_received":1,"events_published":6,"events_published_ok":5,"events_published_failed":1,"ping_latency_current":0,"ping_latency_avg":0,"query_latency_avg":0,"publish_latency_avg":0,"connection_uptime_start":1775312957,"last_event_time":1775312960,"last_publish_error":"rate-limited: you are noting too much"},{"url":"wss://relay.primal.net","status":"connected","events_received":4,"events_published":6,"events_published_ok":6,"events_published_failed":0,"ping_latency_current":0,"ping_latency_avg":0,"query_latency_avg":0,"publish_latency_avg":0,"connection_uptime_start":1775312957,"last_event_time":1775312960}]} +[2026-04-04 10:29:20] [INFO ] [main.c:255] [didactyl] startup checklist [11] Discover self relay list via kind 10002: ok (kind10002=2 added=0 connected=2/2) +[2026-04-04 10:29:20] [INFO ] [main.c:248] [didactyl] startup checklist [12] Subscribe admin context: begin +[2026-04-04 10:29:20] [INFO ] [main.c:1366] [didactyl] startup phase: subscribe admin context begin +[2026-04-04 10:29:20] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_7_1775312960", { + "kinds": [0, 3, 10002], + "authors": ["254d7a7f68b4443f5430564eb3a726f09aea57d27e75ab2b8ef57176f9ca3dac"], + "limit": 32 + }] +[2026-04-04 10:29:20] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_7_1775312960", { + "kinds": [0, 3, 10002], + "authors": ["254d7a7f68b4443f5430564eb3a726f09aea57d27e75ab2b8ef57176f9ca3dac"], + "limit": 32 + }] +[2026-04-04 10:29:20] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_8_1775312960", { + "kinds": [1], + "authors": ["254d7a7f68b4443f5430564eb3a726f09aea57d27e75ab2b8ef57176f9ca3dac"], + "limit": 10 + }] +[2026-04-04 10:29:20] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_8_1775312960", { + "kinds": [1], + "authors": ["254d7a7f68b4443f5430564eb3a726f09aea57d27e75ab2b8ef57176f9ca3dac"], + "limit": 10 + }] +[2026-04-04 10:29:20] [INFO ] [nostr_handler.c:2352] [didactyl] admin context subscriptions active for admin 254d7a7f68b4443f... +[2026-04-04 10:29:20] [INFO ] [main.c:1370] [didactyl] startup phase: subscribe admin context end +[2026-04-04 10:29:20] [INFO ] [main.c:258] [didactyl] startup checklist [12] Subscribe admin context: ok +[2026-04-04 10:29:20] [INFO ] [main.c:248] [didactyl] startup checklist [13] Subscribe agent self context: begin +[2026-04-04 10:29:20] [INFO ] [main.c:1374] [didactyl] startup phase: subscribe agent self context begin +[2026-04-04 10:29:20] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_9_1775312960", { + "kinds": [0, 3, 10002], + "authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"], + "limit": 64 + }] +[2026-04-04 10:29:20] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_9_1775312960", { + "kinds": [0, 3, 10002], + "authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"], + "limit": 64 + }] +[2026-04-04 10:29:20] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_10_1775312960", { + "kinds": [1], + "authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"], + "limit": 10 + }] +[2026-04-04 10:29:20] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_10_1775312960", { + "kinds": [1], + "authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"], + "limit": 10 + }] +[2026-04-04 10:29:20] [INFO ] [nostr_handler.c:2440] [didactyl] agent self-context subscriptions active for pubkey 1eb171136dbb1ba6... +[2026-04-04 10:29:20] [INFO ] [main.c:1378] [didactyl] startup phase: subscribe agent self context end +[2026-04-04 10:29:20] [INFO ] [main.c:258] [didactyl] startup checklist [13] Subscribe agent self context: ok +[2026-04-04 10:29:20] [INFO ] [main.c:248] [didactyl] startup checklist [14] Subscribe self-skill cache: begin +[2026-04-04 10:29:20] [INFO ] [main.c:1382] [didactyl] startup phase: subscribe self skill cache begin +[2026-04-04 10:29:20] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_11_1775312960", { + "kinds": [31123, 31124, 10123], + "authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"], + "limit": 300 + }] +[2026-04-04 10:29:20] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_11_1775312960", { + "kinds": [31123, 31124, 10123], + "authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"], + "limit": 300 + }] +[2026-04-04 10:29:20] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_12_1775312960", { + "kinds": [31123], + "authors": ["254d7a7f68b4443f5430564eb3a726f09aea57d27e75ab2b8ef57176f9ca3dac"], + "limit": 150 + }] +[2026-04-04 10:29:20] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_12_1775312960", { + "kinds": [31123], + "authors": ["254d7a7f68b4443f5430564eb3a726f09aea57d27e75ab2b8ef57176f9ca3dac"], + "limit": 150 + }] +[2026-04-04 10:29:20] [INFO ] [nostr_handler.c:2532] [didactyl] self/admin skill subscriptions active (self=1eb171136dbb1ba6..., admin=254d7a7f68b4443f...) +[2026-04-04 10:29:20] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_7_1775312960"] +[2026-04-04 10:29:20] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_7_1775312960"] +[2026-04-04 10:29:20] [TRACE] [nostr_handler.c:1718] [didactyl] DEBUG on_eose called: event_count=0 +[2026-04-04 10:29:20] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_8_1775312960"] +[2026-04-04 10:29:20] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_8_1775312960"] +[2026-04-04 10:29:20] [TRACE] [nostr_handler.c:1718] [didactyl] DEBUG on_eose called: event_count=0 +[2026-04-04 10:29:20] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 10:29:20] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 10:29:20] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 10:29:20] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 10:29:20] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_9_1775312960"] +[2026-04-04 10:29:20] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_9_1775312960"] +[2026-04-04 10:29:20] [TRACE] [nostr_handler.c:1718] [didactyl] DEBUG on_eose called: event_count=0 +[2026-04-04 10:29:20] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_10_1775312960"] +[2026-04-04 10:29:20] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_10_1775312960"] +[2026-04-04 10:29:20] [TRACE] [nostr_handler.c:1718] [didactyl] DEBUG on_eose called: event_count=0 +[2026-04-04 10:29:20] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 10:29:20] [INFO ] [nostr_handler.c:2151] [didactyl] self-skill cache received kind=31124 d_tag=identity_and_rules id=9f540b534baeaa37d5bdfd1b3ef6ef1f9b65b8f00236ad28d63e098b56e56c35 created_at=1775312960 via wss://relay.primal.net +[2026-04-04 10:29:20] [INFO ] [trigger_manager.c:1364] [didactyl] trigger updated d_tag=identity_and_rules action=0 enabled=1 +[2026-04-04 10:29:20] [INFO ] [nostr_handler.c:1207] [didactyl] live self-skill trigger registered d_tag=identity_and_rules action=llm enabled=1 +[2026-04-04 10:29:20] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 10:29:20] [INFO ] [nostr_handler.c:2151] [didactyl] self-skill cache received kind=31124 d_tag=identity_and_rules id=9f540b534baeaa37d5bdfd1b3ef6ef1f9b65b8f00236ad28d63e098b56e56c35 created_at=1775312960 via wss://relay.damus.io +[2026-04-04 10:29:20] [INFO ] [trigger_manager.c:1364] [didactyl] trigger updated d_tag=identity_and_rules action=0 enabled=1 +[2026-04-04 10:29:20] [INFO ] [nostr_handler.c:1207] [didactyl] live self-skill trigger registered d_tag=identity_and_rules action=llm enabled=1 +[2026-04-04 10:29:20] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 10:29:20] [INFO ] [nostr_handler.c:2151] [didactyl] self-skill cache received kind=10123 d_tag=- id=11bc4eb1ff5e51d8df325c1f16eab167535bd04f4768129437098aef02917a0c created_at=1775312960 via wss://relay.primal.net +[2026-04-04 10:29:20] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 10:29:20] [INFO ] [nostr_handler.c:2151] [didactyl] self-skill cache received kind=10123 d_tag=- id=11bc4eb1ff5e51d8df325c1f16eab167535bd04f4768129437098aef02917a0c created_at=1775312960 via wss://relay.damus.io +[2026-04-04 10:29:20] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] RECV +[2026-04-04 10:29:20] [INFO ] [nostr_handler.c:2151] [didactyl] self-skill cache received kind=31123 d_tag=test-harness-probe id=fbf278b5a6ccd7fd86b0c8c7bfb787aa3f94c578e0bda74241e1d8e613394467 created_at=1775312616 via wss://relay.primal.net +[2026-04-04 10:29:20] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_11_1775312960"] +[2026-04-04 10:29:20] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_11_1775312960"] +[2026-04-04 10:29:20] [INFO ] [nostr_handler.c:1725] [didactyl] self-skill EOSE received: cached skill events=0 +[2026-04-04 10:29:20] [INFO ] [main.c:44] [didactyl] deferred trigger load begin after self-skill EOSE (event_count=0) +[2026-04-04 10:29:20] [INFO ] [trigger_manager.c:1364] [didactyl] trigger updated d_tag=identity_and_rules action=0 enabled=1 +[2026-04-04 10:29:20] [INFO ] [trigger_manager.c:918] [didactyl] trigger manager loaded 1 trigger(s) from self skills (considered=2) +[2026-04-04 10:29:20] [INFO ] [main.c:54] [didactyl] deferred trigger load status: {"success":true,"count":1,"active":1,"triggers":[{"skill_d_tag":"identity_and_rules","filter_json":"{\"from\":\"admin\"}","type":"dm","action":"llm","enabled":true,"subscribed":false,"llm":"","tools":"","has_max_tokens":false,"has_temperature":false,"has_seed":false,"last_fired":0,"last_seen_created_at":0}]} +[2026-04-04 10:29:20] [INFO ] [nostr_handler.c:2757] [didactyl] self-skill cache event: kind=31124 d_tag=identity_and_rules id=9f540b534baeaa37d5bdfd1b3ef6ef1f9b65b8f00236ad28d63e098b56e56c35 created_at=1775312960 +[2026-04-04 10:29:20] [INFO ] [nostr_handler.c:2757] [didactyl] self-skill cache event: kind=10123 d_tag=- id=11bc4eb1ff5e51d8df325c1f16eab167535bd04f4768129437098aef02917a0c created_at=1775312960 +[2026-04-04 10:29:20] [INFO ] [nostr_handler.c:2757] [didactyl] self-skill cache event: kind=31123 d_tag=test-harness-probe id=fbf278b5a6ccd7fd86b0c8c7bfb787aa3f94c578e0bda74241e1d8e613394467 created_at=1775312616 +[2026-04-04 10:29:20] [INFO ] [main.c:1449] [didactyl] startup phase: subscribe self skill cache end +[2026-04-04 10:29:20] [INFO ] [main.c:255] [didactyl] startup checklist [14] Subscribe self-skill cache: ok (skills=2 adoptions=1) +[2026-04-04 10:29:20] [INFO ] [main.c:248] [didactyl] startup checklist [15] Subscribe DMs: begin +[2026-04-04 10:29:20] [INFO ] [main.c:1453] [didactyl] startup phase: subscribe DMs begin +[2026-04-04 10:29:20] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_13_1775312960", { + "kinds": [4], + "#p": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"], + "since": 1775312957, + "limit": 100 + }] +[2026-04-04 10:29:20] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_13_1775312960", { + "kinds": [4], + "#p": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"], + "since": 1775312957, + "limit": 100 + }] +[2026-04-04 10:29:20] [INFO ] [nostr_handler.c:2643] [didactyl] DM subscription active for pubkey 1eb171136dbb1ba6... +[2026-04-04 10:29:20] [TRACE] [nostr_handler.c:2644] [didactyl] DEBUG DM subscription g_start_time=1775312957 now=1775312960 delta=3 relay_count=2 +[2026-04-04 10:29:20] [INFO ] [main.c:1468] [didactyl] startup phase: subscribe DMs end +[2026-04-04 10:29:20] [INFO ] [main.c:258] [didactyl] startup checklist [15] Subscribe DMs: ok +[2026-04-04 10:29:20] [INFO ] [main.c:248] [didactyl] startup checklist [16] Subscribe wallet events: begin +[2026-04-04 10:29:20] [INFO ] [main.c:1472] [didactyl] startup phase: subscribe wallet events begin +[2026-04-04 10:29:20] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_14_1775312960", { + "kinds": [17375, 7375, 5], + "authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"], + "since": 1775312957, + "limit": 500 + }] +[2026-04-04 10:29:20] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_14_1775312960", { + "kinds": [17375, 7375, 5], + "authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"], + "since": 1775312957, + "limit": 500 + }] +[2026-04-04 10:29:20] [INFO ] [nostr_handler.c:2696] [didactyl] wallet event subscription active for pubkey 1eb171136dbb1ba6... +[2026-04-04 10:29:20] [INFO ] [main.c:1476] [didactyl] startup phase: subscribe wallet events end +[2026-04-04 10:29:20] [INFO ] [main.c:258] [didactyl] startup checklist [16] Subscribe wallet events: ok +[2026-04-04 10:29:20] [INFO ] [main.c:248] [didactyl] startup checklist [17] Initialize cashu wallet: begin +[2026-04-04 10:29:20] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["REQ", "pool_15_1775312960", { + "kinds": [17375, 7375], + "authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"] + }] +[2026-04-04 10:29:20] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["REQ", "pool_15_1775312960", { + "kinds": [17375, 7375], + "authors": ["1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc"] + }] +[2026-04-04 10:29:20] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_12_1775312960"] +[2026-04-04 10:29:20] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_12_1775312960"] +[2026-04-04 10:29:20] [TRACE] [nostr_handler.c:1718] [didactyl] DEBUG on_eose called: event_count=0 +[2026-04-04 10:29:21] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_13_1775312960"] +[2026-04-04 10:29:21] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_13_1775312960"] +[2026-04-04 10:29:21] [TRACE] [nostr_handler.c:1718] [didactyl] DEBUG on_eose called: event_count=0 +[2026-04-04 10:29:21] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_14_1775312960"] +[2026-04-04 10:29:21] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_14_1775312960"] +[2026-04-04 10:29:21] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["EOSE","pool_15_1775312960"] +[2026-04-04 10:29:21] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["EOSE","pool_15_1775312960"] +[2026-04-04 10:29:21] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_15_1775312960"] +[2026-04-04 10:29:21] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_15_1775312960"] +[2026-04-04 10:29:21] [WARN ] [main.c:1492] [didactyl] startup phase: cashu wallet load/create deferred to first tool call +[2026-04-04 10:29:21] [INFO ] [main.c:255] [didactyl] startup checklist [17] Initialize cashu wallet: ok (initialized; load/create deferred) +[2026-04-04 10:29:21] [INFO ] [http_api.c:1570] [didactyl] http api listening on http://127.0.0.1:8485 +[2026-04-04 10:29:21] [INFO ] [main.c:1532] [didactyl] HTTP API listening at http://127.0.0.1:8485 +[2026-04-04 10:29:21] [INFO ] [main.c:1535] [didactyl] HTTP API endpoints: http://127.0.0.1:8485/api/context/current http://127.0.0.1:8485/api/context/parts +[2026-04-04 10:29:21] [TRACE] [nostr_handler.c:1499] [didactyl] sending plaintext DM content: Didactyl Test Agent has started up and is online at 2026-04-04 10:29:21 (version v0.2.20, connected relays: 2/2). +[2026-04-04 10:29:21] [TRACE] [nostr_handler.c:1491] [didactyl] sending encrypted DM event: {"pubkey":"1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc","created_at":1775312961,"kind":4,"tags":[["p","254d7a7f68b4443f5430564eb3a726f09aea57d27e75ab2b8ef57176f9ca3dac"]],"content":"TOAe6VwLkGJemoQ1F6TB3lfiGq251GlJjayadl04UGMhnkm/A/PKzaqzzqO5q0Xpn9LM83KjU575uHL+PSEaM76IfvFcMVN8Qa1C3hSF9/7vpn1LuIGBIpQn8fmDpHji+gjRVZk1wvWUPoC63CCTWH/cUgJkILYQaW4XMdJwA1E=?iv=cuZPWOh+jAIjqTR/U47BIg==","id":"d79dcbff5a6b5f37100d1a04e115c64231c28476fa0e21454cdaa30fc5010622","sig":"e356e8244b8930268aab91124db3d3632952b091a6880de13e2a9b1122f538f5fec3b5bffaae2615bcbcb42a3663b85d6f1f66f9f264967c4c5b44009ada977a"} +[2026-04-04 10:29:21] [INFO ] [nostr_handler.c:906] [didactyl] publish DM target relays (2): +[2026-04-04 10:29:21] [INFO ] [nostr_handler.c:908] [didactyl] -> wss://relay.damus.io +[2026-04-04 10:29:21] [INFO ] [nostr_handler.c:908] [didactyl] -> wss://relay.primal.net +[2026-04-04 10:29:21] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] SEND +[2026-04-04 10:29:21] [TRACE] [nostr_handler.c:162] [didactyl] [nostr:websocket] SEND +[2026-04-04 10:29:21] [INFO ] [nostr_handler.c:2843] [didactyl] kind 4 event published to wss://relay.damus.io (async) +[2026-04-04 10:29:21] [INFO ] [nostr_handler.c:2843] [didactyl] kind 4 event published to wss://relay.primal.net (async) +[2026-04-04 10:29:21] [INFO ] [nostr_handler.c:2852] [didactyl] sent DM d79dcbff5a6b5f37... to 254d7a7f68b4443f... via 2 connected relay(s) +[2026-04-04 10:29:21] [INFO ] [main.c:255] [didactyl] startup checklist [18] READY: ok (agent online; entering main poll loop) +[2026-04-04 10:29:21] [INFO ] [main.c:1571] [didactyl] entering main poll loop +[2026-04-04 10:29:21] [INFO ] [main.c:1572] [didactyl] running with pubkey 1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc +[2026-04-04 10:29:21] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.primal.net:443: ["OK","d79dcbff5a6b5f37100d1a04e115c64231c28476fa0e21454cdaa30fc5010622",true,""] +[2026-04-04 10:29:21] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] RECV relay.damus.io:443: ["OK","d79dcbff5a6b5f37100d1a04e115c64231c28476fa0e21454cdaa30fc5010622",false,"rate-limited: you are noting too much"] +[2026-04-04 10:29:21] [INFO ] [main.c:1584] [didactyl] shutting down +[2026-04-04 10:29:21] [INFO ] [trigger_manager.c:1602] [didactyl] trigger manager cleaned up +[2026-04-04 10:29:21] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_7_1775312960"] +[2026-04-04 10:29:21] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_7_1775312960"] +[2026-04-04 10:29:21] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_8_1775312960"] +[2026-04-04 10:29:21] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_8_1775312960"] +[2026-04-04 10:29:21] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_9_1775312960"] +[2026-04-04 10:29:21] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_9_1775312960"] +[2026-04-04 10:29:21] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_10_1775312960"] +[2026-04-04 10:29:21] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_10_1775312960"] +[2026-04-04 10:29:21] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_11_1775312960"] +[2026-04-04 10:29:21] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_11_1775312960"] +[2026-04-04 10:29:21] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_12_1775312960"] +[2026-04-04 10:29:21] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_12_1775312960"] +[2026-04-04 10:29:21] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_13_1775312960"] +[2026-04-04 10:29:21] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_13_1775312960"] +[2026-04-04 10:29:21] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.damus.io:443: ["CLOSE", "pool_14_1775312960"] +[2026-04-04 10:29:21] [TRACE] [nostr_handler.c:169] [didactyl] [nostr:websocket] SEND relay.primal.net:443: ["CLOSE", "pool_14_1775312960"] diff --git a/tests/results/20260404T140545Z/results.json b/tests/results/20260404T140545Z/results.json new file mode 100644 index 0000000..620071e --- /dev/null +++ b/tests/results/20260404T140545Z/results.json @@ -0,0 +1,494 @@ +{ + "run_meta": { + "run_timestamp": "2026-04-04T14:29:21.419470+00:00", + "base_url": "http://127.0.0.1:8485", + "config": "tests/results/20260404T140545Z/runtime_test_genesis.jsonc", + "binary": "didactyl_static_x86_64_debug", + "test_count": 43 + }, + "results": [ + { + "suite": "test_conversation", + "name": "simple_greeting", + "status": "pass", + "message": "greeting response ok", + "duration_seconds": 4.245289196027443, + "agent_errors": [], + "details": { + "response": "Hello! I'm a test agent built on the Didactyl platform. I don't have a specific personal name, but you can think of me as your Didactyl test agent. \n\nI'm here to help you with various tasks using the " + } + }, + { + "suite": "test_conversation", + "name": "agent_responds_about_itself", + "status": "pass", + "message": "self description ok", + "duration_seconds": 27.058145482034888, + "agent_errors": [], + "details": { + "response": "i'm **didactyl**, a nostr-native ai agent. here's what i do:\n\n**core identity:**\n- i'm an autonomous agent built on the nostr protocol, operating with cryptographic keypairs for identity and authentic" + } + }, + { + "suite": "test_conversation", + "name": "empty_message_handling", + "status": "pass", + "message": "empty message handled", + "duration_seconds": 4.667415744974278, + "agent_errors": [], + "details": { + "success": true + } + }, + { + "suite": "test_conversation", + "name": "very_long_message", + "status": "timeout", + "message": "timed out", + "duration_seconds": 76.51407337700948, + "agent_errors": [], + "details": {} + }, + { + "suite": "test_errors", + "name": "invalid_json_body", + "status": "pass", + "message": "invalid json rejected", + "duration_seconds": 0.3308889329782687, + "agent_errors": [], + "details": { + "status": 400 + } + }, + { + "suite": "test_errors", + "name": "missing_message_field", + "status": "pass", + "message": "missing message rejected", + "duration_seconds": 0.3314382230164483, + "agent_errors": [], + "details": { + "status": 400 + } + }, + { + "suite": "test_errors", + "name": "unknown_endpoint", + "status": "pass", + "message": "unknown endpoint 404", + "duration_seconds": 0.33126351796090603, + "agent_errors": [], + "details": { + "status": 404 + } + }, + { + "suite": "test_errors", + "name": "webhook_nonexistent_dtag", + "status": "pass", + "message": "nonexistent d_tag 404", + "duration_seconds": 0.33133913297206163, + "agent_errors": [], + "details": { + "status": 404 + } + }, + { + "suite": "test_health", + "name": "status_returns_200", + "status": "pass", + "message": "status success", + "duration_seconds": 0.3312724310089834, + "agent_errors": [], + "details": { + "keys": [ + "success", + "name", + "version", + "pubkey", + "relay_count", + "connected_relays", + "active_triggers" + ] + } + }, + { + "suite": "test_health", + "name": "status_has_fields", + "status": "pass", + "message": "required fields present", + "duration_seconds": 0.3312905229977332, + "agent_errors": [], + "details": {} + }, + { + "suite": "test_health", + "name": "context_current_returns_messages", + "status": "pass", + "message": "context current ok", + "duration_seconds": 0.33117285498883575, + "agent_errors": [], + "details": { + "message_count": 2 + } + }, + { + "suite": "test_health", + "name": "context_parts_has_system_prompt", + "status": "pass", + "message": "context parts ok", + "duration_seconds": 0.3312868010252714, + "agent_errors": [], + "details": { + "parts": [ + "system_prompt", + "dm_history" + ] + } + }, + { + "suite": "test_restart", + "name": "clean_restart", + "status": "pass", + "message": "restart succeeded", + "duration_seconds": 3.8490588110289536, + "agent_errors": [], + "details": {} + }, + { + "suite": "test_restart", + "name": "status_after_restart", + "status": "pass", + "message": "status stable after restart", + "duration_seconds": 4.151956218993291, + "agent_errors": [], + "details": { + "pubkey": "1eb171136dbb1ba695b2f14296adfe88a1f81d4220cc7d186b0e139b8ce0cdbc" + } + }, + { + "suite": "test_restart", + "name": "conversation_after_restart", + "status": "pass", + "message": "conversation works post-restart", + "duration_seconds": 7.446270982036367, + "agent_errors": [], + "details": {} + }, + { + "suite": "test_timeouts", + "name": "response_within_timeout", + "status": "pass", + "message": "response within timeout", + "duration_seconds": 3.016901847033296, + "agent_errors": [], + "details": { + "elapsed": 3.0168789690360427 + } + }, + { + "suite": "test_timeouts", + "name": "status_responds_fast", + "status": "pass", + "message": "status fast", + "duration_seconds": 0.32919533003587276, + "agent_errors": [], + "details": { + "elapsed": 0.3291910729603842 + } + }, + { + "suite": "test_timeouts", + "name": "context_responds_fast", + "status": "pass", + "message": "context fast", + "duration_seconds": 0.33155528403585777, + "agent_errors": [], + "details": { + "elapsed": 0.3315501930192113 + } + }, + { + "suite": "test_tools_blossom", + "name": "blossom_list", + "status": "timeout", + "message": "timed out", + "duration_seconds": 73.46405867597787, + "agent_errors": [], + "details": {} + }, + { + "suite": "test_tools_cashu", + "name": "wallet_balance", + "status": "pass", + "message": "ok", + "duration_seconds": 7.047360337048303, + "agent_errors": [], + "details": { + "tool_calls": [ + "cashu_wallet_balance" + ], + "final_response": "Your Cashu wallet balance is:\n\n- **Total**: 0 satoshis\n- **Wallet Status**: Not loaded\n- **Entries**: None\n\nThe wallet doesn't currently have any loaded mints or proofs. To start using the wallet, you" + } + }, + { + "suite": "test_tools_identity", + "name": "get_pubkey", + "status": "timeout", + "message": "timed out", + "duration_seconds": 73.42999882198637, + "agent_errors": [], + "details": {} + }, + { + "suite": "test_tools_identity", + "name": "get_npub", + "status": "pass", + "message": "ok", + "duration_seconds": 6.77238113101339, + "agent_errors": [], + "details": { + "tool_calls": [ + "nostr_npub" + ], + "final_response": "My npub is: **npub1r6chzymdhvd6d9dj79pfdt073zsls82zyrx86xrtpcfehr8qek7q3rkewf**" + } + }, + { + "suite": "test_tools_identity", + "name": "agent_identity", + "status": "timeout", + "message": "timed out", + "duration_seconds": 73.39259405602934, + "agent_errors": [], + "details": {} + }, + { + "suite": "test_tools_identity", + "name": "agent_version", + "status": "pass", + "message": "ok", + "duration_seconds": 6.746906190994196, + "agent_errors": [], + "details": { + "tool_calls": [ + "agent_version" + ], + "final_response": "I'm **Didactyl v0.2.20**, a sovereign AI agent daemon on Nostr. \n\nHere are my details:\n- **Name**: Didactyl\n- **Description**: A sovereign AI agent daemon on Nostr\n- **Version**: v0.2.20 (major: 0, mi" + } + }, + { + "suite": "test_tools_identity", + "name": "admin_identity", + "status": "timeout", + "message": "timed out", + "duration_seconds": 74.42458030197304, + "agent_errors": [], + "details": {} + }, + { + "suite": "test_tools_memory", + "name": "task_list", + "status": "pass", + "message": "ok", + "duration_seconds": 8.36226256104419, + "agent_errors": [], + "details": { + "tool_calls": [ + "task_list" + ], + "final_response": "Here's your current task list:\n\n**Active Tasks:**\n- [ ] **Task 1:** test harness probe (Status: pending)\n - Created: 1775311162\n - Last updated: 1775311162\n\nYou have one pending task in your task me" + } + }, + { + "suite": "test_tools_memory", + "name": "task_manage_add_remove", + "status": "timeout", + "message": "timed out", + "duration_seconds": 84.9701488899882, + "agent_errors": [], + "details": {} + }, + { + "suite": "test_tools_memory", + "name": "memory_save_recall", + "status": "pass", + "message": "ok", + "duration_seconds": 10.4175310000428, + "agent_errors": [], + "details": { + "tool_calls": [ + "memory_save", + "memory_recall" + ], + "final_response": "Perfect! The memory system is working correctly. Here's what happened:\n\n**Memory Save Result:**\n- \u2705 Successfully saved to kind 30078 (encrypted agent config)\n- Event ID: `b4aba2eb45a6b41eec9792fa381cf" + } + }, + { + "suite": "test_tools_nostr", + "name": "nostr_post_kind1", + "status": "timeout", + "message": "timed out", + "duration_seconds": 73.96807993302355, + "agent_errors": [], + "details": {} + }, + { + "suite": "test_tools_nostr", + "name": "nostr_query_recent", + "status": "pass", + "message": "ok", + "duration_seconds": 14.233945493993815, + "agent_errors": [], + "details": { + "tool_calls": [ + "nostr_query" + ], + "final_response": "Great! I found the 3 most recent kind 1 notes. Here they are:\n\n## 3 Most Recent Kind 1 Notes\n\n**1. Relaymonitor Latency Test**\n- **Author:** `0b01aa38c2cc9abfbe4a10d54b182793479fb80da14a91d13be38ea555" + } + }, + { + "suite": "test_tools_nostr", + "name": "nostr_my_events", + "status": "timeout", + "message": "timed out", + "duration_seconds": 74.01618752401555, + "agent_errors": [], + "details": {} + }, + { + "suite": "test_tools_nostr", + "name": "nostr_relay_status", + "status": "pass", + "message": "ok", + "duration_seconds": 6.104476057982538, + "agent_errors": [], + "details": { + "tool_calls": [ + "nostr_relay_status" + ], + "final_response": "Great! Here's the status of my relay connections:\n\n**Overall Status:**\n- **Total Relays:** 2\n- **Connected:** 2/2 \u2705\n\n**Detailed Relay Status:**\n\n1. **wss://relay.damus.io**\n - Status: Connected \u2705\n " + } + }, + { + "suite": "test_tools_nostr", + "name": "nostr_dm_send", + "status": "timeout", + "message": "timed out", + "duration_seconds": 74.01507185597438, + "agent_errors": [], + "details": {} + }, + { + "suite": "test_tools_nostr", + "name": "nostr_encode_npub", + "status": "timeout", + "message": "timed out", + "duration_seconds": 73.42353967600502, + "agent_errors": [], + "details": {} + }, + { + "suite": "test_tools_nostr", + "name": "nostr_profile_get", + "status": "timeout", + "message": "timed out", + "duration_seconds": 74.39149832999101, + "agent_errors": [], + "details": {} + }, + { + "suite": "test_tools_skills", + "name": "skill_list", + "status": "pass", + "message": "ok", + "duration_seconds": 7.244145753968041, + "agent_errors": [], + "details": { + "tool_calls": [ + "skill_list" + ], + "final_response": "I have **1 available skill** that I'm currently using:\n\n**1. identity_and_rules** (Private - Agent-owned, Adopted)\n - **Scope:** Private\n - **Status:** Adopted and Active\n - **Description:** Thi" + } + }, + { + "suite": "test_tools_skills", + "name": "trigger_list", + "status": "timeout", + "message": "timed out", + "duration_seconds": 73.40902369801188, + "agent_errors": [], + "details": {} + }, + { + "suite": "test_tools_skills", + "name": "skill_create_and_remove", + "status": "timeout", + "message": "timed out", + "duration_seconds": 73.94731772097293, + "agent_errors": [], + "details": {} + }, + { + "suite": "test_tools_system", + "name": "tool_list", + "status": "pass", + "message": "ok", + "duration_seconds": 44.292774281988386, + "agent_errors": [], + "details": { + "tool_calls": [ + "tool_list" + ], + "final_response": "Great! Here's a comprehensive summary of all **83 available tools** organized by category:\n\n## **Nostr Core Tools**\n1. **nostr_post** - Publish events (notes, reactions, longform)\n2. **nostr_query** -" + } + }, + { + "suite": "test_tools_system", + "name": "model_get", + "status": "timeout", + "message": "timed out", + "duration_seconds": 73.43525368801784, + "agent_errors": [], + "details": {} + }, + { + "suite": "test_tools_system", + "name": "model_list", + "status": "timeout", + "message": "timed out", + "duration_seconds": 73.39378822001163, + "agent_errors": [ + "[2026-04-04 10:26:46] [ERROR] [llm.c:93] [didactyl] llm http request failed: status=401" + ], + "details": {} + }, + { + "suite": "test_tools_system", + "name": "local_http_fetch", + "status": "pass", + "message": "ok", + "duration_seconds": 11.767529862001538, + "agent_errors": [], + "details": { + "tool_calls": [ + "local_http_fetch" + ], + "final_response": "Perfect! I've successfully fetched https://httpbin.org/get. Here's what was returned:\n\n**Response Details:**\n- **Status Code:** 200 (Success)\n- **Content Type:** application/json\n- **Response Body:**\n" + } + }, + { + "suite": "test_tools_system", + "name": "config_store_recall", + "status": "timeout", + "message": "timed out", + "duration_seconds": 73.9523419579491, + "agent_errors": [], + "details": {} + } + ], + "summary": { + "pass": 27, + "timeout": 16 + } +} \ No newline at end of file diff --git a/tests/results/20260404T140545Z/results.txt b/tests/results/20260404T140545Z/results.txt new file mode 100644 index 0000000..96be462 --- /dev/null +++ b/tests/results/20260404T140545Z/results.txt @@ -0,0 +1,140 @@ +== Didactyl Test Results == +Run: 2026-04-04T14:29:21.419470+00:00 +Agent base URL: http://127.0.0.1:8485 + +Pass: 27 +Fail: 0 +Error: 0 +Timeout: 16 +Skip: 0 +Total: 43 + +[PASS] test_conversation::simple_greeting (4.25s) + greeting response ok + +[PASS] test_conversation::agent_responds_about_itself (27.06s) + self description ok + +[PASS] test_conversation::empty_message_handling (4.67s) + empty message handled + +[TIMEOUT] test_conversation::very_long_message (76.51s) + timed out + +[PASS] test_errors::invalid_json_body (0.33s) + invalid json rejected + +[PASS] test_errors::missing_message_field (0.33s) + missing message rejected + +[PASS] test_errors::unknown_endpoint (0.33s) + unknown endpoint 404 + +[PASS] test_errors::webhook_nonexistent_dtag (0.33s) + nonexistent d_tag 404 + +[PASS] test_health::status_returns_200 (0.33s) + status success + +[PASS] test_health::status_has_fields (0.33s) + required fields present + +[PASS] test_health::context_current_returns_messages (0.33s) + context current ok + +[PASS] test_health::context_parts_has_system_prompt (0.33s) + context parts ok + +[PASS] test_restart::clean_restart (3.85s) + restart succeeded + +[PASS] test_restart::status_after_restart (4.15s) + status stable after restart + +[PASS] test_restart::conversation_after_restart (7.45s) + conversation works post-restart + +[PASS] test_timeouts::response_within_timeout (3.02s) + response within timeout + +[PASS] test_timeouts::status_responds_fast (0.33s) + status fast + +[PASS] test_timeouts::context_responds_fast (0.33s) + context fast + +[TIMEOUT] test_tools_blossom::blossom_list (73.46s) + timed out + +[PASS] test_tools_cashu::wallet_balance (7.05s) + ok + +[TIMEOUT] test_tools_identity::get_pubkey (73.43s) + timed out + +[PASS] test_tools_identity::get_npub (6.77s) + ok + +[TIMEOUT] test_tools_identity::agent_identity (73.39s) + timed out + +[PASS] test_tools_identity::agent_version (6.75s) + ok + +[TIMEOUT] test_tools_identity::admin_identity (74.42s) + timed out + +[PASS] test_tools_memory::task_list (8.36s) + ok + +[TIMEOUT] test_tools_memory::task_manage_add_remove (84.97s) + timed out + +[PASS] test_tools_memory::memory_save_recall (10.42s) + ok + +[TIMEOUT] test_tools_nostr::nostr_post_kind1 (73.97s) + timed out + +[PASS] test_tools_nostr::nostr_query_recent (14.23s) + ok + +[TIMEOUT] test_tools_nostr::nostr_my_events (74.02s) + timed out + +[PASS] test_tools_nostr::nostr_relay_status (6.10s) + ok + +[TIMEOUT] test_tools_nostr::nostr_dm_send (74.02s) + timed out + +[TIMEOUT] test_tools_nostr::nostr_encode_npub (73.42s) + timed out + +[TIMEOUT] test_tools_nostr::nostr_profile_get (74.39s) + timed out + +[PASS] test_tools_skills::skill_list (7.24s) + ok + +[TIMEOUT] test_tools_skills::trigger_list (73.41s) + timed out + +[TIMEOUT] test_tools_skills::skill_create_and_remove (73.95s) + timed out + +[PASS] test_tools_system::tool_list (44.29s) + ok + +[TIMEOUT] test_tools_system::model_get (73.44s) + timed out + +[TIMEOUT] test_tools_system::model_list (73.39s) + timed out + Agent errors: 1 + +[PASS] test_tools_system::local_http_fetch (11.77s) + ok + +[TIMEOUT] test_tools_system::config_store_recall (73.95s) + timed out diff --git a/tests/results/20260404T140545Z/runtime_test_genesis.jsonc b/tests/results/20260404T140545Z/runtime_test_genesis.jsonc new file mode 100644 index 0000000..8aa613f --- /dev/null +++ b/tests/results/20260404T140545Z/runtime_test_genesis.jsonc @@ -0,0 +1,53 @@ +{ + // TEST CONFIGURATION + // Use disposable keys/accounts only. + "key": { + "nsec": "d3011e59954403fb84729ab0e9fb239ba0f0238b3f353dfb5c08a32f4b3d21a1" + }, + "admin": { + "pubkey": "254d7a7f68b4443f5430564eb3a726f09aea57d27e75ab2b8ef57176f9ca3dac" + }, + "dm_protocol": "nip04", + "llm": { + "provider": "https://api.ppq.ai", + "api_key": "sk-SBI2Pga0J6n8jpPorHbRHC", + "model": "claude-haiku-4.5", + "base_url": "https://api.ppq.ai", + "max_tokens": 10000, + "temperature": 0.7 + }, + "api": { + "enabled": true, + "port": 8485, + "bind_address": "127.0.0.1" + }, + "startup_events": [ + { + "kind": 0, + "content_fields": { + "name": "Didactyl Test Agent", + "about": "Automated test instance" + }, + "tags": [] + }, + { + "kind": 10002, + "content": "", + "tags": [ + ["r", "wss://relay.damus.io"], + ["r", "wss://relay.primal.net"] + ] + }, + { + "kind": 31124, + "content": "# Test Agent\n\nYou are a test agent. Respond to requests and use tools when needed.", + "tags": [ + ["d", "identity_and_rules"], + ["app", "didactyl"], + ["scope", "private"], + ["trigger", "dm"], + ["filter", "{\"from\":\"admin\"}"] + ] + } + ] +}