Skip to content
Open
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
34 changes: 34 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ jobs:
integrations-paperclip: ${{ steps.filter.outputs.integrations-paperclip }}
integrations-opencode: ${{ steps.filter.outputs.integrations-opencode }}
integrations-cursor: ${{ steps.filter.outputs.integrations-cursor }}
integrations-zed: ${{ steps.filter.outputs.integrations-zed }}
integrations-n8n: ${{ steps.filter.outputs.integrations-n8n }}
integrations-cloudflare-oauth-proxy: ${{ steps.filter.outputs.integrations-cloudflare-oauth-proxy }}
integrations-superagent: ${{ steps.filter.outputs.integrations-superagent }}
Expand Down Expand Up @@ -159,6 +160,8 @@ jobs:
- 'hindsight-integrations/opencode/**'
integrations-cursor:
- 'hindsight-integrations/cursor/**'
integrations-zed:
- 'hindsight-integrations/zed/**'
integrations-n8n:
- 'hindsight-integrations/n8n/**'
integrations-cloudflare-oauth-proxy:
Expand Down Expand Up @@ -479,6 +482,37 @@ jobs:
working-directory: ./hindsight-integrations/cursor
run: python -m pytest tests/ -v

test-zed-integration:
needs: [detect-changes]
if: >-
github.event_name != 'pull_request_review' &&
(github.event_name == 'workflow_dispatch' ||
needs.detect-changes.outputs.integrations-zed == 'true' ||
needs.detect-changes.outputs.ci == 'true')
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || '' }}

- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: '3.11'

- name: Install package and pytest
working-directory: ./hindsight-integrations/zed
# Installs the package (incl. the zstandard runtime dep) so the threads.db
# reader tests can decompress Zed's zstd blobs.
run: pip install -e . pytest

- name: Run tests
working-directory: ./hindsight-integrations/zed
# PR CI runs only the deterministic bucket; the real-LLM E2E bucket
# (requires_real_llm) needs a live Hindsight server and runs separately.
run: python -m pytest tests/ -v -m "not requires_real_llm"

test-omo-integration:
needs: [detect-changes]
if: >-
Expand Down
44 changes: 44 additions & 0 deletions hindsight-docs/docs-integrations/zed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
---
sidebar_position: 7
title: "Zed Persistent Memory with Hindsight | Integration"
description: "Add automatic long-term memory to the Zed editor's AI assistant with Hindsight. Recalls relevant project memory into every conversation and retains sessions — no manual steps."
---

# Zed

Automatic, always-on long-term memory for the [Zed](https://zed.dev) editor's AI assistant, powered by [Hindsight](https://vectorize.io/hindsight). When you chat with Zed's Agent Panel, relevant memory from past sessions on that project is injected automatically — no manual tool calls — and your conversations are retained so the next session builds on them.


## How It Works

Zed has no AI-conversation hook, but it **always includes a project's instruction file** (`.rules` / `AGENTS.md` / …) in every agent conversation, and it stores conversations in a local `threads.db`. `hindsight-zed` runs a small background daemon that uses both:

- **Auto-recall (passive injection):** when a Zed conversation updates, the daemon recalls relevant memory for that project and writes it into a fenced `<!-- HINDSIGHT -->` block in the project's instruction file. Zed includes that file automatically, so memory "just shows up" on the next turn. The block is written into the file Zed actually reads — it never hijacks your existing `AGENTS.md`/`CLAUDE.md`.
- **Auto-retain (passive capture):** the daemon reads finished and updated threads from `threads.db` and retains their transcripts into the project's Hindsight bank.

Memory is **per-project** by default — each git repository gets its own bank, so context from one codebase doesn't leak into another.

## Setup

```bash
pip install hindsight-zed
hindsight-zed init --api-token YOUR_HINDSIGHT_API_KEY
```

`init` writes config to `~/.hindsight/zed.json` and installs a background service (launchd on macOS, systemd user service on Linux). After that it's hands-off — open any project in Zed and memory works.

Use a [Hindsight Cloud](https://hindsight.vectorize.io) key, or point at a self-hosted server with `--api-url http://localhost:8888`. To share one bank across all projects, pass `--fixed-bank-id my-memory`.

## Commands

| Command | Description |
| --- | --- |
| `hindsight-zed init` | One-time setup: config + background daemon |
| `hindsight-zed status` | Whether the daemon is running |
| `hindsight-zed uninstall` | Stop and remove the daemon |

## Limitation

Zed exposes no per-prompt hook, so injection is **periodic** (refreshed when a conversation updates) rather than recomputed against each individual keystroke. In practice the relevant project memory is present in context for every turn.

See the [package README](https://github.com/vectorize-io/hindsight/tree/main/hindsight-integrations/zed) for full configuration options.
26 changes: 18 additions & 8 deletions hindsight-docs/src/data/integrations.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
{
"id": "litellm",
"name": "LiteLLM",
"description": "Add long-term memory to any LLM via LiteLLM proxy callbacks. Zero code changes configure once and every model gets memory.",
"description": "Add long-term memory to any LLM via LiteLLM proxy callbacks. Zero code changes \u2014 configure once and every model gets memory.",
"type": "official",
"by": "hindsight",
"category": "framework",
Expand Down Expand Up @@ -160,6 +160,16 @@
"link": "/sdks/integrations/cursor",
"icon": "/img/icons/cursor.svg"
},
{
"id": "zed",
"name": "Zed",
"description": "Automatic long-term memory for the Zed editor's AI assistant via Hindsight. Recalls relevant project memory into every conversation and retains sessions \u2014 no manual steps.",
"type": "official",
"by": "hindsight",
"category": "tool",
"link": "/sdks/integrations/zed",
"icon": "/img/icons/zed.svg"
},
{
"id": "cline",
"name": "Cline",
Expand Down Expand Up @@ -243,7 +253,7 @@
{
"id": "nemoclaw",
"name": "NemoClaw",
"description": "Persistent memory for NemoClaw sandboxed agents. One-command setup no code changes required.",
"description": "Persistent memory for NemoClaw sandboxed agents. One-command setup \u2014 no code changes required.",
"type": "official",
"by": "hindsight",
"category": "framework",
Expand All @@ -253,7 +263,7 @@
{
"id": "paperclip",
"name": "Paperclip",
"description": "Persistent memory for Paperclip AI agents. Install the plugin once and every agent gets automatic recall before runs and retain after no code changes.",
"description": "Persistent memory for Paperclip AI agents. Install the plugin once and every agent gets automatic recall before runs and retain after \u2014 no code changes.",
"type": "official",
"by": "hindsight",
"category": "framework",
Expand All @@ -263,7 +273,7 @@
{
"id": "right-agent",
"name": "Right Agent",
"description": "Persistent memory for Right Agent closed-box AI agents that run Claude Code inside OpenShell sandboxes. Hindsight is the native memory provider, selected during initialization.",
"description": "Persistent memory for Right Agent \u2014 closed-box AI agents that run Claude Code inside OpenShell sandboxes. Hindsight is the native memory provider, selected during initialization.",
"type": "official",
"by": "hindsight",
"category": "framework",
Expand Down Expand Up @@ -403,7 +413,7 @@
{
"id": "omo",
"name": "OMO (oh-my-openagent)",
"description": "Automatic long-term memory for OMO agent harness via Hindsight hooks. Recalls context before each prompt and retains session learnings zero config for cloud users.",
"description": "Automatic long-term memory for OMO agent harness via Hindsight hooks. Recalls context before each prompt and retains session learnings \u2014 zero config for cloud users.",
"type": "official",
"by": "hindsight",
"category": "tool",
Expand All @@ -423,7 +433,7 @@
{
"id": "obsidian",
"name": "Obsidian",
"description": "Sync your Obsidian vault into Hindsight and chat with an agent grounded on your notes. Your vault stays the source of truth every answer cites the note it came from.",
"description": "Sync your Obsidian vault into Hindsight and chat with an agent grounded on your notes. Your vault stays the source of truth \u2014 every answer cites the note it came from.",
"type": "official",
"by": "hindsight",
"category": "tool",
Expand All @@ -433,7 +443,7 @@
{
"id": "hindclaw",
"name": "HindClaw",
"description": "Production memory infrastructure for AI agents server-side access control via Hindsight extensions, Terraform provider for users/groups/banks/permissions, and an OpenClaw plugin with auto-managed embed.",
"description": "Production memory infrastructure for AI agents \u2014 server-side access control via Hindsight extensions, Terraform provider for users/groups/banks/permissions, and an OpenClaw plugin with auto-managed embed.",
"type": "community",
"by": "mrkhachaturov",
"category": "framework",
Expand All @@ -453,7 +463,7 @@
{
"id": "gemini-spark",
"name": "Gemini Spark",
"description": "Persistent memory for Google's Gemini Spark assistant via MCP. Spark calls Hindsight's recall and retain tools through its built-in MCP client works with Hindsight Cloud or self-hosted via an OAuth proxy.",
"description": "Persistent memory for Google's Gemini Spark assistant via MCP. Spark calls Hindsight's recall and retain tools through its built-in MCP client \u2014 works with Hindsight Cloud or self-hosted via an OAuth proxy.",
"type": "official",
"by": "hindsight",
"category": "tool",
Expand Down
10 changes: 10 additions & 0 deletions hindsight-docs/static/img/icons/zed.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions hindsight-integrations/zed/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.venv/
__pycache__/
*.pyc
77 changes: 77 additions & 0 deletions hindsight-integrations/zed/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# hindsight-zed

Automatic, always-on long-term memory for the [Zed](https://zed.dev) editor's AI
assistant, powered by [Hindsight](https://github.com/vectorize-io/hindsight).

When you chat with Zed's Agent Panel, relevant memory from past sessions on that
project is injected automatically — no manual tool calls, no slash commands —
and your conversations are retained so the next session builds on them.

## How it works

Zed has no AI-conversation hook, but it **always includes a project's
instruction file** (`.rules` / `AGENTS.md` / …) in every agent conversation, and
it stores conversations in a local `threads.db`. `hindsight-zed` runs a small
background daemon that uses both:

- **Auto-recall (passive injection):** when a Zed conversation updates, the
daemon recalls relevant memory for that project and writes it into a fenced
`<!-- HINDSIGHT -->` block in the project's instruction file. Zed includes
that file automatically, so memory "just shows up" on the next turn. The block
is written into the file Zed actually reads — it never hijacks or overwrites
your existing `AGENTS.md`/`CLAUDE.md`.
- **Auto-retain (passive capture):** the daemon reads finished/updated threads
from `threads.db` and retains their transcripts into the project's bank.

Memory is **per-project** by default — each git repo gets its own Hindsight bank,
so context from one codebase doesn't leak into another.

## Install

```bash
pip install hindsight-zed
hindsight-zed init --api-token YOUR_HINDSIGHT_API_KEY
```

`init` writes config to `~/.hindsight/zed.json` and installs a background service
(launchd on macOS, systemd user service on Linux) that runs automatically. After
that it's hands-off — open any project in Zed and memory works.

Use a [Hindsight Cloud](https://hindsight.vectorize.io) key, or point at a
self-hosted server with `--api-url http://localhost:8888`.

To use one shared bank across all projects instead of per-project:

```bash
hindsight-zed init --api-token ... --fixed-bank-id my-memory
```

## Commands

| Command | Description |
| --- | --- |
| `hindsight-zed init` | One-time setup: config + background daemon |
| `hindsight-zed status` | Whether the daemon is running |
| `hindsight-zed uninstall` | Stop and remove the daemon |
| `hindsight-zed run` | Run the daemon in the foreground (used by the service) |

## Configuration

Settings layer (later wins): defaults → `~/.hindsight/zed.json` → environment
variables (`HINDSIGHT_API_URL`, `HINDSIGHT_API_TOKEN`,
`HINDSIGHT_ZED_FIXED_BANK_ID`, `HINDSIGHT_ZED_AUTO_RECALL`, …). See
`hindsight_zed/config.py` for the full list.

## On-demand tools (optional)

The passive daemon above is the headline feature. For explicit recall/retain in
a conversation you can also add Hindsight's MCP server to Zed's `context_servers`
(see Zed's [MCP docs](https://zed.dev/docs/ai/mcp)) pointing at
`<api-url>/mcp/<bank-id>/`.

## Limitation

Zed exposes no per-prompt hook, so injection is **periodic** (refreshed when a
conversation updates), not query-aware on the exact keystroke. In practice the
relevant project memory is present in context; it just isn't recomputed against
each individual message the instant you send it.
3 changes: 3 additions & 0 deletions hindsight-integrations/zed/hindsight_zed/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"""Hindsight memory integration for the Zed editor."""

__version__ = "0.1.0"
84 changes: 84 additions & 0 deletions hindsight-integrations/zed/hindsight_zed/bank.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
"""Per-project memory-bank derivation.

Each project gets its own Hindsight bank, so memory from one codebase doesn't
bleed into another. The bank id is derived from the project's git repository
root (so all linked worktrees of a repo share one bank), falling back to the
directory basename when git is unavailable.
"""

import re
import subprocess
from pathlib import Path
from typing import Optional

from .config import ZedConfig


def _git_repo_root(directory: str) -> Optional[str]:
"""Return the main worktree root for *directory*, or None if not a repo.

``git rev-parse --git-common-dir`` resolves to the *main* worktree's ``.git``
even from a linked worktree, so all worktrees of a repo map to one bank.
"""
try:
out = subprocess.run(
["git", "rev-parse", "--git-common-dir"],
cwd=directory,
capture_output=True,
text=True,
timeout=5,
)
except (OSError, subprocess.SubprocessError):
return None
if out.returncode != 0:
return None
common = out.stdout.strip()
if not common:
return None
common_path = (Path(directory) / common).resolve() if not Path(common).is_absolute() else Path(common)
# ``<root>/.git`` → parent is the worktree root; a bare ``repo.git`` → itself.
if common_path.name == ".git":
return str(common_path.parent)
return str(common_path)


def _slugify(name: str) -> str:
"""Reduce a name to a stable, bank-safe slug."""
slug = re.sub(r"[^a-zA-Z0-9._-]+", "-", name).strip("-").lower()
return slug or "project"


def project_name(directory: str) -> str:
"""Derive a stable project name from a directory (git root basename)."""
root = _git_repo_root(directory) or directory
return _slugify(Path(root).name)


def bank_id_for_project(directory: str, config: ZedConfig) -> str:
"""Resolve the bank id for a project directory.

Honors a configured ``fixed_bank_id`` (single shared bank); otherwise
``<prefix>-<project>`` (per-project).
"""
if config.fixed_bank_id:
return config.fixed_bank_id
name = project_name(directory)
prefix = config.bank_prefix.strip("-")
return f"{prefix}-{name}" if prefix else name


def bank_id_for_thread_paths(folder_paths: list, config: ZedConfig) -> Optional[str]:
"""Resolve the bank id for a thread, from its ``folder_paths``.

Returns None when the thread has no associated project folder (so the caller
can fall back to a default). Uses the first existing folder path.
"""
if config.fixed_bank_id:
return config.fixed_bank_id
for path in folder_paths or []:
if path and Path(path).exists():
return bank_id_for_project(path, config)
# No usable folder — first path by name, or nothing.
if folder_paths:
return bank_id_for_project(folder_paths[0], config)
return None
Loading
Loading