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,toolsfrom the Content Fields table - Add execution-parameter tags to the Trigger Tags table:
llm,temperature,max_tokens,seed,tools - Add
dmtrigger 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
--nsecCLI flag orDIDACTYL_NSECenvironment variable - Accept optional
--api-portand--api-bindfor 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}}callstools_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
llmtag present: temporarily override the LLM model for this execution - If
temperaturetag present: temporarily override temperature - If
max_tokenstag present: temporarily override max_tokens - If
toolstag 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,toolsfrom 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.