> ## Documentation Index
> Fetch the complete documentation index at: https://docs.claude-mem.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Platform Integration Guide

> Complete reference for integrating claude-mem worker service into VSCode extensions, IDE plugins, and CLI tools

<Note>
  **Target Audience:** Developers building claude-mem integrations (VSCode extensions, IDE plugins, CLI tools).
</Note>

## Quick Reference

### Worker Service Basics

```typescript theme={null}
// Resolve the worker port at runtime. The default is per-user (37700 + uid % 100),
// or whatever the user set via CLAUDE_MEM_WORKER_PORT / settings.json. Read it from
// process.env.CLAUDE_MEM_WORKER_PORT, then ~/.claude-mem/settings.json
// (CLAUDE_MEM_WORKER_PORT key), then fall back to the deterministic default.
const WORKER_BASE_URL = `http://127.0.0.1:${workerPort}`;
```

### Most Common Operations

```typescript theme={null}
// Health check
GET /api/health

// Create/get session and queue observation
POST /api/sessions/observations
Body: { claudeSessionId, tool_name, tool_input, tool_response, cwd }

// Queue summary
POST /api/sessions/summarize
Body: { claudeSessionId, last_user_message, last_assistant_message }

// Complete session
POST /api/sessions/complete
Body: { claudeSessionId }

// Search observations
GET /api/search?query=authentication&type=observations&format=index&limit=20

// Get recent context for project
GET /api/context/recent?project=my-project&limit=3
```

### Environment Variables

```bash theme={null}
CLAUDE_MEM_MODEL=claude-haiku-4-5-20251001  # Default Claude model for observations/summaries
CLAUDE_MEM_CONTEXT_OBSERVATIONS=50          # Observations injected at SessionStart
CLAUDE_MEM_WORKER_PORT=                     # Optional override; default = 37700 + (uid % 100)
CLAUDE_MEM_DATA_DIR=                        # Optional override for the data directory
CLAUDE_MEM_PYTHON_VERSION=3.13              # Python version for chroma-mcp
```

### Build Commands (Local Development)

```bash theme={null}
npm run build                 # Compile TypeScript (hooks + worker)
npm run sync-marketplace      # Copy to ~/.claude/plugins
npm run worker:restart        # Restart worker
npm run worker:logs           # View worker logs
npm run worker:status         # Check worker status
```

## Worker Architecture

### Request Flow

```plaintext theme={null}
Platform Hook/Extension
  → HTTP Request to Worker (`${WORKER_BASE_URL}` — per-user, default 37700+uid%100)
    → Route Handler (SessionRoutes/DataRoutes/SearchRoutes/etc.)
      → Domain Service (SessionManager/SearchManager/DatabaseManager)
        → Database (SQLite3 + Chroma vector DB)
          → SSE Broadcast (real-time UI updates)
```

### Domain Services

<CardGroup cols={2}>
  <Card title="DatabaseManager" icon="database">
    SQLite connection management, initialization
  </Card>

  <Card title="SessionManager" icon="timeline">
    Event-driven session lifecycle, message queues
  </Card>

  <Card title="SearchManager" icon="magnifying-glass">
    Search orchestration (FTS5 + Chroma)
  </Card>

  <Card title="SSEBroadcaster" icon="broadcast-tower">
    Server-Sent Events for real-time updates
  </Card>

  <Card title="SDKAgent" icon="robot">
    Claude Agent SDK for generating observations/summaries
  </Card>

  <Card title="PaginationHelper" icon="list">
    Query pagination utilities
  </Card>

  <Card title="SettingsManager" icon="gear">
    User settings CRUD
  </Card>

  <Card title="FormattingService" icon="code">
    Result formatting (index vs full)
  </Card>

  <Card title="TimelineService" icon="clock">
    Unified timeline generation
  </Card>
</CardGroup>

### Route Organization

<AccordionGroup>
  <Accordion title="ViewerRoutes" icon="eye">
    * Health check endpoint
    * Viewer UI (React app)
    * SSE stream for real-time updates
  </Accordion>

  <Accordion title="SessionRoutes" icon="timeline">
    * Session lifecycle (init, observations, summarize, complete)
    * Privacy checks and tag stripping
    * Auto-start SDK agent generators
  </Accordion>

  <Accordion title="DataRoutes" icon="database">
    * Data retrieval (observations, summaries, prompts, stats)
    * Pagination support
    * Processing status
  </Accordion>

  <Accordion title="SearchRoutes" icon="magnifying-glass">
    * All search operations
    * Unified search API
    * Timeline context
    * Semantic shortcuts
  </Accordion>

  <Accordion title="SettingsRoutes" icon="gear">
    * User settings
    * MCP toggle
    * Git branch switching
  </Accordion>
</AccordionGroup>

## API Reference

### Session Lifecycle (SessionRoutes)

#### Create/Get Session + Queue Observation (New API)

<CodeGroup>
  ```http POST /api/sessions/observations theme={null}
  POST /api/sessions/observations
  Content-Type: application/json

  {
    "claudeSessionId": "abc123",      // Claude session identifier (string)
    "tool_name": "Bash",
    "tool_input": { "command": "ls" },
    "tool_response": { "stdout": "..." },
    "cwd": "/path/to/project"
  }
  ```

  ```json Response theme={null}
  { "status": "queued" }
  // or
  { "status": "skipped", "reason": "private" }
  ```
</CodeGroup>

<Info>
  **Privacy Check:** Skips if user prompt was entirely wrapped in `<private>` tags.
  **Tag Stripping:** Removes `<private>` and `<claude-mem-context>` tags before storage.
  **Auto-Start:** Ensures SDK agent generator is running to process the queue.
</Info>

#### Queue Summary (New API)

<CodeGroup>
  ```http POST /api/sessions/summarize theme={null}
  POST /api/sessions/summarize
  Content-Type: application/json

  {
    "claudeSessionId": "abc123",
    "last_user_message": "User's message",
    "last_assistant_message": "Assistant's response"
  }
  ```

  ```json Response theme={null}
  { "status": "queued" }
  // or
  { "status": "skipped", "reason": "private" }
  ```
</CodeGroup>

#### Complete Session (New API)

<CodeGroup>
  ```http POST /api/sessions/complete theme={null}
  POST /api/sessions/complete
  Content-Type: application/json

  {
    "claudeSessionId": "abc123"
  }
  ```

  ```json Response theme={null}
  { "success": true }
  // or
  { "success": true, "message": "No active session found" }
  ```
</CodeGroup>

<Warning>
  **Effect:** Stops SDK agent, marks session complete, broadcasts status change.
</Warning>

#### Legacy Endpoints (Still Supported)

<Tabs>
  <Tab title="Initialize Session">
    ```http theme={null}
    POST /sessions/:sessionDbId/init
    Body: { userPrompt, promptNumber }
    ```
  </Tab>

  <Tab title="Queue Observations">
    ```http theme={null}
    POST /sessions/:sessionDbId/observations
    Body: { tool_name, tool_input, tool_response, prompt_number, cwd }
    ```
  </Tab>

  <Tab title="Queue Summary">
    ```http theme={null}
    POST /sessions/:sessionDbId/summarize
    Body: { last_user_message, last_assistant_message }
    ```
  </Tab>

  <Tab title="Complete Session">
    ```http theme={null}
    POST /sessions/:sessionDbId/complete
    ```
  </Tab>
</Tabs>

<Note>
  New integrations should use `/api/sessions/*` endpoints with `claudeSessionId`.
</Note>

### Data Retrieval (DataRoutes)

#### Get Paginated Data

<Tabs>
  <Tab title="Observations">
    ```http theme={null}
    GET /api/observations?offset=0&limit=20&project=my-project
    ```
  </Tab>

  <Tab title="Summaries">
    ```http theme={null}
    GET /api/summaries?offset=0&limit=20&project=my-project
    ```
  </Tab>

  <Tab title="User Prompts">
    ```http theme={null}
    GET /api/prompts?offset=0&limit=20&project=my-project
    ```
  </Tab>
</Tabs>

```json Response Format theme={null}
{
  "items": [...],
  "hasMore": boolean,
  "offset": number,
  "limit": number
}
```

#### Get by ID

<Tabs>
  <Tab title="Observation">
    ```http theme={null}
    GET /api/observation/:id
    ```
  </Tab>

  <Tab title="Session">
    ```http theme={null}
    GET /api/session/:id
    ```
  </Tab>

  <Tab title="Prompt">
    ```http theme={null}
    GET /api/prompt/:id
    ```
  </Tab>
</Tabs>

#### Get Database Stats

```http theme={null}
GET /api/stats
```

```json Response theme={null}
{
  "worker": {
    "version": "7.0.0",
    "uptime": 12345,
    "activeSessions": 2,
    "sseClients": 1,
    "port": 37742
  },
  "database": {
    "path": "~/.claude-mem/claude-mem.db",
    "size": 1048576,
    "observations": 500,
    "sessions": 50,
    "summaries": 25
  }
}
```

#### Get Projects List

```http theme={null}
GET /api/projects
```

```json Response theme={null}
{
  "projects": ["claude-mem", "other-project", ...]
}
```

#### Get Processing Status

```http theme={null}
GET /api/processing-status
```

```json Response theme={null}
{
  "isProcessing": boolean,
  "queueDepth": number
}
```

### Search Operations (SearchRoutes)

#### Unified Search

```http theme={null}
GET /api/search?query=authentication&type=observations&format=index&limit=20
```

<ParamField query="query" type="string">
  Search query text (optional, omit for filter-only)
</ParamField>

<ParamField query="type" type="string" default="all">
  "observations" | "sessions" | "prompts"
</ParamField>

<ParamField query="format" type="string" default="index">
  "index" | "full"
</ParamField>

<ParamField query="limit" type="number" default={20}>
  Number of results
</ParamField>

<ParamField query="project" type="string">
  Filter by project name
</ParamField>

<ParamField query="obs_type" type="string">
  Filter by observation type (discovery, decision, bugfix, feature, refactor)
</ParamField>

<ParamField query="concepts" type="string">
  Filter by concepts (comma-separated)
</ParamField>

<ParamField query="files" type="string">
  Filter by file paths (comma-separated)
</ParamField>

<ParamField query="dateStart" type="string">
  ISO timestamp (filter start)
</ParamField>

<ParamField query="dateEnd" type="string">
  ISO timestamp (filter end)
</ParamField>

```json Response theme={null}
{
  "observations": [...],
  "sessions": [...],
  "prompts": [...]
}
```

<Info>
  **Format Options:**

  * `index`: Minimal fields for list display (id, title, preview)
  * `full`: Complete entity with all fields
</Info>

#### Unified Timeline

```http theme={null}
GET /api/timeline?anchor=123&depth_before=10&depth_after=10&project=my-project
```

<ParamField query="anchor" type="string" required>
  Anchor point (observation ID, "S123" for session, or ISO timestamp)
</ParamField>

<ParamField query="depth_before" type="number" default={10}>
  Records before anchor
</ParamField>

<ParamField query="depth_after" type="number" default={10}>
  Records after anchor
</ParamField>

<ParamField query="project" type="string">
  Filter by project
</ParamField>

```json Response theme={null}
[
  { "type": "observation", "id": 120, "created_at_epoch": ..., ... },
  { "type": "session", "id": 5, "created_at_epoch": ..., ... },
  { "type": "observation", "id": 123, "created_at_epoch": ..., ... }
]
```

#### Semantic Shortcuts

<CardGroup cols={3}>
  <Card title="Decisions" icon="check-circle">
    ```http theme={null}
    GET /api/decisions?format=index&limit=20
    ```
  </Card>

  <Card title="Changes" icon="code-commit">
    ```http theme={null}
    GET /api/changes?format=index&limit=20
    ```
  </Card>

  <Card title="How It Works" icon="lightbulb">
    ```http theme={null}
    GET /api/how-it-works?format=index&limit=20
    ```
  </Card>
</CardGroup>

#### Search by Concept

```http theme={null}
GET /api/search/by-concept?concept=discovery&format=index&limit=10&project=my-project
```

#### Search by File Path

```http theme={null}
GET /api/search/by-file?filePath=src/services/worker-service.ts&format=index&limit=10
```

#### Search by Type

```http theme={null}
GET /api/search/by-type?type=bugfix&format=index&limit=10
```

#### Get Recent Context

```http theme={null}
GET /api/context/recent?project=my-project&limit=3
```

```json Response theme={null}
{
  "summaries": [...],
  "observations": [...]
}
```

#### Context Preview (for Settings UI)

```http theme={null}
GET /api/context/preview?project=my-project
```

<Note>
  Returns plain text with ANSI colors for terminal display
</Note>

#### Context Injection (for Hooks)

```http theme={null}
GET /api/context/inject?project=my-project&colors=true
```

<Note>
  Returns pre-formatted context string ready for display
</Note>

### Settings & Configuration (SettingsRoutes)

#### Get/Update User Settings

<CodeGroup>
  ```http GET theme={null}
  GET /api/settings
  ```

  ```json GET Response theme={null}
  {
    "sidebarOpen": boolean,
    "selectedProject": string | null
  }
  ```

  ```http POST theme={null}
  POST /api/settings
  Body: { "sidebarOpen": true, "selectedProject": "my-project" }
  ```

  ```json POST Response theme={null}
  { "success": true }
  ```
</CodeGroup>

#### MCP Server Status/Toggle

<CodeGroup>
  ```http GET Status theme={null}
  GET /api/mcp/status
  ```

  ```json GET Response theme={null}
  { "enabled": boolean }
  ```

  ```http POST Toggle theme={null}
  POST /api/mcp/toggle
  Body: { "enabled": true }
  ```

  ```json POST Response theme={null}
  { "success": true, "enabled": boolean }
  ```
</CodeGroup>

#### Git Branch Operations

<Tabs>
  <Tab title="Get Status">
    ```http theme={null}
    GET /api/branch/status
    ```

    ```json theme={null}
    {
      "current": "main",
      "remote": "origin/main",
      "ahead": 0,
      "behind": 0
    }
    ```
  </Tab>

  <Tab title="Switch Branch">
    ```http theme={null}
    POST /api/branch/switch
    Body: { "branch": "feature/new-feature" }
    ```

    ```json theme={null}
    { "success": true }
    ```
  </Tab>

  <Tab title="Update Branch">
    ```http theme={null}
    POST /api/branch/update
    ```

    ```json theme={null}
    { "success": true, "updated": boolean }
    ```
  </Tab>
</Tabs>

### Viewer & Real-Time Updates (ViewerRoutes)

#### Health Check

```http theme={null}
GET /api/health
```

```json Response theme={null}
{ "status": "ok" }
```

#### Viewer UI

```http theme={null}
GET /
```

<Note>
  Returns HTML for React app
</Note>

#### SSE Stream

```http theme={null}
GET /stream
```

<Info>
  **Server-Sent Events stream**

  Event Types:

  * `processing_status`: { type, isProcessing, queueDepth }
  * `session_started`: { type, sessionDbId, project }
  * `observation_queued`: { type, sessionDbId }
  * `summarize_queued`: { type }
  * `observation_created`: { type, observation }
  * `summary_created`: { type, summary }
  * `new_prompt`: { type, id, claude_session_id, project, prompt_number, prompt_text, created_at_epoch }
</Info>

## Data Models

### Active Session (In-Memory)

```typescript theme={null}
interface ActiveSession {
  sessionDbId: number;                  // Database ID (numeric)
  claudeSessionId: string;              // Claude session identifier (string)
  sdkSessionId: string | null;          // SDK session ID
  project: string;                      // Project name
  userPrompt: string;                   // Current user prompt text
  pendingMessages: PendingMessage[];    // Queue of pending operations
  abortController: AbortController;     // For cancellation
  generatorPromise: Promise<void> | null; // SDK agent promise
  lastPromptNumber: number;             // Last processed prompt number
  startTime: number;                    // Session start timestamp
  cumulativeInputTokens: number;        // Total input tokens
  cumulativeOutputTokens: number;       // Total output tokens
}

interface PendingMessage {
  type: 'observation' | 'summarize';
  tool_name?: string;
  tool_input?: any;
  tool_response?: any;
  prompt_number?: number;
  cwd?: string;
  last_user_message?: string;
  last_assistant_message?: string;
}
```

### Database Entities

<Tabs>
  <Tab title="SDK Session">
    ```typescript theme={null}
    interface SDKSessionRow {
      id: number;
      claude_session_id: string;
      sdk_session_id: string;
      project: string;
      user_prompt: string;
      created_at_epoch: number;
      completed_at_epoch?: number;
    }
    ```
  </Tab>

  <Tab title="Observation">
    ```typescript theme={null}
    interface ObservationRow {
      id: number;
      sdk_session_id: string;
      title: string;
      subtitle?: string;
      summary: string;
      facts: string;           // JSON array of fact strings
      concepts: string;        // JSON array of concept strings
      files_touched: string;   // JSON array of file paths
      obs_type: string;        // discovery, decision, bugfix, feature, refactor
      project: string;
      created_at_epoch: number;
      prompt_number: number;
    }
    ```
  </Tab>

  <Tab title="Session Summary">
    ```typescript theme={null}
    interface SessionSummaryRow {
      id: number;
      sdk_session_id: string;
      summary_text: string;
      facts: string;           // JSON array
      concepts: string;        // JSON array
      files_touched: string;   // JSON array
      project: string;
      created_at_epoch: number;
    }
    ```
  </Tab>

  <Tab title="User Prompt">
    ```typescript theme={null}
    interface UserPromptRow {
      id: number;
      claude_session_id: string;
      sdk_session_id: string;
      project: string;
      prompt_number: number;
      prompt_text: string;
      created_at_epoch: number;
    }
    ```
  </Tab>
</Tabs>

### Search Results

```typescript theme={null}
interface ObservationSearchResult {
  id: number;
  title: string;
  subtitle?: string;
  summary: string;
  facts: string[];         // Parsed from JSON
  concepts: string[];      // Parsed from JSON
  files_touched: string[]; // Parsed from JSON
  obs_type: string;
  project: string;
  created_at_epoch: number;
  prompt_number: number;
  rank?: number;           // FTS5 rank score
}

interface SessionSummarySearchResult {
  id: number;
  summary_text: string;
  facts: string[];
  concepts: string[];
  files_touched: string[];
  project: string;
  created_at_epoch: number;
  rank?: number;
}

interface UserPromptSearchResult {
  id: number;
  claude_session_id: string;
  project: string;
  prompt_number: number;
  prompt_text: string;
  created_at_epoch: number;
  rank?: number;
}
```

### Timeline Item

```typescript theme={null}
interface TimelineItem {
  type: 'observation' | 'session' | 'prompt';
  id: number;
  created_at_epoch: number;
  // Entity-specific fields based on type
}
```

## Integration Patterns

### Mapping Claude Code Hooks to Worker API

<Steps>
  <Step title="SessionStart Hook">
    Not needed for new API - sessions are auto-created on first observation
  </Step>

  <Step title="UserPromptSubmit Hook">
    No API call needed - user\_prompt is captured by first observation in the prompt
  </Step>

  <Step title="PostToolUse Hook">
    ```typescript theme={null}
    async function onPostToolUse(context: HookContext) {
      const { session_id, tool_name, tool_input, tool_result, cwd } = context;

      const response = await fetch(`${WORKER_BASE_URL}/api/sessions/observations`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          claudeSessionId: session_id,
          tool_name,
          tool_input,
          tool_response: tool_result,
          cwd
        })
      });

      const result = await response.json();
      // result.status === 'queued' | 'skipped'
    }
    ```
  </Step>

  <Step title="Summary Hook">
    ```typescript theme={null}
    async function onSummary(context: HookContext) {
      const { session_id, last_user_message, last_assistant_message } = context;

      await fetch(`${WORKER_BASE_URL}/api/sessions/summarize`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          claudeSessionId: session_id,
          last_user_message,
          last_assistant_message
        })
      });
    }
    ```
  </Step>

  <Step title="SessionEnd Hook">
    ```typescript theme={null}
    async function onSessionEnd(context: HookContext) {
      const { session_id } = context;

      await fetch(`${WORKER_BASE_URL}/api/sessions/complete`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          claudeSessionId: session_id
        })
      });
    }
    ```
  </Step>
</Steps>

### VSCode Extension Integration

#### Language Model Tool Registration

```typescript theme={null}
import * as vscode from 'vscode';

interface SearchTool extends vscode.LanguageModelChatTool {
  invoke(
    options: vscode.LanguageModelToolInvocationOptions<{ query: string }>,
    token: vscode.CancellationToken
  ): vscode.ProviderResult<vscode.LanguageModelToolResult>;
}

const searchTool: SearchTool = {
  invoke: async (options, token) => {
    const { query } = options.input;

    try {
      const response = await fetch(
        `${WORKER_BASE_URL}/api/search?query=${encodeURIComponent(query)}&format=index&limit=10`
      );

      if (!response.ok) {
        throw new Error(`Search failed: ${response.statusText}`);
      }

      const results = await response.json();

      // Format results for language model
      return new vscode.LanguageModelToolResult([
        new vscode.LanguageModelTextPart(JSON.stringify(results, null, 2))
      ]);
    } catch (error) {
      return new vscode.LanguageModelToolResult([
        new vscode.LanguageModelTextPart(`Error: ${error.message}`)
      ]);
    }
  }
};

// Register tool
vscode.lm.registerTool('claude-mem-search', searchTool);
```

#### Chat Participant Implementation

```typescript theme={null}
const participant = vscode.chat.createChatParticipant('claude-mem', async (request, context, stream, token) => {
  const claudeSessionId = context.session.id;

  // First message in conversation - no initialization needed
  // Session is auto-created on first observation

  // Process user message
  stream.markdown(`Searching memory for: ${request.prompt}\n\n`);

  const response = await fetch(
    `${WORKER_BASE_URL}/api/search?query=${encodeURIComponent(request.prompt)}&format=index&limit=5`
  );

  const results = await response.json();

  if (results.observations?.length > 0) {
    stream.markdown('**Found observations:**\n');
    for (const obs of results.observations) {
      stream.markdown(`- ${obs.title} (${obs.project})\n`);
    }
  }

  return { metadata: { command: 'search' } };
});
```

## Error Handling & Resilience

### Connection Failures

```typescript theme={null}
async function callWorkerWithFallback<T>(
  endpoint: string,
  options?: RequestInit
): Promise<T | null> {
  try {
    const response = await fetch(`${WORKER_BASE_URL}${endpoint}`, {
      ...options,
      signal: AbortSignal.timeout(5000) // 5s timeout
    });

    if (!response.ok) {
      throw new Error(`HTTP ${response.status}: ${response.statusText}`);
    }

    return await response.json();
  } catch (error) {
    console.error(`Worker unavailable (${endpoint}):`, error);
    return null; // Graceful degradation
  }
}
```

### Retry Logic with Exponential Backoff

```typescript theme={null}
async function retryWithBackoff<T>(
  fn: () => Promise<T>,
  maxRetries = 3,
  baseDelay = 100
): Promise<T> {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      return await fn();
    } catch (error) {
      if (attempt === maxRetries - 1) throw error;

      const delay = baseDelay * Math.pow(2, attempt);
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
  throw new Error('Max retries exceeded');
}
```

### Worker Health Check

```typescript theme={null}
async function isWorkerHealthy(): Promise<boolean> {
  try {
    const response = await fetch(`${WORKER_BASE_URL}/api/health`, {
      signal: AbortSignal.timeout(2000)
    });
    return response.ok;
  } catch {
    return false;
  }
}
```

### Privacy Tag Handling

<Info>
  The worker automatically strips privacy tags before storage:

  * `<private>content</private>` - User-level privacy control
  * `<claude-mem-context>content</claude-mem-context>` - System-level tag (prevents recursive storage)

  **Privacy Check:** Observations/summaries are skipped if the entire user prompt was wrapped in `<private>` tags.
</Info>

### Custom Error Classes

```typescript theme={null}
class WorkerUnavailableError extends Error {
  constructor() {
    super('Claude-mem worker is not running or unreachable');
    this.name = 'WorkerUnavailableError';
  }
}

class WorkerTimeoutError extends Error {
  constructor(endpoint: string) {
    super(`Worker request timed out: ${endpoint}`);
    this.name = 'WorkerTimeoutError';
  }
}
```

### SSE Stream Error Handling

```typescript theme={null}
function connectToSSE(onEvent: (event: any) => void) {
  const eventSource = new EventSource(`${WORKER_BASE_URL}/stream`);

  eventSource.onmessage = (event) => {
    try {
      const data = JSON.parse(event.data);
      onEvent(data);
    } catch (error) {
      console.error('SSE parse error:', error);
    }
  };

  eventSource.onerror = (error) => {
    console.error('SSE connection error:', error);
    eventSource.close();

    // Reconnect after 5 seconds
    setTimeout(() => connectToSSE(onEvent), 5000);
  };

  return eventSource;
}
```

## Development Workflow

### Project Structure (Recommended)

```plaintext theme={null}
vscode-extension/
├── src/
│   ├── extension.ts              # Extension entry point
│   ├── services/
│   │   ├── WorkerClient.ts       # HTTP client for worker
│   │   └── MemoryManager.ts      # High-level memory operations
│   ├── chat/
│   │   └── participant.ts        # Chat participant implementation
│   └── tools/
│       ├── search.ts             # Search language model tool
│       └── context.ts            # Context injection tool
├── package.json
├── tsconfig.json
└── README.md
```

### Build Configuration (esbuild)

```javascript theme={null}
// build.js
const esbuild = require('esbuild');

esbuild.build({
  entryPoints: ['src/extension.ts'],
  bundle: true,
  outfile: 'dist/extension.js',
  external: ['vscode'],
  format: 'cjs',
  platform: 'node',
  target: 'node18',
  sourcemap: true
}).catch(() => process.exit(1));
```

### package.json (VSCode Extension)

```json theme={null}
{
  "name": "claude-mem-vscode",
  "displayName": "Claude-Mem",
  "version": "1.0.0",
  "engines": {
    "vscode": "^1.95.0"
  },
  "activationEvents": [
    "onStartupFinished"
  ],
  "main": "./dist/extension.js",
  "contributes": {
    "chatParticipants": [
      {
        "id": "claude-mem",
        "name": "memory",
        "description": "Search your persistent memory"
      }
    ],
    "languageModelTools": [
      {
        "name": "claude-mem-search",
        "displayName": "Search Memory",
        "description": "Search persistent memory for observations, sessions, and prompts"
      }
    ]
  },
  "scripts": {
    "build": "node build.js",
    "watch": "node build.js --watch",
    "package": "vsce package"
  },
  "devDependencies": {
    "@types/vscode": "^1.95.0",
    "esbuild": "^0.19.0",
    "typescript": "^5.3.0"
  }
}
```

### Local Testing Loop

<Steps>
  <Step title="Terminal 1: Watch build">
    ```bash theme={null}
    npm run watch
    ```
  </Step>

  <Step title="Terminal 2: Check worker status">
    ```bash theme={null}
    npm run worker:status
    npm run worker:logs
    ```
  </Step>

  <Step title="Terminal 3: Test API manually">
    ```bash theme={null}
    curl http://127.0.0.1:$WORKER_PORT/api/health
    curl "http://127.0.0.1:$WORKER_PORT/api/search?query=test&limit=5"
    ```
  </Step>

  <Step title="VSCode: Launch extension host">
    Press F5 to launch extension host
  </Step>
</Steps>

### Debug Configuration (.vscode/launch.json)

```json theme={null}
{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Run Extension",
      "type": "extensionHost",
      "request": "launch",
      "args": [
        "--extensionDevelopmentPath=${workspaceFolder}"
      ],
      "outFiles": [
        "${workspaceFolder}/dist/**/*.js"
      ],
      "preLaunchTask": "npm: build"
    }
  ]
}
```

## Testing Strategy

### Unit Tests (Worker Client)

```typescript theme={null}
import { describe, it, expect } from 'vitest';
import { WorkerClient } from '../src/services/WorkerClient';

describe('WorkerClient', () => {
  it('should check worker health', async () => {
    const client = new WorkerClient();
    const healthy = await client.isHealthy();
    expect(healthy).toBe(true);
  });

  it('should queue observation', async () => {
    const client = new WorkerClient();
    const result = await client.queueObservation({
      claudeSessionId: 'test-123',
      tool_name: 'Bash',
      tool_input: { command: 'ls' },
      tool_response: { stdout: 'file1.txt' },
      cwd: '/tmp'
    });
    expect(result.status).toBe('queued');
  });

  it('should search observations', async () => {
    const client = new WorkerClient();
    const results = await client.search({ query: 'test', limit: 5 });
    expect(results).toHaveProperty('observations');
  });
});
```

### Integration Tests (With Worker Spawning)

```typescript theme={null}
import { spawn } from 'child_process';
import { describe, it, expect, beforeAll, afterAll } from 'vitest';

describe('Worker Integration', () => {
  let workerProcess: ReturnType<typeof spawn>;

  beforeAll(async () => {
    // Start worker process
    workerProcess = spawn('node', ['dist/worker-service.js'], {
      env: { ...process.env, CLAUDE_MEM_WORKER_PORT: '37778' }
    });

    // Wait for worker to be ready
    await new Promise(resolve => setTimeout(resolve, 2000));
  });

  afterAll(() => {
    workerProcess.kill();
  });

  it('should respond to health check', async () => {
    const response = await fetch('http://localhost:37778/api/health');
    expect(response.ok).toBe(true);
  });
});
```

### Manual Testing Checklist

<AccordionGroup>
  <Accordion title="Phase 1: Connection & Health">
    * [ ] Worker starts successfully (`npm run worker:status`)
    * [ ] Health endpoint responds (`curl http://127.0.0.1:$WORKER_PORT/api/health`)
    * [ ] SSE stream connects (`curl http://127.0.0.1:$WORKER_PORT/stream`)
  </Accordion>

  <Accordion title="Phase 2: Session Lifecycle">
    * [ ] Queue observation creates session
    * [ ] Observation appears in database
    * [ ] Privacy tags are stripped
    * [ ] Private prompts are skipped
    * [ ] Queue summary creates summary
    * [ ] Complete session stops processing
  </Accordion>

  <Accordion title="Phase 3: Search & Retrieval">
    * [ ] Search observations by query
    * [ ] Search sessions by query
    * [ ] Search prompts by query
    * [ ] Get recent context for project
    * [ ] Get timeline around observation
    * [ ] Semantic shortcuts (decisions, changes, how-it-works)
  </Accordion>

  <Accordion title="Phase 4: Real-Time Updates">
    * [ ] SSE broadcasts processing status
    * [ ] SSE broadcasts new observations
    * [ ] SSE broadcasts new summaries
    * [ ] SSE broadcasts new prompts
  </Accordion>

  <Accordion title="Phase 5: Error Handling">
    * [ ] Graceful degradation when worker unavailable
    * [ ] Timeout handling for slow requests
    * [ ] Retry logic for transient failures
  </Accordion>
</AccordionGroup>

## Code Examples

### Complete WorkerClient Implementation

<CodeGroup>
  ```typescript WorkerClient.ts theme={null}
  export class WorkerClient {
    private baseUrl: string;

    // Resolve the active worker port via env / settings.json, falling back
    // to the deterministic per-user default. See parseWorkerPort() for an
    // example helper; never hardcode a single value.
    constructor(port: number = resolveWorkerPort()) {
      this.baseUrl = `http://127.0.0.1:${port}`;
    }

    async isHealthy(): Promise<boolean> {
      try {
        const response = await fetch(`${this.baseUrl}/api/health`, {
          signal: AbortSignal.timeout(2000)
        });
        return response.ok;
      } catch {
        return false;
      }
    }

    async queueObservation(data: {
      claudeSessionId: string;
      tool_name: string;
      tool_input: any;
      tool_response: any;
      cwd?: string;
    }): Promise<{ status: string; reason?: string }> {
      const response = await fetch(`${this.baseUrl}/api/sessions/observations`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(data),
        signal: AbortSignal.timeout(5000)
      });

      if (!response.ok) {
        throw new Error(`Failed to queue observation: ${response.statusText}`);
      }

      return await response.json();
    }

    async queueSummarize(data: {
      claudeSessionId: string;
      last_user_message?: string;
      last_assistant_message?: string;
    }): Promise<{ status: string; reason?: string }> {
      const response = await fetch(`${this.baseUrl}/api/sessions/summarize`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(data),
        signal: AbortSignal.timeout(5000)
      });

      if (!response.ok) {
        throw new Error(`Failed to queue summary: ${response.statusText}`);
      }

      return await response.json();
    }

    async completeSession(claudeSessionId: string): Promise<void> {
      const response = await fetch(`${this.baseUrl}/api/sessions/complete`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ claudeSessionId }),
        signal: AbortSignal.timeout(5000)
      });

      if (!response.ok) {
        throw new Error(`Failed to complete session: ${response.statusText}`);
      }
    }

    async search(params: {
      query?: string;
      type?: 'observations' | 'sessions' | 'prompts';
      format?: 'index' | 'full';
      limit?: number;
      project?: string;
    }): Promise<any> {
      const queryString = new URLSearchParams(
        Object.entries(params)
          .filter(([_, v]) => v !== undefined)
          .map(([k, v]) => [k, String(v)])
      ).toString();

      const response = await fetch(
        `${this.baseUrl}/api/search?${queryString}`,
        { signal: AbortSignal.timeout(10000) }
      );

      if (!response.ok) {
        throw new Error(`Search failed: ${response.statusText}`);
      }

      return await response.json();
    }

    connectSSE(onEvent: (event: any) => void): EventSource {
      const eventSource = new EventSource(`${this.baseUrl}/stream`);

      eventSource.onmessage = (event) => {
        try {
          const data = JSON.parse(event.data);
          onEvent(data);
        } catch (error) {
          console.error('SSE parse error:', error);
        }
      };

      eventSource.onerror = (error) => {
        console.error('SSE connection error:', error);
      };

      return eventSource;
    }
  }
  ```
</CodeGroup>

### Search Language Model Tool

```typescript theme={null}
import * as vscode from 'vscode';
import { WorkerClient } from './WorkerClient';

export function registerSearchTool(context: vscode.ExtensionContext) {
  const client = new WorkerClient();

  const searchTool = vscode.lm.registerTool('claude-mem-search', {
    description: 'Search persistent memory for observations, sessions, and prompts',
    inputSchema: {
      type: 'object',
      properties: {
        query: {
          type: 'string',
          description: 'Search query text'
        },
        type: {
          type: 'string',
          enum: ['observations', 'sessions', 'prompts'],
          description: 'Type of results to return'
        },
        limit: {
          type: 'number',
          description: 'Maximum number of results',
          default: 10
        }
      },
      required: ['query']
    },
    invoke: async (options, token) => {
      const { query, type, limit = 10 } = options.input;

      try {
        const results = await client.search({
          query,
          type,
          format: 'index',
          limit
        });

        // Format results for language model
        let formatted = '';

        if (results.observations?.length > 0) {
          formatted += '## Observations\n\n';
          for (const obs of results.observations) {
            formatted += `- **${obs.title}** (${obs.project})\n`;
            formatted += `  ${obs.summary}\n`;
            if (obs.concepts?.length > 0) {
              formatted += `  Concepts: ${obs.concepts.join(', ')}\n`;
            }
            formatted += '\n';
          }
        }

        return new vscode.LanguageModelToolResult([
          new vscode.LanguageModelTextPart(formatted)
        ]);
      } catch (error) {
        return new vscode.LanguageModelToolResult([
          new vscode.LanguageModelTextPart(`Error: ${error.message}`)
        ]);
      }
    }
  });

  context.subscriptions.push(searchTool);
}
```

## Critical Implementation Notes

<Warning>
  ### sessionDbId vs claudeSessionId

  **IMPORTANT:** Use `claudeSessionId` (string) for new API endpoints, not `sessionDbId` (number).

  * `sessionDbId` - Numeric database ID (legacy endpoints only)
  * `claudeSessionId` - String identifier from Claude platform (new endpoints)
</Warning>

<Warning>
  ### JSON String Fields

  Fields like `facts`, `concepts`, and `files_touched` are stored as JSON strings and require parsing:

  ```typescript theme={null}
  const observation = await client.getObservationById(123);
  const facts = JSON.parse(observation.facts); // string[] array
  const concepts = JSON.parse(observation.concepts); // string[] array
  ```
</Warning>

<Warning>
  ### Timestamps

  All `created_at_epoch` fields are in **milliseconds**, not seconds:

  ```typescript theme={null}
  const date = new Date(observation.created_at_epoch); // ✅ Correct
  const date = new Date(observation.created_at_epoch * 1000); // ❌ Wrong (already in ms)
  ```
</Warning>

<Info>
  ### Asynchronous Processing

  Workers process observations/summaries asynchronously. Results appear in the database 1-2 seconds after queuing. Use SSE events for real-time notifications.
</Info>

<Info>
  ### Privacy Tags

  Always wrap sensitive content in `<private>` tags to prevent storage:

  ```typescript theme={null}
  const userMessage = '<private>API key: sk-1234567890</private>';
  // This observation will be skipped (entire prompt is private)
  ```
</Info>

## Additional Resources

<CardGroup cols={2}>
  <Card title="Documentation" icon="book" href="https://claude-mem.ai">
    Complete claude-mem documentation
  </Card>

  <Card title="GitHub" icon="github" href="https://github.com/thedotmack/claude-mem">
    Source code and issue tracker
  </Card>

  <Card title="Worker Service" icon="server" href="/architecture/worker-service">
    Worker architecture details
  </Card>

  <Card title="Database Schema" icon="database" href="/architecture/database">
    Database structure and queries
  </Card>
</CardGroup>
