Files
didactyl/plans/skill_driven_architecture.md

12 KiB

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

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

["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

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.