Files
Nicolò Boschi 682f77a221 fix(cursor): register hooks on init, ship rules_file.py, flush on sessionEnd (#4041)
* 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>
2026-09-02 16:57:44 +02:00

13 KiB

sidebar_position, title, description
sidebar_position title description
6 Cursor Persistent Memory with Hindsight | Integration 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 using 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 →

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

# 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 to get a token, or start Hindsight locally with Docker:

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

Connect to a running Hindsight server (cloud or self-hosted). No local LLM needed — the server handles fact extraction.

{
  "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:

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:

# 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.