diff --git a/hindsight-api-slim/hindsight_api/engine/memory_engine.py b/hindsight-api-slim/hindsight_api/engine/memory_engine.py index 71edf373c..b10cbbf6c 100644 --- a/hindsight-api-slim/hindsight_api/engine/memory_engine.py +++ b/hindsight-api-slim/hindsight_api/engine/memory_engine.py @@ -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( diff --git a/hindsight-api-slim/tests/test_knowledge_base.py b/hindsight-api-slim/tests/test_knowledge_base.py index a2a24981f..457d6e138 100644 --- a/hindsight-api-slim/tests/test_knowledge_base.py +++ b/hindsight-api-slim/tests/test_knowledge_base.py @@ -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( diff --git a/skills/hindsight-docs/references/sdks/integrations/agent-plugin.md b/skills/hindsight-docs/references/sdks/integrations/agent-plugin.md new file mode 100644 index 000000000..c47aa3256 --- /dev/null +++ b/skills/hindsight-docs/references/sdks/integrations/agent-plugin.md @@ -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)