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.
Search
GET /search
- Authentication: Required
- Description: Hybrid keyword + date-range search across all (or a subset of) memory sources. When
qis 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 mewmatches both terms with prefix expansion.- Alias:
queryis accepted as a synonym forq. If both are sent,qwins.
- Alias:
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 ofconversations,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 bytimestamp DESCand have noscorefield.
Get Trace Detail
GET /trace/:executionId
- Authentication: Required
- Description: Full detail for a single
agent_executionsrow plus itsagent_tool_executionschildren. Convenience wrapper aroundGET /api/executions/agents/:idthat additionally parses each tool call'sinput/outputJSON. - Parameters:
executionId(path): Theagent_executions.idUUID. Most easily obtained fromresult.meta.execution_idon a/searchor/recentresult.
- 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()inbackend/src/models/database/fts.js. Existing rows are backfilled once; thereafterAFTER INSERT / UPDATE / DELETEtriggers 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 (pokemonmatchespokemons,pokemon-red). - All queries are scoped to
req.user.userId— there is no cross-user visibility.workflow_versionsdoesn't storeuser_iddirectly, so the versions source joins throughworkflows.user_idfor scoping. - Wrong-method requests (e.g.
POST /api/memory/search) respond405with{ success: false, error: "Method POST not allowed. Use GET ..." }and anAllow: GETheader — 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 IDstatus(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, ortooltargetId(path): Target entity IDstatus(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, orworkflowsourceId(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 byAutonomyPolicy.evaluate(insight, settings, ctx)againstEvolutionSettingsModel.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 aspendingwithautonomy_decision: 'escalate'(high blast OR low confidence OR over budget)skip— autonomy disabled (the default state)
Every applied insight gets a row in
mutation_historywithfitness_beforecaptured for canary detection. Prefer this over/:id/applyfor 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 /routebut 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.enabledtotrueis 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/applyis the human-confirmed path — bypassesAutonomyPolicyentirely and just runs the applicator. Use this when the user clicks an "Apply" button. UsePOST /routeorPOST /:id/routefor unattended / batched application that respects the user's policy. Do not call/applyon 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 IDmemoryType(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:
lookbackDays1–90,pendingInsightLimit1–5000,populationSize1–200,generations1–50,eliteCount1–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 5000sort(query, optional):recent(default) orrelevance
- Response:
{
"success": true,
"memories": [],
"count": 0
}Delete Orphaned Memories
DELETE /api/insights/memory/orphaned
- Authentication: Required
- Description: Deletes every memory whose
agent_idno longer exists (i.e. the agent was deleted). The special idsorchestratorand__orchestrator__are always preserved. - Response:
{
"success": true,
"deleted": 7
}