8.7 KiB
DM History Context Cleanup, Skill Rename & Startup Events Unification
Problems
1. DM History as JSON in System Prompt
The {{nostr_dm_history}} template variable resolves to a raw JSON array string pasted into the system prompt. The LLM receives conversation history as JSON-within-text, wasting tokens and forcing it to parse structured data embedded in prose.
2. Misleading Skill Name
The skill default_admin_dm is really an identity and rules skill. Rename to identity_and_rules.
3. default_skill Config Section Is Redundant
The default_skill section in genesis.jsonc is a special-case config path that converts a skill into a startup event and auto-adopts it. This is unnecessary — skills should just be startup events like everything else. The program should treat all startup events equally and halt if any fail to publish.
Solution
Three coordinated changes:
Part A: DM History Text Format
Add a format parameter to the nostr_dm_history tool. Create a separate dm_history skill.
Part B: Skill Rename
Rename default_admin_dm → identity_and_rules everywhere.
Part C: Eliminate default_skill Config Section
Move skills into startup_events. Auto-adopt kind 31123/31124 startup events. Make startup event publishing mandatory (halt on failure).
Implementation Steps
Part A: DM History Text Format
A1. Add format parameter to execute_nostr_dm_history()
File: src/tools/tool_agent.c — line 265
Add a format parameter: "json" (default, current behavior) or "text".
When format is "text", after the filtering loop (lines 320-360), iterate the filtered array and build plain text:
User: Good morning
Assistant: Good morning! ☀️ How can I help you today?
User: What is the capital of England?
Assistant: London! 🇬🇧
Existing include_current=false default already deduplicates the current live message (line 339).
A2. Add format to the tool schema
File: src/tools/tools_schema.c — around line 1493
Add format property with description. Update tool description to mention the parameter.
Part B: Skill Rename
B1. Update src/default_events.h
DIDACTYL_DEFAULT_SKILL_D_TAG:"default_admin_dm"→"identity_and_rules"DIDACTYL_DEFAULT_SKILL_TAGS_JSON: update d-tag and descriptionDIDACTYL_DEFAULT_SKILL_TEMPLATE: keep as-is (already clean, no{{nostr_dm_history}})
B2. Update tool description examples in src/tools/tools_schema.c
- Line 854:
skill_getexample - Line 883:
skill_viewexample - Line 911:
skill_setexample
B3. Update docs/GENESIS.md
- Line 33 and 124:
default_admin_dm→identity_and_rules
Part C: Eliminate default_skill Config Section
C1. Auto-adopt kind 31123/31124 startup events
File: src/config.c
Replace config_ensure_default_skill_startup_events() with a new function config_ensure_startup_skill_adoption() that:
- Scans all startup events for kind 31123 and 31124
- For each skill found, extracts its
d_tagfrom tags - Builds the skill address (
kind:pubkey:d_tag) - Ensures a kind 10123 adoption list startup event exists with all skill addresses
- If no 10123 event exists, creates one
This replaces the single-skill logic with a generic "adopt all startup skills" approach.
C2. Remove default_skill config parsing
File: src/config.c
- Remove
parse_default_skill_config()function (lines 733-816) - Remove the call at line 1369
- Keep backward compatibility: if
default_skillsection exists in genesis.jsonc, convert it to a startup event during parsing (migration path) — OR just remove support and let users update their genesis.jsonc
C3. Remove default_skill_config_t from config struct
File: src/config.h
- Remove
default_skill_config_ttypedef (lines 67-72) - Remove
default_skillfield fromdidactyl_config_t(line 134)
C4. Update src/main.c
- Line 1060: Replace
config_ensure_default_skill_startup_events()call withconfig_ensure_startup_skill_adoption()
C5. Make startup event publishing mandatory
File: src/nostr_handler.c
In the startup publish flow (around line 3150), after all relays have been tried:
- Check that each startup event was published to at least one relay
- If any event failed on all relays, log an error and return failure
- The caller in
main.cshould halt on this failure
C6. Update setup wizard
File: src/setup_wizard.c
configure_default_skill_for_agent()(line 498): Instead of populatingcfg->default_skill, append the identity skill as a startup event directly- Add the
dm_historyskill as a second startup event - The auto-adoption logic (C1) handles the adoption list
C7. Update default_events.h
Add the dm_history skill template:
#define DIDACTYL_DEFAULT_DM_HISTORY_SKILL_D_TAG "dm_history"
static const char* DIDACTYL_DEFAULT_DM_HISTORY_SKILL_TEMPLATE =
"## Recent Conversation\n\n"
"{{nostr_dm_history({\"format\":\"text\",\"limit\":12})}}";
Part D: Genesis Config Updates
D1. Update genesis.jsonc.example
Remove default_skill section. Add skills as startup events:
"startup_events": [
{
"kind": 0,
"content_fields": {
"name": "Didactyl",
"about": "I am a Didactyl agent living on Nostr"
},
"tags": []
},
{
"kind": 3,
"content": "",
"tags": [["p", "ADMIN_HEX_PUBKEY"]]
},
{
"kind": 10002,
"content": "",
"tags": [
["r", "wss://relay.damus.io"],
["r", "wss://relay.primal.net"]
]
},
{
"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": [
["d", "identity_and_rules"],
["app", "didactyl"],
["scope", "private"],
["description", "Agent identity and behavioral rules"],
["trigger", "dm"],
["filter", "{\"from\":\"admin\"}"]
]
},
{
"kind": 31124,
"content": "## Recent Conversation\n\n{{nostr_dm_history({\"format\":\"text\",\"limit\":12})}}",
"tags": [
["d", "dm_history"],
["app", "didactyl"],
["scope", "private"],
["description", "DM conversation history for context continuity"],
["trigger", "dm"],
["filter", "{\"from\":\"admin\"}"]
]
}
]
D2. Update live genesis.jsonc
Same changes as D1 but for the actual deployment config.
Result
Before
[
{"role": "system", "content": "You are {...}\n\nYour npub: npub1...\n\n## Rules\n...\n[{\"role\":\"user\",\"content\":\"Good morning\",...}]"},
{"role": "user", "content": "And Fiji?"}
]
After
[
{"role": "system", "content": "# Didactyl Agent\n\nYou are {...}\n\nYour npub: npub1...\n\n## Rules\n...\n\n---\n\n## Recent Conversation\n\nUser: Good morning\nAssistant: Good morning! ☀️ How can I help you today?\nUser: What is the capital of England?\nAssistant: London! 🇬🇧\nUser: How about France?\nAssistant: Paris! 🇫🇷"},
{"role": "user", "content": "And Fiji?"}
]
Files Changed
| File | Change |
|---|---|
src/tools/tool_agent.c |
Add format parameter to execute_nostr_dm_history() |
src/tools/tools_schema.c |
Add format to schema; update example d-tags |
src/default_events.h |
Rename d-tag; add dm_history skill template |
src/config.h |
Remove default_skill_config_t and field |
src/config.c |
Remove parse_default_skill_config(); replace config_ensure_default_skill_startup_events() with config_ensure_startup_skill_adoption() |
src/main.c |
Update call to new adoption function |
src/nostr_handler.c |
Make startup event publishing mandatory (halt on total failure) |
src/setup_wizard.c |
Put skills in startup_events; add dm_history |
genesis.jsonc.example |
Remove default_skill; add skills as startup events |
genesis.jsonc |
Same as example |
docs/GENESIS.md |
Update references |
Migration Note
For backward compatibility, consider keeping parse_default_skill_config() as a migration shim that converts default_skill into a startup event with a deprecation warning. This way existing genesis.jsonc files with default_skill still work but users are prompted to update.