Skip to main content

Worker Service

The worker service is a long-running HTTP API built with Express.js and managed natively by Bun. It processes observations through the Claude Agent SDK separately from hook execution to prevent timeout issues.

Overview

  • Technology: Express.js HTTP server
  • Runtime: Bun (auto-installed if missing)
  • Process Manager: Native Bun process management via ProcessManager
  • Port: Fixed port 37777 (configurable via CLAUDE_MEM_WORKER_PORT)
  • Location: src/services/worker-service.ts
  • Built Output: plugin/scripts/worker-service.cjs
  • Model: Configurable via CLAUDE_MEM_MODEL environment variable (default: sonnet)

REST API Endpoints

The worker service exposes 22 HTTP endpoints organized into six categories:

Viewer & Health Endpoints

1. Viewer UI

GET /
Purpose: Serves the web-based viewer UI (v5.1.0+) Response: HTML page with embedded React application Features:
  • Real-time memory stream visualization
  • Infinite scroll pagination
  • Project filtering
  • SSE-based live updates
  • Theme toggle (light/dark mode) as of v5.1.2

2. Health Check

GET /health
Purpose: Worker health status check Response:
{
  "status": "ok",
  "uptime": 12345,
  "port": 37777
}

3. Server-Sent Events Stream

GET /stream
Purpose: Real-time updates for viewer UI Response: SSE stream with events:
  • observation-created: New observation added
  • session-summary-created: New summary generated
  • user-prompt-created: New prompt recorded
Event Format:
event: observation-created
data: {"id": 123, "title": "...", ...}

Data Retrieval Endpoints

4. Get Prompts

GET /api/prompts?project=my-project&limit=20&offset=0
Purpose: Retrieve paginated user prompts Query Parameters:
  • project (optional): Filter by project name
  • limit (default: 20): Number of results
  • offset (default: 0): Pagination offset
Response:
{
  "prompts": [{
    "id": 1,
    "session_id": "abc123",
    "prompt": "User's prompt text",
    "prompt_number": 1,
    "created_at": "2025-11-06T10:30:00Z"
  }],
  "total": 150,
  "hasMore": true
}

5. Get Observations

GET /api/observations?project=my-project&limit=20&offset=0
Purpose: Retrieve paginated observations Query Parameters:
  • project (optional): Filter by project name
  • limit (default: 20): Number of results
  • offset (default: 0): Pagination offset
Response:
{
  "observations": [{
    "id": 123,
    "title": "Fix authentication bug",
    "type": "bugfix",
    "narrative": "...",
    "created_at": "2025-11-06T10:30:00Z"
  }],
  "total": 500,
  "hasMore": true
}

6. Get Summaries

GET /api/summaries?project=my-project&limit=20&offset=0
Purpose: Retrieve paginated session summaries Query Parameters:
  • project (optional): Filter by project name
  • limit (default: 20): Number of results
  • offset (default: 0): Pagination offset
Response:
{
  "summaries": [{
    "id": 456,
    "session_id": "abc123",
    "request": "User's original request",
    "completed": "Work finished",
    "created_at": "2025-11-06T10:30:00Z"
  }],
  "total": 100,
  "hasMore": true
}

7. Get Observation by ID

GET /api/observation/:id
Purpose: Retrieve a single observation by its ID Path Parameters:
  • id (required): Observation ID
Response:
{
  "id": 123,
  "sdk_session_id": "abc123",
  "project": "my-project",
  "type": "bugfix",
  "title": "Fix authentication bug",
  "narrative": "...",
  "created_at": "2025-11-06T10:30:00Z",
  "created_at_epoch": 1730886600000
}
Error Response (404):
{
  "error": "Observation #123 not found"
}

8. Get Observations by IDs (Batch)

POST /api/observations/batch
Purpose: Retrieve multiple observations by their IDs in a single request Request Body:
{
  "ids": [123, 456, 789],
  "orderBy": "date_desc",
  "limit": 10,
  "project": "my-project"
}
Body Parameters:
  • ids (required): Array of observation IDs
  • orderBy (optional): Sort order - date_desc or date_asc (default: date_desc)
  • limit (optional): Maximum number of results to return
  • project (optional): Filter by project name
Response:
[
  {
    "id": 789,
    "sdk_session_id": "abc123",
    "project": "my-project",
    "type": "feature",
    "title": "Add new feature",
    "narrative": "...",
    "created_at": "2025-11-06T12:00:00Z",
    "created_at_epoch": 1730891400000
  },
  {
    "id": 456,
    "sdk_session_id": "abc124",
    "project": "my-project",
    "type": "bugfix",
    "title": "Fix authentication bug",
    "narrative": "...",
    "created_at": "2025-11-06T10:30:00Z",
    "created_at_epoch": 1730886600000
  }
]
Error Responses:
  • 400 Bad Request: {"error": "ids must be an array of numbers"}
  • 400 Bad Request: {"error": "All ids must be integers"}
Use Case: This endpoint is used by the get_observations MCP tool to efficiently retrieve multiple observations in a single request, avoiding the overhead of multiple individual requests.

9. Get Session by ID

GET /api/session/:id
Purpose: Retrieve a single session by its ID Path Parameters:
  • id (required): Session ID
Response:
{
  "id": 456,
  "sdk_session_id": "abc123",
  "project": "my-project",
  "request": "User's original request",
  "completed": "Work finished",
  "created_at": "2025-11-06T10:30:00Z"
}
Error Response (404):
{
  "error": "Session #456 not found"
}

10. Get Prompt by ID

GET /api/prompt/:id
Purpose: Retrieve a single user prompt by its ID Path Parameters:
  • id (required): Prompt ID
Response:
{
  "id": 1,
  "session_id": "abc123",
  "prompt": "User's prompt text",
  "prompt_number": 1,
  "created_at": "2025-11-06T10:30:00Z"
}
Error Response (404):
{
  "error": "Prompt #1 not found"
}

12. Get Stats

GET /api/stats
Purpose: Get database statistics by project Response:
{
  "byProject": {
    "my-project": {
      "observations": 245,
      "summaries": 12,
      "prompts": 48
    },
    "other-project": {
      "observations": 156,
      "summaries": 8,
      "prompts": 32
    }
  },
  "total": {
    "observations": 401,
    "summaries": 20,
    "prompts": 80,
    "sessions": 20
  }
}

13. Get Projects

GET /api/projects
Purpose: Get list of distinct projects from observations Response:
{
  "projects": ["my-project", "other-project", "test-project"]
}

Settings Endpoints

14. Get Settings

GET /api/settings
Purpose: Retrieve user settings Response:
{
  "sidebarOpen": true,
  "selectedProject": "my-project",
  "theme": "dark"
}

15. Save Settings

POST /api/settings
Purpose: Persist user settings Request Body:
{
  "sidebarOpen": false,
  "selectedProject": "other-project",
  "theme": "light"
}
Response:
{
  "success": true
}

Queue Management Endpoints

16. Get Pending Queue Status

GET /api/pending-queue
Purpose: View current processing queue status and identify stuck messages Response:
{
  "queue": {
    "messages": [
      {
        "id": 123,
        "session_db_id": 45,
        "claude_session_id": "abc123",
        "message_type": "observation",
        "status": "pending",
        "retry_count": 0,
        "created_at_epoch": 1730886600000,
        "started_processing_at_epoch": null,
        "completed_at_epoch": null
      }
    ],
    "totalPending": 5,
    "totalProcessing": 2,
    "totalFailed": 0,
    "stuckCount": 1
  },
  "recentlyProcessed": [
    {
      "id": 122,
      "session_db_id": 44,
      "status": "processed",
      "completed_at_epoch": 1730886500000
    }
  ],
  "sessionsWithPendingWork": [44, 45, 46]
}
Status Definitions:
  • pending: Message queued, not yet processed
  • processing: Message currently being processed by SDK agent
  • processed: Message completed successfully
  • failed: Message failed after max retry attempts (3 by default)
Stuck Detection: Messages in processing status for >5 minutes are considered stuck and included in stuckCount Use Case: Check queue health after worker crashes or restarts to identify unprocessed observations

17. Trigger Manual Recovery

POST /api/pending-queue/process
Purpose: Manually trigger processing of pending queues (replaces automatic recovery in v5.x+) Request Body:
{
  "sessionLimit": 10
}
Body Parameters:
  • sessionLimit (optional): Maximum number of sessions to process (default: 10, max: 100)
Response:
{
  "success": true,
  "totalPendingSessions": 15,
  "sessionsStarted": 10,
  "sessionsSkipped": 2,
  "startedSessionIds": [44, 45, 46, 47, 48, 49, 50, 51, 52, 53]
}
Response Fields:
  • totalPendingSessions: Total sessions with pending messages in database
  • sessionsStarted: Number of sessions we started processing this request
  • sessionsSkipped: Sessions already actively processing (not restarted)
  • startedSessionIds: Database IDs of sessions started
Behavior:
  • Processes up to sessionLimit sessions with pending work
  • Skips sessions already actively processing (prevents duplicate agents)
  • Starts non-blocking SDK agents for each session
  • Returns immediately with status (processing continues in background)
Use Case: Manually recover stuck observations after worker crashes, or when automatic recovery was disabled Recovery Strategy Note: As of v5.x, automatic recovery on worker startup is disabled by default. Users must manually trigger recovery using this endpoint or the CLI tool (bun scripts/check-pending-queue.ts) to maintain explicit control over reprocessing.

Session Management Endpoints

19. Initialize Session

POST /sessions/:sessionDbId/init
Request Body:
{
  "sdk_session_id": "abc-123",
  "project": "my-project"
}
Response:
{
  "success": true,
  "session_id": "abc-123"
}

20. Add Observation

POST /sessions/:sessionDbId/observations
Request Body:
{
  "tool_name": "Read",
  "tool_input": {...},
  "tool_result": "...",
  "correlation_id": "xyz-789"
}
Response:
{
  "success": true,
  "observation_id": 123
}

21. Generate Summary

POST /sessions/:sessionDbId/summarize
Request Body:
{
  "trigger": "stop"
}
Response:
{
  "success": true,
  "summary_id": 456
}

22. Session Status

GET /sessions/:sessionDbId/status
Response:
{
  "session_id": "abc-123",
  "status": "active",
  "observation_count": 42,
  "summary_count": 1
}

23. Delete Session

DELETE /sessions/:sessionDbId
Response:
{
  "success": true
}
Note: As of v4.1.0, the cleanup hook no longer calls this endpoint. Sessions are marked complete instead of deleted to allow graceful worker shutdown.

Bun Process Management

Overview

The worker is managed by the native ProcessManager class which handles:
  • Process spawning with Bun runtime
  • PID file tracking at ~/.claude-mem/worker.pid
  • Health checks with automatic retry
  • Graceful shutdown with SIGTERM/SIGKILL fallback

Commands

# Start worker (auto-starts on first session)
npm run worker:start

# Stop worker
npm run worker:stop

# Restart worker
npm run worker:restart

# View logs
npm run worker:logs

# Check status
npm run worker:status

Auto-Start Behavior

The worker service auto-starts when the SessionStart hook fires. Manual start is optional.

Bun Requirement

Bun is required to run the worker service. If Bun is not installed, the smart-install script will automatically install it on first run:
  • Windows: powershell -c "irm bun.sh/install.ps1 | iex"
  • macOS/Linux: curl -fsSL https://bun.sh/install | bash
You can also install manually via:
  • winget install Oven-sh.Bun (Windows)
  • brew install oven-sh/bun/bun (macOS)

Claude Agent SDK Integration

The worker service routes observations to the Claude Agent SDK for AI-powered processing:

Processing Flow

  1. Observation Queue: Observations accumulate in memory
  2. SDK Processing: Observations sent to Claude via Agent SDK
  3. XML Parsing: Responses parsed for structured data
  4. Database Storage: Processed observations stored in SQLite

SDK Components

  • Prompts (src/sdk/prompts.ts): Builds XML-structured prompts
  • Parser (src/sdk/parser.ts): Parses Claude’s XML responses
  • Worker (src/sdk/worker.ts): Main SDK agent loop

Model Configuration

Set the AI model used for processing via environment variable:
export CLAUDE_MEM_MODEL=sonnet
Available shorthand models (forward to latest version):
  • haiku - Fast, cost-efficient
  • sonnet - Balanced (default)
  • opus - Most capable

Port Allocation

The worker uses a fixed port (37777 by default) for consistent communication:
  • Default: Port 37777
  • Override: Set CLAUDE_MEM_WORKER_PORT environment variable
  • Port File: ${CLAUDE_PLUGIN_ROOT}/data/worker.port tracks current port
If port 37777 is in use, the worker will fail to start. Set a custom port via environment variable.

Data Storage

The worker service stores data in the user data directory:
~/.claude-mem/
├── claude-mem.db           # SQLite database (bun:sqlite)
├── worker.pid              # PID file for process tracking
├── settings.json           # User settings
└── logs/
    └── worker-YYYY-MM-DD.log  # Daily rotating logs

Error Handling

The worker implements graceful degradation:
  • Database Errors: Logged but don’t crash the service
  • SDK Errors: Retried with exponential backoff
  • Network Errors: Logged and skipped
  • Invalid Input: Validated and rejected with error response

Performance

  • Async Processing: Observations processed asynchronously
  • In-Memory Queue: Fast observation accumulation
  • Batch Processing: Multiple observations processed together
  • Connection Pooling: SQLite connections reused

Troubleshooting

See Troubleshooting - Worker Issues for common problems and solutions.