ESC
Deep Dive Web UI

Web UI

robotmem includes a built-in web dashboard for browsing, searching, and managing memories. It runs as a Flask application with 12 REST API endpoints.

Starting the Web UI

python -m robotmem web
# Server starts at http://localhost:6889

The web UI shares the same SQLite database as the MCP server.

Architecture

python -m robotmem web
         │
    ┌────▼────────────────────────┐
    │  Flask App Factory          │
    │  create_app()               │
    │                             │
    │  ┌────────────────────────┐ │
    │  │  api_bp (Blueprint)    │ │
    │  │  /api/*  (12 routes)   │ │
    │  └────────────────────────┘ │
    │                             │
    │  GET / → index.html         │
    │  CogDatabase (shared conn)  │
    └─────────────────────────────┘

The Flask app uses the app factory pattern (create_app()):

  1. Creates a CogDatabase instance with shared SQLite connection
  2. Registers the API blueprint at /api
  3. Serves the frontend at /

The database and config are stored in app.config:

app.config["ROBOTMEM_DB"] = db
app.config["ROBOTMEM_CONFIG"] = config

REST API Endpoints

Health & Statistics

GET /api/doctor

Health check endpoint — verifies database integrity and reports system status.

Response:

{
    "memories": {
        "total": 42,
        "by_type": {"fact": 35, "perception": 7}
    },
    "sessions": {
        "total": 8,
        "by_status": {"active": 1, "ended": 7}
    },
    "fts5": {
        "indexed": 42,
        "expected": 42,
        "ok": true
    },
    "vec0": {
        "indexed": 40,
        "expected": 42,
        "ok": false
    },
    "zero_hit": {
        "count": 5,
        "total": 42,
        "rate": 11.9
    },
    "db_size_bytes": 524288
}
Field Description
memories.total Active memories count
fts5.ok FTS5 index is in sync with memories table
vec0.ok vec0 index is in sync with memories table
zero_hit.rate Percentage of memories never recalled (potential cleanup candidates)
db_size_bytes Database file size on disk

GET /api/stats

Aggregate statistics.

Response:

{
    "total": 42,
    "by_type": {"fact": 35, "perception": 7},
    "by_category": {"observation": 20, "pattern": 8, "decision": 5, "constraint": 3},
    "collections": ["default", "grasping", "navigation"],
    "recent_24h": 3
}

Memory Operations

GET /api/memories

Paginated memory listing with filters.

Query Parameters:

Parameter Type Default Description
page int 0 Page number (0-indexed)
limit int 30 Items per page (1-100)
collection string Filter by collection
type string Filter by type (fact or perception)
status string active Filter by status
category string Filter by category (comma-separated for multiple)
confidence_min float Minimum confidence threshold
confidence_max float Maximum confidence threshold
days int Only memories from last N days
perception_type string Filter by perception type

Response:

{
    "memories": [
        {
            "id": 1,
            "collection": "default",
            "type": "fact",
            "content": "grip_force=12.5N optimal for cylindrical objects",
            "human_summary": "grip_force=12.5N optimal...",
            "perception_type": null,
            "category": "observation",
            "confidence": 0.85,
            "decay_rate": 0.01,
            "source": "tool",
            "scope": "project",
            "status": "active",
            "access_count": 5,
            "created_at": "2026-03-09T10:30:00",
            "updated_at": "2026-03-09T10:30:00"
        }
    ],
    "total": 42,
    "page": 0,
    "limit": 30,
    "pages": 2
}

GET /api/search?q=

Full-text search across memories using FTS5 BM25 ranking.

Query Parameters:

Parameter Type Default Description
q string (required) Search query
collection string Limit to collection (empty = all collections)
top_k int 10 Maximum results (1-50)

Response:

{
    "results": [
        {
            "id": 1,
            "content": "grip_force=12.5N optimal for cylindrical objects",
            "type": "fact",
            "category": "observation",
            "confidence": 0.85,
            "bm25_score": -3.45,
            "params": {"grip_force": {"value": 12.5, "unit": "N"}},
            "spatial": {"object_position": [1.3, 0.7, 0.42]},
            "created_at": "2026-03-09T10:30:00"
        }
    ],
    "total": 1,
    "query": "grip force"
}

When no collection is specified, the search runs across all collections and merges results by BM25 score.

Context fields (params, spatial, robot, task) are automatically extracted from the context JSON for convenience.

GET /api/memory/:id

Get full details for a single memory.

Response: Complete memory object (all fields from memories table, excluding embedding binary).

PUT /api/memory/:id

Update memory fields.

Request Body (JSON):

{
    "content": "updated content text",
    "confidence": 0.90,
    "category": "pattern"
}

Allowed fields: content, human_summary, category, confidence, decay_rate, scope, context

Response:

{
    "status": "updated",
    "memory_id": 1,
    "fields": ["content", "confidence"]
}

DELETE /api/memory/:id

Soft-delete a memory (sets status to invalidated).

Request Body (JSON):

{
    "reason": "Incorrect sensor reading"
}

If no reason is provided, defaults to "Web UI delete".

Response:

{
    "status": "deleted",
    "memory_id": 1
}

Session Operations

GET /api/sessions

Paginated session listing with memory counts.

Query Parameters:

Parameter Type Default Description
page int 0 Page number
limit int 20 Items per page (1-50)

Response:

{
    "sessions": [
        {
            "id": 1,
            "external_id": "550e8400-e29b-41d4-a716-446655440000",
            "collection": "default",
            "context": "{\"robot_id\": \"arm-01\"}",
            "session_count": 1,
            "status": "ended",
            "created_at": "2026-03-09T10:00:00",
            "updated_at": "2026-03-09T11:00:00",
            "memory_count": 15
        }
    ],
    "total": 8,
    "page": 0,
    "limit": 20
}

The memory_count field shows how many active memories belong to each session (via LEFT JOIN).

GET /api/sessions/:external_id/memories

List all memories within a session, ordered chronologically (timeline view).

Query Parameters:

Parameter Type Default Description
limit int 20 Maximum results (1-100)

Response:

{
    "memories": [
        {
            "id": 1,
            "type": "fact",
            "content": "Starting calibration...",
            "human_summary": "Starting calibration...",
            "perception_type": null,
            "category": "observation",
            "confidence": 0.85,
            "created_at": "2026-03-09T10:05:00"
        }
    ],
    "total": 15
}

Results are ordered by created_at ASC for chronological timeline viewing.

Metadata Operations

GET /api/collections

List all collections with memory counts.

Response:

{
    "collections": [
        {"name": "default", "count": 30},
        {"name": "grasping", "count": 10},
        {"name": "navigation", "count": 2}
    ]
}

GET /api/categories

List all categories present in the database with counts.

Response:

{
    "categories": [
        {"name": "observation", "count": 20},
        {"name": "pattern", "count": 8},
        {"name": "decision", "count": 5},
        {"name": "constraint", "count": 3}
    ]
}

GET /api/recent-failures

Recent postmortem and gotcha memories — useful for reviewing recent lessons learned.

Query Parameters:

Parameter Type Default Description
limit int 5 Maximum results (1-20)

Response:

{
    "failures": [
        {
            "id": 10,
            "collection": "default",
            "type": "fact",
            "content": "Lesson: always calibrate before new session",
            "human_summary": "Always calibrate before new session",
            "perception_type": null,
            "category": "postmortem",
            "confidence": 0.95,
            "created_at": "2026-03-09T10:30:00"
        }
    ],
    "total": 1
}

API Summary

Method Endpoint Description
GET /api/doctor Health check: FTS5/vec0 sync, zero-hit rate, DB size
GET /api/stats Total counts, type/category distribution, collections
GET /api/memories Paginated list with filters
GET /api/search?q= FTS5 full-text search
GET /api/memory/:id Single memory detail
PUT /api/memory/:id Update memory fields
DELETE /api/memory/:id Soft-delete memory
GET /api/sessions Paginated session list with memory counts
GET /api/sessions/:id/memories Memories within a session (timeline)
GET /api/collections Collection list with counts
GET /api/categories Category list with counts
GET /api/recent-failures Recent postmortem/gotcha memories

Three-Layer Defense

All API endpoints follow the same defense pattern:

Layer Phase Implementation
L1 Before Parameter validation (type, range, non-empty)
L2 During try-except wrapping all DB operations
L3 After Unified JSON response + error codes

Error Responses

All errors return JSON with an error field:

// 400 Bad Request
{"error": "q parameter cannot be empty"}

// 404 Not Found
{"error": "Memory #42 does not exist"}

// 500 Internal Server Error
{"error": "Query failed"}

The server never returns raw exception traces to the client.

Configuration

The web UI shares configuration with the MCP server. Key settings:

Setting Default Description
Port 6889 HTTP server port
Database ~/.robotmem/memory.db Same database as MCP server
ROBOTMEM_HOME ~/.robotmem Override config/database directory

Custom Port

Currently the port is hardcoded to 6889 in __main__.py. To change it, modify the Flask run command.

Shared Database

The Web UI and MCP server can run simultaneously — SQLite WAL mode supports concurrent readers. However, only one writer can operate at a time (5-second busy timeout).