# Skill-Driven Architecture — Implementation Plan

## Summary

Overhaul Didactyl so that **skills are the universal unit of behavior** and **Nostr is the source of truth for all agent state**. The agent boots from a single nsec; everything else — context template, LLM config, relay list, personality — lives on Nostr as skills or encrypted events.

---

## Architecture Decisions (Finalized)

| Decision | Detail |
|----------|--------|
| Genesis config | `genesis.jsonc` is consumed on first run only; can be deleted afterward |
| Runtime identity seed | nsec (via CLI flag or systemd credential) is the only required input after genesis |
| Context template | Becomes the `didactyl_default` skill — first entry in kind 10123 adoption list |
| Context modes | Removed. `inject`/`full`/`override` replaced by adoption-list-order composition |
| Execution params | `llm`, `temperature`, `max_tokens`, `seed`, `tools` move from skill content to trigger tags |
| DM trigger type | New trigger type `dm` — makes DM handling consistent with all other triggers |
| LLM config storage | Kind 30078 `d=llm_config`, NIP-44 encrypted to self, stored on Nostr |
| Admin config storage | Admin pubkey stored as tag on soul event or dedicated config event on Nostr |
| Bootstrap relays | Listed in `genesis.jsonc`; agent adopts admin relay list on first run |
| Skill portability | Skills are general Nostr events; implementation-specific variables resolve to empty |

---

## Phase 1 — Specification Updates

Update the authoritative documentation to reflect all decisions before writing code.

### 1.1 Update docs/SKILLS.md

- Remove `llm`, `temperature`, `max_tokens`, `seed`, `tools` from the Content Fields table
- Add execution-parameter tags to the Trigger Tags table: `llm`, `temperature`, `max_tokens`, `seed`, `tools`
- Add `dm` trigger type with filter format `{"from":"admin"}`, `{"from":"wot"}`, `{"from":"any"}`
- Update all examples to show execution params as tags, not content fields
- Update the private skill decrypted payload example accordingly
- Remove the LLM Specification section header (move fallback-chain docs into trigger tag reference)
- Update the Execution Flow mermaid diagram

### 1.2 Update docs/CONTEXT.md

- Remove the Context Modes section entirely (inject/full/override)
- Remove the context_mode branching from the Context Assembly Flow mermaid diagram
- Replace with adoption-list-order composition model
- Update Context Parts table to reference skills instead of soul event
- Update Token Budget section to remove context_mode references

### 1.3 Write docs/GENESIS.md (new)

- Document the genesis.jsonc format and purpose
- Document the first-run flow: genesis consumed, events published, LLM config encrypted
- Document the subsequent-run flow: nsec only, everything fetched from Nostr
- Document bootstrap relay strategy

---

## Phase 2 — Genesis Startup Flow

### 2.1 Define genesis.jsonc schema

```jsonc
{
  "key": { "nsec": "nsec1..." },
  "admin": { "pubkey": "npub1..." },
  "dm_protocol": "nip04",
  "llm": {
    "provider": "...",
    "api_key": "...",
    "model": "...",
    "base_url": "...",
    "max_tokens": 512,
    "temperature": 0.7
  },
  "api": {
    "enabled": true,
    "port": 8484,
    "bind_address": "127.0.0.1"
  },
  "bootstrap_relays": [
    "wss://relay.damus.io",
    "wss://nos.lol"
  ],
  "startup_events": [
    // Kind 0 profile, kind 10002 relay list, kind 10050 DM relays,
    // kind 3 contact list, kind 31123/31124 skills,
    // kind 10123 adoption list
  ],
  "default_skill": {
    // The didactyl_default skill content — soul + context template
  }
}
```

### 2.2 Implement first-run detection

- On startup, check if the agent has published a kind 10002 event (relay list) on bootstrap relays
- If no kind 10002 found: this is a first run — consume genesis.jsonc
- If kind 10002 found: this is a subsequent run — fetch everything from Nostr

### 2.3 Implement genesis event publishing

- Connect to bootstrap relays
- Fetch admin kind 10002 (relay list) and adopt those relays
- Publish all startup_events from genesis.jsonc
- Publish default_skill as kind 31123 with `["d", "didactyl_default"]`
- Publish kind 10123 adoption list with didactyl_default as first entry
- Encrypt and publish LLM config as kind 30078 `d=llm_config`
- Encrypt and publish admin pubkey + dm_protocol as kind 30078 `d=agent_config`

### 2.4 Implement nsec-only startup

- Accept nsec via `--nsec` CLI flag or `DIDACTYL_NSEC` environment variable
- Accept optional `--api-port` and `--api-bind` for local API config
- Derive pubkey from nsec
- Connect to hardcoded bootstrap relays (compiled-in fallback list)
- Fetch own kind 10002 → connect to own relays
- Fetch kind 30078 `d=llm_config` → decrypt → initialize LLM client
- Fetch kind 30078 `d=agent_config` → decrypt → get admin pubkey, dm_protocol
- Fetch kind 10123 → get adoption list
- Fetch adopted skills → build context template
- Proceed to normal operation

---

## Phase 3 — Adoption-List-Order Context Assembly

### 3.1 Refactor context building in agent.c

Current flow (agent_build_admin_messages_json):
- If prompt template exists: use prompt_template_build_messages
- Else: hardcoded context assembly (append_admin_identity_context, append_startup_events_context, append_adopted_skills_context, etc.)

New flow:
- Fetch kind 10123 adoption list
- For each adopted skill in order:
  - Fetch skill content
  - If skill has a template: resolve template variables via tools_execute
  - Append resolved content as system message(s)
- Append DM history (if dm trigger)
- Append user message / triggering event

### 3.2 Unify template variable resolution

Current state: two template formats exist:
- YAML section format with `tool:` directives (prompt_template.c)
- `{{variable}}` inline syntax (documented in SKILLS.md but not fully implemented)

Target: single `{{variable}}` format that resolves through tools_execute.

- Implement `{{variable}}` resolution in a new or updated template engine
- Each `{{variable_name}}` calls `tools_execute(ctx, variable_name, "{}")`
- Unknown variables resolve to empty string
- The YAML section format becomes legacy (still supported for backward compat during transition)

### 3.3 Remove hardcoded context assembly

- Remove append_admin_identity_context, append_startup_events_context, append_adopted_skills_context from agent.c
- These become unnecessary because the didactyl_default skill template handles all of it via {{variable}} resolution
- Keep the functions available as tools so templates can call them

---

## Phase 4 — Execution Params on Trigger Tags

### 4.1 Update trigger_manager to read execution params from tags

Current trigger registration reads: `trigger`, `filter`, `action`, `enabled`

Add reading of: `llm`, `temperature`, `max_tokens`, `seed`, `tools`

Store these on the trigger_entry_t struct.

### 4.2 Apply execution params at invocation time

When a trigger fires:
- If `llm` tag present: temporarily override the LLM model for this execution
- If `temperature` tag present: temporarily override temperature
- If `max_tokens` tag present: temporarily override max_tokens
- If `tools` tag present: filter available tools for this execution
- After execution: restore defaults

Use the existing model_set/model_get pattern (llm_set_config/llm_get_config) for temporary overrides.

### 4.3 Remove execution params from skill content parsing

- Stop reading `llm`, `temperature`, `max_tokens`, `seed`, `tools` from skill content JSON
- These fields in content are ignored (backward compat: warn if present)

---

## Phase 5 — DM Trigger Type

### 5.1 Add dm trigger type to trigger_manager

- New trigger type: `TRIGGER_TYPE_DM`
- Filter format: `{"from":"admin"}`, `{"from":"wot"}`, `{"from":"any"}`
- Registration: when loading skills with `["trigger", "dm"]`, register as DM trigger

### 5.2 Refactor agent_on_message to use trigger dispatch

Current flow:
- agent_on_message receives DM
- Checks sender tier
- Builds context directly
- Calls LLM

New flow:
- agent_on_message receives DM
- Checks sender tier
- Finds matching DM trigger(s) from registered triggers
- For each matching trigger: execute via the standard trigger execution path
- If no DM trigger matches: fall back to default behavior (or reject)

### 5.3 didactyl_default gets a dm trigger

The default skill in genesis.jsonc includes:

```json
["trigger", "dm"],
["filter", "{\"from\":\"admin\"}"],
["llm", "default"],
["tools", "true"],
["enabled", "true"]
```

This makes the normal admin DM conversation a triggered skill execution, consistent with everything else.

---

## Phase 6 — Encrypted Config Storage

### 6.1 Implement config_store tool

New tool: `config_store` — encrypts and publishes agent config to Nostr

- Kind 30078 with configurable d-tag
- NIP-44 encrypted to self (same pattern as memory tool)
- Used for: `d=llm_config`, `d=agent_config`

### 6.2 Implement config_recall tool

New tool: `config_recall` — fetches and decrypts agent config from Nostr

- Query kind 30078 by d-tag and own pubkey
- NIP-44 decrypt
- Return JSON content

### 6.3 Use config tools during startup

- During genesis: call config_store for llm_config and agent_config
- During nsec-only startup: call config_recall to recover LLM and agent config
- These tools are also available to the LLM for runtime config changes

---

## Phase 7 — Documentation Cleanup

### 7.1 Update README.md

- Update startup instructions to reflect genesis.jsonc
- Update architecture overview
- Remove references to config.jsonc as the primary config

### 7.2 Deprecate config.jsonc

- Keep config.jsonc.example as reference but mark as legacy
- Document migration path from config.jsonc to genesis.jsonc

### 7.3 Update context_template.md

- Mark as legacy/deprecated
- Point to didactyl_default skill as the replacement

---

## File Change Summary

| File | Change Type | Description |
|------|------------|-------------|
| `docs/SKILLS.md` | Modify | Execution params to trigger tags, add dm trigger type |
| `docs/CONTEXT.md` | Modify | Remove context_mode, adoption-list composition |
| `docs/GENESIS.md` | New | Genesis config documentation |
| `genesis.jsonc` | Modify | Add bootstrap_relays, flesh out default_skill and startup_events |
| `src/agent.c` | Major refactor | Adoption-list context assembly, DM trigger dispatch |
| `src/config.c` | Modify | Support genesis.jsonc format, nsec-only mode |
| `src/config.h` | Modify | New config structs for minimal runtime config |
| `src/main.c` | Modify | First-run detection, nsec CLI flag, genesis flow |
| `src/trigger_manager.c` | Modify | DM trigger type, execution params from tags |
| `src/trigger_manager.h` | Modify | New trigger type enum, execution param fields |
| `src/prompt_template.c` | Modify | {{variable}} resolution engine |
| `src/tools/tool_config.c` | New | config_store and config_recall tools |
| `src/tools/tools_dispatch.c` | Modify | Register new config tools |
| `src/tools/tools_schema.c` | Modify | Schema for new config tools |
| `README.md` | Modify | Updated startup docs |
| `context_template.md` | Deprecate | Replaced by didactyl_default skill |
| `config.jsonc.example` | Deprecate | Replaced by genesis.jsonc |

---

## Dependency Order

```mermaid
graph TD
    P1[Phase 1: Spec Updates] --> P2[Phase 2: Genesis Startup]
    P1 --> P3[Phase 3: Adoption-List Context]
    P1 --> P4[Phase 4: Execution Params on Tags]
    P1 --> P5[Phase 5: DM Trigger Type]
    P2 --> P6[Phase 6: Encrypted Config Storage]
    P3 --> P5
    P4 --> P5
    P5 --> P7[Phase 7: Documentation Cleanup]
    P6 --> P7
```

Phases 2, 3, 4 can proceed in parallel after Phase 1. Phase 5 depends on 3 and 4. Phase 6 depends on 2. Phase 7 is last.
