# 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, convert JSON to markdown, insert │ │ │ ├─ Adopted skill d-tag? → insert skill content │ │ │ └─ Unknown? → resolve to empty │ │ │ │ │ └─ NO → skip (not in this context) │ │ │ └─ Continue to next skill in list │ ├─ Format Document │ ├─ Add `# Agent Name` title │ ├─ Bump all skill headings down one level (# -> ##) │ └─ Concatenate skills with `---` separators │ ├─ Build API roles │ └─ Runtime constructs OpenAI-compatible `messages` (system + user + assistant/tool loop) │ ├─ 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 The assembled context starts as a single markdown document (skills + resolved variables), then the runtime maps it into API role messages. ```markdown # Didactyl Agent - **npub**: `npub1...` ## Personality You speak concisely and directly. --- ## Chat Respond helpfully to the admin. Use tools as needed. ``` For DM triggers, the runtime appends the incoming DM text as the `user` message. For non-DM triggers, it appends the trigger payload as the `user` message. ### 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. The tool returns JSON, which the template engine automatically converts into readable markdown (bullet lists, bold keys) before inserting it into the document. 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 and formats its JSON output as markdown, 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. --- ## Context Debug Logging Didactyl can persist what it sends to the LLM in markdown files for inspection. Enable with runtime flag: ```bash ./didactyl --context-debug full ``` Or with environment variable: ```bash DIDACTYL_CONTEXT_DEBUG=full ./didactyl ``` Modes: | Mode | Behavior | |------|----------| | `off` | Disable context logging | | `init` | Log only initial pre-loop context | | `full` | Log initial context and each tool-loop turn | Output files are written to: - `context.log.md` — rolling latest-first log - `context.logs/_init.md` — initial context for a run - `context.logs/_t001.md`, `_t002.md`, ... — turn snapshots ### Snapshot Formats Initial snapshots (`*_init.md`) are the assembled markdown context document (with heading-bump effects). They reflect authored skill markdown before runtime message-role packaging: ```markdown # Didactyl Agent ## Anvil Agent ... ``` Turn snapshots (`*_tNNN.md`) are human-readable markdown renderings of the OpenAI-compatible `messages` array used for that loop turn: ```markdown **Message 1 (system)** - **role**: system - **content**: # Didactyl Agent ## Anvil Agent ... --- **Message 2 (assistant)** - **role**: assistant - **tool_calls**: - **name**: nostr_admin_profile - **arguments**: {} --- **Message 3 (tool)** - **role**: tool - **tool_call_id**: toolu_... - **content**: - **success**: true - **content**: - **display_name**: WSB ``` Notes: - Internal transport fields such as `_ts` are omitted from snapshots. - Tool result payloads are rendered as markdown structures when they contain JSON. - Snapshot wrappers use bold message labels rather than markdown headings to avoid conflicting with message content headings. --- ## Related Documentation - Skills spec: [SKILLS.md](SKILLS.md) - Tool catalog: [TOOLS.md](TOOLS.md) - API details: [API.md](API.md)