8.4 KiB
Context Optimization Plan
Analysis of context.log.md (13,609 bytes / ~3,402 tokens across 20 sections) and context_template.md.
Issues Found
1. Massive Duplication in startup_events Section
The startup_events section (line 74-80 in the log) dumps the entire config.startup_events array as raw JSON — including the full soul/system prompt (kind 31120) which is already sent verbatim as the system_prompt section. The soul text appears twice in every request.
Estimated waste: ~1,500-2,000 tokens per request.
Fix: Filter out kind 31120 (soul) from the startup_events JSON blob, or better yet, only include kinds the model actually needs to reference (kind 0 profile, kind 10002 relay list, kind 3 contacts). The soul is already the system prompt — repeating it as data is pure waste.
2. Duplicate Startup Messages in DM History
The DM history contains four separate Didactyl has started up and is online (version v0.0.29, connected relays: 4/4). assistant messages (lines 123-127, 130-134, 144-148, 222-226, 245-249). These are startup announcement DMs that got stored as separate events. The model sees the same boilerplate startup message repeated across the conversation.
Estimated waste: ~200-300 tokens.
Fix: Deduplicate consecutive identical assistant messages in the DM history builder, or filter out startup announcement messages (they carry no conversational value).
3. Skills Rendered as Raw JSON Instead of Structured Text
Skill instructions at lines 97-118 are dumped as raw JSON objects. Models parse structured natural language far more reliably than nested JSON. The content_fields serialization format wastes tokens on JSON syntax characters and key quoting.
Estimated waste: ~100-200 tokens of JSON overhead per skill, plus reduced comprehension quality.
Fix: When serializing content_fields-based skills for context, flatten them into readable text:
Skill: long_form_note
Description: How to publish a NIP-23 long-form article (kind 30023)
NIP: NIP-23
Event Kind: 30023
Format: The content field must be markdown text...
Required Tags:
- d: Addressable identifier slug...
- title: Human-readable article title
- published_at: Unix timestamp as string...
Procedure:
1. Determine title and d tag...
2. Draft markdown body content...
4. Empty Sections Still Sent
The admin_relay_list section (line 65-71) has no data — the JSON value is empty. Sending an empty section wastes tokens on the header/framing with no informational value.
Estimated waste: ~30-40 tokens.
Fix: Skip sections where the resolved variable is empty or whitespace-only.
5. Admin Identity Could Be Merged with Admin Profile
The admin_identity section (line 47-53) sends just the hex pubkey, then admin_profile (line 56-62) sends the full kind 0 JSON which implicitly identifies the admin. These could be a single section.
Estimated savings: ~40-50 tokens of framing overhead.
6. admin_notes Placement Breaks Conversation Flow
In the template, admin_notes is placed after dm_history (expand). In the actual log, this means a system message appears sandwiched between DM history messages (line 252, between assistant messages and the final user message at line 274). This breaks the natural conversation flow and may confuse the model about message ordering.
Fix: Move admin_notes before dm_history in the template so all system context is grouped together before the conversation begins.
7. No Agent Self-Identity Section
The model knows it is Didactyl from the system prompt, but there is no section telling it its own pubkey/npub. The admin pubkey is provided but the agent's own key is not in the context (it is only available via the nostr_pubkey tool). Adding a small self-identity section would let the model reference its own key without a tool call.
Estimated cost: ~20-30 tokens.
Priority Summary
| Priority | Issue | Token Savings | Complexity |
|---|---|---|---|
| P0 | Soul duplicated in startup_events | ~1,500-2,000 | Low — filter kind 31120 from startup blob |
| P1 | Duplicate startup DMs in history | ~200-300 | Medium — dedup logic in history builder |
| P1 | Skills as raw JSON | ~100-200 + quality | Medium — flatten content_fields to text |
| P2 | Empty sections still sent | ~30-40 | Low — skip empty resolved vars |
| P2 | admin_notes after dm_history | 0 (quality) | Low — reorder template |
| P3 | Merge admin_identity + admin_profile | ~40-50 | Low — template change |
| P3 | Add agent self-identity section | -20-30 (adds) | Low — new template var |
Bug: Kind 10002 Relay List Is Always Empty
At nostr_handler.c:705 the kind 10002 handler stores content->valuestring, but NIP-65 relay list events have an empty content field — the relay URLs live in the tags as ["r", "wss://relay.example.com"] entries. So g_admin_kind10002_json is always "".
Fix: Parse the "r" tags from the kind 10002 event and serialize them as a JSON array of relay URL strings (or plain-text list).
Sender Verification Status
The tier enum (DIDACTYL_SENDER_ADMIN, DIDACTYL_SENDER_WOT, DIDACTYL_SENDER_STRANGER) is already resolved before agent_on_message() is called, but it is not passed into the context builder. The model has no way to know whether the current message was cryptographically verified as coming from the administrator vs. a web-of-trust contact.
Fix: Pass the sender tier into the context builder and expose it as a template variable (e.g. {{sender_verification}}) that resolves to text like:
"This message has been cryptographically verified as coming from your administrator.""This message is from a web-of-trust contact (not the administrator)."
Proposed Optimized Template
- section: agent_identity
role: system
content: |
Agent Identity
Your pubkey (hex): {{agent_pubkey}}
- section: sender_context
role: system
content: |
{{sender_verification}}
- section: admin_context
role: system
content: |
Administrator Context
Pubkey (hex): {{admin_pubkey}}
{{admin_profile_plain}}
{{admin_relay_list_plain}}
- section: startup_events
role: system
skip_if_empty: true
content: |
Startup Events Memory
{{startup_events_json}}
- section: adopted_skills
role: system
skip_if_empty: true
content: |
{{adopted_skills_content}}
- section: admin_notes
role: system
skip_if_empty: true
content: |
Administrator Recent Notes (source: nostr kind 1)
{{admin_notes_content}}
- section: dm_history
role: expand
limit: 12
Key changes from current template:
- No markdown headers in system sections — plain English throughout
- Merged admin section combines identity, profile, and relay list
{{admin_profile_plain}}— new variable that renders kind 0 JSON as readable text (e.g.Name: WSB, About: ...){{admin_relay_list_plain}}— new variable that renders relay URLs from tags as a plain list{{sender_verification}}— new variable stating cryptographic verification statusadmin_notesmoved beforedm_historyso all system context is grouped before conversationskip_if_emptyprevents sending empty sections
Implementation Steps
- Fix kind 10002 relay list bug — extract relay URLs from tags instead of content in
nostr_handler.c:705 - Filter kind 31120 from
startup_events_jsonvariable resolver inagent.c - Deduplicate consecutive identical messages in DM history builder
- Flatten
content_fieldsJSON skills into readable text format - Add
skip_if_emptysupport to template engine (skip section when resolved content is blank) - Reorder template — move
admin_notesbeforedm_history - Add
agent_pubkeytemplate variable and agent identity section - Merge admin sections — combine identity + profile (plain English) + relay list into one section
- Add
admin_profile_plainvariable — parse kind 0 JSON into readable text - Add
admin_relay_list_plainvariable — parse kind 10002 tags into relay URL list - Pass sender tier to context builder and add
sender_verificationtemplate variable - Remove markdown formatting from system section content — use plain English