mirror of
https://github.com/vectorize-io/hindsight.git
synced 2026-09-14 19:31:49 +08:00
682f77a221
* fix(cursor): make init register sessionStart hooks session_start imports lib.rules_file but init never copied it, and Cursor never saw the hooks because project .cursor/hooks.json was missing. Fixes #3864 * fix(cursor): flush sessions on sessionEnd, and harden init/uninstall Follow-up on top of lei_lei's #4023, which fixed the two reasons the plugin never ran at all: `scripts/lib/rules_file.py` was missing from `_PLUGIN_FILES` (so `session_start.py` ImportError'd on every invocation), and `init` never wrote a project `.cursor/hooks.json` (Cursor only loads hooks from the workspace or user file, not from `.cursor-plugin/`). Verified end to end against a real Cursor install and a local hindsight-embed: hooks register, `sessionStart` fires, and the agent answered from a seeded memory that existed nowhere in the project files. Four gaps surfaced while testing. Retain never ran for short sessions. `stop` is the only retain trigger, and every firing is subject to the turn window — at the default retainEveryNTurns of 10, a seven-turn conversation fires `stop` seven times, is gated seven times, and is never stored. `retain.py` is now registered on `sessionEnd` too and treats it as a final flush that bypasses the window. The two events overlap at the end of a conversation, so retains are made idempotent: a per-session watermark of messages already retained (`retained.json`) makes a run with nothing new a no-op. In chunked mode the flush stores only the tail past the watermark, keeping chunks non-overlapping. `python3` was hardcoded into the generated hook commands. Windows has no `python3` on PATH and the plugin explicitly supports it (`state.py` has a win32 branch), so every hook was a silent no-op there. `init` reported success over an install that copied nothing. When the bundled payload is incomplete it warned per file and still printed "Plugin files copied." and "Done!" — the same silent-no-op shape as #3864. It now aborts with a non-zero exit and no half-installed state. `uninstall` left the generated session rules file behind. It is written with `alwaysApply: true`, so a dead session's memories kept being injected into every agent turn after removal; its `.gitignore` line is stripped too. Co-authored-by: lei_lei <96427312+leilei3167@users.noreply.github.com> Claude-Session: https://claude.ai/code/session_01F3i5UVdZRK16AZ9oFQqsg4 --------- Co-authored-by: lei_lei <96427312+leilei3167@users.noreply.github.com>
246 lines
13 KiB
Markdown
246 lines
13 KiB
Markdown
---
|
|
sidebar_position: 6
|
|
title: "Cursor Persistent Memory with Hindsight | Integration"
|
|
description: "Add long-term memory to Cursor with Hindsight. Automatically recalls relevant context at session start and retains conversations after each task using Cursor's plugin architecture."
|
|
---
|
|
|
|
# Cursor
|
|
|
|
Biomimetic long-term memory for [Cursor](https://cursor.com) using [Hindsight](https://vectorize.io/hindsight). Automatically recalls relevant project context at session start and retains conversation transcripts after each task — adapted to Cursor's hook-based plugin architecture with MCP for on-demand tools.
|
|
|
|
[View Changelog →](/changelog/integrations/cursor)
|
|
|
|
## How It Works
|
|
|
|
The Hindsight plugin uses two complementary mechanisms:
|
|
|
|
| | Plugin Hooks (automatic) | MCP Tools (on-demand) |
|
|
|--|--------------------------|----------------------|
|
|
| **Install** | `pip install hindsight-cursor && hindsight-cursor init` | Configured automatically by `init` |
|
|
| **Recall** | Session start — memories injected via `additionalContext` | Agent calls `recall` tool mid-session |
|
|
| **Retain** | Automatic on task stop | Agent calls `retain` tool explicitly |
|
|
| **Reflect** | Not available via hooks | Available as a tool |
|
|
| **Best for** | Ambient project memory with no user intervention | Targeted lookups and explicit memory operations |
|
|
|
|
Both are set up by a single `hindsight-cursor init` command. Use `--no-mcp` to skip the MCP integration if you only want hooks.
|
|
|
|
## Quick Start
|
|
|
|
```bash
|
|
# 1. Install the plugin
|
|
pip install hindsight-cursor
|
|
cd /path/to/your-project
|
|
|
|
# 2a. Connect to Hindsight Cloud (fastest — no local server needed)
|
|
hindsight-cursor init --api-url https://api.hindsight.vectorize.io --api-token YOUR_HINDSIGHT_API_TOKEN
|
|
|
|
# 2b. Or connect to a local Hindsight server
|
|
hindsight-cursor init --api-url http://localhost:8888
|
|
|
|
# 3. Fully quit and reopen Cursor — plugins load at startup
|
|
```
|
|
|
|
Sign up at [Hindsight Cloud](https://ui.hindsight.vectorize.io/signup) to get a token, or start Hindsight locally with Docker:
|
|
|
|
```bash
|
|
export OPENAI_API_KEY=your-key
|
|
docker run --rm -it --pull always -p 8888:8888 \
|
|
-e HINDSIGHT_API_LLM_API_KEY=$OPENAI_API_KEY \
|
|
-e HINDSIGHT_API_LLM_MODEL=gpt-4o-mini \
|
|
-v $HOME/.hindsight-docker:/home/hindsight/.pg0 \
|
|
ghcr.io/vectorize-io/hindsight:latest
|
|
```
|
|
|
|
### What `init` does
|
|
|
|
- Copies plugin files into `.cursor-plugin/hindsight-memory/`
|
|
- Writes/merges project `.cursor/hooks.json` so Cursor registers the hooks. Cursor only loads hooks from `.cursor/hooks.json` (workspace) or `~/.cursor/hooks.json` (user) — files under `.cursor-plugin/` are not enough on their own. Hooks you already had are preserved, and re-running `init` replaces only Hindsight's own entries.
|
|
- Creates `~/.hindsight/cursor.json` with your connection settings (if the file does not already exist)
|
|
- Writes `.cursor/mcp.json` with the Hindsight MCP endpoint for on-demand tools
|
|
- Use `--force` to overwrite an existing installation
|
|
- Use `--no-mcp` to skip the MCP configuration
|
|
|
|
`hindsight-cursor uninstall` reverses all of it: the plugin directory, Hindsight's `.cursor/hooks.json` entries, the MCP server entry, the generated session rules file, and its `.gitignore` line.
|
|
|
|
:::caution
|
|
If you add the plugin to an already-open workspace, **fully quit Cursor and reopen it**. Plugins are loaded at startup — a simple window reload is not enough.
|
|
:::
|
|
|
|
## Features
|
|
|
|
- **Session recall** — at the start of each session, queries Hindsight for relevant project memories and injects them as context via `additionalContext` (invisible to the chat, visible to the agent)
|
|
- **Auto-retain** — after every task completion, extracts and retains conversation content to Hindsight for long-term storage
|
|
- **On-demand MCP tools** — `recall`, `retain`, and `reflect` tools for explicit mid-session memory operations
|
|
- **On-demand recall skill** — use the `hindsight-recall` skill for manual memory lookups
|
|
- **Daemon management** — can auto-start/stop `hindsight-embed` locally or connect to an external Hindsight server
|
|
- **Dynamic bank IDs** — supports per-agent, per-project, or per-session memory isolation
|
|
- **Zero runtime dependencies** — plugin scripts use pure Python stdlib only
|
|
|
|
## Architecture
|
|
|
|
The plugin uses Cursor's hook system:
|
|
|
|
| Hook | Event | Purpose |
|
|
|------|-------|---------|
|
|
| `session_start.py` | `sessionStart` | **Session recall** — query memories, inject as `additionalContext` |
|
|
| `retain.py` | `stop` | **Auto-retain** — extract transcript, POST to Hindsight |
|
|
| `retain.py` | `sessionEnd` | **Final flush** — retain whatever the turn window never got to |
|
|
|
|
The `sessionStart` hook fires once when a new Cursor session begins. It performs a broad project-level recall and injects relevant memories as hidden context.
|
|
|
|
`retain.py` runs on `sessionEnd` as well. Without that, every conversation shorter than `retainEveryNTurns` is lost outright: at the default of 10, a seven-turn chat fires `stop` seven times, the turn gate rejects all seven, and the session is never stored. `sessionEnd` bypasses the turn window so the tail of a session is always retained. The plugin tracks how many transcript messages it has already retained per session, so the overlap between the two events cannot double-store.
|
|
|
|
The `init` command also configures Cursor's MCP support (`.cursor/mcp.json`) to connect to Hindsight's MCP endpoint, giving the agent explicit `recall`, `retain`, and `reflect` tools for mid-session use.
|
|
|
|
Additionally, the plugin provides:
|
|
- **Skill** (`hindsight-recall`) — on-demand memory querying
|
|
- **Rule** (`hindsight-memory.mdc`) — always-on rule instructing the agent to leverage recalled memories and MCP tools
|
|
|
|
## Connection Modes
|
|
|
|
### 1. External API (recommended for production)
|
|
|
|
Connect to a running Hindsight server (cloud or self-hosted). No local LLM needed — the server handles fact extraction.
|
|
|
|
```json
|
|
{
|
|
"hindsightApiUrl": "https://your-hindsight-server.com",
|
|
"hindsightApiToken": "your-token"
|
|
}
|
|
```
|
|
|
|
### 2. Local Daemon (auto-managed)
|
|
|
|
The plugin automatically starts and stops `hindsight-embed` via `uvx`. Requires an LLM provider API key for local fact extraction.
|
|
|
|
Set an LLM provider:
|
|
```bash
|
|
export OPENAI_API_KEY="sk-your-key"
|
|
# or
|
|
export ANTHROPIC_API_KEY="your-key"
|
|
```
|
|
|
|
The model is selected automatically by the Hindsight API. To override, set `HINDSIGHT_LLM_MODEL`.
|
|
|
|
### 3. Existing Local Server
|
|
|
|
If you already have `hindsight-embed` running, leave `hindsightApiUrl` empty and set `apiPort` to match your server's port. The plugin will detect it automatically.
|
|
|
|
## Configuration
|
|
|
|
All settings live in `~/.hindsight/cursor.json`. Every setting can also be overridden via environment variables. The plugin ships with sensible defaults — you only need to configure what you want to change.
|
|
|
|
**Loading order** (later entries win):
|
|
1. Built-in defaults (hardcoded in the plugin)
|
|
2. Plugin `settings.json` (ships with the plugin, at `CURSOR_PLUGIN_ROOT/settings.json`)
|
|
3. User config (`~/.hindsight/cursor.json` — recommended for your overrides)
|
|
4. Environment variables
|
|
|
|
---
|
|
|
|
### Connection & Daemon
|
|
|
|
| Setting | Env Var | Default | Description |
|
|
|---------|---------|---------|-------------|
|
|
| `hindsightApiUrl` | `HINDSIGHT_API_URL` | `""` (empty) | URL of an external Hindsight API server. When empty, the plugin uses a local daemon instead. |
|
|
| `hindsightApiToken` | `HINDSIGHT_API_TOKEN` | `null` | Authentication token for the external API. Only needed when `hindsightApiUrl` is set. |
|
|
| `apiPort` | `HINDSIGHT_API_PORT` | `9077` | Port used by the local `hindsight-embed` daemon. |
|
|
| `embedVersion` | `HINDSIGHT_EMBED_VERSION` | `"latest"` | Which version of `hindsight-embed` to install via `uvx`. |
|
|
| `embedPackagePath` | `HINDSIGHT_EMBED_PACKAGE_PATH` | `null` | Local path to a `hindsight-embed` checkout for development. |
|
|
|
|
---
|
|
|
|
### LLM Provider (local daemon only)
|
|
|
|
These settings configure which LLM the local daemon uses for fact extraction. They are **ignored** when connecting to an external API.
|
|
|
|
| Setting | Env Var | Default | Description |
|
|
|---------|---------|---------|-------------|
|
|
| `llmProvider` | `HINDSIGHT_LLM_PROVIDER` | auto-detect | LLM provider: `openai`, `anthropic`, `gemini`, `groq`, `ollama`. Auto-detects by checking for API key env vars. |
|
|
| `llmModel` | `HINDSIGHT_LLM_MODEL` | provider default | Override the default model for the chosen provider. |
|
|
| `llmApiKeyEnv` | — | provider standard | Name of the env var holding the API key, if non-standard. |
|
|
|
|
---
|
|
|
|
### Memory Bank
|
|
|
|
A **bank** is an isolated memory store — like a separate "brain."
|
|
|
|
| Setting | Env Var | Default | Description |
|
|
|---------|---------|---------|-------------|
|
|
| `bankId` | `HINDSIGHT_BANK_ID` | `"cursor"` | The bank ID when `dynamicBankId` is `false`. |
|
|
| `bankMission` | `HINDSIGHT_BANK_MISSION` | generic assistant prompt | Description of the agent's identity and purpose. |
|
|
| `dynamicBankId` | `HINDSIGHT_DYNAMIC_BANK_ID` | `false` | When `true`, derives a unique bank ID from context fields (see `dynamicBankGranularity`). |
|
|
| `dynamicBankGranularity` | — | `["agent", "project"]` | Fields to combine for dynamic bank IDs: `agent`, `project`, `session`, `channel`, `user`. |
|
|
| `bankIdPrefix` | — | `""` | String prepended to all bank IDs for namespacing. |
|
|
| `agentName` | `HINDSIGHT_AGENT_NAME` | `"cursor"` | Name used for the `agent` field in dynamic bank ID derivation. |
|
|
|
|
---
|
|
|
|
### Session Recall
|
|
|
|
Session recall runs once at the start of each session. It queries Hindsight for relevant project memories and injects them into the agent's context as invisible `additionalContext`.
|
|
|
|
| Setting | Env Var | Default | Description |
|
|
|---------|---------|---------|-------------|
|
|
| `autoRecall` | `HINDSIGHT_AUTO_RECALL` | `true` | Master switch for session recall. |
|
|
| `recallBudget` | `HINDSIGHT_RECALL_BUDGET` | `"mid"` | Search thoroughness: `"low"`, `"mid"`, `"high"`. |
|
|
| `recallMaxTokens` | `HINDSIGHT_RECALL_MAX_TOKENS` | `1024` | Max tokens in the recalled memory block. |
|
|
| `recallTypes` | — | `["world", "experience"]` | Memory types to retrieve. |
|
|
| `recallMaxQueryChars` | `HINDSIGHT_RECALL_MAX_QUERY_CHARS` | `800` | Max character length of the query. |
|
|
|
|
---
|
|
|
|
### Auto-Retain
|
|
|
|
Auto-retain runs after the agent completes a task. It extracts the conversation transcript and sends it to Hindsight.
|
|
|
|
| Setting | Env Var | Default | Description |
|
|
|---------|---------|---------|-------------|
|
|
| `autoRetain` | `HINDSIGHT_AUTO_RETAIN` | `true` | Master switch for auto-retain. |
|
|
| `retainMode` | `HINDSIGHT_RETAIN_MODE` | `"full-session"` | Retention strategy. `"full-session"` or `"chunked"`. |
|
|
| `retainEveryNTurns` | `HINDSIGHT_RETAIN_EVERY_N_TURNS` | `10` | How often to retain. `1` = every turn. |
|
|
| `retainOverlapTurns` | — | `2` | Extra turns included from the previous chunk for continuity. |
|
|
| `retainContext` | `HINDSIGHT_RETAIN_CONTEXT` | `"cursor"` | Source label for retained memories. |
|
|
| `retainToolCalls` | — | `false` | Whether to include tool calls in the retained transcript. |
|
|
|
|
---
|
|
|
|
### Debug
|
|
|
|
| Setting | Env Var | Default | Description |
|
|
|---------|---------|---------|-------------|
|
|
| `debug` | `HINDSIGHT_DEBUG` | `false` | Enable verbose logging to stderr. Prefixed with `[Hindsight]`. |
|
|
|
|
## Verifying Plugin Hooks
|
|
|
|
The plugin writes a status file on every hook invocation — even when no memories are found or retain is skipped. Check them to confirm hooks are firing:
|
|
|
|
```bash
|
|
# Default location when CURSOR_PLUGIN_DATA is not set:
|
|
cat ~/.hindsight/cursor-state/state/last_recall.json
|
|
cat ~/.hindsight/cursor-state/state/last_retain.json
|
|
```
|
|
|
|
Each file contains:
|
|
- `saved_at` — timestamp of the last invocation
|
|
- `status` — one of `success`, `empty`, `skipped`, or `error`
|
|
- `bank_id` — which bank was used (present on `success` and `empty`)
|
|
- `mode` — always `plugin`
|
|
- `hook` — `sessionStart` for recall
|
|
- `result_count` (recall) or `message_count` (retain) — present on `success`
|
|
|
|
If `saved_at` updates when you use Cursor, the hooks are firing. Check `status` to understand what happened.
|
|
|
|
## Troubleshooting
|
|
|
|
**Plugin not activating**: Check that `.cursor-plugin/plugin.json` exists in the plugin directory. Enable `"debug": true` in `~/.hindsight/cursor.json` and check stderr output.
|
|
|
|
**Seeing "Ran Recall in hindsight" in the Agent Window?** That is MCP, not the plugin. Plugin-based recall is silent — it injects context via `additionalContext` without a visible tool call. If you see explicit Hindsight tool calls, you have MCP configured in `.cursor/mcp.json`. Both can work together.
|
|
|
|
**Recall returning no memories**: Verify the Hindsight server is reachable (`curl http://localhost:9077/health`). Memories need at least one retain cycle.
|
|
|
|
**Daemon not starting**: Ensure an LLM API key is set. Review daemon logs at `~/.hindsight/profiles/cursor.log`.
|
|
|
|
**High latency on session start**: The session recall hook has a 15-second timeout. Use `recallBudget: "low"` or reduce `recallMaxTokens`.
|