# Cursor IDE Observability Schema

**Cross-validated against actual data from ~/.cursor and ~/Library/Application Support/Cursor**

---

## Overview

Cursor stores data across multiple locations:

1. **`~/Library/Application Support/Cursor/`** (macOS) / **`~/.config/Cursor/`** (Linux) - Main app data (SQLite DBs, settings)
2. **`~/.cursor/`** - User config, MCP servers, projects metadata, agent transcripts (newer)
3. **`<workspace>/.cursor/`** - Per-project plans, scratchpad, rules

### Where data lives depending on setup

| Setup | SQLite DBs (state.vscdb, ai-code-tracking.db) | Transcripts (~/.cursor/projects/) |
|-------|-----------------------------------------------|-----------------------------------|
| Local (Cursor on your machine) | On local machine | On local machine |
| Remote SSH (Cursor on Mac → VM) | **Mac only** — not on the VM | **VM only** — synced via remote extension |
| Codespace / Dev Container | **Local machine only** | **Remote only** |

The SQLite DBs are part of Cursor's Electron app and live wherever the GUI runs. The `~/.cursor/projects/` folder (transcripts, terminals, MCP configs) lives on the machine where the code runs. In remote setups these are on **different machines**.

---

## 1. Core SQLite Databases

> **Note:** These DBs only exist on the machine running Cursor's GUI (typically macOS). They will not be present on remote VMs/SSH targets.

> **SQLite WAL mode:** Cursor's `state.vscdb` uses SQLite WAL (Write-Ahead Logging). New writes go to `state.vscdb-wal`, not the main DB file. The main file's mtime/size stays unchanged until Cursor checkpoints (flushes WAL → main DB). Change detection must check both the main `.vscdb` and the `-wal` file to detect new sessions — see `watcher.py`'s `_check_file()`.

### 1.1 Global State Database

**Path:** `~/Library/Application Support/Cursor/User/globalStorage/state.vscdb` (macOS) / `~/.config/Cursor/User/globalStorage/state.vscdb` (Linux)

**Associated files:**
- `state.vscdb-wal` — Write-ahead log (active writes land here)
- `state.vscdb-shm` — Shared memory file (WAL index)

**Tables:**

```sql
CREATE TABLE ItemTable (key TEXT UNIQUE ON CONFLICT REPLACE, value BLOB);
CREATE TABLE cursorDiskKV (key TEXT UNIQUE ON CONFLICT REPLACE, value BLOB);
```

#### ItemTable Key Patterns (Verified)

| Key Pattern | Description | Value Type |
|-------------|-------------|------------|
| `aiCodeTracking.dailyStats.v1.5.<date>` | Daily usage stats | JSON |
| `aiCodeTracking.recentCommit` | Most recent tracked commit | JSON |
| `aiCodeTrackingLines` | Tracked code hashes with composer IDs | JSON array |
| `aiCodeTrackingScoredCommits` | Commits scored for AI attribution | JSON |
| `cursor/memoriesEnabled` | Memories feature flag | boolean |
| `cursor/pendingMemories` | Memories queue for review | JSON array |
| `cursor/agentLayout.*` | Agent panel layout preferences | various |
| `cursor/composerAutocompleteHeuristics*` | Autocomplete settings | boolean |
| `cursorAuth/accessToken` | Auth token | string |
| `cursorAuth/cachedEmail` | User email | string |
| `cursorAuth/stripeMembershipType` | Subscription tier | string |
| `cursorai/serverConfig` | Server-side feature config | JSON |
| `cursorai/featureConfigCache` | Feature flags | JSON |
| `cursorai/donotchange/newPrivacyMode2` | Privacy mode setting | string |
| `workbench.panel.aichat.view.aichat.chatdata` | Legacy chat panel data (tabs with bubbles) | JSON |
| `workbench.backgroundComposer.persistentData` | Background agent state | JSON |
| `backgroundComposer.windowBcMapping` | Window → background composer ID mapping | JSON |
| `cursor/notepadState` | Notepad entries (`isNotepadEnabled`) | JSON |
| `cursorPendingMemories` | Separate pending memories list (often empty) | JSON array |
| `aicontext.personalContext` | User's personal context/preferences | string |
| `aiCodeTrackingStartTime` | When AI tracking was first enabled | JSON (`{timestamp}`) |
| `conversationClassificationScoredConversations` | Scored conversation classifications | JSON array |
| `externalCliAnalytics.lastTimestamp.claude` | Last Claude Code CLI usage timestamp | integer |
| `externalCliAnalytics.lastTimestamp.codex` | Last Codex CLI usage timestamp | integer |
| `mcpService.knownServerIds` | Registered MCP server IDs | JSON array |
| `browserAutomation.history` | Browser automation URL history with visit counts | JSON array |
| `freeBestOfN.promptCount` | Best-of-N prompt counter | integer |
| `workbench.panel.composerChatViewPane.<uuid>` | Per-composer pane state | JSON |

#### Key JSON Schemas

**Daily Stats (`aiCodeTracking.dailyStats.v1.5.<date>`):**
```json
{
  "date": "2026-02-05",
  "tabSuggestedLines": 11,
  "tabAcceptedLines": 2,
  "composerSuggestedLines": 701,
  "composerAcceptedLines": 702
}
```

**Pending Memories (`cursor/pendingMemories`):**
```json
[
  {
    "id": "uuid",
    "memory": "User preference description",
    "title": "Short title",
    "requestId": "uuid",
    "composerId": "uuid",
    "timestamp": 1753426132473
  }
]
```

**Server Config (`cursorai/serverConfig`) - Key fields:**
```json
{
  "chatConfig": {
    "fullContextTokenLimit": 30000,
    "maxRuleLength": 100000,
    "maxMcpTools": 100
  },
  "backgroundComposerConfig": {
    "enableBackgroundAgent": true,
    "maxWindowInWindows": 10
  },
  "indexingConfig": {
    "maxConcurrentUploads": 64,
    "absoluteMaxNumberFiles": 100000
  }
}
```

#### cursorDiskKV Table (Verified Feb 2026)

> **Previously reported as empty** — now contains ~36K rows with rich conversation data. This is the primary source of per-message bubble data, richer than the plain-text `agent-transcripts/` files.

**Key format:** `<prefix>:<composerId>:<itemId>` (stored as BLOBs, readable via `CAST(key AS TEXT)`)

| Key Prefix | Count (sample) | Description | Value Type |
|------------|---------------|-------------|------------|
| `bubbleId` | ~17K | Individual message bubbles | JSON |
| `checkpointId` | ~10K | File state snapshots between agent steps | JSON |
| `codeBlockDiff` | ~3.7K | Diffs for code blocks (original vs modified) | JSON |
| `composerData` | ~2K | Full conversation threads with all messages inline | JSON (can be >500KB) |
| `messageRequestContext` | ~1.6K | Per-request context (terminal files, rules, folder listings) | JSON |
| `agentKv` | ~870 | Agent system/user messages, keyed by content hash | JSON or binary blob |
| `codeBlockPartialInlineDiffFates` | ~16 | Inline diff resolution states | JSON |
| `inlineDiffs-<hash or timestamp>` | ~250 | Per-workspace/per-timestamp inline diff state (all 2 bytes = empty) | empty |
| `inlineDiffsData` | 1 | Global inline diffs state (2 bytes = empty) | empty |
| `composer.content.<sha256>` | ~18 | Content-addressed file snapshots (full source code) | plain text |
| `composer.autoAccept.lastSeenHead*` | 2 | Auto-accept git tracking (SHA + timestamp) | string/integer |
| `patchGraph-v1-<workspaceHash>` | 1 | Patch dependency graph per workspace | JSON |

**composerData Value Schema (`composerData:<composerId>`):**

This is the richest data source — contains the full conversation thread inline:

```json
{
  "composerId": "uuid",
  "text": "current input text",
  "richText": "markdown input with @mentions",
  "hasLoaded": true,
  "conversation": [
    {
      "type": 1,                          // 1=user, 2=assistant
      "bubbleId": "uuid",
      "text": "message content",
      "richText": "markdown with refs",
      "isAgentic": true,
      "relevantFiles": ["path/to/file.py"],
      "context": { "fileSelections": [], "quotes": [], ... },
      "capabilitiesRan": { "mutate-request": [], "before-submit-chat": [], ... },
      "tokenCountUpUntilHere": 6268,
      "tokenDetailsUpUntilHere": [
        { "relativeWorkspacePath": "src/foo.py", "count": 4512, "lineCount": 627 }
      ],
      "unifiedMode": "agent",
      "checkpoint": { "files": [], "nonExistentFiles": [], ... },

      // Assistant-only fields:
      "timingInfo": {
        "clientStartTime": 1742468378450,
        "clientRpcSendTime": 1742468378500,
        "clientSettleTime": 1742468664865,
        "clientEndTime": 1742468664865
      },
      "codeBlocks": [...],
      "intermediateChunks": [...],
      "serverBubbleId": "uuid",
      "cachedConversationSummary": "...",

      // Capability iteration bubbles (agent tool-use steps):
      "isCapabilityIteration": true,
      "capabilityType": 15,               // numeric enum, 15 = observed agentic step
      "isThought": true,
      "allThinkingBlocks": [...]
    }
  ],
  "status": "none",
  "context": { "fileSelections": [], "quotes": [], "mentions": { ... } },
  "codeBlockData": {},
  "lastUpdatedAt": 1770897729498,
  "createdAt": 1770811357927,
  "tabs": [...],
  "selectedTabIndex": 0
}
```

**bubbleId Value Schema (`bubbleId:<composerId>:<bubbleId>`):**

Per-message metadata stored separately (supplements the inline conversation data):

```json
{
  "_v": 2,
  "type": 1,                              // 1=user, 2=assistant
  "bubbleId": "uuid",
  "relevantFiles": ["src/foo.py"],
  "toolResults": [],
  "suggestedCodeBlocks": [],
  "codebaseContextChunks": [],
  "commits": [],
  "pullRequests": [],
  "attachedCodeChunks": [],
  "assistantSuggestedDiffs": [],
  "gitDiffs": [],
  "interpreterResults": [],
  "images": [],
  "notepads": [],
  "capabilities": [],
  "capabilitiesRan": { ... },
  "capabilityStatuses": { ... },
  "tokenCount": { "inputTokens": 42257, "outputTokens": 108 }
}
```

**checkpointId Value Schema (`checkpointId:<composerId>:<checkpointId>`):**

```json
{
  "files": [
    {
      "uri": { "$mid": 1, "path": "/abs/path/to/file.py", "scheme": "file" },
      "originalModelDiffWrtV0": [],
      "isNewlyCreated": false
    }
  ],
  "nonExistentFiles": [],
  "newlyCreatedFolders": [],
  "activeInlineDiffs": [],
  "inlineDiffNewlyCreatedResources": { "files": [], "folders": [] }
}
```

**codeBlockDiff Value Schema (`codeBlockDiff:<composerId>:<diffId>`):**

```json
{
  "originalModelDiffWrtV0": [],
  "newModelDiffWrtV0": [
    {
      "original": { "startLineNumber": 1, "endLineNumberExclusive": 2 },
      "modified": ["import json", "import grpc", "..."]
    }
  ]
}
```

**messageRequestContext Value Schema (`messageRequestContext:<composerId>:<bubbleId>`):**

```json
{
  "terminalFiles": [],
  "cursorRules": [],
  "attachedFoldersListDirResults": ["{ JSON directory listing }"],
  "summarizedComposers": []
}
```

**agentKv Value Schema (`agentKv:blob:<sha256>`):**

Content-addressed storage for agent conversation messages, keyed by SHA-256 hash of the content. Three value formats exist:

**Format 1: JSON (319 of ~870 entries)** — Standard chat messages with all four roles:

```json
// system
{"role": "system", "content": "You are an AI coding assistant, powered by Composer..."}

// user (text or multipart)
{"role": "user", "content": "<user_info>\nOS Version: darwin 25.2.0\n..."}
{"role": "user", "content": [{"type": "text", "text": "<open_and_recently_viewed_files>..."}]}

// assistant (with thinking)
{"id": "1", "role": "assistant", "content": [{"type": "text", "text": "<think>\n..."}]}

// tool results
{"role": "tool", "id": "tool_e75a66f2-...", "content": [{"type": "text", ...}]}
```

**Format 2: Protobuf (~552 entries)** — User messages with rich IDE context. Decoded with `protoc --decode_raw`:

```protobuf
// Field mapping (inferred):
1: "can you count R's in that file?"     // message text
2: "007c995c-3860-4709-..."              // bubbleId
3 {                                       // context attachments
  2 {
    3 {
      1 {                                 // file reference
        1: "/abs/path/to/file.py"         // absolute path
        2: "relative/path/to/file.py"     // workspace-relative path
        3 { 1: 99  2: "    line content" }  // line number + preview
        4: 481                            // token count for this file
      }
    }
  }
}
4: 1                                      // message type (1=user)
8: "{\"root\":{\"children\":[...]}}"      // Lexical richText JSON
10: "<binary 32 bytes>"                   // unknown hash/ID
```

**Format 3: Plain text (~19 entries)** — Raw file contents attached as context (e.g., markdown docs, source files). Starts with the file content directly (no envelope).

> **Note:** Protobuf entries contain remote file paths (e.g., `/home/sagar/trace/...`) confirming that remote SSH session data is stored in the local Mac's DB.

---

### 1.2 Workspace State Database

**Path:** `~/Library/Application Support/Cursor/User/workspaceStorage/<hash>/state.vscdb` (macOS) / `~/.config/Cursor/User/workspaceStorage/<hash>/state.vscdb` (Linux)

**Mapping:** Each `<hash>` folder has a `workspace.json`:
```json
{
  "folder": "file:///path/to/workspace"
}
```

**Tables:** Same as global — `ItemTable` and `cursorDiskKV`. However, `cursorDiskKV` is **always empty** in workspace DBs (all conversation data lives in the global DB's `cursorDiskKV`).

#### ItemTable Key Patterns (Verified)

| Key Pattern | Description | Value Type |
|-------------|-------------|------------|
| `aiService.prompts` | Prompt history (user queries with `commandType`) | JSON array |
| `aiService.generations` | Generation history (UUID, timestamp, type: "cmdk"/"composer") | JSON array |
| `composer.composerData` | Composer threads index | JSON |
| `workbench.panel.composerChatViewPane.<uuid>` | Chat pane states | JSON |
| `workbench.backgroundComposer.workspacePersistentData` | Per-workspace background composer config (git state, terminals, setup) | JSON |
| `cursor/sandboxSupported` | Whether sandbox is supported for this workspace | boolean |
| `cursor/workspaceEligibleForSnippetLearning` | Snippet learning eligibility | boolean |
| `cursor/editorLayout.*` | Editor layout dimensions | various |

**Composer Data Schema (`composer.composerData`):**
```json
{
  "allComposers": [
    {
      "type": "head",
      "composerId": "uuid",
      "createdAt": 1768989268373,
      "lastUpdatedAt": 1769708023016,
      "unifiedMode": "agent",           // "agent", "chat", "edit"
      "forceMode": "edit",
      "hasUnreadMessages": false,
      "totalLinesAdded": 0,
      "totalLinesRemoved": 0,
      "hasBlockingPendingActions": false,
      "isArchived": false,
      "isDraft": true,
      "isWorktree": false,
      "isSpec": false,
      "isProject": false,
      "isBestOfNSubcomposer": false,
      "numSubComposers": 0,
      "referencedPlans": [],
      "name": "Conversation title",
      "subtitle": "Context info",
      "contextUsagePercent": 23.35,
      "filesChangedCount": 0,
      "createdOnBranch": "main"
    }
  ],
  "selectedComposerIds": ["uuid"],
  "lastFocusedComposerIds": ["uuid"],
  "hasMigratedComposerData": true
}
```

**AI Service Prompts (`aiService.prompts`):**
```json
[
  {
    "text": "user query",
    "commandType": 4                       // 4 = composer, 1 = other
  }
]
```

**AI Service Generations (`aiService.generations`):**
```json
[
  {
    "unixMs": 1770317823074,
    "generationUUID": "uuid",
    "type": "cmdk"                         // "cmdk" (Cmd+K) or "composer"
  },
  {
    "unixMs": 1770490619148,
    "generationUUID": "uuid",
    "type": "composer",
    "textDescription": "user prompt text"  // only present for composer type
  }
]
```

---

### 1.3 AI Code Tracking Database

**Path:** `~/.cursor/ai-tracking/ai-code-tracking.db`

**Tables:**

```sql
CREATE TABLE ai_code_hashes (
  hash TEXT PRIMARY KEY,
  source TEXT NOT NULL,          -- "tab", "composer"
  fileExtension TEXT,
  fileName TEXT,
  requestId TEXT,
  conversationId TEXT,
  timestamp INTEGER,
  createdAt INTEGER NOT NULL,
  model TEXT
);
CREATE INDEX idx_ai_code_hashes_createdAt ON ai_code_hashes(createdAt);

CREATE TABLE scored_commits (
  commitHash TEXT NOT NULL,
  branchName TEXT NOT NULL,
  scoredAt INTEGER NOT NULL,
  PRIMARY KEY (commitHash, branchName)
);

CREATE TABLE conversation_summaries (
  conversationId TEXT PRIMARY KEY,
  title TEXT,
  tldr TEXT,
  overview TEXT,
  summaryBullets TEXT,
  model TEXT,
  mode TEXT,
  updatedAt INTEGER NOT NULL
);
CREATE INDEX idx_conversation_summaries_updatedAt ON conversation_summaries(updatedAt);

CREATE TABLE tracking_state (
  key TEXT PRIMARY KEY,
  value TEXT NOT NULL
);
```

**Sample `ai_code_hashes` row:**
```
hash|source|fileExtension|fileName|requestId|conversationId|timestamp|createdAt|model
bcb53981|tab||Untitled-2|52a841d6-...|...|1767665819549|1767665819549|
96658c10|composer|sh|/Users/sagar/bash/claude-grid.sh||79f0b7e3-...|1770811875677|1770811875677|default
```

**`tracking_state` contents:**
```
trackingStartTime|{"timestamp":1766379258558}
```

**`conversation_summaries`:** Empty in tested DB (0 rows). Schema exists but Cursor may not populate it consistently.

**`scored_commits` sample:**
```
commitHash|branchName|scoredAt
b5f2580c...|main|1771542309133
```

---

## 2. File-Based Storage

### 2.1 Agent Transcripts (NEW - Not in original research)

**Path:** `~/.cursor/projects/<project-slug>/agent-transcripts/<composerId>.txt`

**Format:** Plain text with YAML frontmatter and structured conversation:
```
user:
<user_query>
User message here
</user_query>

A:
<think>
Agent reasoning...
</think>

[Tool call] ToolName
  param1: value1
  param2: value2

[Tool result] ToolName

A:
Response text...
```

### 2.2 Terminal Sessions

**Path:** `~/.cursor/projects/<project-slug>/terminals/<id>.txt`

**Format:** Plain text with YAML frontmatter:
```yaml
---
pid: 79706
cwd: /path/to/workspace
---
<terminal output content>
```

### 2.3 MCP Server Metadata

**Path:** `~/.cursor/projects/<project-slug>/mcps/<server-name>/`

**Structure:**
```
mcps/<server-name>/
├── SERVER_METADATA.json    # Server identity
├── INSTRUCTIONS.md         # Optional instructions
├── resources/              # Cached resources
└── tools/                  # Tool definitions
    └── <tool-name>.json    # Tool schema
```

**SERVER_METADATA.json:**
```json
{
  "serverIdentifier": "server-id",
  "serverName": "Server Display Name"
}
```

**Tool Definition (`tools/<name>.json`):**
```json
{
  "name": "tool_name",
  "description": "Tool description",
  "arguments": {
    "type": "object",
    "properties": { ... },
    "required": ["param1"]
  },
  "outputSchema": {
    "type": "object",
    "additionalProperties": true
  }
}
```

### 2.4 IDE State

**Path:** `~/.cursor/ide_state.json`

```json
{
  "recentlyViewedFiles": [
    {
      "relativePath": "path/in/project",
      "absolutePath": "/full/path/to/file"
    }
  ]
}
```

### 2.5 Global MCP Configuration

**Path:** `~/.cursor/mcp.json`

```json
{
  "mcpServers": {
    "server-name": {
      "command": "uvx",
      "args": ["package-name"]
    }
  }
}
```

### 2.6 Skills

**Path:** `~/.cursor/skills-cursor/<skill-name>/SKILL.md`

Stores custom skills/instructions for the agent. Built-in skills include: `create-rule`, `create-skill`, `create-subagent`, `migrate-to-skills`, `update-cursor-settings`.

### 2.7 Other `~/.cursor` Files

| File | Description |
|------|-------------|
| `~/.cursor/argv.json` | VS Code command-line args (crash reporter ID, hardware acceleration) |
| `~/.cursor/extensions/` | Installed extensions (including Claude Code versions) |
| `~/.cursor/skip-cursor-agent-download` | Empty marker file to skip agent binary download |

---

## 3. Per-Project Files

### 3.1 Plans (Plan Mode)

**Path:** `<workspace>/.cursor/plans/*.md`

Markdown files created by Plan Mode.

### 3.2 Scratchpad

**Path:** `<workspace>/.cursor/scratchpad.md`

Used by long-running agents for iterative state.

### 3.3 Rules

**Path:** 
- `<workspace>/.cursorrules` - Legacy
- `<workspace>/.cursor/rules/*.mdc` - New format

Custom rules/instructions for the project.

---

## 4. Supporting Files

### 4.1 User Settings

**Path:** `~/Library/Application Support/Cursor/User/settings.json`

Standard VS Code settings format.

### 4.2 Profile Associations

**Path:** `~/Library/Application Support/Cursor/User/globalStorage/storage.json`

```json
{
  "profileAssociations": {
    "workspaces": {
      "file:///path/to/workspace": "__default__profile__"
    }
  }
}
```

---

## 5. Observability Schema Summary

### Core Tables for Extraction

| Table/Source | Purpose | Key Fields |
|--------------|---------|------------|
| `globalStorage/state.vscdb:cursorDiskKV` | **Richest source** — full conversations, per-message bubbles, diffs, checkpoints, token counts, timing | `composerData:*`, `bubbleId:*`, `checkpointId:*`, `codeBlockDiff:*` |
| `globalStorage/state.vscdb:ItemTable` | Global state, usage stats, memories | `aiCodeTracking.*`, `cursor/*` |
| `workspaceStorage/*/state.vscdb:ItemTable` | Per-workspace composer index, prompts | `composer.composerData`, `aiService.*` |
| `ai-tracking/ai-code-tracking.db:ai_code_hashes` | Code attribution | `hash`, `source`, `composerId` |
| `ai-tracking/ai-code-tracking.db:conversation_summaries` | Chat summaries | `conversationId`, `title`, `tldr` |
| `projects/*/agent-transcripts/*.txt` | Agent transcripts (plain text, less detail than cursorDiskKV) | Plain text |
| `projects/*/terminals/*.txt` | Terminal sessions | Plain text |
| `projects/*/mcps/*/tools/*.json` | MCP tool definitions | Tool schemas |

### Relationships

```
composerId (in workspace state.vscdb → composer.composerData)
    ├─→ cursorDiskKV: composerData:<composerId>    (full conversation + context)
    ├─→ cursorDiskKV: bubbleId:<composerId>:<bubbleId>  (per-message metadata + tokens)
    ├─→ cursorDiskKV: checkpointId:<composerId>:<id>    (file snapshots)
    ├─→ cursorDiskKV: codeBlockDiff:<composerId>:<id>   (line-level diffs)
    ├─→ cursorDiskKV: messageRequestContext:<composerId>:<bubbleId>  (request context)
    ├─→ agent-transcripts/<composerId>.txt              (plain text fallback)
    └─→ conversationId (in ai_code_hashes, conversation_summaries)
            └─→ Code changes tracked via hash in ai_code_hashes
```

### Key Identifiers

- **composerId**: UUID for a chat/agent session — primary key linking all data sources
- **bubbleId**: UUID for individual messages within a conversation
- **serverBubbleId**: Server-assigned bubble ID (on assistant responses only)
- **requestId**: UUID for specific LLM requests
- **workspace hash**: MD5-like hash mapping to workspace folder
- **agentKv blob hash**: SHA-256 content hash for content-addressed agent message storage

---

## 6. Research vs Reality Comparison

| Research Claim | Actual Finding |
|----------------|----------------|
| Chat data in `cursorDiskKV` with `composerData:<id>` keys | ✅ **Confirmed (Feb 2026)** — ~2K `composerData:<uuid>` entries with full inline conversations. Previously reported empty (may have been a timing issue or Cursor version difference). |
| Bubbles stored as `bubbleId:<composerId>:<bubbleId>` | ✅ **Confirmed (Feb 2026)** — ~17K entries with per-message metadata, token counts, capabilities. Keys stored as BLOBs (need `CAST(key AS TEXT)` to read). |
| Plan files in `.cursor/plans/` | ✅ Confirmed |
| Workspace DB has `state.vscdb` | ✅ Confirmed |
| NEW: `checkpointId` entries in `cursorDiskKV` | ~10K entries tracking file state snapshots between agent steps |
| NEW: `codeBlockDiff` entries in `cursorDiskKV` | ~3.7K entries with line-level diffs for code blocks |
| NEW: `messageRequestContext` in `cursorDiskKV` | ~1.6K entries with per-request context (terminal, rules, folder listings) |
| NEW: `agentKv` content-addressed message store | ~870 entries, keyed by `agentKv:blob:<sha256>`, storing system/user prompts |
| NEW: Agent transcripts as `.txt` files | Plain text files in `~/.cursor/projects/` |
| NEW: MCP tool definitions cached per-project | Not in research - new discovery |
| NEW: `ai-code-tracking.db` for attribution | Not in research - new discovery |
| NEW: Terminal sessions saved | Not in research - new discovery |

---

## 7. Extraction Priority

### P0 - High Value

1. `cursorDiskKV:composerData:*` - Full conversation threads with inline messages, token counts, timing, context
2. `cursorDiskKV:bubbleId:*` - Per-message metadata and token counts
3. `cursorDiskKV:checkpointId:*` - File state snapshots for agent step replay
4. `ai_code_hashes` - Code attribution tracking
5. `aiCodeTracking.dailyStats.*` - Usage metrics
6. `workspaceStorage:composer.composerData` - Composer session index (links composerId → workspace)

### P1 - Medium Value

1. `cursorDiskKV:codeBlockDiff:*` - Line-level diffs
2. `cursorDiskKV:messageRequestContext:*` - Per-request context snapshots
3. `agent-transcripts/*.txt` - Fallback for conversations (less detail than cursorDiskKV but available on remote)
4. `cursor/pendingMemories` - Learned preferences
5. `conversation_summaries` - Quick overview
6. Terminal sessions

### P2 - Low Value / Settings

1. `cursorai/serverConfig` - Feature flags
2. User settings
3. Profile associations

---

## 8. Known Transcript Limitations

Issues confirmed by testing on actual Cursor sessions (Feb 2026):

| Limitation | Impact | Mitigated by cursorDiskKV? | Tracking |
|------------|--------|---------------------------|----------|
| **Tool result content is stripped** — `[Tool result] ToolName` is logged but the actual output is empty in `.txt` transcripts | Cannot determine success/failure programmatically | ✅ **Yes** — `agentKv:blob:*` entries with `role:tool` contain full stdout, exit codes, structured `isError` status. Available for ~108 sessions via `conversationState` hash chain. | [#78](https://github.com/quickcall-dev/trace/issues/78) |
| **No per-message timestamps** — transcript `.txt` files have no time info | All messages get the same timestamp (file mtime) | ✅ **Yes** — Two sources: (1) `composerData` inline conversations have `timingInfo` on assistant messages (~1,691 sessions); (2) v3 `bubbleId:*` entries have `createdAt` ISO timestamps on all messages + `thinkingDurationMs` (~30 sessions). ~17 sessions have both timestamps and tool results. | [#79](https://github.com/quickcall-dev/trace/issues/79) |
| **No token counts** in `.txt` transcripts | Cannot track token usage per message | ✅ **Yes** — `bubbleId` entries have `tokenCount: {inputTokens, outputTokens}`, and conversation entries have `tokenCountUpUntilHere` with per-file breakdowns | — |
| **SQLite DBs not available on remote targets** — DBs live on the Mac running Cursor's GUI, transcripts live on the remote VM | Cannot correlate transcript data with DB data in remote SSH setups | ❌ No — `cursorDiskKV` is in the same local-only `state.vscdb` | — |

Parent tracking issue: [#80](https://github.com/quickcall-dev/trace/issues/80)

---

## 9. cursorDiskKV Nuances

Observations from Feb 2026 testing:

### 9.1 Three Conversation Storage Modes

`composerData:<composerId>` entries store conversations in one of three ways, depending on session type and Cursor version:

| Mode | Count (sample) | `conversation[]` | `conversationState` | Timestamps | Tool results | How to reconstruct |
|------|---------------|-------------------|---------------------|------------|-------------|-------------------|
| **Inline** | ~1,691 | Populated with full messages | Absent | ✅ `timingInfo` on assistant msgs | ❌ | Read `conversation[]` directly |
| **Agent hash chain + v3 bubbles** | ~17 | Empty or absent | Base64 hash list | ✅ `createdAt` on all `bubbleId:*` entries | ✅ `agentKv` `role:tool` | **Full signal** — decode `conversationState` for content + `bubbleId:*` for timestamps |
| **Agent hash chain (older)** | ~91 | Empty or absent | Base64 hash list | ❌ | ✅ `agentKv` `role:tool` | Decode `conversationState` → `agentKv:blob:<hash>` |
| **Headers-only** | ~282 | Empty `[]` | Absent | ❌ | ❌ | Use `fullConversationHeadersOnly` → look up `bubbleId:*` entries |

> **The ~17 "full signal" sessions** have v3 `bubbleId` entries with `createdAt` (ISO 8601 timestamps on every message, plus `thinkingDurationMs` on assistant messages) combined with `conversationState` hash chains providing full tool results. This is a newer Cursor feature — the overlap will grow with updates.

#### Agent Hash Chain (conversationState)

For newer agentic sessions, `conversationState` is a base64-encoded protobuf containing an ordered list of SHA-256 hashes. Each hash maps to an `agentKv:blob:<hash>` entry:

```
composerData:<composerId>
  → conversationState: "~CiA/nASb..." (base64)
    → decode → repeated protobuf field 1 (0x0a 0x20 + 32 bytes)
      → [0] agentKv:blob:3f9c04... → {"role":"system", "content":"..."}
      → [1] agentKv:blob:e79f70... → {"role":"user", "content":"..."}
      → [2] agentKv:blob:32ac3b... → {"role":"user", "content":[multipart]}
      → [3] agentKv:blob:cec654... → {"role":"assistant", "content":[...]}
      → [4] agentKv:blob:2b66c5... → {"role":"tool", "content":[{"toolName":"read_file", "result":"..."}]}
      → ...
```

**Decoding conversationState:**
1. Strip leading `~` prefix
2. Base64-decode (add `=` padding if needed)
3. Parse as repeated protobuf field 1, wire type 2, length 32 (SHA-256 hashes)
4. Look up each hash as `agentKv:blob:<hex(hash)>`
5. After the hash list, byte `0x2a` marks additional metadata (can be ignored for conversation reconstruction)

**This is the only path to full tool results** — the `agentKv` entries with `"role":"tool"` contain complete stdout, exit codes, file contents, and structured `providerOptions.cursor.highLevelToolCallResult` with `isError` status. This data is stripped from the `.txt` transcripts (see [#78](https://github.com/quickcall-dev/trace/issues/78)).

### 9.2 BLOB Keys

All `cursorDiskKV` keys are stored as BLOBs, not TEXT. They appear null/empty with normal `SELECT key` queries. You must use `CAST(key AS TEXT)` to read them. This is likely why earlier testing reported the table as "empty."

### 9.3 Data Overlap

There is significant overlap between data sources:

| Data | `composerData:*` (inline) | `bubbleId:*` (separate) | `agentKv:blob:*` | `agent-transcripts/*.txt` |
|------|--------------------------|------------------------|-------------------|--------------------------|
| Message text | ✅ `conversation[].text` | ✅ `text` (v3 only) | ✅ (JSON `content` or protobuf field 1) | ✅ Plain text |
| Token counts | ✅ `tokenCountUpUntilHere` (cumulative) | ✅ `tokenCount` (per-message) | ❌ | ❌ |
| Timing | ✅ `timingInfo` (assistant only, ms precision) | ✅ `createdAt` (v3 only, ISO 8601, all messages) | ❌ | ❌ |
| Thinking duration | ❌ | ✅ `thinkingDurationMs` (v3, assistant only) | ❌ | ❌ |
| Tool results | ❌ | ❌ | ✅ `role:tool` entries (stdout, exit codes, `isError`) | ❌ (stripped) |
| File context | ✅ `relevantFiles` (paths only) | ✅ `relevantFiles` | ✅ (protobuf: paths + line previews + token counts) | ❌ |
| Code diffs | Via `codeBlockDiff:*` | ❌ | ❌ | ❌ |
| Checkpoints | Via `checkpointId:*` | ❌ | ❌ | ❌ |
| Available on remote | ❌ (local Mac only) | ❌ (local Mac only) | ❌ (local Mac only) | ✅ (on remote VM) |

### 9.4 Three `agentKv` Value Formats

The `agentKv:blob:<sha256>` entries use content-addressed storage (deduplicated by SHA-256). Values come in three formats, distinguishable by the first byte:

| First byte | Format | Count | Contains |
|-----------|--------|-------|----------|
| `0x7B` (`{`) | JSON | ~319 | System prompts, user messages, assistant responses, tool results — standard `{"role":..., "content":...}` |
| `0x0A`, `0x12`, `0x1A`, `0x2A` | Protobuf | ~384 | User messages with rich IDE context (file references, line previews, richText, token counts per file) |
| Other (`0x0A` with hash payloads) | Protobuf index | ~168 | Reference entries pointing to other blobs (contain SHA-256 hashes as field values) |
| `0x23` (`#`), `0x66` (`f`), `0x3C` (`<`) | Plain text | ~19 | Raw file contents attached as context |
