Files
didactyl/plans/context_optimization.md

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 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
  2. Filter kind 31120 from startup_events_json variable resolver in 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