6.1 KiB
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
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
{
"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:
{
"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:
- Parses the JSON body to extract
message, optionalmodel, optionalmax_turns - Applies model override if present via
maybe_model_override_begin() - Calls
agent_build_admin_messages_json(message, DIDACTYL_SENDER_ADMIN, &base_messages_json)— same call the Nostr path uses - Parses the result into a cJSON array
- Appends
{role: "user", content: message}to the array — same asagent_on_message()does at line 1916 - Builds tool schema via
tools_build_openai_schema_json() - Runs the same tool loop as
run_prompt_with_tools()but using the context-enriched messages - Logs context via
agent_append_context_log("http_api_agent", "llm_chat_with_tools_messages", messages_json) - Returns the same response format as
/api/prompt/run
Key reference points in existing code:
- Context building:
agent_build_admin_messages_json()atsrc/agent.c:1737 - User message append:
append_simple_message()pattern atsrc/agent.c:1916 - Tool loop: reuse the loop logic from
run_prompt_with_tools()atsrc/http_api.c:278-345 - Context logging:
agent_append_context_log()atsrc/agent.c:1932
2. Register the Route in http_handler()
Add before the existing /api/prompt/run route at src/http_api.c:648:
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/agentinstead 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
transcriptarray 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/currentand/api/context/parts— unchangedagent_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.