14 KiB
Didactyl Admin HTTP API
Overview
Didactyl exposes a localhost-only HTTP API for external tools and dashboards to inspect agent state, explore LLM context, craft prompts, and compare prompt variants. The API runs inside the same process as the agent — no separate server, no authentication required.
All responses are JSON. CORS headers are included on every response for browser access from any local origin.
Configuration
Enable the API in config.jsonc:
{
"api": {
"enabled": true,
"port": 8484,
"bind_address": "127.0.0.1"
}
}
| Field | Type | Default | Description |
|---|---|---|---|
enabled |
bool | false |
Must be explicitly set to true to start the HTTP server |
port |
int | 8484 |
TCP port to listen on |
bind_address |
string | "127.0.0.1" |
Bind address — use 127.0.0.1 for localhost-only access |
The API is disabled by default. When disabled, no listener is created and no resources are consumed.
CORS
Every response includes:
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: Content-Type
OPTIONS requests return 204 No Content with these headers for browser preflight support.
Endpoints
GET /api/status
Returns agent runtime status.
Response:
{
"success": true,
"name": "Didactyl",
"version": "v0.0.26",
"pubkey": "52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8",
"relay_count": 4,
"connected_relays": 4,
"active_triggers": 0
}
| Field | Description |
|---|---|
name |
Agent display name constant |
version |
Build version string |
pubkey |
Agent public key in hex |
relay_count |
Number of configured relays |
connected_relays |
Number of currently connected relays |
active_triggers |
Number of active triggered-skill subscriptions |
GET /api/context/current
Returns the full LLM context message array that would be sent to the model right now. This is the same context the agent builds for an admin DM conversation.
Response:
{
"success": true,
"total_chars": 13131,
"total_estimated_tokens": 3283,
"messages": [
{"role": "system", "content": "# Didactyl Agent\n\nYou are Didactyl..."},
{"role": "system", "content": "This is your administrator! Admin pubkey..."},
{"role": "system", "content": "Administrator kind 0 profile content..."},
{"role": "system", "content": "Administrator kind 10002 relay-list content..."},
{"role": "system", "content": "Startup events memory..."},
{"role": "system", "content": "Adopted skills memory..."},
{"role": "system", "content": "Administrator recent public notes..."}
]
}
| Field | Description |
|---|---|
total_chars |
Total character count across all messages |
total_estimated_tokens |
Rough token estimate using chars / 4 heuristic |
messages |
OpenAI-format messages array with role and content |
GET /api/context/parts
Returns the context broken into labeled, individually-sized parts. Useful for understanding what consumes context budget.
Response:
{
"success": true,
"total_chars": 13131,
"total_estimated_tokens": 3283,
"parts": [
{
"name": "system_prompt",
"role": "system",
"chars": 1200,
"estimated_tokens": 300,
"content": "# Didactyl Agent..."
},
{
"name": "admin_identity",
"role": "system",
"chars": 120,
"estimated_tokens": 30,
"content": "This is your administrator!..."
},
{
"name": "admin_kind0",
"role": "system",
"chars": 450,
"estimated_tokens": 113,
"content": "Administrator kind 0 profile content..."
},
{
"name": "admin_relay_list",
"role": "system",
"chars": 50,
"estimated_tokens": 13,
"content": "Administrator kind 10002 relay-list content..."
},
{
"name": "startup_events",
"role": "system",
"chars": 4800,
"estimated_tokens": 1200,
"content": "Startup events memory..."
},
{
"name": "adopted_skills",
"role": "system",
"chars": 2100,
"estimated_tokens": 525,
"content": "Adopted skills memory..."
},
{
"name": "admin_notes",
"role": "system",
"chars": 680,
"estimated_tokens": 170,
"content": "Administrator recent public notes..."
}
],
"messages": [...]
}
Part names:
| Name | Description |
|---|---|
system_prompt |
The agent base/default skill prompt (first system message) |
admin_identity |
Admin pubkey identification message |
admin_kind0 |
Admin kind 0 profile metadata |
admin_relay_list |
Admin kind 10002 relay list |
startup_events |
Startup events memory block |
adopted_skills |
Adopted skills behavioral instructions |
admin_notes |
Admin recent kind 1 public notes |
dm_history |
Recent DM conversation history |
context_part |
Any other context message |
POST /api/trigger/:d_tag
Fires a webhook-triggered skill by d_tag.
Path params:
| Param | Required | Description |
|---|---|---|
d_tag |
yes | Skill d tag of a skill configured with trigger=webhook |
Request body:
Optional JSON payload; if present it is passed into the synthetic trigger event as payload.
{
"source": "external-cron",
"note": "run maintenance sweep"
}
Response:
{
"success": true,
"d_tag": "maintenance-sweep",
"fired": true
}
Notes:
- Returns
404if no skill exists ford_tagor no active trigger is registered. - Returns
400if the matched skill is not awebhooktrigger or is disabled. - Trigger execution uses the same trigger cooldown policy as other trigger types.
POST /api/prompt/run-simple
Submit a system prompt and user message for a simple LLM call with no tools. Useful for quick prompt iteration.
Request:
{
"system": "You are a helpful assistant that writes tweets.",
"user": "Write a tweet about AI agents on Nostr",
"model": "claude-haiku-4.5"
}
| Field | Required | Description |
|---|---|---|
system |
yes | System prompt string |
user |
yes | User message string |
model |
no | Override the current model for this request only |
Response:
{
"success": true,
"response": "AI agents are finding their home on Nostr...",
"model_used": "claude-haiku-4.5",
"input_tokens_estimate": 85,
"output_tokens_estimate": 42
}
POST /api/prompt/agent
Submit one user message and let Didactyl build full admin context server-side (same context assembly path used for Nostr admin DMs, including admin profile, adopted skills, and recent DM history).
Request:
{
"message": "Tweet about the weather",
"model": "claude-haiku-4.5",
"max_turns": 5
}
| Field | Required | Description |
|---|---|---|
message |
yes | User message string |
model |
no | Override the current model for this request only |
max_turns |
no | Maximum tool-call loop iterations (default: 4, max: 16) |
Response:
{
"success": true,
"final_response": "Done! I posted a tweet about the weather.",
"turns": [
{
"turn": 1,
"tool_calls": [
{
"name": "nostr_post",
"arguments": "{\"kind\":1,\"content\":\"Beautiful day!\"}",
"result": "{\"success\":true,\"event_id\":\"abc123\"}"
}
]
}
],
"model_used": "claude-haiku-4.5",
"total_input_tokens_estimate": 3200,
"total_output_tokens_estimate": 180
}
| Field | Description |
|---|---|
final_response |
The LLM final text response after all tool calls complete |
turns |
Array of turn objects, each containing tool calls made in that turn |
turns[].tool_calls[] |
Each tool call with name, arguments JSON, and result JSON |
model_used |
The model that was actually used |
total_input_tokens_estimate |
Estimated input tokens for the full conversation |
total_output_tokens_estimate |
Estimated output tokens for the final response |
POST /api/prompt/run
Submit a full messages array with the agent tool set enabled. This endpoint runs exactly what you provide and does not auto-build Didactyl admin context.
Request:
{
"messages": [
{"role": "system", "content": "You are Didactyl..."},
{"role": "user", "content": "Tweet about the weather"}
],
"model": "claude-haiku-4.5",
"max_turns": 5
}
| Field | Required | Description |
|---|---|---|
messages |
yes | OpenAI-format messages array |
model |
no | Override the current model for this request only |
max_turns |
no | Maximum tool-call loop iterations (default: 4, max: 16) |
Response:
{
"success": true,
"final_response": "Done! I posted a tweet about the weather.",
"turns": [
{
"turn": 1,
"tool_calls": [
{
"name": "nostr_post",
"arguments": "{\"kind\":1,\"content\":\"Beautiful day!\"}",
"result": "{\"success\":true,\"event_id\":\"abc123\"}"
}
]
}
],
"model_used": "claude-haiku-4.5",
"total_input_tokens_estimate": 3200,
"total_output_tokens_estimate": 180
}
| Field | Description |
|---|---|
final_response |
The LLM final text response after all tool calls complete |
turns |
Array of turn objects, each containing tool calls made in that turn |
turns[].tool_calls[] |
Each tool call with name, arguments JSON, and result JSON |
model_used |
The model that was actually used |
total_input_tokens_estimate |
Estimated input tokens for the full conversation |
total_output_tokens_estimate |
Estimated output tokens for the final response |
POST /api/prompt/compare
A/B testing: submit two prompt variants, both are executed sequentially, responses returned side-by-side. Each variant can optionally use a different model for cross-model comparison.
Request:
{
"variant_a": {
"messages": [
{"role": "system", "content": "You are concise. No emoji."},
{"role": "user", "content": "Say hi."}
],
"model": "claude-haiku-4.5",
"max_turns": 1
},
"variant_b": {
"messages": [
{"role": "system", "content": "You are verbose and friendly."},
{"role": "user", "content": "Say hi."}
],
"model": "claude-haiku-4.5",
"max_turns": 1
}
}
| Field | Required | Description |
|---|---|---|
variant_a |
yes | First prompt variant — same shape as /api/prompt/run request |
variant_b |
yes | Second prompt variant — same shape as /api/prompt/run request |
Response:
{
"success": true,
"variant_a": {
"success": true,
"final_response": "Hi. How can I help?",
"turns": [{"turn": 1, "tool_calls": []}],
"model_used": "claude-haiku-4.5",
"total_input_tokens_estimate": 21,
"total_output_tokens_estimate": 6
},
"variant_b": {
"success": true,
"final_response": "Hey there! Nice to meet you! I am here and ready to help...",
"turns": [{"turn": 1, "tool_calls": []}],
"model_used": "claude-haiku-4.5",
"total_input_tokens_estimate": 21,
"total_output_tokens_estimate": 72
}
}
Variant A runs first, then variant B. The original model config is restored after each variant completes.
Error Responses
All error responses follow this shape:
{
"success": false,
"error": "description of what went wrong"
}
Common HTTP status codes:
| Code | Meaning |
|---|---|
200 |
Success |
204 |
OPTIONS preflight success |
400 |
Bad request — missing or invalid parameters |
404 |
Endpoint not found |
500 |
Internal server error |
Token Estimation
All token estimates use a simple chars / 4 heuristic. This is a rough approximation that works well enough for English text across major model families. No real tokenizer is used.
Security
- Binds to
127.0.0.1only — not accessible from the network - No authentication — this is a local development and administration tool
- The
api.enabledconfig flag defaults tofalseand must be explicitly opted in - Prompt execution endpoints have full tool access equivalent to admin-tier DM conversations
- The agent process must be running for the API to be available
Architecture
The HTTP server is embedded in the didactyl process using the Mongoose library. It runs in the same thread as the main poll loop — each iteration calls http_api_poll() which does non-blocking accept/read/write. This avoids threading complexity and gives the API direct access to all agent state.
flowchart LR
subgraph didactyl process
MAIN[main loop] --> POLL[nostr_handler_poll]
MAIN --> TPOLL[trigger_manager_poll]
MAIN --> HPOLL[http_api_poll]
HPOLL --> ROUTER[request router]
ROUTER --> AGENT[agent internals]
ROUTER --> LLM[LLM client]
ROUTER --> TOOLS[tools context]
ROUTER --> CONFIG[config]
ROUTER --> TRIGGERS[trigger_manager]
end
BROWSER[Web Dashboard] -- HTTP localhost:8484 --> HPOLL
Source Files
| File | Purpose |
|---|---|
src/http_api.c |
HTTP server, request router, all endpoint handlers |
src/http_api.h |
Public API: http_api_init, http_api_poll, http_api_cleanup |
src/mongoose.c |
Mongoose embedded HTTP library |
src/mongoose.h |
Mongoose header |
Future Endpoints
The following endpoints are planned but not yet implemented:
| Method | Path | Description |
|---|---|---|
| GET | /api/config |
Current runtime config with redacted secrets |
| GET | /api/events/skills |
List published skills |
| GET | /api/events/skills/:d_tag |
Fetch skill by d_tag |
| PUT | /api/events/skills/:d_tag |
Update skill |
| GET | /api/events/adoption |
Fetch adoption list |
| GET | /api/events/profile |
Fetch agent profile |
| PUT | /api/events/profile |
Update agent profile |
| GET | /api/triggers |
List active triggers |
| GET | /api/model |
Current model config |
| PUT | /api/model |
Update model config |
| GET | /api/models |
List available models |
| GET | /api/relays |
Relay connection status |
| GET | /api/tools |
List tool schemas |
| POST | /api/tools/:name/execute |
Execute a tool directly |
| GET | /api/context/log |
Recent context.log entries |
| POST | /api/context/preview |
Dry-run context preview |