303 lines
9.3 KiB
Markdown
303 lines
9.3 KiB
Markdown
# 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/<timestamp>_init.md` — initial context for a run
|
|
- `context.logs/<timestamp>_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)
|