Files
didactyl/plans/v0.2.0_migration.md

8.8 KiB

v0.2.0 Migration Plan

Agent Event Cleanup

Delete all old skill/soul events from each agent's relay set before restarting with the new genesis file.


Genesis File Changes

Kind 0 profile event (new)

Add a startup event for the agent's profile:

{
  "kind": 0,
  "content_fields": {
    "name": "Didactyl",
    "about": "I am a Didactyl agent living on Nostr"
  },
  "tags": []
}

Kind 3 contacts event (new)

Add a startup event following the admin:

{
  "kind": 3,
  "content": "",
  "tags": [
    ["p", "ADMIN_HEX_PUBKEY"]
  ]
}

The admin hex pubkey is derived from the admin.pubkey field at startup.

Default skill rename and trigger tags

Before:

"default_skill": {
  "d_tag": "didactyl-default",
  "kind": 31124,
  "content": "# Didactyl Agent\n\n...\n\n---template---\n\n- section: admin_identity\n  ...",
  "tags": [["app", "didactyl"], ["scope", "private"]]
}

After:

"default_skill": {
  "d_tag": "default_admin_dm",
  "kind": 31124,
  "content": "# Didactyl Agent\n\nYou are {{my_kind0_profile}}\n\nYour npub: {{my_npub}}\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",
  "tags": [
    ["app", "didactyl"],
    ["scope", "private"],
    ["description", "Default admin DM handler"],
    ["trigger", "dm"],
    ["filter", "{\"from\":\"admin\"}"]
  ]
}

Key changes:

  • d_tag renamed to default_admin_dm
  • ---template--- DSL removed entirely
  • Content uses {{my_kind0_profile}} and {{my_npub}} for identity injection
  • Trigger/filter tags added so the new trigger-scoped system fires it on admin DMs
  • Description tag added

Source Code Changes

Phase 1: Remove ---template--- DSL

src/prompt_template.h

  • Remove PROMPT_TEMPLATE_MARKER, PROMPT_TEMPLATE_MAX_SECTIONS, PROMPT_TEMPLATE_MAX_NAME_LEN, PROMPT_TEMPLATE_MAX_ROLE_LEN
  • Remove prompt_template_section_t struct
  • Remove prompt_template_t struct
  • Remove prompt_template_emit_hook_fn typedef
  • Remove prompt_template_parse() declaration
  • Remove prompt_template_build_messages() declaration
  • Remove prompt_template_free() declaration
  • Keep the file if resolve_inline_variables_local() needs a public header, otherwise fold into agent.c

src/prompt_template.c

  • Remove prompt_template_parse() and all section-parsing code (lines 231-502)
  • Remove prompt_template_build_messages() (lines 551-639)
  • Remove prompt_template_free() (lines 641-667)
  • Remove section-related helpers: init_section_defaults(), parse_int_or_zero(), extract_tool_result_content(), append_message_object()
  • Keep resolve_inline_variables_local() — it handles {{...}} variable resolution used by build_context_from_triggers()
  • Keep map_variable_tool_name() — maps variable names to tool names
  • Keep string helpers: dup_range(), ltrim_inplace(), rtrim_inplace(), append_text()

src/agent.c

  • Remove g_prompt_template static variable (line 28)
  • Remove g_has_prompt_template static variable (line 29)
  • Remove template_emit_hook() callback
  • Remove the prompt_template_parse() call in agent_init() (line 2127-2128)
  • Remove the prompt_template_free() calls in agent_init() and agent_cleanup()
  • Remove the g_has_prompt_template branch in agent_build_admin_messages_json() (lines 2334-2035) — replace with trigger-composed context path

Phase 2: Remove g_system_context

src/agent.c

  • Remove g_system_context global (line 24)
  • Remove system_context parameter from agent_init()
  • Remove strdup(system_context) and all g_system_context references
  • Remove g_system_context null checks from agent_on_trigger(), agent_on_message(), agent_build_admin_messages_json()
  • In agent_cleanup(): remove free(g_system_context)

src/agent.h

  • Update agent_init() signature: int agent_init(didactyl_config_t* config);

src/nostr_handler.c

  • Remove g_system_context global (line 28)
  • Remove g_system_context initialization in nostr_handler_reconcile_startup_events() (lines 3124-3157)
  • Remove nostr_handler_get_system_context() function (lines 3289-3291)
  • Remove seed_default_skill_into_cache() if it only serves g_system_context

src/nostr_handler.h

  • Remove nostr_handler_get_system_context() declaration

src/main.c

  • Remove system_context variable (line 1261)
  • Remove fallback logic (lines 1262-1270)
  • Call agent_init(&cfg) without system_context parameter

Phase 3: Rename default skill constants

src/default_events.h

  • DIDACTYL_DEFAULT_SKILL_D_TAG: "didactyl-default""default_admin_dm"
  • DIDACTYL_DEFAULT_SKILL_TAGS_JSON: add trigger/filter/description tags
  • DIDACTYL_DEFAULT_SKILL_TEMPLATE: remove ---template--- section, replace with lean markdown using {{my_kind0_profile}} and {{my_npub}}

Phase 4: Update agent_build_admin_messages_json()

This function is used by the HTTP API. Currently has two paths (template DSL and legacy hardcoded). Replace both with a single path using build_context_from_triggers() — same as agent_on_message() now uses.

Phase 5: Delete legacy files

File Action
config.jsonc.example Delete
context_template.md Delete
context_template.recovered.md Delete

Phase 6: Update genesis files

genesis.jsonc

  • Add kind 0 startup event with name and about
  • Rename default_skill.d_tag to default_admin_dm
  • Add trigger/filter/description tags to default_skill.tags
  • Replace content with lean markdown (no ---template---)

genesis.jsonc.example

  • Same changes as genesis.jsonc
  • Add kind 0 and kind 3 startup events

Phase 7: Update documentation

README.md

  • Remove kind 31120 from Didactyl Kinds table
  • Remove soul references
  • Update architecture description

docs/GENESIS.md

  • Remove config.jsonc migration references
  • Update default_skill section to show default_admin_dm
  • Document kind 0 and kind 3 startup events

docs/API.md

  • Remove /api/events/soul endpoint
  • Remove config.jsonc references

docs/SKILLS.md, docs/CONTEXT.md, docs/TOOLS.md

  • Verify no soul/31120/config.jsonc references remain

Variable Resolution for Default Skill

The default admin DM skill content uses:

  • {{my_npub}} — resolves via map_variable_tool_name()nostr_npub tool → returns agent npub
  • {{my_kind0_profile}} — resolves via map_variable_tool_name()my_kind0_profile tool → returns agent kind 0 profile context

These already work in resolve_inline_variables_local() because map_variable_tool_name() returns NULL for unknown names, and the skill lookup callback handles the rest. But my_npub and my_kind0_profile are not in the current map_variable_tool_name() mapping — they need to be added, OR the function needs to pass through unknown names as tool names (which it currently does NOT do after the Phase 1a change that made it return NULL for unknowns).

Fix needed: In map_variable_tool_name(), return the variable name itself as a tool name when it doesn't match a known alias. This way {{my_npub}} calls the my_npub tool directly.

Current (after Phase 1a change):

return NULL;  // unknown → try skill lookup

Should be:

return var_name;  // unknown → try as tool name first, then skill lookup

Wait — this was the ORIGINAL behavior before Phase 1a changed it. The Phase 1a change made map_variable_tool_name() return NULL for unknowns so the skill lookup callback would fire. But now we need BOTH: try as tool name first, then skill lookup.

Resolution: Restore return var_name; in map_variable_tool_name(). The skill lookup callback in resolve_inline_variables_local() already only fires when replacement_owned is NULL after the tool attempt. So the flow is:

  1. Check known aliases (admin_profile → nostr_admin_profile, etc.)
  2. Try the variable name as a direct tool name
  3. If tool returns nothing useful, try skill lookup callback
  4. If skill lookup returns nothing, resolve to empty string

This is exactly the resolution order specified in SKILLS.md.


Execution Order

  1. Phase 3 — rename constants (safe, no behavioral change)
  2. Phase 5 — delete legacy files
  3. Phase 6 — update genesis files
  4. Phase 1 — remove template DSL
  5. Phase 2 — remove g_system_context
  6. Phase 4 — update agent_build_admin_messages_json
  7. Phase 7 — update docs
  8. Build and test
  9. increment_and_push.sh -p for v0.2.1