docs/public/architecture/worker-service.mdx
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.
37700 + (uid % 100) (override with CLAUDE_MEM_WORKER_PORT). The active port is stored in ~/.claude-mem/settings.json and reported by GET /api/health.src/services/worker-service.tsplugin/scripts/worker-service.cjsCLAUDE_MEM_MODEL (default: claude-haiku-4-5-20251001)The worker service exposes 22 HTTP endpoints organized into six categories:
GET /
Purpose: Serves the web-based viewer UI (v5.1.0+)
Response: HTML page with embedded React application
Features:
GET /health
Purpose: Worker health status check
Response:
{
"status": "ok",
"uptime": 12345,
"port": 37742
}
The port value is the actual worker port for the current user — per-user default 37700 + (uid % 100), or whatever CLAUDE_MEM_WORKER_PORT is set to. The example above is illustrative; your value will differ.
GET /stream
Purpose: Real-time updates for viewer UI
Response: SSE stream with events:
observation-created: New observation addedsession-summary-created: New summary generateduser-prompt-created: New prompt recordedEvent Format:
event: observation-created
data: {"id": 123, "title": "...", ...}
GET /api/prompts?project=my-project&limit=20&offset=0
Purpose: Retrieve paginated user prompts
Query Parameters:
project (optional): Filter by project namelimit (default: 20): Number of resultsoffset (default: 0): Pagination offsetResponse:
{
"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
}
GET /api/observations?project=my-project&limit=20&offset=0
Purpose: Retrieve paginated observations
Query Parameters:
project (optional): Filter by project namelimit (default: 20): Number of resultsoffset (default: 0): Pagination offsetResponse:
{
"observations": [{
"id": 123,
"title": "Fix authentication bug",
"type": "bugfix",
"narrative": "...",
"created_at": "2025-11-06T10:30:00Z"
}],
"total": 500,
"hasMore": true
}
GET /api/summaries?project=my-project&limit=20&offset=0
Purpose: Retrieve paginated session summaries
Query Parameters:
project (optional): Filter by project namelimit (default: 20): Number of resultsoffset (default: 0): Pagination offsetResponse:
{
"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
}
GET /api/observation/:id
Purpose: Retrieve a single observation by its ID
Path Parameters:
id (required): Observation IDResponse:
{
"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"
}
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 IDsorderBy (optional): Sort order - date_desc or date_asc (default: date_desc)limit (optional): Maximum number of results to returnproject (optional): Filter by project nameResponse:
[
{
"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.
GET /api/session/:id
Purpose: Retrieve a single session by its ID
Path Parameters:
id (required): Session IDResponse:
{
"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"
}
GET /api/prompt/:id
Purpose: Retrieve a single user prompt by its ID
Path Parameters:
id (required): Prompt IDResponse:
{
"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"
}
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
}
}
GET /api/projects
Purpose: Get list of distinct projects from observations
Response:
{
"projects": ["my-project", "other-project", "test-project"]
}
GET /api/settings
Purpose: Retrieve user settings
Response:
{
"sidebarOpen": true,
"selectedProject": "my-project",
"theme": "dark"
}
POST /api/settings
Purpose: Persist user settings
Request Body:
{
"sidebarOpen": false,
"selectedProject": "other-project",
"theme": "light"
}
Response:
{
"success": true
}
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 processedprocessing: Message currently being processed by SDK agentprocessed: Message completed successfullyfailed: 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
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 databasesessionsStarted: Number of sessions we started processing this requestsessionsSkipped: Sessions already actively processing (not restarted)startedSessionIds: Database IDs of sessions startedBehavior:
sessionLimit sessions with pending workUse 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.
POST /sessions/:sessionDbId/init
Request Body:
{
"sdk_session_id": "abc-123",
"project": "my-project"
}
Response:
{
"success": true,
"session_id": "abc-123"
}
POST /sessions/:sessionDbId/observations
Request Body:
{
"tool_name": "Read",
"tool_input": {...},
"tool_result": "...",
"correlation_id": "xyz-789"
}
Response:
{
"success": true,
"observation_id": 123
}
POST /sessions/:sessionDbId/summarize
Request Body:
{
"trigger": "stop"
}
Response:
{
"success": true,
"summary_id": 456
}
GET /sessions/:sessionDbId/status
Response:
{
"session_id": "abc-123",
"status": "active",
"observation_count": 42,
"summary_count": 1
}
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.
The worker is managed by the native ProcessManager class which handles:
~/.claude-mem/worker.pid# 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
The worker service auto-starts when the SessionStart hook fires. Manual start is optional.
Bun is required to run the worker service. If Bun is not installed, npx claude-mem install (and npx claude-mem repair) installs it globally during setup, with a visible clack spinner:
powershell -c "irm bun.sh/install.ps1 | iex"curl -fsSL https://bun.sh/install | bashYou can also install manually via:
winget install Oven-sh.Bun (Windows)brew install oven-sh/bun/bun (macOS)The worker service routes observations to the Claude Agent SDK for AI-powered processing:
src/sdk/prompts.ts): Builds XML-structured promptssrc/sdk/parser.ts): Parses Claude's XML responsessrc/sdk/worker.ts): Main SDK agent loopSet the Claude model used for compression via environment variable or ~/.claude-mem/settings.json:
export CLAUDE_MEM_MODEL=claude-haiku-4-5-20251001
Allowed values:
claude-haiku-4-5-20251001 - default, fast and cheap (best for compression)claude-sonnet-4-6 - balanced quality and costclaude-opus-4-7 - highest quality, most expensiveThe worker uses a per-user default port so different OS users on the same machine never collide:
37700 + (uid % 100) (set in src/shared/SettingsDefaultsManager.ts)CLAUDE_MEM_WORKER_PORT (env or ~/.claude-mem/settings.json)GET /api/health returns the active port; ~/.claude-mem/settings.json stores the configured valueIf the chosen port is occupied, the worker fails to start — pin a different port via CLAUDE_MEM_WORKER_PORT and restart.
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
The worker implements graceful degradation:
See Troubleshooting - Worker Issues for common problems and solutions.