Base URL http://localhost:3333/api/ · Authentication · Conventions

Memory Routes

Base path: /api/memory

Hybrid full-text search across the user's persistent history — the "remember anything" layer. Backed by SQLite FTS5 virtual tables that shadow conversation_logs, agent_executions, content_outputs, insights, agent_memory, and workflow_versions. Triggers keep the indexes in sync; the source tables remain the system of record.

The same surface is also exposed to the chat orchestrator as the recall, list_recent, and get_trace tools.

GET /search

  • Authentication: Required
  • Description: Hybrid keyword + date-range search across all (or a subset of) memory sources. When q is provided, results are ranked by BM25 relevance; when omitted, the endpoint falls back to time-ordered recent rows (same shape as /recent).
  • Query Parameters:
    • q (string, optional): Keyword(s). Tokens are sanitized (alphanumeric + -_ only), prefix-matched, and AND-ed. e.g. q=pokemon mew matches both terms with prefix expansion.
      • Alias: query is accepted as a synonym for q. If both are sent, q wins.
    • since (ISO-8601, optional): Lower bound on the source's timestamp column (e.g. 2026-05-19T00:00:00Z).
    • until (ISO-8601, optional): Upper bound.
    • sources (CSV, optional): Subset of conversations,executions,outputs,insights,memory,versions. Omit to search all.
    • limit (integer, optional): Max results to return. Default 50, cap 200.
  • Example: GET /api/memory/search?q=pokemon&since=2026-05-19T00:00:00Z&sources=conversations,outputs&limit=20
  • Response:
{
  "success": true,
  "count": 12,
  "results": [
    {
      "kind": "conversation",
      "id": "conversation-uuid",
      "timestamp": "2026-05-24T19:23:00Z",
      "title": "make a pokemon red mew starter save",
      "snippet": "...generated «pokemon» starter screenshots...",
      "score": -7.42,
      "meta": { "conversation_id": "conversation-uuid", "row_id": 4221 }
    },
    {
      "kind": "execution",
      "id": "execution-uuid",
      "timestamp": "2026-05-24T19:24:11Z",
      "title": "Orchestrator run · completed",
      "snippet": "...wrote ascii_screen.py for «pokemon»...",
      "score": -6.91,
      "meta": {
        "execution_id": "execution-uuid",
        "conversation_id": "conversation-uuid",
        "agent_id": null,
        "agent_name": "Orchestrator",
        "status": "completed",
        "provider": "OpenAI-Codex",
        "model": "gpt-5.5",
        "end_time": "2026-05-24T19:25:03Z"
      }
    }
  ]
}

Each result row is normalized to { kind, id, timestamp, title, snippet, score?, meta }. kind is one of conversation | execution | output | insight | memory | version. score is BM25 (lower = more relevant) and is only present when q was provided. meta carries kind-specific identifiers — most importantly meta.execution_id, which you can pass to /trace/:id for the full trace.

Recent

GET /recent

  • Authentication: Required
  • Description: Time-bounded "what happened recently?" lookup without a keyword. Useful for "what did you do last week" style questions where the user wants a chronological summary, not a search.
  • Query Parameters:
    • days (integer, optional): Days back from now. Default 7. Minimum 1.
    • kind (string, optional): Restrict to a single source: conversations | executions | outputs | insights | memory | versions. Omit to include all.
    • limit (integer, optional): Max results to return. Default 100, cap 500.
  • Example: GET /api/memory/recent?days=7&kind=executions&limit=50
  • Response: Same shape as /search, but rows are sorted by timestamp DESC and have no score field.

Get Trace Detail

GET /trace/:executionId

  • Authentication: Required
  • Description: Full detail for a single agent_executions row plus its agent_tool_executions children. Convenience wrapper around GET /api/executions/agents/:id that additionally parses each tool call's input / output JSON.
  • Parameters:
    • executionId (path): The agent_executions.id UUID. Most easily obtained from result.meta.execution_id on a /search or /recent result.
  • Response:
{
  "success": true,
  "trace": {
    "id": "execution-uuid",
    "agentId": null,
    "agentName": "Orchestrator",
    "conversationId": "conversation-uuid",
    "userId": "user-uuid",
    "status": "completed",
    "startTime": "2026-05-24T19:24:11Z",
    "endTime": "2026-05-24T19:25:03Z",
    "initialPrompt": "make a pokemon red mew starter save",
    "finalResponse": "Created the starter save and saved screenshots to ...",
    "provider": "OpenAI-Codex",
    "model": "gpt-5.5",
    "totalTokens": 66319,
    "estimatedCost": 0.0906,
    "toolExecutions": [
      {
        "id": "tool-exec-uuid",
        "tool_name": "execute_shell_command",
        "start_time": "2026-05-24T19:24:14Z",
        "end_time": "2026-05-24T19:24:15Z",
        "status": "completed",
        "input": { "command": "node inspect_save.py", "cwd": "." },
        "output": { "success": true, "stdout": "..." },
        "error": null,
        "credits_used": 0.12
      }
    ]
  }
}

If the trace doesn't exist (or belongs to a different user), responds 404 { success: false, error: "Trace not found" }.

Implementation Notes

  • FTS5 indexes are created on first boot via setupFullTextSearch() in backend/src/models/database/fts.js. Existing rows are backfilled once; thereafter AFTER INSERT / UPDATE / DELETE triggers keep the FTS tables in sync with their source.
  • Keyword sanitization strips everything but [a-zA-Z0-9_-], then appends * to each surviving token for prefix expansion. This is both safe (no FTS5 syntax injection) and forgiving (pokemon matches pokemons, pokemon-red).
  • All queries are scoped to req.user.userId — there is no cross-user visibility. workflow_versions doesn't store user_id directly, so the versions source joins through workflows.user_id for scoping.
  • Wrong-method requests (e.g. POST /api/memory/search) respond 405 with { success: false, error: "Method POST not allowed. Use GET ..." } and an Allow: GET header — never an HTML error page.

Evolution / Insight Routes

Base path: /api/insights

The unified evolution system extracts actionable insights from agent chats, goal executions, and workflow runs. Insights can target agents, skills, workflows, or tools and are automatically generated when executions complete. The system also manages per-agent memory (facts, preferences, corrections learned from conversations).

List Insights

GET /

  • Authentication: Required
  • Parameters:
    • targetType (query, optional): Filter by target type (agent, skill, workflow, tool)
    • targetId (query, optional): Filter by target ID
    • status (query, optional): Filter by status (pending, applied, rejected)
    • category (query, optional): Filter by category (memory, prompt_refinement, skill_recommendation, tool_preference, bottleneck, optimization, error_pattern, skill_candidate)
    • limit (query, optional): Max results (default: 100)
  • Response:
{
  "success": true,
  "insights": [
    {
      "id": "insight-uuid",
      "user_id": "user-id",
      "source_type": "agent_chat|goal|workflow",
      "source_id": "execution-id",
      "target_type": "agent|skill|workflow|tool",
      "target_id": "agent-id",
      "category": "prompt_refinement",
      "title": "Insight title",
      "description": "Detailed description",
      "confidence": 0.85,
      "priority": "medium",
      "status": "pending",
      "source_context": {},
      "evidence": {},
      "applied_result": null,
      "created_at": "2024-01-01T00:00:00Z"
    }
  ]
}

Get Insight Stats

GET /stats

  • Authentication: Required
  • Description: Get aggregated insight counts grouped by status and target type
  • Response:
{
  "success": true,
  "statusCounts": { "pending": 12, "applied": 8, "rejected": 2 },
  "targetCounts": { "agent": 10, "skill": 5, "workflow": 7 }
}

Get Insights by Target

GET /target/:targetType/:targetId

  • Authentication: Required
  • Parameters:
    • targetType (path): agent, skill, workflow, or tool
    • targetId (path): Target entity ID
    • status (query, optional): Filter by status
  • Description: Get all insights targeting a specific entity (e.g., all insights for a particular agent)
  • Response:
{
  "success": true,
  "insights": [ ... ]
}

Get Insights by Source

GET /source/:sourceType/:sourceId

  • Authentication: Required
  • Parameters:
    • sourceType (path): agent_chat, goal, or workflow
    • sourceId (path): Source execution ID
  • Description: Get all insights generated from a specific execution (e.g., all insights extracted from a particular goal run)
  • Response:
{
  "success": true,
  "insights": [ ... ]
}

Get Single Insight

GET /:id

  • Authentication: Required
  • Parameters:
    • id (path): Insight ID
  • Response:
{
  "success": true,
  "insight": { ... }
}
  • Error (404): Insight not found

Autonomy Router — Route Pending Insights (PRD-091 Layer 4)

POST /route

  • Authentication: Required
  • Body (optional):
{
  "provider": "anthropic",
  "model": "claude-sonnet-4-20250514"
}
  • Description: Sweeps every pending insight for the user through the InsightAutonomyRouter. Each insight is evaluated by AutonomyPolicy.evaluate(insight, settings, ctx) against EvolutionSettingsModel.autonomy. Per-insight verdicts:

    • direct — apply now (memory, low blast radius, high confidence)
    • gated — sandbox-test before applying (blast radius ≥ requireGateAbove)
    • escalate — surface to the user as pending with autonomy_decision: 'escalate' (high blast OR low confidence OR over budget)
    • skip — autonomy disabled (the default state)

    Every applied insight gets a row in mutation_history with fitness_before captured for canary detection. Prefer this over /:id/apply for unattended flows.

  • Response:

{
  "success": true,
  "summary": {
    "evaluated": 12,
    "direct": 4,
    "gated": 2,
    "escalated": 5,
    "skipped": 1,
    "mutationIds": ["m-uuid", "m-uuid", "..."]
  }
}

Autonomy Router — Route One Insight

POST /:id/route

  • Authentication: Required
  • Body (optional): same { provider, model } override as above
  • Description: Same logic as POST /route but applied to a single insight by id. Useful when surfacing "auto-handle this?" affordance per row.
  • Response:
{
  "success": true,
  "result": {
    "decision": "direct|gated|escalate|skip",
    "reason": "low_blast_high_confidence",
    "blastRadius": 0.1,
    "mutationId": "m-uuid|null"
  }
}

Evolution Settings — Get

GET /settings

  • Authentication: Required
  • Description: Returns the user's evolution settings, including the autonomy policy block. Defaults live in AutonomyPolicy.DEFAULTS (see Closed Loop System section above) — only user overrides are persisted.
  • Response:
{
  "success": true,
  "settings": {
    "autonomy": {
      "enabled": false,
      "minConfidence": 0.7,
      "maxBlastRadius": 0.5,
      "dailyBudget": 20,
      "allowedCategories": ["memory", "prompt_refinement", "..."],
      "requireGateAbove": 0.45
    }
  }
}

Evolution Settings — Update (Opt-In Switch)

POST /settings

  • Authentication: Required
  • Body:
{
  "autonomy": { "enabled": true, "minConfidence": 0.8 }
}
  • Description: This is the user opt-in switch. Flipping autonomy.enabled to true is what permits the router to direct-apply insights. The agent must never flip this without explicit, in-conversation confirmation from the user.
  • Response:
{ "success": true, "settings": { ... } }

Apply Insight (Direct, Bypasses Router)

POST /:id/apply

  • Authentication: Required
  • Parameters:
    • id (path): Insight ID
  • Body (optional):
{
  "provider": "anthropic",
  "model": "claude-sonnet-4-20250514"
}
  • Description: Apply an insight to its target entity. For agent prompt refinements, uses LLM to merge the improvement into the agent's system prompt. Provider/model override the user's defaults for the LLM call. The insight status is updated to applied.

Note vs. router endpoints above: /:id/apply is the human-confirmed path — bypasses AutonomyPolicy entirely and just runs the applicator. Use this when the user clicks an "Apply" button. Use POST /route or POST /:id/route for unattended / batched application that respects the user's policy. Do not call /apply on critical-priority insights without explicit confirmation.

  • Response:
{
  "success": true,
  "result": {
    "applied": true,
    "changes": { ... }
  }
}

Reject Insight

POST /:id/reject

  • Authentication: Required
  • Parameters:
    • id (path): Insight ID
  • Description: Mark an insight as rejected
  • Response:
{
  "success": true,
  "message": "Insight rejected"
}

Delete Insight

DELETE /:id

  • Authentication: Required
  • Parameters:
    • id (path): Insight ID
  • Response:
{
  "success": true,
  "deleted": true
}

Trigger Periodic Rollup

POST /rollup

  • Authentication: Required
  • Description: Manually trigger tool usage rollup analysis. Extracts tool preference insights from recent execution history.
  • Response:
{
  "success": true,
  "count": 3,
  "insightIds": ["id-1", "id-2", "id-3"]
}

Get Agent Memories

GET /memory/:agentId

  • Authentication: Required
  • Parameters:
    • agentId (path): Agent ID
    • memoryType (query, optional): Filter by type (fact, preference, correction)
  • Description: Get all memories for an agent. Memories are facts, preferences, and corrections learned from conversations.
  • Response:
{
  "success": true,
  "memories": [
    {
      "id": "memory-uuid",
      "agent_id": "agent-id",
      "user_id": "user-id",
      "memory_type": "fact",
      "content": "User prefers TypeScript over JavaScript",
      "relevance_score": 0.9,
      "created_at": "2024-01-01T00:00:00Z"
    }
  ]
}

Add Agent Memory

POST /memory/:agentId

  • Authentication: Required
  • Parameters:
    • agentId (path): Agent ID
  • Body:
{
  "memoryType": "fact|preference|correction",
  "content": "User prefers concise answers"
}
  • Response:
{
  "success": true,
  "id": "memory-uuid"
}

Update Agent Memory

PUT /memory/entry/:id

  • Authentication: Required
  • Parameters:
    • id (path): Memory entry ID
  • Body:
{
  "content": "Updated memory content",
  "relevanceScore": 0.95,
  "memoryType": "preference"
}
  • Response:
{
  "success": true,
  "updated": true
}

Delete Agent Memory

DELETE /memory/entry/:id

  • Authentication: Required
  • Parameters:
    • id (path): Memory entry ID
  • Response:
{
  "success": true,
  "deleted": true
}

Run Core Evolution Loop

POST /api/evolution/core/run

  • Authentication: Required
  • Description: Runs the built-in evolution loop in recommendation-first mode. GA work is synchronous/CPU-bound, so all numeric inputs are defensively clamped server-side.
  • Body (all optional):
{
  "lookbackDays": 7,
  "pendingInsightLimit": 250,
  "populationSize": 24,
  "generations": 10,
  "eliteCount": 6,
  "apply": false
}
  • Clamps: lookbackDays 1–90, pendingInsightLimit 1–5000, populationSize 1–200, generations 1–50, eliteCount 1–populationSize
  • Response:
{
  "success": true,
  "recommendation": {}
}

List Core Evolution Runs

GET /api/evolution/core/runs

  • Authentication: Required
  • Parameters:
    • limit (query, optional): 1–2000, default 200
  • Response: { "success": true, "runs": [...] }

List Performance Snapshots

GET /api/evolution/core/snapshots

  • Authentication: Required
  • Parameters:
    • limit (query, optional): 1–2000, default 200
  • Description: Recent user-scoped evolution performance snapshots
  • Response: { "success": true, "snapshots": [...] }

Get All Memories

GET /api/insights/memory

  • Authentication: Required
  • Description: All agent memories for the current user, across all agents
  • Parameters:
    • limit (query, optional): 1–50000, default 5000
    • sort (query, optional): recent (default) or relevance
  • Response:
{
  "success": true,
  "memories": [],
  "count": 0
}

Delete Orphaned Memories

DELETE /api/insights/memory/orphaned

  • Authentication: Required
  • Description: Deletes every memory whose agent_id no longer exists (i.e. the agent was deleted). The special ids orchestrator and __orchestrator__ are always preserved.
  • Response:
{
  "success": true,
  "deleted": 7
}