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
36 changes: 34 additions & 2 deletions hindsight-api-slim/hindsight_api/engine/reflect/delta_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,20 +73,32 @@ class InsertBlockOp(_OpBase):


class ReplaceBlockOp(_OpBase):
"""Replace the block at ``index`` of an existing section."""
"""Replace the block at ``index`` of an existing section.

When ``expected_block`` is set the operation is only applied if the block
currently at ``index`` serialises to the same JSON — protecting against
LLM-computed index drift where a wrong-but-in-range index would otherwise
silently overwrite an unrelated block (#3273).
"""

op: Literal["replace_block"] = "replace_block"
section_id: str
index: int = Field(ge=0)
block: Block
expected_block: Block | None = None


class RemoveBlockOp(_OpBase):
"""Remove the block at ``index`` of an existing section."""
"""Remove the block at ``index`` of an existing section.

When ``expected_block`` is set the operation is only applied if the block
currently at ``index`` serialises to the same JSON (#3273).
"""

op: Literal["remove_block"] = "remove_block"
section_id: str
index: int = Field(ge=0)
expected_block: Block | None = None


class AddSectionOp(_OpBase):
Expand Down Expand Up @@ -299,6 +311,11 @@ def _op_summary(op: Operation) -> dict[str, Any]:
}


def _blocks_equal(a: Block, b: Block) -> bool:
"""Return True when two blocks serialise to identical JSON."""
return a.model_dump_json() == b.model_dump_json()


def apply_operations(
doc: StructuredDocument,
operations: list[Operation],
Expand Down Expand Up @@ -355,6 +372,14 @@ def skip(op: Operation, reason: str) -> None:
f"index out of range: {op.index} >= {len(section.blocks)}",
)
continue
# Content-based validation: when expected_block is provided,
# refuse to replace a block that doesn't match (#3273).
if op.expected_block is not None and not _blocks_equal(section.blocks[op.index], op.expected_block):
skip(
op,
f"expected_block does not match current block at index {op.index} — index drift detected",
)
continue
section.blocks[op.index] = op.block
applied.append(_op_summary(op))
continue
Expand All @@ -370,6 +395,13 @@ def skip(op: Operation, reason: str) -> None:
f"index out of range: {op.index} >= {len(section.blocks)}",
)
continue
# Content-based validation (#3273)
if op.expected_block is not None and not _blocks_equal(section.blocks[op.index], op.expected_block):
skip(
op,
f"expected_block does not match current block at index {op.index} — index drift detected",
)
continue
section.blocks.pop(op.index)
applied.append(_op_summary(op))
continue
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)