5.9 KiB
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:
- Skills are never injected — adopted skills exist on Nostr but the LLM never sees their content
- No configurability — changing context order, content, or framing requires C code changes and recompilation
- No A/B testing — can't experiment with different prompt structures, ordering, or model-specific tuning
- No token budget awareness — context grows unbounded as skills/history/notes accumulate
- 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:
- In-memory skill cache — load adopted skills at startup and cache them; invalidate on
skill_create,skill_adopt,skill_remove append_adopted_skills_context()— inject cached skills into the conversation as a system message- 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
customslot 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: │
│ - slug (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_pipelineconfig section - Refactor
agent_on_message()to iterate pipeline slots - Per-slot
max_charstruncation - Total pipeline
max_total_charsbudget customslot 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)