# Fix: Local File Persistence & Stall Diagnostic Improvements

## Context

The agent got stuck in a stall loop when trying to create a skill via DM. The root cause is `max_tokens=512` truncating the LLM's tool call JSON. The admin tried to fix it with `/model_set {"max_tokens": 10000}` but got `"failed to persist llm config to config file"` because `model_set` tries to write to a local config file that doesn't exist in the Nostr-native deployment model.

Investigation revealed two tools that incorrectly persist state to local files instead of kind 30078 Nostr events, plus missing diagnostic information in the stall detector.

## Hardcoded File Locations to Remove

### tool_model.c
- `ctx->cfg->config_path` — references a local config file (genesis.jsonc or config.jsonc)
- The entire `persist_llm_config()` function (lines 82-139) reads/writes this file
- The `read_entire_file_local()` helper (lines 42-80) exists only for this purpose

### tool_task.c
- `"tasks.json"` — hardcoded at line 188: `build_tool_path_local(ctx, "tasks.json", ...)`
- `tasks_load_root_local()` (lines 82-141) reads from this file
- `tasks_save_root_local()` (lines 143-159) writes to this file

### tools_schema.c
- Line 985: `"Update active LLM configuration and persist it to config.jsonc"` — mentions config.jsonc
- Line 1285: `"Build current task list context block from tasks.json"` — mentions tasks.json
- Line 1314: `"Manage agent short-term task memory stored in tasks.json"` — mentions tasks.json

## Fixes

### Fix 1: model_set — Persist to Kind 30078

**File:** `src/tools/tool_model.c`

Replace `persist_llm_config()` (lines 82-139) with a function that:
1. Builds a JSON object with LLM config fields (provider, model, base_url, max_tokens, temperature — NOT api_key for security)
2. NIP-44 self-encrypts it using the same pattern as `config_store` in `tool_config.c`
3. Publishes as kind 30078 with tags `["d", "llm_config"]` and `["app", "didactyl"]`

Delete the `read_entire_file_local()` helper (lines 42-80) and the `json_object_set_string()` helper (lines 33-40) — they only exist for the file-based persist.

Also update `execute_model_set()` (line 234) to call the new Nostr-based persist, and return success with a warning if persist fails (since runtime update already succeeded at line 229).

### Fix 2: model_set — Don't Fail When Only Persist Fails

**File:** `src/tools/tool_model.c`, lines 229-236

Currently:
```c
if (llm_set_config(&cfg) != 0) {
    return json_error_local("failed to update runtime llm config");
}
ctx->cfg->llm = cfg;
if (persist_llm_config(ctx, &cfg) != 0) {
    return json_error_local("failed to persist llm config to config file");
}
```

Change to: return success with a `"persist_warning"` field if persist fails, since the runtime update already took effect.

### Fix 3: task_manage — Persist to Kind 30078

**File:** `src/tools/tool_task.c`

Replace `tasks_load_root_local()` and `tasks_save_root_local()` with:
- `tasks_load_root_nostr()` — queries kind 30078 `d=tasks` from self, NIP-44 decrypts, parses JSON
- `tasks_save_root_nostr()` — serializes JSON, NIP-44 self-encrypts, publishes kind 30078 `d=tasks`

This follows the exact same pattern as `config_store`/`config_recall` in `tool_config.c` and `memory_save`/`memory_recall` in `tool_memory.c`.

Remove the hardcoded `"tasks.json"` string at line 188 and the `build_tool_path_local()` call for it.

Remove `tasks_path` from the response JSON at line 424.

### Fix 4: Update Schema Descriptions

**File:** `src/tools/tools_schema.c`

- Line 985: Change `"Update active LLM configuration and persist it to config.jsonc"` to `"Update active LLM configuration and persist it to Nostr"`
- Line 1285: Change `"Build current task list context block from tasks.json"` to `"Build current task list context block from agent task memory"`
- Line 1314: Change `"Manage agent short-term task memory stored in tasks.json (list/add/update/remove/clear/replace)"` to `"Manage agent short-term task memory stored on Nostr (list/add/update/remove/clear/replace)"`

### Fix 5: Add finish_reason to LLM Response

**File:** `src/llm.h`

Add `char* finish_reason;` to `llm_response_t`:
```c
typedef struct {
    char* content;
    llm_tool_call_t* tool_calls;
    int tool_call_count;
    char* finish_reason;
} llm_response_t;
```

**File:** `src/llm.c`

In `parse_llm_response()` (line 276), after extracting `msg` from `choices[0]`, extract `finish_reason`:
```c
cJSON* fr = first ? cJSON_GetObjectItemCaseSensitive(first, "finish_reason") : NULL;
if (fr && cJSON_IsString(fr) && fr->valuestring) {
    out->finish_reason = strdup(fr->valuestring);
}
```

In `llm_response_free()`, add `free(response->finish_reason);`.

### Fix 6: Add max_tokens Truncation Hint to Stall Diagnostic

**File:** `src/agent.c`

In the stall detection block at line 2217, before freeing `resp`, check `finish_reason`:
```c
if (repeated_tool_turns >= stall_repeat_threshold) {
    int truncated = resp.finish_reason && strcmp(resp.finish_reason, "length") == 0;
    exited_on_stall = 1;
    llm_response_free(&resp);
    break;
}
```

In `notify_admin_limit_diagnostic()` (line 106), add fields:
- `possible_cause` — "max_tokens_truncation" when finish_reason was "length"
- `current_max_tokens` — the current max_tokens value
- `hint` — "Increase max_tokens: /model_set {\"max_tokens\": 4096}"

**File:** `src/http_api.c`

Same changes in the HTTP API tool loop stall detection at line 966.

## Execution Order

1. Fix 5 (finish_reason in llm_response_t) — no dependencies
2. Fix 6 (stall diagnostic) — depends on Fix 5
3. Fix 1 (model_set persist to Nostr) — independent
4. Fix 2 (model_set graceful failure) — can be done with Fix 1
5. Fix 3 (task_manage persist to Nostr) — independent
6. Fix 4 (schema descriptions) — do last, trivial

## Files Modified

- `src/llm.h` — add finish_reason field
- `src/llm.c` — parse finish_reason, free it
- `src/agent.c` — stall diagnostic with truncation hint
- `src/http_api.c` — stall diagnostic with truncation hint
- `src/tools/tool_model.c` — replace file persist with kind 30078, graceful failure
- `src/tools/tool_task.c` — replace file I/O with kind 30078
- `src/tools/tools_schema.c` — update 3 descriptions
