Files
didactyl/plans/unified_prompt_context.md
T

159 lines
6.1 KiB
Markdown

# Plan: Unified Prompt Context for HTTP API and Nostr Paths
## Problem
The agent produces completely different LLM context depending on whether a message arrives via **Nostr DM** or the **HTTP API CLI chat app**.
### Nostr Path (working correctly)
- `agent_on_message()` → `agent_build_admin_messages_json()` → tool loop
- Builds **18 sections, ~8224 bytes** of context including:
- System prompt / personality (from soul template)
- Agent identity (pubkey)
- Sender verification (admin tier)
- Admin context (kind 0 profile, relay list, recent posts)
- Startup events memory
- Adopted skills
- DM history (decrypted from Nostr relays)
- Current user message
### HTTP API Path (broken)
- CLI sends `{messages: [{role: "user", content: "Hello"}]}` to `/api/prompt/run`
- `run_prompt_with_tools()` passes these raw messages directly to the LLM
- Result: **1 section, ~35 bytes** — just the bare user message, zero agent context
## Solution: New `POST /api/prompt/agent` Endpoint
Add a new endpoint that mirrors the Nostr path's context assembly, so the CLI gets the same full agent context.
### Architecture
```mermaid
flowchart TD
A[Nostr DM arrives] --> B[agent_on_message]
B --> C[agent_build_admin_messages_json]
C --> D[Append user message]
D --> E[Tool loop with llm_chat_with_tools_messages]
E --> F[Send DM reply]
G[CLI sends POST /api/prompt/agent] --> H[handle_prompt_agent]
H --> C
C --> I[Append user message]
I --> J[Tool loop - same as run_prompt_with_tools but with context]
J --> K[Return JSON response]
style C fill:#4a9,stroke:#333,color:#fff
```
Both paths share `agent_build_admin_messages_json()` as the single source of truth for context assembly.
### Request Format
```json
{
"message": "What is the capital of France?",
"model": "claude-haiku-4.5",
"max_turns": 4
}
```
| Field | Type | Required | Description |
|---|---|---|---|
| `message` | string | yes | The user message to send to the agent |
| `model` | string | no | Override the configured LLM model for this request |
| `max_turns` | int | no | Max tool-use turns, default 4, max 16 |
### Response Format
Same as existing `/api/prompt/run`:
```json
{
"success": true,
"final_response": "The capital of France is Paris.",
"turns": [...],
"model_used": "claude-haiku-4.5",
"total_input_tokens_estimate": 1973,
"total_output_tokens_estimate": 12
}
```
## Implementation Steps
### 1. Add `handle_prompt_agent()` in `src/http_api.c`
New function that:
1. Parses the JSON body to extract `message`, optional `model`, optional `max_turns`
2. Applies model override if present via `maybe_model_override_begin()`
3. Calls `agent_build_admin_messages_json(message, DIDACTYL_SENDER_ADMIN, &base_messages_json)` — same call the Nostr path uses
4. Parses the result into a cJSON array
5. Appends `{role: "user", content: message}` to the array — same as `agent_on_message()` does at line 1916
6. Builds tool schema via `tools_build_openai_schema_json()`
7. Runs the same tool loop as `run_prompt_with_tools()` but using the context-enriched messages
8. Logs context via `agent_append_context_log("http_api_agent", "llm_chat_with_tools_messages", messages_json)`
9. Returns the same response format as `/api/prompt/run`
Key reference points in existing code:
- Context building: `agent_build_admin_messages_json()` at `src/agent.c:1737`
- User message append: `append_simple_message()` pattern at `src/agent.c:1916`
- Tool loop: reuse the loop logic from `run_prompt_with_tools()` at `src/http_api.c:278-345`
- Context logging: `agent_append_context_log()` at `src/agent.c:1932`
### 2. Register the Route in `http_handler()`
Add before the existing `/api/prompt/run` route at `src/http_api.c:648`:
```c
if (method_is(hm, "POST") && mg_match(hm->uri, mg_str("/api/prompt/agent"), NULL)) {
handle_prompt_agent(c, hm);
return;
}
```
### 3. Update `chat-didactyl-cli.js`
Change the CLI to call the new endpoint:
- Change `callDidactyl()` to POST to `/api/prompt/agent` instead of `/api/prompt/run`
- Send `{message: "user text", max_turns: N}` instead of `{messages: [...], max_turns: N}`
- The CLI no longer needs to maintain a `transcript` array for context — the server handles DM history from Nostr relays
- Keep the transcript for local display purposes only
### 4. Context Logging Parity
Use a distinct but parallel phase label:
- Nostr path: `llm_chat_with_tools_messages` (existing)
- HTTP API agent path: `llm_chat_with_tools_messages_agent_api` (new)
- HTTP API raw path: `llm_chat_with_tools_messages_http_api` (existing, unchanged)
This lets you distinguish the source in `context.log.md` while confirming the context structure is identical.
### 5. Update `docs/API.md`
Add documentation for the new `POST /api/prompt/agent` endpoint following the existing documentation style.
## Files to Modify
| File | Change |
|---|---|
| `src/http_api.c` | Add `handle_prompt_agent()` function and route registration |
| `chat-didactyl-cli.js` | Switch to `/api/prompt/agent`, simplify payload |
| `docs/API.md` | Document new endpoint |
## What Stays the Same
- `/api/prompt/run` — unchanged, still accepts raw message arrays for custom/advanced use
- `/api/prompt/run-simple` — unchanged
- `/api/context/current` and `/api/context/parts` — unchanged
- `agent_build_admin_messages_json()` — unchanged, already does exactly what we need
- Nostr message handling — unchanged
## Remaining Consideration: Conversation History
The Nostr path gets DM history by querying encrypted kind-4 events from relays. The new `/api/prompt/agent` endpoint will include this same history since it calls `agent_build_admin_messages_json()`. This means:
- Messages sent via the CLI will NOT appear in the Nostr DM history (they are not published as Nostr events)
- Messages sent via Nostr WILL appear in the context when using the CLI
- This is acceptable — the CLI is a development/admin tool that piggybacks on the agent's full context
If in the future you want CLI messages to also appear in history, that would require either publishing them as Nostr DMs or maintaining a separate local history store — but that is out of scope for this change.