API Reference¶
Complete endpoint documentation for the FastAPI router created by
create_agent_router(). All endpoints are prefixed with the prefix
argument (default /api/v1).
Authentication¶
All endpoints require the X-Tenant-ID header for multi-tenant isolation.
| Header | Required | Description |
|---|---|---|
X-Tenant-ID |
Yes | Unique tenant identifier |
X-Sub-Tenant-ID |
No | Sub-tenant for hierarchical tenancy |
X-Namespace |
No | Memory namespace override |
Missing X-Tenant-ID returns 401 Unauthorized.
Chat Endpoints¶
POST /api/v1/chat¶
Execute the agent synchronously and return the full response.
Request Body (AgentRequest):
{
"query": "What do you know about our brand?",
"tenant_id": "acme-corp",
"sub_tenant_id": null,
"namespace": null,
"session_id": null,
"metadata": null
}
| Field | Type | Required | Description |
|---|---|---|---|
query |
string |
Yes | The user message (min 1 char) |
tenant_id |
string |
Yes | Tenant identifier (min 1 char) |
sub_tenant_id |
string |
No | Sub-tenant identifier |
namespace |
string |
No | Memory namespace |
session_id |
string |
No | Session ID for conversation continuity |
metadata |
object |
No | Arbitrary key-value metadata |
Response (AgentResponse):
{
"final_response": "Your brand voice is friendly and expert...",
"messages": [],
"memory_entries_used": 3,
"system_prompt_tokens": 1250,
"run_id": "a1b2c3d4-...",
"duration_ms": 1423.5,
"extracted_ops": [
{"type": "upsert_node", "label": "SOUL", "properties": {"brand_voice": "friendly"}}
],
"graph_edges": [
{"source": "SOUL", "target": "BRAND", "relationship": "DEFINES"}
],
"activation_log": {
"activated_nodes": [{"name": "SOUL", "score": 0.92}],
"inference_paths": [["SOUL", "BRAND"]],
"pending_connections": []
}
}
| Field | Type | Description |
|---|---|---|
final_response |
string |
The agent's text response |
messages |
list |
Raw LangChain message history |
memory_entries_used |
int |
Number of memory entries injected |
system_prompt_tokens |
int |
Token count of the system prompt |
run_id |
string |
Unique execution identifier |
duration_ms |
float |
Total execution time in milliseconds |
extracted_ops |
list |
Memory operations extracted from the response |
graph_edges |
list |
Knowledge graph edges created or referenced |
activation_log |
object |
Spreading activation metadata |
Error Codes: 400 (invalid scope), 403 (security scope error), 500 (agent failure)
Example:
curl -X POST http://localhost:8000/api/v1/chat \
-H "Content-Type: application/json" \
-H "X-Tenant-ID: acme-corp" \
-d '{"query": "What is our brand voice?", "tenant_id": "acme-corp"}'
POST /api/v1/stream¶
Stream agent execution as Server-Sent Events (SSE). Same request body as
/chat, but the response is an SSE event stream.
As of v5.3.0, text chunks are filtered through ExtractionFilter --
<MEMORY_EXTRACT> and <GRAPH_OPERATIONS> blocks are never sent to the
client. The done event includes the stripped final_response.
Event Types:
| Event | Description |
|---|---|
thinking |
Agent is processing (extended thinking content) |
acting |
Agent is executing a tool |
text_delta |
Incremental text chunk from the LLM (extraction tags stripped) |
tool_call |
Tool invocation details |
tool_result |
Tool execution result |
consolidating |
Memory consolidation in progress |
spreading_activation |
Activation expansion metadata |
memory_extracted |
Extraction blocks parsed from the response (v5.3.0) |
consolidation_done |
Background memory write complete (v5.3.0) |
response_complete |
All processing finished (v5.3.0) |
done |
Stream complete, final metadata |
error |
Error occurred during streaming |
StreamChunk Schema (each SSE data field):
{
"event_type": "text_delta",
"data": "partial text...",
"timestamp": "2026-03-30T12:00:00Z",
"run_id": "a1b2c3d4-..."
}
Example:
curl -N -X POST http://localhost:8000/api/v1/stream \
-H "Content-Type: application/json" \
-H "X-Tenant-ID: acme-corp" \
-d '{"query": "Analyze our sales data", "tenant_id": "acme-corp"}'
JavaScript Client:
const eventSource = new EventSource('/api/v1/stream', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Tenant-ID': 'acme-corp',
},
body: JSON.stringify({ query: 'Hello!', tenant_id: 'acme-corp' }),
});
eventSource.addEventListener('text_delta', (e) => {
const chunk = JSON.parse(e.data);
document.getElementById('output').textContent += chunk.data;
});
eventSource.addEventListener('done', () => eventSource.close());
eventSource.addEventListener('error', (e) => console.error(e));
Session Endpoints¶
GET /api/v1/chats¶
List active chat sessions for the current tenant.
Response: Array of session objects.
[
{
"session_id": "sess-abc123",
"tenant_id": "acme-corp",
"created_at": "2026-03-30T10:00:00Z",
"message_count": 5
}
]
Example:
Memory Endpoints¶
GET /api/v1/memories/status¶
Return the status of all known memory blocks for the current tenant, grouped by label. Useful for dashboards showing memory health.
Response (MemoryBlockStatus[]):
[
{
"label": "SOUL",
"exists": true,
"entry_count": 6,
"last_updated": "2026-03-30T12:00:00Z",
"importance": 8.5,
"layer": "semantic",
"enabled": true,
"is_required": true
}
]
| Field | Type | Description |
|---|---|---|
label |
string |
Block label (e.g. SOUL, PRODUCT) |
exists |
bool |
Whether any entries exist for this label |
entry_count |
int |
Number of entries in the block |
last_updated |
datetime |
Most recent entry timestamp |
importance |
float |
Average importance score (0.0-10.0) |
layer |
string |
Memory layer name |
enabled |
bool |
Whether this layer is enabled in config |
is_required |
bool |
Whether the domain template requires this block |
Example:
GET /api/v1/memories¶
The kernel router lists committed memories visible to the authenticated scope.
Use layer to narrow the layer, limit (1–500; default 200) to bound a page,
and cursor to continue. Records are ordered by record ID, not recall score.
Pending and retracted graph records are excluded before pagination.
The response remains an array. X-Memory-Has-More: true means another page is
available; pass X-Memory-Next-Cursor verbatim as the next request's cursor.
Keep the same scope and layer filter. No cursor header means the traversal
has finished. Invalid cursors or limits return 400 (malformed integer input
returns 422); a custom store without inventory support returns 501 explicitly.
Pagination is a keyset traversal of a stable set, not a snapshot. Concurrent inserts behind the cursor require restarting the listing. Deleting an earlier record does not shift later pages. Listing pages do not constrain recall or spreading activation; those retain their independent scopes and budgets.
Response: Array of node objects.
[
{
"id": "node-uuid-...",
"layer": "semantic",
"content": "Brand voice is friendly and expert",
"scope_path": "acme-corp",
"salience": 0.9,
"origin": "extraction",
"revision": "example-revision",
"metadata": {}
}
]
Example:
To inspect continuation headers, use curl -i; then request the next page
with curl --get --data-urlencode 'cursor=TOKEN' --data 'limit=200' and the same
authentication headers. The generated Brain graph uses the separate
/graph/nodes and /edges APIs.
PATCH /api/v1/memories/{node_id}¶
Update a memory node's content, properties, or importance. Adds an
audit trail (_last_edited_by: user_manual_edit).
Request Body (all fields optional):
{
"content": "Updated brand voice description",
"properties": {"brand_voice": "professional yet approachable"},
"importance": 9.5
}
Response:
{
"id": "node-uuid-...",
"label": "Updated brand voice description",
"properties": {"brand_voice": "professional yet approachable", "_last_edited_by": "user_manual_edit"}
}
Example:
curl -X PATCH http://localhost:8000/api/v1/memories/node-uuid-123 \
-H "Content-Type: application/json" \
-H "X-Tenant-ID: acme-corp" \
-d '{"importance": 9.5}'
DELETE /api/v1/memories/{node_id}¶
Delete a memory node and cascade-delete its edges.
Response:
Error: 404 if the node does not exist.
Example:
Procedural Endpoints (Workflows)¶
GET /api/v1/procedures¶
List all procedural nodes (learned workflows and skills).
Response:
[
{
"id": "proc-uuid-...",
"label": "Monthly Inventory Check",
"content": "Monthly Inventory Check",
"steps": ["Pull inventory levels", "Compare to reorder points", "Generate PO"],
"properties": {"active": true},
"active": true,
"importance": 7.0,
"created_at": "2026-03-29T08:00:00Z",
"updated_at": "2026-03-30T12:00:00Z"
}
]
Example:
PATCH /api/v1/procedures/{node_id}¶
Update a procedure's steps, properties, or active flag.
Request Body (all fields optional):
{
"steps": ["Step 1", "Step 2", "Step 3"],
"active": true,
"properties": {"category": "inventory"}
}
Response: Updated procedure object (same shape as list item).
Example:
curl -X PATCH http://localhost:8000/api/v1/procedures/proc-uuid-123 \
-H "Content-Type: application/json" \
-H "X-Tenant-ID: acme-corp" \
-d '{"steps": ["Check levels", "Generate PO", "Notify supplier"]}'
POST /api/v1/procedures/deduplicate¶
Merge semantically similar procedural nodes. Uses SemanticMerge with
a similarity threshold of 0.75. Absorbed duplicates are deleted;
canonical nodes are updated in-place.
Response:
{
"merged": 2,
"remaining": [
{
"id": "proc-uuid-...",
"label": "Monthly Inventory Check",
"content": "Monthly Inventory Check",
"steps": ["..."],
"active": true,
"importance": 7.0
}
]
}
Example:
POST /api/v1/procedures/{node_id}/toggle¶
Toggle a procedure's active flag (true becomes false and vice versa).
Response:
{
"id": "proc-uuid-123",
"label": "Monthly Inventory Check",
"active": false,
"properties": {"active": false, "_last_edited_by": "user_toggle"}
}
Example:
curl -X POST http://localhost:8000/api/v1/procedures/proc-uuid-123/toggle \
-H "X-Tenant-ID: acme-corp"
Consolidation Endpoints¶
POST /api/v1/memory/consolidate¶
Trigger manual Deep Sleep consolidation for the tenant. Runs a 7-phase process: replay traces, strengthen recurring nodes, prune orphans, generate meta-nodes from clusters, update SOUL schema from user corrections, and write pending inferred edges.
Request Body (optional):
The pending_connections array is typically obtained from the
activation_log.pending_connections field returned by /chat.
Response (ConsolidationReport):
{
"strengthened_count": 4,
"pruned_count": 1,
"meta_nodes_created": 2,
"edges_written": 3,
"soul_updates": 1,
"duration_ms": 245.3
}
Example:
curl -X POST http://localhost:8000/api/v1/memory/consolidate \
-H "Content-Type: application/json" \
-H "X-Tenant-ID: acme-corp" \
-d '{}'
Graph Service Endpoints¶
Added in v5.4.0. These endpoints are mounted by create_graph_router() and
provide graph traversal, pathfinding, edge CRUD, and cluster projections.
All endpoints require the X-Tenant-ID header.
GET /api/v1/graph/nodes/{node_id}/neighborhood¶
Return a node's neighborhood up to depth hops via BFS traversal.
Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
depth |
int |
2 |
Maximum traversal depth |
min_weight |
float |
0 |
Minimum edge weight to include |
Response (NeighborhoodResponse):
{
"center": {"id": "node-1", "label": "SOUL: brand_voice", "layer": "semantic", "importance": 9.0},
"neighbors": [
{"id": "node-2", "label": "BRAND: voice", "layer": "semantic", "importance": 7.5, "depth": 1}
],
"edges": [
{"id": "edge-1", "source": "node-1", "target": "node-2", "relationship": "DEFINES", "weight": 1.0}
]
}
| Field | Type | Description |
|---|---|---|
center |
object |
The queried node (id, label, layer, importance) |
neighbors |
NeighborNode[] |
Discovered neighbor nodes with BFS depth |
edges |
EdgeData[] |
Edges connecting the traversed subgraph |
Error Codes: 400 (invalid scope), 403 (access denied), 404 (node not found or semantic layer unavailable), 500 (internal error)
Example:
curl http://localhost:8000/api/v1/graph/nodes/node-uuid-123/neighborhood?depth=2\&min_weight=0.5 \
-H "X-Tenant-ID: acme-corp"
GET /api/v1/graph/paths¶
Find the shortest path between two nodes in the knowledge graph.
Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
start |
string |
Yes | Source node ID |
end |
string |
Yes | Target node ID |
Response (PathResponse):
{
"path": [
{"id": "node-1", "label": "SOUL", "layer": "semantic"},
{"id": "node-2", "label": "BRAND", "layer": "semantic"}
],
"edges": [
{"id": "edge-1", "source": "node-1", "target": "node-2", "relationship": "DEFINES", "weight": 1.0}
],
"hops": 1
}
| Field | Type | Description |
|---|---|---|
path |
PathNode[] |
Ordered list of nodes from start to end |
edges |
EdgeData[] |
Connecting edges between consecutive path nodes |
hops |
int |
Number of edges in the path (0 if no path found) |
Returns an empty path (hops: 0) when no path exists.
Error Codes: 400 (invalid scope), 403 (access denied), 404 (start or end node not found), 500 (internal error)
Example:
curl "http://localhost:8000/api/v1/graph/paths?start=node-1&end=node-2" \
-H "X-Tenant-ID: acme-corp"
GET /api/v1/edges¶
List edges for the current tenant with optional pagination and filtering.
Query Parameters (added in v5.5.0):
| Parameter | Type | Default | Description |
|---|---|---|---|
limit |
int |
50 |
Maximum number of edges to return |
offset |
int |
0 |
Number of edges to skip (for pagination) |
relationship |
string |
null |
Filter by relationship type (e.g. CO_OCCURRED) |
Response: Array of EdgeData objects.
[
{"id": "edge-1", "source": "node-1", "target": "node-2", "relationship": "DEFINES", "weight": 1.0}
]
Example:
curl http://localhost:8000/api/v1/edges \
-H "X-Tenant-ID: acme-corp"
# With pagination and filtering
curl "http://localhost:8000/api/v1/edges?limit=20&offset=0&relationship=CO_OCCURRED" \
-H "X-Tenant-ID: acme-corp"
POST /api/v1/edges¶
Create a new edge between two existing nodes. If an edge with the same
source, target, and relationship already exists, its weight is incremented
by 1 (upsert semantics, added in v5.5.0). Postgres uses
ON CONFLICT DO UPDATE SET weight = weight + 1. Weight is capped at
MAX_CO_OCCUR_DEGREE = 10 to prevent unbounded growth.
Request Body (CreateEdgeRequest):
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
source |
string |
Yes | -- | Source node ID |
target |
string |
Yes | -- | Target node ID |
relationship |
string |
Yes | -- | Edge relationship type (e.g. DEFINES, CO_OCCURRED) |
weight |
float |
No | 1.0 |
Edge weight (must be > 0) |
Response: The created EdgeData object with server-assigned id.
Example:
curl -X POST http://localhost:8000/api/v1/edges \
-H "Content-Type: application/json" \
-H "X-Tenant-ID: acme-corp" \
-d '{"source": "node-1", "target": "node-2", "relationship": "DEFINES", "weight": 1.0}'
DELETE /api/v1/edges/{edge_id}¶
Delete an edge by ID.
Response:
Example:
GET /api/v1/graph/clusters¶
Project all semantic nodes into clusters grouped by label prefix. Each cluster includes a representative (highest-importance member).
Response: Array of ClusterProjection objects.
[
{
"prefix": "SOUL",
"count": 3,
"members": [
{"id": "node-1", "label": "SOUL: brand_voice"},
{"id": "node-2", "label": "SOUL: name"},
{"id": "node-3", "label": "SOUL: tone"}
],
"representative": {"id": "node-1", "label": "SOUL: brand_voice"}
}
]
| Field | Type | Description |
|---|---|---|
prefix |
string |
The label prefix used for grouping |
count |
int |
Number of nodes in the cluster |
members |
ClusterMember[] |
All nodes in the cluster |
representative |
ClusterMember |
Highest-importance member (or null) |
Example:
Bulk Operations¶
Added in v5.5.0. Batch operations for edge and memory cleanup.
POST /api/v1/edges/bulk-delete¶
Delete multiple edges in a single request.
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
ids |
string[] |
Yes | List of edge IDs to delete |
Response:
Example:
curl -X POST http://localhost:8000/api/v1/edges/bulk-delete \
-H "Content-Type: application/json" \
-H "X-Tenant-ID: acme-corp" \
-d '{"ids": ["edge-1", "edge-2", "edge-3"]}'
POST /api/v1/memories/bulk-delete¶
Delete multiple memory nodes in a single request. Edges connected to deleted nodes are cascade-deleted.
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
ids |
string[] |
Yes | List of memory node IDs to delete |
Response:
Example:
curl -X POST http://localhost:8000/api/v1/memories/bulk-delete \
-H "Content-Type: application/json" \
-H "X-Tenant-ID: acme-corp" \
-d '{"ids": ["node-1", "node-2"]}'
Graph Maintenance Endpoints¶
Added in v5.5.0. Audit and prune operations for knowledge graph health.
GET /api/v1/memory/audit¶
Analyze graph health: detect orphan nodes, weak edges, and calculate graph density metrics.
Response (AuditReport):
{
"total_nodes": 42,
"total_edges": 87,
"orphan_nodes": [
{"id": "node-15", "label": "PRODUCT: legacy_sku"}
],
"weak_edges": [
{"id": "edge-33", "source": "node-5", "target": "node-15", "weight": 0.1}
],
"density": 0.10,
"avg_edge_weight": 2.3
}
| Field | Type | Description |
|---|---|---|
total_nodes |
int |
Total node count for the tenant |
total_edges |
int |
Total edge count for the tenant |
orphan_nodes |
NodeSummary[] |
Nodes with zero edges |
weak_edges |
EdgeData[] |
Edges below the default weight threshold |
density |
float |
Graph density (edges / possible edges) |
avg_edge_weight |
float |
Mean edge weight across all edges |
Example:
POST /api/v1/memory/prune¶
Remove orphan nodes and weak edges from the knowledge graph. Supports dry-run mode to preview changes without applying them.
Request Body (all fields optional):
| Field | Type | Default | Description |
|---|---|---|---|
prune_orphans |
bool |
true |
Remove nodes with zero edges |
prune_weak_edges |
bool |
true |
Remove edges below min_edge_weight |
min_edge_weight |
float |
1.0 |
Minimum weight threshold for edge retention |
dry_run |
bool |
false |
If true, return what would be pruned without applying changes |
Response (PruneReport):
| Field | Type | Description |
|---|---|---|
orphans_removed |
int |
Number of orphan nodes removed (or that would be) |
edges_removed |
int |
Number of weak edges removed (or that would be) |
dry_run |
bool |
Whether this was a dry run |
Example:
# Preview what would be pruned
curl -X POST http://localhost:8000/api/v1/memory/prune \
-H "Content-Type: application/json" \
-H "X-Tenant-ID: acme-corp" \
-d '{"dry_run": true}'
# Actually prune weak edges with custom threshold
curl -X POST http://localhost:8000/api/v1/memory/prune \
-H "Content-Type: application/json" \
-H "X-Tenant-ID: acme-corp" \
-d '{"prune_orphans": false, "prune_weak_edges": true, "min_edge_weight": 2.0}'
Demo-Only Endpoints¶
The following endpoints are provided by the full demo application
(examples/full_demo/server.py) and are not part of the core
create_agent_router(). They serve as reference implementations.
GET /api/v1/memory/graph¶
Returns knowledge graph data formatted for vis.js visualization. Nodes are colored by layer and sized by importance.
Response:
{
"nodes": [
{"id": "node-1", "label": "SOUL: brand_voice", "group": "semantic", "value": 9.0}
],
"edges": [
{"from": "node-1", "to": "node-2", "label": "DEFINES"}
]
}
Example:
GET /api/v1/memory/metrics¶
Returns memory layer metrics: entry counts, average importance, and last-updated timestamps per layer.
Response:
{
"semantic": {"count": 12, "avg_importance": 7.3},
"episodic": {"count": 45, "avg_importance": 5.1},
"working": {"count": 3, "avg_importance": 6.0},
"procedural": {"count": 8, "avg_importance": 7.0},
"prospective": {"count": 2, "avg_importance": 6.5}
}
Example:
Error Responses¶
All endpoints return standard HTTP error codes with a JSON body:
| Code | Meaning |
|---|---|
| 400 | Invalid scope or request validation failure |
| 401 | Missing X-Tenant-ID header |
| 403 | Security scope error (invalid tenant access) |
| 404 | Resource not found (node, layer) |
| 500 | Internal server error or agent execution failure |