Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Full-featured example applications demonstrating Hindsight integration patterns:
- **[sanity-blog-memory](./applications/sanity-blog-memory)** - Syncing Sanity CMS content to Hindsight
- **[chat-sdk-multi-platform](./applications/chat-sdk-multi-platform)** - Cross-platform Slack + Discord bot with shared memory (Vercel Chat SDK)
- **[stancetracker](./applications/stancetracker)** - Track political stances using AI-powered memory
- **[cursor-memory](./applications/cursor-memory)** - Persistent cross-session memory for Cursor using the Hindsight plugin

## Notebooks

Expand Down
92 changes: 92 additions & 0 deletions applications/cursor-memory/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
---
description: "Give Cursor 3 persistent memory across sessions using the Hindsight Cursor plugin"
tags: { sdk: "hindsight-cursor", topic: "Agents" }
---

# Cursor Memory

Give [Cursor](https://cursor.com) persistent memory across sessions using the Hindsight Cursor plugin. The plugin recalls relevant project context at session start and retains conversation transcripts after each task, so Cursor 3 agents can pick up where they left off.

## What This Demonstrates

- **Session recall** — Hindsight injects relevant project memories at the start of each Cursor session via the `sessionStart` hook
- **On-demand MCP tools** — the agent can use `recall`, `retain`, and `reflect` tools mid-session for deeper memory lookups
- **Automatic retain** — Cursor task transcripts are stored to Hindsight after each task via `stop`
- **Cross-session continuity** — Cursor remembers project context, preferences, and decisions across separate chats
- **Cursor 3 workflow fit** — works with the new Agents Window, rules, and skills model
- **Per-project or per-session isolation** — optional dynamic bank IDs keep memory scoped however you want

## Prerequisites

1. **Cursor 3** with plugin support enabled
2. **Python 3.9+** with `uv` or `pip` to run the demo scripts
```bash
python3 --version
```
3. **A running Hindsight API**
- Local: `http://localhost:8888`
- Or Hindsight Cloud / self-hosted API

## Quick Start

### 1. Install the Cursor plugin

```bash
pip install hindsight-cursor
cd /path/to/your-project
hindsight-cursor init --api-url http://localhost:8888
```

**Using Hindsight Cloud instead?** [Sign up](https://ui.hindsight.vectorize.io/signup) and create an API key under **Settings > API Keys**, then:

```bash
hindsight-cursor init --api-url https://api.hindsight.vectorize.io --api-token YOUR_HINDSIGHT_API_TOKEN
```

> If Cursor is already open, **fully quit and reopen it** after adding the plugin. Plugins load at startup.

### 2. Seed the demo bank

For local Hindsight:

```bash
uv run --with hindsight-client python seed_memory.py --reset
```

For Hindsight Cloud, export your connection details first:

```bash
export HINDSIGHT_URL=https://api.hindsight.vectorize.io
export HINDSIGHT_API_KEY=YOUR_HINDSIGHT_API_TOKEN
uv run --with hindsight-client python seed_memory.py --reset
```

### 3. Verify the seed data

```bash
uv run --with hindsight-client python verify_memory.py
```

### 4. Open Cursor 3

Open the target project in Cursor and start a conversation. Ask questions like:

- `What testing framework do I prefer for Python work?`
- `What stack is this project using?`
- `What coding style do I usually prefer?`

The plugin injects matching memories at session start, and the agent can use MCP tools for deeper lookups mid-session.

## Files

| File | Description |
|------|-------------|
| `seed_memory.py` | Seeds sample developer facts for the Cursor memory demo |
| `verify_memory.py` | Queries Hindsight directly to verify the seed and recall flow |
| `requirements.txt` | Optional dependencies if you prefer `pip install -r requirements.txt` |

## Notes

- Use `dynamicBankId: true` with `["agent", "project"]` to isolate memory per repository.
- Use `["session"]` if you want one bank per Cursor conversation.
- The `init` command also sets up `.cursor/mcp.json` for on-demand MCP tools (`recall`, `retain`, `reflect`). Use `--no-mcp` to skip this.
1 change: 1 addition & 0 deletions applications/cursor-memory/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
hindsight-client
63 changes: 63 additions & 0 deletions applications/cursor-memory/seed_memory.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
"""Seed sample developer facts into Hindsight for the Cursor memory demo.

Usage:
python seed_memory.py
python seed_memory.py --reset
"""

import asyncio
import os
import sys

from hindsight_client import Hindsight

BANK_ID = os.environ.get("BANK_ID", "cursor")
HINDSIGHT_URL = os.environ.get("HINDSIGHT_URL", "http://localhost:8888")
HINDSIGHT_API_KEY = os.environ.get("HINDSIGHT_API_KEY")

SAMPLE_FACTS = [
"User prefers pytest for Python testing and Vitest for TypeScript projects",
"Current project stack: FastAPI backend, React frontend, PostgreSQL database",
"User prefers TypeScript over JavaScript for new frontend work",
"Team convention: snake_case in the database, camelCase in the API layer",
"User prefers functional patterns over class-heavy designs when practical",
"The team uses GitHub Actions for CI and requires tests before merging",
"Cursor should keep edits minimal and avoid unnecessary file churn",
"When debugging, the user prefers reproducing the bug first and then adding targeted tests",
]


async def reset_bank(client: Hindsight) -> None:
try:
await client.adelete_bank(bank_id=BANK_ID)
print(f"Cleared memory bank '{BANK_ID}'")
except Exception:
pass


async def main() -> None:
client_kwargs = {"base_url": HINDSIGHT_URL, "timeout": 30.0}
if HINDSIGHT_API_KEY:
client_kwargs["api_key"] = HINDSIGHT_API_KEY

client = Hindsight(**client_kwargs)
try:
if "--reset" in sys.argv:
await reset_bank(client)

content = "\n".join(f"- {fact}" for fact in SAMPLE_FACTS)
await client.aretain(
bank_id=BANK_ID,
content=content,
document_id="cursor-seed-demo",
context="cursor-demo",
)

print(f"Seeded {len(SAMPLE_FACTS)} developer facts into bank '{BANK_ID}'")
print("Next: run `python verify_memory.py` or open Cursor and ask about your preferences.")
finally:
await client.aclose()


if __name__ == "__main__":
asyncio.run(main())
56 changes: 56 additions & 0 deletions applications/cursor-memory/verify_memory.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
"""Verify the Cursor memory demo by recalling a known preference from Hindsight.

Retries automatically if memories are not yet available (retain may still be
processing after seeding).
"""

import asyncio
import os

from hindsight_client import Hindsight

BANK_ID = os.environ.get("BANK_ID", "cursor")
HINDSIGHT_URL = os.environ.get("HINDSIGHT_URL", "http://localhost:8888")
HINDSIGHT_API_KEY = os.environ.get("HINDSIGHT_API_KEY")

MAX_RETRIES = 5
RETRY_DELAY_SECS = 3


async def main() -> None:
client_kwargs = {"base_url": HINDSIGHT_URL, "timeout": 30.0}
if HINDSIGHT_API_KEY:
client_kwargs["api_key"] = HINDSIGHT_API_KEY

client = Hindsight(**client_kwargs)
try:
items = []
for attempt in range(1, MAX_RETRIES + 1):
result = await client.arecall(
bank_id=BANK_ID,
query="What testing frameworks and coding preferences does the user have?",
max_tokens=512,
budget="mid",
)
items = result.results
if items:
break
if attempt < MAX_RETRIES:
print(f"No memories yet (attempt {attempt}/{MAX_RETRIES}), retrying in {RETRY_DELAY_SECS}s...")
await asyncio.sleep(RETRY_DELAY_SECS)

if not items:
print(f"Warning: No memories found after {MAX_RETRIES} attempts. The seed data may still be processing.")
print(" Try running again in a few seconds, or check that the Hindsight server is running.")

print(f"Recalled {len(items)} memories from '{BANK_ID}'")
for item in items[:5]:
text = item.text.strip()
memory_type = item.type or "unknown"
print(f"- [{memory_type}] {text}")
finally:
await client.aclose()


if __name__ == "__main__":
asyncio.run(main())
Loading