v0.2.22 - Improve markdown context documentation and skill authoring guidance: document context debug snapshot formats, clarify role-marker defaults, enrich skill_create/skill_edit tool schema guidance for markdown skills, and update tool docs examples
This commit is contained in:
@@ -54,11 +54,11 @@ Skills compose by adoption-list order (`10123`) and trigger tags carry runtime e
|
||||
|
||||
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.2.21
|
||||
## Current Status — v0.2.22
|
||||
|
||||
**Active build — this project is barely working. Experiment at your own risk.**
|
||||
|
||||
> Last release update: v0.2.21 — Fix markdown context assembly and logging: add Anvil DM trigger skill config, preserve H1 title on fallback, convert full-mode turn snapshots to readable markdown, and sync Anvil service restart/deploy behavior
|
||||
> Last release update: v0.2.22 — Improve markdown context documentation and skill authoring guidance: document context debug snapshot formats, clarify role-marker defaults, enrich skill_create/skill_edit tool schema guidance for markdown skills, and update tool docs examples
|
||||
|
||||
- Connects to configured relays with auto-reconnect and relay state transition logging
|
||||
- Publishes configured startup events per relay as each relay becomes connected
|
||||
|
||||
@@ -216,6 +216,91 @@ Use runtime context inspection endpoints (`GET /api/context/current`, `GET /api/
|
||||
|
||||
---
|
||||
|
||||
## Context Debug Logging
|
||||
|
||||
Didactyl can persist what it sends to the LLM in markdown files for inspection.
|
||||
|
||||
Enable with runtime flag:
|
||||
|
||||
```bash
|
||||
./didactyl --context-debug full
|
||||
```
|
||||
|
||||
Or with environment variable:
|
||||
|
||||
```bash
|
||||
DIDACTYL_CONTEXT_DEBUG=full ./didactyl
|
||||
```
|
||||
|
||||
Modes:
|
||||
|
||||
| Mode | Behavior |
|
||||
|------|----------|
|
||||
| `off` | Disable context logging |
|
||||
| `init` | Log only initial pre-loop context |
|
||||
| `full` | Log initial context and each tool-loop turn |
|
||||
|
||||
Output files are written to:
|
||||
|
||||
- `context.log.md` — rolling latest-first log
|
||||
- `context.logs/<timestamp>_init.md` — initial context for a run
|
||||
- `context.logs/<timestamp>_t001.md`, `_t002.md`, ... — turn snapshots
|
||||
|
||||
### Snapshot Formats
|
||||
|
||||
Initial snapshots (`*_init.md`) are the assembled markdown context document, including role markers and heading bump effects:
|
||||
|
||||
```markdown
|
||||
# Didactyl Agent
|
||||
|
||||
system:
|
||||
## Anvil Agent
|
||||
...
|
||||
|
||||
user:
|
||||
Who was WSB historically?
|
||||
```
|
||||
|
||||
Turn snapshots (`*_tNNN.md`) are human-readable markdown renderings of the OpenAI-compatible `messages` array used for that loop turn:
|
||||
|
||||
```markdown
|
||||
**Message 1 (system)**
|
||||
|
||||
- **role**: system
|
||||
- **content**: # Didactyl Agent
|
||||
|
||||
## Anvil Agent
|
||||
...
|
||||
|
||||
---
|
||||
|
||||
**Message 2 (assistant)**
|
||||
|
||||
- **role**: assistant
|
||||
- **tool_calls**:
|
||||
- **name**: nostr_admin_profile
|
||||
- **arguments**: {}
|
||||
|
||||
---
|
||||
|
||||
**Message 3 (tool)**
|
||||
|
||||
- **role**: tool
|
||||
- **tool_call_id**: toolu_...
|
||||
- **content**:
|
||||
- **success**: true
|
||||
- **content**:
|
||||
- **display_name**: WSB
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- Internal transport fields such as `_ts` are omitted from snapshots.
|
||||
- Tool result payloads are rendered as markdown structures when they contain JSON.
|
||||
- Snapshot wrappers use bold message labels rather than markdown headings to avoid conflicting with message content headings.
|
||||
|
||||
---
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- Skills spec: [SKILLS.md](SKILLS.md)
|
||||
|
||||
@@ -81,6 +81,7 @@ The `content` field of a skill event IS the template — markdown instructions t
|
||||
```
|
||||
|
||||
- **`content`** — the template in markdown. May include `{{...}}` template variables and `system:` / `user:` role markers. This is what goes into the context window.
|
||||
- **Role Markers** — `system:`, `user:`, and `assistant:` are parsed only when they appear at the start of a line. Content without a marker defaults to `system:`.
|
||||
- **Heading Levels** — The runtime owns the `#` (h1) heading for the document title. All headings in skill content are automatically bumped down one level (`#` becomes `##`, `##` becomes `###`) during context assembly. Skill authors should use `##` for their top-level sections.
|
||||
- **`["description", "..."]`** — human-readable description for discovery and UI display.
|
||||
- Each `["tag", "value"]` is a separate tag on the Nostr event.
|
||||
|
||||
@@ -114,8 +114,8 @@ These tools manage skill and trigger lifecycle; skill semantics and trigger exec
|
||||
|
||||
| Tool | Description |
|
||||
|---|---|
|
||||
| `skill_create` | Create or update a skill definition as kind `31123`/`31124` and optionally auto-adopt it |
|
||||
| `skill_edit` | Edit an existing self skill by d tag and republish it as kind `31123`/`31124` |
|
||||
| `skill_create` | Create or update a skill definition as kind `31123`/`31124` and optionally auto-adopt it (content should be markdown with optional `system:`/`user:`/`assistant:` role markers) |
|
||||
| `skill_edit` | Edit an existing self skill by d tag and republish it as kind `31123`/`31124` (content should remain markdown-first) |
|
||||
| `skill_list` | List available skills discovered online (agent + admin), with adoption status and optional filters |
|
||||
| `skill_adopt` | Adopt a skill by adding its address to kind `10123` adoption list |
|
||||
| `skill_remove` | Remove a skill address from kind `10123` adoption list |
|
||||
@@ -469,8 +469,10 @@ These examples show the JSON structure for tool calls.
|
||||
"name": "skill_create",
|
||||
"arguments": {
|
||||
"d": "weather-skill",
|
||||
"content": "I can tell you the weather.",
|
||||
"content": "system:\n## Weather Assistant\n\nYou provide concise weather summaries.\n\nuser:\n{{message}}",
|
||||
"description": "Fetches weather data",
|
||||
"trigger": "dm",
|
||||
"filter": "{\"from\":\"admin\"}",
|
||||
"auto_adopt": true
|
||||
}
|
||||
}
|
||||
|
||||
83
src/agent.c
83
src/agent.c
@@ -945,26 +945,89 @@ static char* render_messages_json_as_markdown(const char* messages_json) {
|
||||
cJSON* role = cJSON_GetObjectItemCaseSensitive(msg, "role");
|
||||
const char* role_s = (role && cJSON_IsString(role) && role->valuestring) ? role->valuestring : "message";
|
||||
|
||||
char* msg_json = cJSON_PrintUnformatted(msg);
|
||||
char* msg_md = msg_json ? json_to_markdown(msg_json, 8) : NULL;
|
||||
const char* body = msg_md ? msg_md : (msg_json ? msg_json : "");
|
||||
if (i > 0) {
|
||||
if (append_textf_local(&out, &cap, &used, "\n---\n\n") != 0) {
|
||||
free(out);
|
||||
cJSON_Delete(root);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (append_textf_local(&out,
|
||||
&cap,
|
||||
&used,
|
||||
"### Message %d (%s)\n\n%s\n\n",
|
||||
"**Message %d (%s)**\n\n",
|
||||
i + 1,
|
||||
role_s,
|
||||
body) != 0) {
|
||||
free(msg_md);
|
||||
free(msg_json);
|
||||
role_s) != 0) {
|
||||
free(out);
|
||||
cJSON_Delete(root);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
free(msg_md);
|
||||
free(msg_json);
|
||||
cJSON* field = NULL;
|
||||
cJSON_ArrayForEach(field, msg) {
|
||||
if (!field || !field->string) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const char* key = field->string;
|
||||
if (strcmp(key, "_ts") == 0) {
|
||||
continue; // Internal timestamp metadata; not useful in human-readable snapshots
|
||||
}
|
||||
|
||||
char* raw_value = NULL;
|
||||
char* rendered_md = NULL;
|
||||
|
||||
if (strcmp(key, "content") == 0 && strcmp(role_s, "tool") == 0 && cJSON_IsString(field) && field->valuestring) {
|
||||
cJSON* tool_payload = cJSON_Parse(field->valuestring);
|
||||
if (tool_payload && cJSON_IsObject(tool_payload)) {
|
||||
cJSON* inner = cJSON_GetObjectItemCaseSensitive(tool_payload, "content");
|
||||
if (inner && cJSON_IsString(inner) && inner->valuestring) {
|
||||
cJSON* inner_json = cJSON_Parse(inner->valuestring);
|
||||
if (inner_json) {
|
||||
cJSON_ReplaceItemInObjectCaseSensitive(tool_payload, "content", inner_json);
|
||||
}
|
||||
}
|
||||
raw_value = cJSON_PrintUnformatted(tool_payload);
|
||||
cJSON_Delete(tool_payload);
|
||||
}
|
||||
}
|
||||
|
||||
if (!raw_value) {
|
||||
if (cJSON_IsString(field) && field->valuestring) {
|
||||
raw_value = strdup(field->valuestring);
|
||||
} else {
|
||||
raw_value = cJSON_PrintUnformatted(field);
|
||||
}
|
||||
}
|
||||
|
||||
if (raw_value) {
|
||||
rendered_md = json_to_markdown(raw_value, 8);
|
||||
}
|
||||
|
||||
const char* display = rendered_md ? rendered_md : (raw_value ? raw_value : "");
|
||||
if (append_textf_local(&out,
|
||||
&cap,
|
||||
&used,
|
||||
"- **%s**: %s\n",
|
||||
key,
|
||||
display) != 0) {
|
||||
free(rendered_md);
|
||||
free(raw_value);
|
||||
free(out);
|
||||
cJSON_Delete(root);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
free(rendered_md);
|
||||
free(raw_value);
|
||||
}
|
||||
|
||||
if (append_textf_local(&out, &cap, &used, "\n") != 0) {
|
||||
free(out);
|
||||
cJSON_Delete(root);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
cJSON_Delete(root);
|
||||
|
||||
@@ -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 2
|
||||
#define DIDACTYL_VERSION_PATCH 21
|
||||
#define DIDACTYL_VERSION "v0.2.21"
|
||||
#define DIDACTYL_VERSION_PATCH 22
|
||||
#define DIDACTYL_VERSION "v0.2.22"
|
||||
|
||||
// Agent metadata
|
||||
#define DIDACTYL_NAME "Didactyl"
|
||||
|
||||
@@ -754,7 +754,7 @@ char* tools_build_openai_schema_json_legacy(const tools_context_t* ctx) {
|
||||
|
||||
cJSON_AddStringToObject(t22, "type", "function");
|
||||
cJSON_AddStringToObject(t22_fn, "name", "skill_create");
|
||||
cJSON_AddStringToObject(t22_fn, "description", "Create or update a skill definition as kind 31123/31124 and optionally auto-adopt it");
|
||||
cJSON_AddStringToObject(t22_fn, "description", "Create or update a skill definition as kind 31123/31124 and optionally auto-adopt it. Write content as markdown context instructions: use role markers (system:/user:/assistant:) at line start when needed; unmarked content defaults to system. The runtime owns document H1 and will bump skill headings down one level, so prefer ## for top-level sections in skill content.");
|
||||
cJSON_AddStringToObject(t22_params, "type", "object");
|
||||
cJSON_AddItemToObject(t22_params, "properties", t22_props);
|
||||
cJSON_AddItemToObject(t22_params, "required", t22_required);
|
||||
@@ -764,6 +764,9 @@ char* tools_build_openai_schema_json_legacy(const tools_context_t* ctx) {
|
||||
cJSON_AddItemToObject(t22_props, "d", p_skill_create_d);
|
||||
cJSON* p_skill_create_content = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(p_skill_create_content, "type", "string");
|
||||
cJSON_AddStringToObject(p_skill_create_content,
|
||||
"description",
|
||||
"Markdown template for the skill body. May include role markers at line start (system:/user:/assistant:), markdown headings/lists/code fences, and template variables like {{message}} or {{skill_d_tag}}. Unmarked content defaults to system. Runtime will prepend document title and bump headings one level.");
|
||||
cJSON_AddItemToObject(t22_props, "content", p_skill_create_content);
|
||||
cJSON* p_skill_create_scope = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(p_skill_create_scope, "type", "string");
|
||||
@@ -993,7 +996,7 @@ char* tools_build_openai_schema_json_legacy(const tools_context_t* ctx) {
|
||||
|
||||
cJSON_AddStringToObject(t25b, "type", "function");
|
||||
cJSON_AddStringToObject(t25b_fn, "name", "skill_edit");
|
||||
cJSON_AddStringToObject(t25b_fn, "description", "Edit an existing self skill by d tag and republish it as kind 31123/31124");
|
||||
cJSON_AddStringToObject(t25b_fn, "description", "Edit an existing self skill by d tag and republish it as kind 31123/31124. Keep skill content markdown-first with optional role markers (system:/user:/assistant:) at line start; unmarked content defaults to system. Runtime owns H1 and bumps skill headings down one level.");
|
||||
cJSON_AddStringToObject(t25b_params, "type", "object");
|
||||
cJSON_AddItemToObject(t25b_params, "properties", t25b_props);
|
||||
cJSON_AddItemToObject(t25b_params, "required", t25b_required);
|
||||
@@ -1004,6 +1007,9 @@ char* tools_build_openai_schema_json_legacy(const tools_context_t* ctx) {
|
||||
|
||||
cJSON* p_skill_edit_content = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(p_skill_edit_content, "type", "string");
|
||||
cJSON_AddStringToObject(p_skill_edit_content,
|
||||
"description",
|
||||
"Replacement markdown template for this skill. Supports role markers (system:/user:/assistant:) at line start, markdown structure, and template variables ({{...}}). If omitted, existing content is retained.");
|
||||
cJSON_AddItemToObject(t25b_props, "content", p_skill_edit_content);
|
||||
|
||||
cJSON* p_skill_edit_scope = cJSON_CreateObject();
|
||||
|
||||
Reference in New Issue
Block a user