# Skill Tools — Architecture Plan

## Overview

Add a family of five skill-management tools to Didactyl so the agent can **create, list, adopt, remove, and discover** skills at runtime — all through the existing LLM tool-calling loop.

Skills are Nostr events. The tools are thin orchestration wrappers over the existing `nostr_handler_publish_kind_event()` and `nostr_handler_query_json()` primitives.

---

## Nostr Kind Reference

| Kind | Purpose | Replaceable? | Key tag |
|---|---|---|---|
| `31123` | Public skill definition | Yes (d-tag) | `d=<d_tag>` |
| `31124` | Private skill definition | Yes (d-tag) | `d=<d_tag>` |
| `10123` | Public skill adoption list | Yes (replaceable) | `a` refs to 31123 events |

All skill events carry these standard tags:
- `["d", "<d_tag>"]` — unique identifier within the author's pubkey
- `["app", "didactyl"]` — app namespace
- `["scope", "public"]` or `["scope", "private"]`

---

## Tool Family

### 1. `skill_create`

**Purpose:** Create or update a skill definition and optionally auto-adopt it.

**OpenAI schema:**

```json
{
  "name": "skill_create",
  "description": "Create or update a skill definition (kind 31123 public / 31124 private) and optionally auto-adopt it",
  "parameters": {
    "type": "object",
    "properties": {
      "d_tag":        { "type": "string",  "description": "Unique skill identifier (lowercase, hyphens allowed)" },
      "content":     { "type": "string",  "description": "Skill body — markdown instructions or structured JSON" },
      "scope":       { "type": "string",  "description": "public (kind 31123) or private (kind 31124). Default: public" },
      "description": { "type": "string",  "description": "Short one-line description for the skill" },
      "auto_adopt":  { "type": "boolean", "description": "Automatically add to adoption list (kind 10123). Default: true" }
    },
    "required": ["d_tag", "content"]
  }
}
```

**Execution logic (`execute_skill_create`):**

1. Validate `d_tag` — must be non-empty, lowercase alphanumeric + hyphens, no spaces
2. Determine kind: `31123` if scope is `"public"` or absent; `31124` if `"private"`
3. Build tags array:
   - `["d", d_tag]`
   - `["app", "didactyl"]`
   - `["scope", scope]`
   - `["description", description]` if provided
4. Call `nostr_handler_publish_kind_event(kind, content, tags, &result)`
5. If `auto_adopt` is true (default), update the kind `10123` adoption list:
   - Query existing `10123` event for own pubkey (same pattern as `execute_nostr_list_manage`)
   - Add `["a", "31123:<own_pubkey>:<d_tag>"]` tag if not already present
   - Republish the updated `10123` event
6. Return JSON with `success`, `event_id`, `naddr_uri`, `d_tag`, `adopted`

**Key design decisions:**
- Auto-adopt defaults to `true` — creating a skill you don't adopt is unusual
- Private skills (31124) are NOT added to the public adoption list (10123)
- Republishing with the same d_tag replaces the previous version (replaceable event)

---

### 2. `skill_list`

**Purpose:** List the agent's own published skills.

**OpenAI schema:**

```json
{
  "name": "skill_list",
  "description": "List skills published by this agent, optionally filtered by scope",
  "parameters": {
    "type": "object",
    "properties": {
      "scope": { "type": "string", "description": "Filter by public or private. Omit for both." }
    }
  }
}
```

**Execution logic (`execute_skill_list`):**

1. Build filter based on scope:
   - Both: `{"kinds": [31123, 31124], "authors": [own_pubkey]}`
   - Public only: `{"kinds": [31123], "authors": [own_pubkey]}`
   - Private only: `{"kinds": [31124], "authors": [own_pubkey]}`
2. Call `nostr_handler_query_json(filter, 8000)`
3. Parse results, extract for each event:
   - `d_tag` (from d-tag)
   - `kind`
   - `scope` (from scope tag)
   - `description` (from description tag, if present)
   - `created_at` timestamp
   - `content` preview (first 200 chars)
4. Return JSON array of skill summaries

---

### 3. `skill_adopt`

**Purpose:** Adopt a skill published by another author (or self) into the agent's adoption list.

**OpenAI schema:**

```json
{
  "name": "skill_adopt",
  "description": "Add a skill to the agent's public adoption list (kind 10123)",
  "parameters": {
    "type": "object",
    "properties": {
      "pubkey": { "type": "string", "description": "Hex pubkey of the skill author" },
      "d_tag":   { "type": "string", "description": "Skill d_tag (d-tag value)" },
      "kind":   { "type": "integer", "description": "Skill kind (31123 or 31124). Default: 31123" }
    },
    "required": ["pubkey", "d_tag"]
  }
}
```

**Execution logic (`execute_skill_adopt`):**

1. Validate pubkey (64-char hex) and d_tag (non-empty)
2. Default kind to 31123 if not provided
3. Build the `a`-tag value: `"<kind>:<pubkey>:<d_tag>"`
4. Query existing kind `10123` event for own pubkey
5. Check if `["a", "<kind>:<pubkey>:<d_tag>"]` already exists — if so, return success with `already_adopted: true`
6. Add the tag, republish `10123`
7. Return JSON with `success`, `adopted_address`, `event_id`

---

### 4. `skill_remove`

**Purpose:** Remove a skill from the agent's adoption list.

**OpenAI schema:**

```json
{
  "name": "skill_remove",
  "description": "Remove a skill from the agent's public adoption list (kind 10123)",
  "parameters": {
    "type": "object",
    "properties": {
      "pubkey": { "type": "string", "description": "Hex pubkey of the skill author. Defaults to own pubkey." },
      "d_tag":   { "type": "string", "description": "Skill d_tag to remove" },
      "kind":   { "type": "integer", "description": "Skill kind (31123 or 31124). Default: 31123" }
    },
    "required": ["d_tag"]
  }
}
```

**Execution logic (`execute_skill_remove`):**

1. Default pubkey to own pubkey if not provided
2. Default kind to 31123
3. Build the `a`-tag value: `"<kind>:<pubkey>:<d_tag>"`
4. Query existing kind `10123` event for own pubkey
5. Find and remove matching `["a", ...]` tag
6. Republish `10123`
7. Return JSON with `success`, `removed_address`, `event_id`

---

### 5. `skill_search`

**Purpose:** Search for skills across the agent's Web of Trust.

**OpenAI schema:**

```json
{
  "name": "skill_search",
  "description": "Search for skills adopted by Web of Trust contacts, or query public skill definitions",
  "parameters": {
    "type": "object",
    "properties": {
      "query":  { "type": "string",  "description": "Optional keyword to filter skill slugs or descriptions" },
      "pubkey": { "type": "string",  "description": "Search skills by a specific author pubkey" },
      "popular": { "type": "boolean", "description": "If true, query WoT adoption lists to find most-adopted skills" }
    }
  }
}
```

**Execution logic (`execute_skill_search`):**

1. If `popular` is true:
   - Query `{"kinds": [10123]}` from relays (with reasonable limit)
   - Parse all `a`-tags from results
   - Count occurrences of each `a`-tag address
   - Sort by adoption count descending
   - Return top N skill addresses with counts
2. If `pubkey` is provided:
   - Query `{"kinds": [31123], "authors": [pubkey]}`
   - Return skill summaries
3. If `query` is provided (keyword search):
   - Query `{"kinds": [31123]}` with limit
   - Filter results client-side by matching `query` against d_tag, description tag, or content
   - Return matching skill summaries
4. Default (no params): return own adopted skills from `10123`

---

## Implementation Architecture

### Shared helper: adoption list update

Since `skill_create`, `skill_adopt`, and `skill_remove` all modify the kind `10123` list, extract a shared helper:

```c
// Fetch current 10123 event, return duplicated tags array (or empty array if none exists)
static cJSON* fetch_adoption_list_tags(tools_context_t* ctx);

// Publish updated 10123 event with new tags
static int publish_adoption_list(tools_context_t* ctx, cJSON* tags, nostr_publish_result_t* result);
```

This is essentially the same pattern already used in `execute_nostr_list_manage()` but specialized for kind `10123`.

### Shared helper: skill summary extraction

```c
// Extract d_tag, kind, scope, description, created_at from a skill event JSON
static cJSON* extract_skill_summary(cJSON* event);
```

### Flow diagram

```mermaid
flowchart TD
    SC[skill_create] --> PUB[nostr_handler_publish_kind_event]
    SC --> ADOPT_HELPER[update adoption list helper]
    
    SL[skill_list] --> QUERY[nostr_handler_query_json]
    SL --> SUMMARY[extract_skill_summary]
    
    SA[skill_adopt] --> ADOPT_HELPER
    SR[skill_remove] --> ADOPT_HELPER
    
    SS[skill_search] --> QUERY
    SS --> SUMMARY
    
    ADOPT_HELPER --> QUERY
    ADOPT_HELPER --> PUB
```

---

## Changes Required

### `src/tools.c`

1. **Schema registration** — Add 5 new tool definitions in `tools_build_openai_schema_json()` (t22–t26)
2. **Execution functions** — Add 5 new `execute_skill_*()` static functions
3. **Dispatch** — Add 5 new `strcmp` branches in `tools_execute()`
4. **Shared helpers** — Add `fetch_adoption_list_tags()`, `publish_adoption_list()`, `extract_skill_summary()`, and `validate_skill_d_tag()`

### `README.md`

1. Add skill tools to the Tooling Interface section under a new "Skill management" category

### No changes needed to:
- `src/tools.h` — the `tools_context_t` already has `cfg` which provides `keys.public_key_hex`
- `src/nostr_handler.h` — all needed APIs already exist
- `src/config.h` — no new config fields needed

---

## Slug Validation Rules

A valid skill d_tag must:
- Be 1–64 characters
- Contain only lowercase letters, digits, and hyphens
- Not start or end with a hyphen
- Not contain consecutive hyphens

```c
static int validate_skill_d_tag(const char* d_tag) {
    if (!d_tag || d_tag[0] == '\0' || strlen(d_tag) > 64) return 0;
    if (d_tag[0] == '-') return 0;
    int prev_dash = 0;
    for (size_t i = 0; d_tag[i]; i++) {
        char c = d_tag[i];
        if (c == '-') {
            if (prev_dash) return 0;
            prev_dash = 1;
        } else if (islower(c) || isdigit(c)) {
            prev_dash = 0;
        } else {
            return 0;
        }
    }
    if (d_tag[strlen(d_tag) - 1] == '-') return 0;
    return 1;
}
```

---

## Implementation Order

1. **Shared helpers** — `validate_skill_d_tag`, `fetch_adoption_list_tags`, `publish_adoption_list`, `extract_skill_summary`
2. **`skill_create`** — most important tool, enables the agent to author skills
3. **`skill_list`** — lets the agent see what it has published
4. **`skill_adopt`** — adopt skills from other authors
5. **`skill_remove`** — remove skills from adoption list
6. **`skill_search`** — discover skills across WoT
7. **Schema registration** — add all 5 tools to `tools_build_openai_schema_json()`
8. **Dispatch wiring** — add all 5 to `tools_execute()`
9. **README update** — document the new tools
10. **Build and test** — verify compilation and basic tool execution

---

## Security Considerations

- **Admin-only**: Skill tools inherit the existing ADMIN tier restriction — only the admin can trigger tool calls
- **Slug validation**: Prevents injection of malformed d-tags
- **No arbitrary kind**: `skill_create` only publishes kind 31123 or 31124, not arbitrary kinds
- **Adoption list integrity**: The helpers always fetch-then-update to avoid clobbering existing adoption entries
- **Content size**: No explicit limit on skill content size — relies on relay limits and LLM context window constraints
