# 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`:

```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

```json
{
  "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)
