v0.0.21 - Add full skill_* tool family with schema, execution, dispatch, and README updates
This commit is contained in:
46
README.md
46
README.md
@@ -51,11 +51,11 @@ Agents learn capabilities through skills — Nostr events that any agent can di
|
||||
|
||||
Didactyl will support local inference, which is very privacy preserving. Remote inference does however have it's advantages, and in those cases Didactyl supports using Bitcoin Lightning and eCash inference providers.
|
||||
|
||||
## Current Status — v0.0.20
|
||||
## Current Status — v0.0.21
|
||||
|
||||
**Active build — this project is barely working. Experiment at your own risk.**
|
||||
|
||||
> Last release update: v0.0.20 — Add Tier2/Tier3 Nostr tools: nip05 lookup, encode/decode, dm send, relay info, nip44 encrypt/decrypt, nip17 dm, list manage
|
||||
> Last release update: v0.0.21 — Add full skill_* tool family with schema, execution, dispatch, and README updates
|
||||
|
||||
- Connects to configured relays with auto-reconnect and relay state transition logging
|
||||
- Publishes configured startup events per relay as each relay becomes connected
|
||||
@@ -276,15 +276,43 @@ Every serialized LLM context payload is appended to [`context.log`](context.log)
|
||||
|
||||
## Tooling Interface
|
||||
|
||||
Current tool schema exposed to the LLM in [`tools_build_openai_schema_json()`](src/tools.c:72):
|
||||
Current tool schema exposed to the LLM in [`tools_build_openai_schema_json()`](src/tools.c:881):
|
||||
|
||||
- `nostr_post`
|
||||
- `nostr_query`
|
||||
- `shell_exec`
|
||||
- `file_read`
|
||||
- `file_write`
|
||||
- Nostr publish/query:
|
||||
- `nostr_post`
|
||||
- `nostr_post_readme`
|
||||
- `nostr_query`
|
||||
- Nostr interaction and moderation:
|
||||
- `nostr_delete`
|
||||
- `nostr_react`
|
||||
- `nostr_profile_get`
|
||||
- `nostr_relay_status`
|
||||
- `nostr_relay_info`
|
||||
- `nostr_nip05_lookup`
|
||||
- Nostr encode/decode + encryption/DM:
|
||||
- `nostr_encode`
|
||||
- `nostr_decode`
|
||||
- `nostr_encrypt`
|
||||
- `nostr_decrypt`
|
||||
- `nostr_dm_send`
|
||||
- `nostr_dm_send_nip17`
|
||||
- Nostr list management:
|
||||
- `nostr_list_manage`
|
||||
- Skill management:
|
||||
- `skill_create`
|
||||
- `skill_list`
|
||||
- `skill_adopt`
|
||||
- `skill_remove`
|
||||
- `skill_search`
|
||||
- Local/host tools:
|
||||
- `shell_exec`
|
||||
- `file_read`
|
||||
- `file_write`
|
||||
- `http_fetch`
|
||||
- Agent metadata:
|
||||
- `my_version`
|
||||
|
||||
Execution entrypoint: [`tools_execute()`](src/tools.c:434).
|
||||
Execution entrypoint: [`tools_execute()`](src/tools.c:3765).
|
||||
|
||||
## Project Structure
|
||||
|
||||
|
||||
337
plans/skill_tools.md
Normal file
337
plans/skill_tools.md
Normal file
@@ -0,0 +1,337 @@
|
||||
# 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=<slug>` |
|
||||
| `31124` | Private skill definition | Yes (d-tag) | `d=<slug>` |
|
||||
| `10123` | Public skill adoption list | Yes (replaceable) | `a` refs to 31123 events |
|
||||
|
||||
All skill events carry these standard tags:
|
||||
- `["d", "<slug>"]` — 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": {
|
||||
"slug": { "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": ["slug", "content"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Execution logic (`execute_skill_create`):**
|
||||
|
||||
1. Validate `slug` — 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", slug]`
|
||||
- `["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>:<slug>"]` tag if not already present
|
||||
- Republish the updated `10123` event
|
||||
6. Return JSON with `success`, `event_id`, `naddr_uri`, `slug`, `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 slug 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:
|
||||
- `slug` (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" },
|
||||
"slug": { "type": "string", "description": "Skill slug (d-tag value)" },
|
||||
"kind": { "type": "integer", "description": "Skill kind (31123 or 31124). Default: 31123" }
|
||||
},
|
||||
"required": ["pubkey", "slug"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Execution logic (`execute_skill_adopt`):**
|
||||
|
||||
1. Validate pubkey (64-char hex) and slug (non-empty)
|
||||
2. Default kind to 31123 if not provided
|
||||
3. Build the `a`-tag value: `"<kind>:<pubkey>:<slug>"`
|
||||
4. Query existing kind `10123` event for own pubkey
|
||||
5. Check if `["a", "<kind>:<pubkey>:<slug>"]` 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." },
|
||||
"slug": { "type": "string", "description": "Skill slug to remove" },
|
||||
"kind": { "type": "integer", "description": "Skill kind (31123 or 31124). Default: 31123" }
|
||||
},
|
||||
"required": ["slug"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**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>:<slug>"`
|
||||
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 slug, 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 slug, 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_slug()`
|
||||
|
||||
### `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 slug 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_slug(const char* slug) {
|
||||
if (!slug || slug[0] == '\0' || strlen(slug) > 64) return 0;
|
||||
if (slug[0] == '-') return 0;
|
||||
int prev_dash = 0;
|
||||
for (size_t i = 0; slug[i]; i++) {
|
||||
char c = slug[i];
|
||||
if (c == '-') {
|
||||
if (prev_dash) return 0;
|
||||
prev_dash = 1;
|
||||
} else if (islower(c) || isdigit(c)) {
|
||||
prev_dash = 0;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
if (slug[strlen(slug) - 1] == '-') return 0;
|
||||
return 1;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Implementation Order
|
||||
|
||||
1. **Shared helpers** — `validate_skill_slug`, `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
|
||||
139
plans/tool_testing.md
Normal file
139
plans/tool_testing.md
Normal file
@@ -0,0 +1,139 @@
|
||||
# Didactyl Tool Testing Plan
|
||||
|
||||
## Overview
|
||||
|
||||
Testing agent-mediated tools is different from unit testing pure functions because the execution path spans multiple boundaries:
|
||||
|
||||
```
|
||||
LLM schema interpretation → JSON argument generation → argument parsing → business logic → Nostr/network I/O → JSON response → LLM interpretation
|
||||
```
|
||||
|
||||
This plan defines three testing layers, ordered by implementation priority.
|
||||
|
||||
---
|
||||
|
||||
## Layer 1: Direct Tool Execution
|
||||
|
||||
Call `tools_execute()` directly with known JSON arguments and assert the JSON response structure.
|
||||
|
||||
### Implementation
|
||||
|
||||
Add a `--test-tool <name> <args_json>` CLI flag to `src/main.c` that:
|
||||
1. Initializes config and nostr_handler (for network-dependent tools)
|
||||
2. Calls `tools_execute(&ctx, name, args_json)`
|
||||
3. Prints the raw JSON result to stdout
|
||||
4. Exits with 0 if `success: true`, 1 otherwise
|
||||
|
||||
### Pure Computation Tools (no network needed)
|
||||
|
||||
| Tool | Test Command | Expected |
|
||||
|------|-------------|----------|
|
||||
| `nostr_encode` | `--test-tool nostr_encode '{"type":"npub","hex":"<64-char-hex>"}'` | `success: true`, uri starts with `nostr:npub1` |
|
||||
| `nostr_decode` | `--test-tool nostr_decode '{"uri":"npub1..."}'` | `success: true`, pubkey is 64-char hex |
|
||||
| `nostr_encrypt` | `--test-tool nostr_encrypt '{"recipient_pubkey":"<hex>","plaintext":"hello"}'` | `success: true`, ciphertext is base64 |
|
||||
| `nostr_decrypt` | `--test-tool nostr_decrypt '{"sender_pubkey":"<hex>","ciphertext":"<from encrypt>"}'` | `success: true`, plaintext is "hello" |
|
||||
|
||||
### Network-Dependent Tools (need relay or HTTP)
|
||||
|
||||
| Tool | Test Command | Expected |
|
||||
|------|-------------|----------|
|
||||
| `nostr_nip05_lookup` | `--test-tool nostr_nip05_lookup '{"identifier":"_@laantungir.com"}'` | `success: true`, pubkey returned |
|
||||
| `nostr_relay_info` | `--test-tool nostr_relay_info '{"relay_url":"wss://relay.damus.io"}'` | `success: true`, info.basic.name present |
|
||||
| `nostr_relay_status` | `--test-tool nostr_relay_status '{}'` | `success: true`, relay_count > 0 |
|
||||
| `nostr_dm_send` | `--test-tool nostr_dm_send '{"recipient_pubkey":"<hex>","message":"test"}'` | `success: true` |
|
||||
| `nostr_post` | `--test-tool nostr_post '{"kind":1,"content":"test"}'` | `success: true`, event_id present |
|
||||
| `nostr_delete` | `--test-tool nostr_delete '{"event_ids":["<64-char-hex>"]}'` | `success: true` |
|
||||
| `nostr_react` | `--test-tool nostr_react '{"event_id":"<hex>","event_pubkey":"<hex>"}'` | `success: true` |
|
||||
| `nostr_profile_get` | `--test-tool nostr_profile_get '{"pubkey":"<hex>"}'` | `success: true`, found: true/false |
|
||||
| `nostr_dm_send_nip17` | `--test-tool nostr_dm_send_nip17 '{"recipient_pubkey":"<hex>","message":"test"}'` | `success: true` |
|
||||
| `nostr_list_manage` | `--test-tool nostr_list_manage '{"list_kind":10000,"action":"add","items":[["p","<hex>"]]}'` | `success: true` |
|
||||
|
||||
### Error Case Tests
|
||||
|
||||
Each tool should also be tested with:
|
||||
- Empty args: `'{}'` — should return descriptive error
|
||||
- Missing required fields — should return specific error message
|
||||
- Invalid hex strings — should reject gracefully
|
||||
- Double-encoded JSON string args — should parse correctly (regression for the `invalid arguments JSON` bug)
|
||||
|
||||
---
|
||||
|
||||
## Layer 2: Schema-Parse Fidelity
|
||||
|
||||
Validates that the OpenAI function schema matches what executors actually accept.
|
||||
|
||||
### Implementation
|
||||
|
||||
A Python script (`tests/validate_schemas.py`) that:
|
||||
1. Runs `didactyl_static --dump-schemas` (new flag) to get the tool schema JSON
|
||||
2. For each tool in the schema:
|
||||
- Generates a minimal valid argument payload from `required` + `properties`
|
||||
- Runs `didactyl_static --test-tool <name> '<generated_json>'`
|
||||
- Asserts exit code 0 or expected network error
|
||||
3. Reports mismatches between schema and executor expectations
|
||||
|
||||
### What This Catches
|
||||
|
||||
- Schema says `required: ["hex"]` but executor checks for `"pubkey"` — mismatch
|
||||
- Schema says `type: "integer"` but executor reads it as string
|
||||
- Schema advertises parameters the executor ignores
|
||||
- Missing required parameters in schema that executor demands
|
||||
|
||||
---
|
||||
|
||||
## Layer 3: Agent Integration Testing (Live DM)
|
||||
|
||||
The most natural test — DM the running agent and verify it uses tools correctly.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Local relay running at `ws://127.0.0.1:7777`
|
||||
- Didactyl running with valid config
|
||||
- A separate Nostr client (or script) to send/receive DMs
|
||||
|
||||
### Test Prompts
|
||||
|
||||
| # | Tool | DM Prompt | Assert in Response |
|
||||
|---|------|-----------|-------------------|
|
||||
| 1 | `nostr_encode` | "Encode this pubkey as npub: `<64-char hex>`" | Contains `nostr:npub1` |
|
||||
| 2 | `nostr_decode` | "Decode this npub: `npub1...`" | Contains the hex pubkey |
|
||||
| 3 | `nostr_dm_send` | "Send a DM to `<your-own-pubkey>` saying 'hello test'" | Confirms sent; you receive it |
|
||||
| 4 | `nostr_encrypt` | "Encrypt 'secret message' for `<pubkey>`" | Contains base64 ciphertext |
|
||||
| 5 | `nostr_decrypt` | "Decrypt this NIP-44 payload: `<ciphertext from #4>`" | Contains 'secret message' |
|
||||
| 6 | `nostr_nip05_lookup` | "Look up `_@laantungir.com`" | Returns a pubkey |
|
||||
| 7 | `nostr_relay_info` | "Get NIP-11 info for `wss://relay.damus.io`" | Returns relay name and supported NIPs |
|
||||
| 8 | `nostr_relay_status` | "Show me relay connection status" | Lists connected relays with stats |
|
||||
| 9 | `nostr_react` | "React with 🤙 to event `<id>` from `<pubkey>`" | Confirms kind 7 published |
|
||||
| 10 | `nostr_delete` | "Delete event `<id>`" | Confirms kind 5 published |
|
||||
| 11 | `nostr_profile_get` | "Look up the profile for `<pubkey>`" | Returns name/about/picture |
|
||||
| 12 | `nostr_post` | "Post a kind 1 note saying 'tool test'" | Confirms published with event_id |
|
||||
| 13 | `nostr_list_manage` | "Add `<pubkey>` to my mute list" | Confirms kind 10000 published |
|
||||
| 14 | `nostr_dm_send_nip17` | "Send a private NIP-17 DM to `<pubkey>` saying 'gift wrap test'" | Confirms gift wrap sent |
|
||||
| 15 | `nostr_post_readme` | "Publish the README to Nostr" | Confirms kind 30023 with d=readme.md |
|
||||
|
||||
### Verification Methods
|
||||
|
||||
- **stdout logs**: Watch `[didactyl] executing tool call: <name>` in terminal
|
||||
- **context.log**: Full LLM conversation including tool calls and results
|
||||
- **Relay inspection**: Query the local relay for published events
|
||||
- **DM receipt**: For DM tools, verify the message arrives at the recipient
|
||||
|
||||
---
|
||||
|
||||
## Implementation Priority
|
||||
|
||||
1. **Immediate (no code changes)**: Run Layer 3 tests by DMing the live agent
|
||||
2. **Next sprint**: Add `--test-tool` CLI flag for Layer 1
|
||||
3. **Later**: Add `--dump-schemas` flag and Python schema validator for Layer 2
|
||||
4. **CI integration**: Wrap Layer 1 pure-computation tests in a shell script that runs after `build_static.sh`
|
||||
|
||||
---
|
||||
|
||||
## Local Relay Setup for Testing
|
||||
|
||||
A local relay like `strfry` or `nostr-rs-relay` at `ws://127.0.0.1:7777` provides:
|
||||
- All published events are observable and queryable
|
||||
- No rate limits or content policies
|
||||
- NIP-42 auth testing in isolation
|
||||
- Gift-wrapped NIP-17 DMs stay in your test environment
|
||||
- Database can be wiped between test runs
|
||||
@@ -12,8 +12,8 @@
|
||||
// Using DIDACTYL_ prefix to avoid conflicts with nostr_core_lib VERSION macros
|
||||
#define DIDACTYL_VERSION_MAJOR 0
|
||||
#define DIDACTYL_VERSION_MINOR 0
|
||||
#define DIDACTYL_VERSION_PATCH 20
|
||||
#define DIDACTYL_VERSION "v0.0.20"
|
||||
#define DIDACTYL_VERSION_PATCH 21
|
||||
#define DIDACTYL_VERSION "v0.0.21"
|
||||
|
||||
// Agent metadata
|
||||
#define DIDACTYL_NAME "Didactyl"
|
||||
|
||||
1299
src/tools.c
1299
src/tools.c
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user