Files
didactyl/docs/CONTEXT.md

9.3 KiB

Didactyl — LLM Context

See also: SKILLS.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 for the canonical skill specification.


OpenAI-Compatible Chat Format

Didactyl uses OpenAI-compatible chat completions.

{
  "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.

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

./didactyl --context-debug full

Or with environment variable:

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:

# Didactyl Agent

## Anvil Agent
...

Turn snapshots (*_tNNN.md) are human-readable markdown renderings of the OpenAI-compatible messages array used for that loop turn:

**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.