Files
didactyl/docs/API.md

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 404 if no skill exists for d_tag or no active trigger is registered.
  • Returns 400 if the matched skill is not a webhook trigger 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.1 only — not accessible from the network
  • No authentication — this is a local development and administration tool
  • The api.enabled config flag defaults to false and 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