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