# Didactyl — LLM Context

See also: [SKILLS.md](SKILLS.md) · [TOOLS.md](TOOLS.md)

## What Is Context?

Every time Didactyl talks to an LLM, it sends a **context** — the complete package of information the model needs to reason and respond.

Context is not just a prompt string; it is the full request payload:

1. **Messages** — system/user/assistant/tool history
2. **Tool schemas** — JSON descriptions of callable tools
3. **Model parameters** — model, temperature, max tokens, seed, etc.

The context window is composed of **skills** — blocks of markdown instructions stacked together. See [SKILLS.md](SKILLS.md) for the canonical skill specification.

---

## OpenAI-Compatible Chat Format

Didactyl uses OpenAI-compatible chat completions.

```json
{
  "model": "claude-opus-4.6",
  "messages": [
    {"role": "system", "content": "..."},
    {"role": "user", "content": "..."}
  ],
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "nostr_post",
        "description": "Publish a Nostr event",
        "parameters": {"type":"object"}
      }
    }
  ],
  "temperature": 0.7,
  "max_tokens": 512
}
```

### Message Roles

| Role | Purpose |
|------|---------|
| `system` | Instructions and injected context (from skills) |
| `user` | Input message or trigger payload |
| `assistant` | Model responses / tool call envelopes |
| `tool` | Tool execution results fed back to model |

---

## Context Assembly — Two-Layer Model

Context is assembled using a two-layer model driven by triggers and the adoption list.

### Layer 1: Triggered Skills

When a trigger event occurs (DM, cron, subscription, webhook, chain), Didactyl walks the `10123` adoption list and finds all skills whose trigger matches the current event. These skills form layer 1 of the context window, in adoption-list order.

Only triggered skills can be in layer 1 — the trigger system is what puts them there.

### Layer 2: Referenced Skills

Within each layer 1 skill, `{{skill_d_tag}}` template variables resolve to adopted skills' content. These are embedded inline — the same way tool-based template variables are resolved.

Non-triggered skills (skills with no trigger tags) can only enter the context window via layer 2 references.

### Assembly Steps

```
Trigger event occurs (DM, cron, subscription, webhook, chain)
  │
  ├─ Walk adoption list (10123)
  │    │
  │    ├─ Skill has trigger matching this event?
  │    │    ├─ YES → add to context (layer 1)
  │    │    │    └─ Resolve {{...}} references (layer 2)
  │    │    │         ├─ Known tool? → execute tool, insert result
  │    │    │         ├─ Adopted skill d-tag? → insert skill content
  │    │    │         └─ Unknown? → resolve to empty
  │    │    │
  │    │    └─ NO → skip (not in this context)
  │    │
  │    └─ Continue to next skill in list
  │
  ├─ Append triggering event payload
  │    └─ For DM triggers: always append raw message content
  │
  ├─ Attach tool schemas (filtered by skill requires_tool tags)
  │
  ├─ Apply execution parameters (llm, temperature, max_tokens)
  │    └─ Walk LLM fallback chain until usable model found
  │
  └─ Send to LLM
```

### Visualization

```
╔══════════════════════════════════════════╗
║           CONTEXT WINDOW                 ║
║                                          ║
║  ┌────────────────────────────────────┐  ║
║  │ Layer 1: personality (dm trigger)  │  ║
║  │                                    │  ║
║  │  ┌──────────────────────────────┐  │  ║
║  │  │ Layer 2: {{identity}}        │  │  ║
║  │  │ You are Didactyl. npub1...   │  │  ║
║  │  └──────────────────────────────┘  │  ║
║  │                                    │  ║
║  │  You speak concisely and directly. │  ║
║  └────────────────────────────────────┘  ║
║  ┌────────────────────────────────────┐  ║
║  │ Layer 1: chat (dm trigger)         │  ║
║  │                                    │  ║
║  │  Respond helpfully to the admin.   │  ║
║  │  Use tools as needed.              │  ║
║  │                                    │  ║
║  │  tools: [nostr_query, nostr_dm]    │  ║
║  └────────────────────────────────────┘  ║
║  ┌────────────────────────────────────┐  ║
║  │ DM content (always last)           │  ║
║  │                                    │  ║
║  │ "Who mentioned me today?"          │  ║
║  └────────────────────────────────────┘  ║
║                                          ║
╚══════════════════════════════════════════╝
```

### Why Order Matters

- Earlier skills in the adoption list appear first in the context window.
- Earlier instructions generally set broader tone/policy.
- Later instructions can narrow/specialize behavior.
- If instructions conflict, prompt-order effects apply.

---

## Context Parts

| Part | Source | Description |
|------|--------|-------------|
| Layer 1 skills | Triggered skills from adoption list | Skills whose trigger matches the current event, in adoption-list order |
| Layer 2 skills | `{{skill_d_tag}}` references | Adopted skills embedded inside layer 1 skills |
| Resolved variables | Tool outputs | Runtime data inserted into templates via `{{...}}` |
| Triggering event | DM content / event payload | Current request — always appended after skills |
| Tool schemas | Tool registry, filtered by skill `requires_tool` tags | Capability declaration for tool calling |
| Runtime params | Skill event tags + LLM fallback chain | Model, temperature, max_tokens, etc. |

---

## Template Variable Resolution

When the engine encounters `{{variable_name}}` in a skill template:

1. **Check known tools** — if it matches a tool name, execute the tool and insert the result
2. **Check adopted skills** — if it matches an adopted skill's d-tag, insert that skill's content (layer 2)
3. **Neither** — resolve to empty (for portability)

This means `{{admin_profile}}` calls the `nostr_admin_profile` tool, while `{{identity}}` inserts the adopted "identity" skill's content. The skill author doesn't need to know which is which — the resolution is transparent.

See [SKILLS.md — Template Variables](SKILLS.md#template-variables) for the full variable table.

---

## Execution Parameters

Execution parameters control the LLM call: which model, what temperature, how many tokens, which tools.

### Resolution Order

1. Start with agent defaults
2. Apply top-level execution tags from the skill event
3. Walk the `llm` fallback chain until a usable model is found
4. Execute
5. Restore defaults after the run

### LLM Fallback Chain

The `llm` tag uses a CSS font-stack style fallback: `provider/model, provider/model, ..., capability_keyword`

```
["llm", "anthropic/claude-sonnet-4-20250514, openai/gpt-4o-mini, cheap"]
```

See [SKILLS.md — LLM Fallback Chain](SKILLS.md#llm-fallback-chain) for the full format and capability keywords.

---

## Context Compaction

During long-running tool loops, the context window can grow as tool call/result pairs accumulate. When context approaches the model's token limit, compaction prevents overflow:

1. Track approximate token usage of the messages array
2. When approaching ~70% of the model's context window, inject a summarization request
3. The LLM summarizes progress so far into a condensed form
4. Replace detailed tool history with the summary
5. Continue execution with the compacted context

This allows skills to run complex multi-step tasks without hitting context limits.

---

## Token Budget

Context cost is controlled by:

- Number of triggered skills matching the event (layer 1 count)
- Size of referenced skills (layer 2 content)
- Tool call/result accumulation during execution
- Context compaction threshold (~70% of model window)
- Per-skill model/runtime parameter choices

Use runtime context inspection endpoints (`GET /api/context/current`, `GET /api/context/parts`) to see the exact payload before LLM calls.

---

## Related Documentation

- Skills spec: [SKILLS.md](SKILLS.md)
- Tool catalog: [TOOLS.md](TOOLS.md)
- API details: [API.md](API.md)
