Files
didactyl/plans/context_architecture.md

5.9 KiB

Didactyl Context Architecture Plan

Problem Statement

The agent's context assembly is a hardcoded sequence of C function calls in agent_on_message(). This creates several issues:

  1. Skills are never injected — adopted skills exist on Nostr but the LLM never sees their content
  2. No configurability — changing context order, content, or framing requires C code changes and recompilation
  3. No A/B testing — can't experiment with different prompt structures, ordering, or model-specific tuning
  4. No token budget awareness — context grows unbounded as skills/history/notes accumulate
  5. Model-agnostic — different models respond differently to the same prompt structure; no way to tune per-model

Current Context Pipeline

agent_on_message() builds messages array:
  1. system: g_system_context (kind 31120 "soul" content)
  2. system: admin identity (pubkey + kind 0 profile + kind 10002 relays)
  3. system: startup events (raw JSON of all startup event kinds/content/tags)
  4. user/assistant: recent DM history (last 12 turns)
  5. system: admin kind 1 notes (recent public posts)
  6. user: the actual incoming message

Skills are completely absent. The LLM has no knowledge of adopted skill instructions.

Proposed Architecture: Context Pipeline with Configurable Slots

Core Idea

Replace the hardcoded function chain with a configurable context pipeline defined in config.json. Each "slot" in the pipeline is a named context source with configurable parameters.

Context Slot Types

Slot Type Source Description
soul Kind 31120 startup event Agent personality and behavioral rules
identity Config + relay queries Agent's own pubkey, admin pubkey, admin profile
startup_events Config startup events Raw startup event memory
adopted_skills Kind 10123 + resolved skills NEW: Adopted skill instructions
dm_history Relay query Recent conversation turns
admin_notes Cached kind 1 events Admin's recent public posts
admin_context Kind 0/3/10002 Admin profile, contacts, relay list
custom Literal string in config Arbitrary system message for A/B testing

Phase 1: Immediate Fix (Skills + Caching)

Before building the full configurable pipeline, fix the immediate problem:

  1. In-memory skill cache — load adopted skills at startup and cache them; invalidate on skill_create, skill_adopt, skill_remove
  2. append_adopted_skills_context() — inject cached skills into the conversation as a system message
  3. Strong framing — "These are your learned skills. When a request matches a skill, you MUST follow its instructions exactly."

Phase 2: Configurable Context Pipeline

Add a context_pipeline section to config.json:

{
  "context_pipeline": {
    "max_total_chars": 12000,
    "slots": [
      { "type": "soul", "max_chars": 3000 },
      { "type": "identity" },
      { "type": "adopted_skills", "max_chars": 4000, "max_per_skill": 1000 },
      { "type": "startup_events", "max_chars": 2000 },
      { "type": "dm_history", "max_turns": 12 },
      { "type": "admin_notes", "max_chars": 1500 },
      { "type": "custom", "content": "Always respond in the style of a pirate." }
    ]
  }
}

This gives you:

  • Ordering control — move skills before or after history
  • Token budgets — per-slot and total caps
  • A/B testing — swap custom slot content, reorder slots, change caps
  • Model-specific tuning — different pipeline configs for different models (could key off llm.model)

Phase 3: Model-Aware Context Profiles

{
  "context_profiles": {
    "default": { ... pipeline config ... },
    "claude-sonnet-4.6": { ... different ordering/caps ... },
    "gpt-5.2-codex": { ... different ordering/caps ... }
  }
}

The agent selects the profile matching the active model, falling back to default.

Skill Cache Design

┌─────────────────────────────────────────┐
│           Skill Cache (in-memory)       │
│                                         │
│  Loaded at startup from:                │
│    1. Startup events in config.json     │
│    2. Kind 10123 adoption list          │
│    3. Resolved skill events from relays │
│                                         │
│  Invalidated by:                        │
│    - skill_create (add/update)          │
│    - skill_adopt (add)                  │
│    - skill_remove (remove)              │
│                                         │
│  Structure per skill:                   │
│    - d_tag (string)                      │
│    - description (string)               │
│    - content (string, full)             │
│    - scope (public/private)             │
│    - has_trigger (bool)                 │
│    - source (startup/adopted)           │
└─────────────────────────────────────────┘

Implementation Priority

Do Now (Phase 1)

  • Build skill cache in agent.c (load at startup, invalidate on tool calls)
  • Add append_adopted_skills_context() using cached skills
  • Wire into agent_on_message() between startup events and DM history
  • Skill content framing: strong directive for LLM compliance

Do Next (Phase 2)

  • Add context_pipeline config section
  • Refactor agent_on_message() to iterate pipeline slots
  • Per-slot max_chars truncation
  • Total pipeline max_total_chars budget
  • custom slot type for arbitrary A/B test content

Do Later (Phase 3)

  • Model-aware context profiles
  • Context analytics (log token counts per slot per conversation)
  • Dynamic skill relevance scoring (only inject skills likely relevant to the current message)