# Didactyl Prompt Template System — Design Plan

## Summary

Replace the hardcoded context assembly in `src/agent.c` with a template-driven system. The template lives inside the soul event (kind 31120) — because the template defines how the agent perceives the world, and that is fundamentally part of who the agent is.

Different agents (architect, eGirl, analyst) are different processes with different souls, different templates, different Nostr identities. Multi-agent is Approach A: multiple `./didactyl --config` processes communicating via Nostr.

---

## Core Principle

**The soul IS the template.** An agent's soul defines both its personality (prose instructions) and its perception (what context sections it sees, in what order, with what limits). You cannot meaningfully separate "who you are" from "how you see the world."

---

## Current State

Today, context assembly is hardcoded in `agent_build_admin_messages_json()`:

```
1. System prompt (soul content)
2. Admin identity (pubkey)
3. Admin kind 0 profile
4. Admin kind 10002 relay list
5. Startup events memory
6. Adopted skills memory
7. DM history (last 12 turns)
8. Admin recent notes (kind 1)
```

The order, formatting, limits, and section headers are all baked into C code. Changing anything requires editing `src/agent.c` and recompiling.

---

## Proposed Design

### Soul Event Structure

The kind 31120 soul event content gains a template section, delimited by a marker:

```markdown
# Didactyl Agent

You are Didactyl, a sovereign AI agent living on Nostr.

## Communication Rules
- You communicate through encrypted Nostr direct messages.
- Keep responses concise and clear.

## Behavior
- Be helpful and technically accurate.
...

## Safety
- Never reveal your private key.
...

---template---

- section: admin_identity
  role: system
  content: |
    This is your administrator! Admin pubkey: {{admin_pubkey}}

- section: admin_profile
  role: system
  content: |
    Administrator profile: {{admin_kind0_json}}

- section: admin_relay_list
  role: system
  content: |
    Administrator relay list: {{admin_kind10002_json}}

- section: startup_events
  role: system
  content: |
    Startup events memory: {{startup_events_json}}

- section: adopted_skills
  role: system
  content: |
    {{adopted_skills_content}}

- section: dm_history
  role: expand
  limit: 12

- section: admin_notes
  role: system
  limit: 10
  content: |
    {{admin_notes_content}}
```

Everything above `---template---` is the system prompt (personality/rules). Everything below defines the context assembly template.

If no `---template---` marker is found, the agent falls back to the current hardcoded assembly — backward compatible.

### Template Syntax

Simple YAML-like format parsed in C. Each section has:

| Field | Required | Description |
|---|---|---|
| `section` | yes | Section name for logging and API identification |
| `role` | yes | Chat message role: `system`, `user`, `assistant`, or `expand` |
| `tool` | recommended | Tool name executed by template builder (for example `nostr_admin_profile`) |
| `args` | no | JSON args string passed to the tool (default `{}`) |
| `result_field` | no | Field extracted from tool JSON result (default `content`) |
| `content` | optional fallback | Literal content when no `tool` is specified |
| `limit` | no | Integer limit for variable-length sections like DM history |
| `provider` | no | Provider-specific override — see below |

### Tool-Driven Context Resolution

Context sections should use `tool:` directives as the single runtime data path, which removes the legacy per-variable resolver duplication.

### Special Section Types

**`role: expand`** — The `dm_history` section expands into multiple messages (user/assistant pairs). The `limit` field controls how many turns to include. This is the only section type that produces multiple chat messages from one template entry.

### Provider-Specific Overrides

Within a section, you can specify provider-specific formatting:

```yaml
- section: admin_identity
  role: system
  content: |
    ## Administrator Identity
    This is your administrator! Admin pubkey: {{admin_pubkey}}
  provider:
    anthropic: |
      <admin_identity>
      This is your administrator! Admin pubkey: {{admin_pubkey}}
      </admin_identity>
```

When the configured `llm.provider` matches a provider key, that override is used instead of the default `content`. This lets one soul/template work well across providers without needing separate soul events.

If no provider override matches, the default `content` is used.

---

## Context Assembly Flow

```mermaid
flowchart TD
    BOOT[Agent boots] --> LOAD_SOUL[Load soul event - kind 31120]
    LOAD_SOUL --> PARSE[Parse soul content]
    PARSE --> SPLIT{Contains ---template--- marker?}
    SPLIT -->|Yes| EXTRACT[Extract personality above marker]
    SPLIT -->|No| FALLBACK[Use hardcoded assembly - backward compat]
    EXTRACT --> PARSE_TPL[Parse template sections below marker]
    PARSE_TPL --> STORE[Store template_section_t array in memory]
    
    DM[Incoming DM] --> BUILD[Build context from template]
    BUILD --> EMIT_SOUL[Emit personality as first system message]
    EMIT_SOUL --> FOREACH[For each template section]
    FOREACH --> CALL_TOOL[Execute section tool or use literal content]
    CALL_TOOL --> PICK_FIELD[Extract result_field or content]
    PICK_FIELD --> CHECK_PROVIDER{Provider override?}
    CHECK_PROVIDER -->|Yes| USE_OVERRIDE[Use provider-specific content]
    CHECK_PROVIDER -->|No| USE_DEFAULT[Use tool/literal output]
    USE_OVERRIDE --> EMIT[Emit as chat message]
    USE_DEFAULT --> EMIT
    EMIT --> FOREACH
    FOREACH --> DONE[Complete messages array]
    DONE --> LLM[Send to LLM]
```

---

## Context.log Formatting

With templates, the log formatter uses section names directly from the template instead of detecting them from content prefixes. The `detect_context_section()` function is replaced by the template section name.

Log format becomes:

```
[2026-03-02 14:54:30] phase=llm_chat_with_tools_messages sender=8ff747...

Sections: 8

============================================================
Section: system_prompt | role=system
============================================================

# Didactyl Agent
...


============================================================
Section: admin_identity | role=system
============================================================

This is your administrator! Admin pubkey: 8ff747...


============================================================
Section: dm_history | role=user
============================================================

Good afternoon.
```

Note: "Message 01" is replaced with "Section: admin_identity" — the section name from the template, which is much more meaningful.

---

## Data Structures

```c
typedef struct {
    char name[64];           // section name
    char role[16];           // system, user, assistant, expand
    char* content_template;  // content with {{var}} placeholders
    int limit;               // for expand sections, 0 = unlimited
    char* provider_overrides; // JSON object of provider->content pairs, or NULL
} template_section_t;

typedef struct {
    char* personality;                // everything above ---template---
    template_section_t* sections;     // parsed template sections
    int section_count;
} prompt_template_t;
```

---

## Implementation Plan

### New Files

| File | Purpose |
|---|---|
| `src/prompt_template.c` | Template parser, variable resolver, context builder |
| `src/prompt_template.h` | Public API: parse, build context, free |

### Modified Files

| File | Change |
|---|---|
| `src/agent.c` | Replace `agent_build_admin_messages_json()` internals with template-driven builder. Keep function signature unchanged for API compatibility. |
| `src/agent.c` | Remove hardcoded `append_admin_identity_context()`, `append_startup_events_context()`, etc. — these become template variable resolvers |
| `src/agent.c` | Update `format_context_payload_for_log()` to use section names from template |
| `src/http_api.c` | Remove `classify_part_name()` / `detect_context_section()` — section names come from template |
| `Makefile` | Add `src/prompt_template.c` to SRCS |
| `Dockerfile.alpine-musl` | Add `src/prompt_template.c` to gcc command |

### Implementation Order

1. Create `src/prompt_template.h` with data structures and API
2. Implement template parser in `src/prompt_template.c` — parse soul content, split at `---template---`, parse sections
3. Implement variable resolver — map `{{var}}` names to data source functions
4. Implement context builder — iterate sections, resolve variables, emit messages
5. Wire into `agent_build_admin_messages_json()` — if template exists, use it; otherwise fall back to hardcoded
6. Update `format_context_payload_for_log()` to use section names
7. Update `classify_part_name()` in `http_api.c` to use section names from template
8. Update default soul in `config.json.example` to include a `---template---` section
9. Test with existing soul (no template marker) — verify backward compatibility
10. Test with template soul — verify new assembly
11. Test provider overrides

---

## Backward Compatibility

If the soul event content does NOT contain `---template---`, the agent uses the current hardcoded assembly. This means:

- Existing agents continue to work without changes
- The template system is opt-in
- Migration is gradual — add a template section to your soul when ready

---

## Example Souls

### Architect Agent

```markdown
# Technical Architect

You are a technical architect agent. You analyze systems, design solutions, and produce detailed technical plans.

## Behavior
- Think systematically about architecture
- Consider tradeoffs explicitly
- Produce diagrams when helpful
- Never implement code — only design

---template---

- section: admin_identity
  role: system
  content: |
    Administrator pubkey: {{admin_pubkey}}

- section: admin_profile
  role: system
  content: |
    Administrator profile: {{admin_kind0_json}}

- section: startup_events
  role: system
  content: |
    System configuration and startup state: {{startup_events_json}}

- section: adopted_skills
  role: system
  content: |
    {{adopted_skills_content}}

- section: dm_history
  role: expand
  limit: 20

- section: admin_notes
  role: system
  limit: 5
  content: |
    Recent administrator notes for context: {{admin_notes_content}}
```

### eGirl Agent

```markdown
# Social Butterfly

You are a friendly, social Nostr personality. You love interacting with people, commenting on their posts, and being part of the community.

## Personality
- Warm, enthusiastic, uses emoji freely
- Interested in what people are posting about
- Remembers details about conversations
- Keeps responses casual and fun

---template---

- section: admin_identity
  role: system
  content: |
    Your creator: {{admin_pubkey}}

- section: admin_profile
  role: system
  content: |
    Creator profile: {{admin_kind0_json}}

- section: admin_notes
  role: system
  limit: 20
  content: |
    Recent posts from people you follow — use these for social context and conversation starters: {{admin_notes_content}}

- section: adopted_skills
  role: system
  content: |
    {{adopted_skills_content}}

- section: dm_history
  role: expand
  limit: 8
```

Note the differences:
- The eGirl sees 20 recent notes (social context) but only 8 DM turns
- The architect sees 5 notes but 20 DM turns (needs conversation continuity)
- The eGirl puts notes BEFORE skills (social context is primary)
- The architect puts skills BEFORE notes (technical knowledge is primary)
- No startup events for the eGirl (doesn't need system config details)

---

## Nostr Shareability

Since the template is part of the soul event (kind 31120), sharing works naturally:

- Publish your soul → others get your complete agent personality + perception template
- Discover interesting agents on Nostr → adopt their soul as a starting point
- Community can develop and share optimized templates for different use cases
- Templates evolve through the same Nostr discovery mechanisms as skills

---

## Security Notes

- Template variable resolution is sandboxed — only predefined variables are resolved
- No arbitrary code execution from templates
- The `limit` field is capped at compile-time maximums to prevent resource exhaustion
- Provider overrides are optional and safe — they only change formatting, not data sources
