Skip to content
Merged
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
45 changes: 34 additions & 11 deletions hindsight-api-slim/hindsight_api/engine/memory_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -14238,18 +14238,41 @@ async def rename_knowledge_node(
)
await self._validate_operation(self._operation_validator.validate_bank_write(ctx))
backend = await self._get_backend()
# A page's searchable document is its backing mental model's name + content,
# so the rename must also update mental_models.name — and re-tokenize its
# search_vector for vchord (native is a generated column, the other backends
# index base columns; same helper as create/update/clear_mental_model). Both
# writes share one transaction, so a knowledge_pages name-uniqueness
# violation rolls the mental-model name back with it. Folders carry no
# backing model (mental_model_id is NULL), so only the node row is touched.
sv_expr = pg_search_vector_expr(
get_config(), text_col="$3", context_col="content", signals_col=None, native_inline=False
)
sv_clause = f", search_vector = {sv_expr}" if sv_expr else ""
async with acquire_with_retry(backend) as conn:
row = await conn.fetchrow(
f"""
UPDATE {fq_table("knowledge_pages")}
SET name = $3, updated_at = now()
WHERE bank_id = $1 AND id = $2
RETURNING {self._KP_COLUMNS}
""",
bank_id,
node_id,
name,
)
async with conn.transaction():
row = await conn.fetchrow(
f"""
UPDATE {fq_table("knowledge_pages")}
SET name = $3, updated_at = now()
WHERE bank_id = $1 AND id = $2
RETURNING {self._KP_COLUMNS}
""",
bank_id,
node_id,
name,
)
if row is not None and row["mental_model_id"] is not None:
await conn.execute(
f"""
UPDATE {fq_table("mental_models")}
SET name = $3{sv_clause}
WHERE bank_id = $1 AND id = $2
""",
bank_id,
row["mental_model_id"],
name,
)
return self._row_to_knowledge_node(row) if row else None

async def update_knowledge_page(
Expand Down
25 changes: 25 additions & 0 deletions hindsight-api-slim/tests/test_knowledge_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,31 @@ async def test_rename(self, api_client, kb_bank):
assert resp.status_code == 200, resp.text
assert resp.json()["name"] == "Compliance"

async def test_rename_page_syncs_backing_model_and_search(self, api_client, kb_bank, memory, request_context):
"""Renaming a page must also rename its backing mental model so the page's
searchable document (name + content) reflects the new name — #3307. Before
the fix the visible name changed but the mental model kept the old name,
leaving stale lexical/vector projections."""
bank_id, ids = kb_bank
resp = await api_client.patch(
f"/v1/default/banks/{_enc(bank_id)}/knowledge-base/nodes/{ids.orders}",
json={"name": "Purchase Receipts"},
)
assert resp.status_code == 200, resp.text
assert resp.json()["name"] == "Purchase Receipts"

# The backing mental model's name is updated in the same transaction.
mm = await memory.get_mental_model(bank_id, ids.orders_mm, request_context=request_context)
assert mm["name"] == "Purchase Receipts"

# The new name is now searchable (the BM25 arm indexes page name + content).
hit = await api_client.get(
f"/v1/default/banks/{_enc(bank_id)}/knowledge-base/search",
params={"q": "purchase receipts", "limit": 5},
)
assert hit.status_code == 200, hit.text
assert any(r["id"] == ids.orders for r in hit.json()["results"])

async def test_update_page_options(self, api_client, kb_bank):
bank_id, ids = kb_bank
resp = await api_client.patch(
Expand Down
85 changes: 85 additions & 0 deletions skills/hindsight-docs/references/sdks/integrations/agent-plugin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@

# Agent Plugins

Portable long-term memory for any [Agent Plugins](https://agent-plugins.org) client, powered by [Hindsight](https://vectorize.io/hindsight).

[Agent Plugins](https://agent-plugins.org) is the vendor-neutral open standard (developed with Amazon, Cursor, Microsoft, OpenAI, and Vercel) for packaging **Agent Skills + MCP servers** into a single distributable plugin. Instead of a separate integration per tool, Hindsight ships **one** plugin that every compatible client can load — at launch: **ChatGPT / Codex, Cursor, GitHub Copilot, Kiro, and VS Code**.

## Quick Start

> **💡 Recommended: Hindsight Cloud**
>
[Sign up free](https://ui.hindsight.vectorize.io/signup) for a Hindsight Cloud API key — no self-hosting, no local daemon to manage.
1. Get your `hsk_...` API key from [ui.hindsight.vectorize.io/connect](https://ui.hindsight.vectorize.io/connect).
2. Set the environment variables the plugin reads:

```bash
export HINDSIGHT_API_KEY="hsk_your_token"
export HINDSIGHT_BANK_ID="my-project" # optional; defaults to "default"
```

3. Install the plugin in your client (through its plugin/MCP UI, or by pointing it at the plugin directory — installation is client-specific per the standard).

Once installed, ask the agent something that depends on past context, or tell it a durable preference — it calls `recall` and `retain` automatically, guided by the bundled skill.

## What's in the plugin

The plugin is a thin, transport-only wrapper — all memory logic stays server-side in Hindsight. It follows the Agent Plugins `1.0.0` layout:

```
agent-plugin/
├── plugin.json # manifest ($schema + name + metadata)
├── mcp.json # Hindsight MCP server (Streamable HTTP)
└── skills/
└── hindsight-memory/
└── SKILL.md # teaches the agent when to recall / retain / reflect
```

- **`mcp.json`** connects the client to Hindsight's built-in [MCP server](../../developer/mcp-server.md) over Streamable HTTP.
- **`skills/hindsight-memory/SKILL.md`** is loaded into the agent's context so it knows *when* to reach for memory, not just that the tools exist.

## Memory tools

Via the MCP server, the agent gets Hindsight's full memory surface. The three it reaches for most:

| Tool | When | What it does |
|------|------|--------------|
| `recall` | Before answering, when past context could help | Semantic + keyword + graph + temporal retrieval over the bank |
| `retain` | After learning a durable, reusable fact | Stores the fact for future sessions |
| `reflect` | When a lookup is too shallow and you need synthesized reasoning | Disposition-aware reasoning over everything remembered |

Additional tools (knowledge pages, mental models, documents, tags) are exposed too — see the [MCP Server reference](../../developer/mcp-server.md).

## Configuration

The plugin reads two environment variables, interpolated into `mcp.json`:

| Setting | Env Var | Default | Description |
|---------|---------|---------|-------------|
| API key | `HINDSIGHT_API_KEY` | — | Your `hsk_...` key. Sent as `Authorization: Bearer`. Required for Hindsight Cloud. |
| Memory bank | `HINDSIGHT_BANK_ID` | `default` | Bank to read from and write to (sent as `X-Bank-Id`). Use one bank per user, project, or team for isolation. |

> **📝 Env-var syntax varies by client**
>
Most clients substitute `${VAR}`; some (VS Code, Cursor) use `${env:VAR}`. If your client doesn't interpolate, paste the literal key and bank id into `mcp.json`.
**Self-hosting:** replace the host in `mcp.json` (`https://api.hindsight.vectorize.io`) with your deployment's URL. A local server with the MCP endpoint open needs no API key.

## Explicit tools vs. automatic capture

Agent Plugins `1.0.0` standardizes **Skills + MCP**, not session lifecycle hooks. This plugin therefore delivers **explicit, tool-driven** memory that works identically across every supported client.

For the fully automatic experience — recall injected before every prompt and transcripts retained on session end — use the native, hook-based integration built for your specific tool, such as [Claude Code](claude-code.md) or [Codex](codex.md). Both share the same Hindsight banks, so memory captured by the hook-based integration is recalled through the Agent Plugin, and vice versa.

## Troubleshooting

**No memories recalled**: `recall` returns results only after something has been retained. Retain a fact first, or seed the bank via the [API](../../developer/api/quickstart.md).

**401 Unauthorized**: Check `HINDSIGHT_API_KEY` is set and your client is interpolating it into the `Authorization` header (see the env-var syntax note above).

**Wrong or empty memory**: Confirm `HINDSIGHT_BANK_ID` points at the bank you expect. Different tools writing to different banks won't share memory.

## Learn more

- [Agent Plugins standard](https://agent-plugins.org)
- [Hindsight MCP Server reference](../../developer/mcp-server.md)
- [Hindsight Cloud sign-up](https://ui.hindsight.vectorize.io/signup)