Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c42128da36 | ||
|
|
95df1fb16a | ||
|
|
02f0ebd05b | ||
|
|
4d3e7abb14 | ||
|
|
fccf5a6b38 |
@@ -1,35 +1,22 @@
|
||||
import asyncio
|
||||
import sys
|
||||
import types
|
||||
import unittest
|
||||
"""
|
||||
Regression tests for Codex provider tool_choice normalization.
|
||||
|
||||
The reflect agent forces tool selection via named tool_choice dicts on early iterations:
|
||||
{"type": "function", "function": {"name": "recall"}}
|
||||
|
||||
The Codex Responses API expects the function name at the top level instead:
|
||||
{"type": "function", "name": "recall"}
|
||||
|
||||
Without normalization, Codex rejects the request with:
|
||||
400 Unknown parameter: 'tool_choice.function'
|
||||
"""
|
||||
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
PACKAGE_ROOT = ROOT / "hindsight_api"
|
||||
if str(ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(ROOT))
|
||||
|
||||
|
||||
def ensure_package(name: str, path: Path) -> None:
|
||||
module = sys.modules.get(name)
|
||||
if module is None:
|
||||
module = types.ModuleType(name)
|
||||
module.__path__ = [str(path)]
|
||||
sys.modules[name] = module
|
||||
|
||||
|
||||
ensure_package("hindsight_api", PACKAGE_ROOT)
|
||||
ensure_package("hindsight_api.engine", PACKAGE_ROOT / "engine")
|
||||
ensure_package("hindsight_api.engine.providers", PACKAGE_ROOT / "engine" / "providers")
|
||||
fake_metrics = types.ModuleType("hindsight_api.metrics")
|
||||
fake_metrics.get_metrics_collector = lambda: types.SimpleNamespace(record_llm_call=lambda **kwargs: None)
|
||||
sys.modules["hindsight_api.metrics"] = fake_metrics
|
||||
import pytest
|
||||
|
||||
from hindsight_api.engine.providers.codex_llm import CodexLLM
|
||||
|
||||
|
||||
TOOLS = [
|
||||
{
|
||||
"type": "function",
|
||||
@@ -56,52 +43,46 @@ def build_llm() -> CodexLLM:
|
||||
)
|
||||
|
||||
|
||||
class CodexToolChoiceTests(unittest.TestCase):
|
||||
def test_codex_normalizes_legacy_named_tool_choice_shape(self) -> None:
|
||||
async def scenario() -> dict:
|
||||
llm = build_llm()
|
||||
response = MagicMock()
|
||||
response.status_code = 200
|
||||
response.raise_for_status.return_value = None
|
||||
with patch.object(llm._client, "post", new_callable=AsyncMock) as mock_post:
|
||||
mock_post.return_value = response
|
||||
with patch.object(llm, "_parse_sse_tool_stream", new_callable=AsyncMock) as mock_parse:
|
||||
mock_parse.return_value = (None, [])
|
||||
await llm.call_with_tools(
|
||||
messages=[{"role": "user", "content": "recall the memory"}],
|
||||
tools=TOOLS,
|
||||
tool_choice={"type": "function", "function": {"name": "recall"}},
|
||||
max_retries=0,
|
||||
)
|
||||
return mock_post.call_args.kwargs["json"]
|
||||
@pytest.mark.asyncio
|
||||
async def test_codex_normalizes_legacy_named_tool_choice_shape():
|
||||
llm = build_llm()
|
||||
response = MagicMock()
|
||||
response.status_code = 200
|
||||
response.raise_for_status.return_value = None
|
||||
with patch.object(llm._client, "post", new_callable=AsyncMock) as mock_post:
|
||||
mock_post.return_value = response
|
||||
with patch.object(llm, "_parse_sse_tool_stream", new_callable=AsyncMock) as mock_parse:
|
||||
mock_parse.return_value = (None, [])
|
||||
await llm.call_with_tools(
|
||||
messages=[{"role": "user", "content": "recall the memory"}],
|
||||
tools=TOOLS,
|
||||
tool_choice={"type": "function", "function": {"name": "recall"}},
|
||||
max_retries=0,
|
||||
)
|
||||
sent_payload = mock_post.call_args.kwargs["json"]
|
||||
|
||||
sent_payload = asyncio.run(scenario())
|
||||
self.assertEqual(sent_payload["tool_choice"], {"type": "function", "name": "recall"})
|
||||
|
||||
def test_codex_forced_tool_choice_still_yields_tool_calls(self) -> None:
|
||||
async def scenario() -> tuple[object, dict]:
|
||||
llm = build_llm()
|
||||
response = MagicMock()
|
||||
response.status_code = 200
|
||||
response.raise_for_status.return_value = None
|
||||
tool_call = {"id": "call-1", "name": "recall", "arguments": {"query": "memory"}}
|
||||
with patch.object(llm._client, "post", new_callable=AsyncMock) as mock_post:
|
||||
mock_post.return_value = response
|
||||
with patch.object(llm, "_parse_sse_tool_stream", new_callable=AsyncMock) as mock_parse:
|
||||
mock_parse.return_value = (None, [tool_call])
|
||||
result = await llm.call_with_tools(
|
||||
messages=[{"role": "user", "content": "recall the memory"}],
|
||||
tools=TOOLS,
|
||||
tool_choice={"type": "function", "function": {"name": "recall"}},
|
||||
max_retries=0,
|
||||
)
|
||||
return result, mock_post.call_args.kwargs["json"]
|
||||
|
||||
result, sent_payload = asyncio.run(scenario())
|
||||
self.assertEqual(len(result.tool_calls), 1)
|
||||
self.assertEqual(result.tool_calls[0].name, "recall")
|
||||
self.assertEqual(sent_payload["tool_choice"], {"type": "function", "name": "recall"})
|
||||
assert sent_payload["tool_choice"] == {"type": "function", "name": "recall"}
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@pytest.mark.asyncio
|
||||
async def test_codex_forced_tool_choice_still_yields_tool_calls():
|
||||
llm = build_llm()
|
||||
response = MagicMock()
|
||||
response.status_code = 200
|
||||
response.raise_for_status.return_value = None
|
||||
tool_call = {"id": "call-1", "name": "recall", "arguments": {"query": "memory"}}
|
||||
with patch.object(llm._client, "post", new_callable=AsyncMock) as mock_post:
|
||||
mock_post.return_value = response
|
||||
with patch.object(llm, "_parse_sse_tool_stream", new_callable=AsyncMock) as mock_parse:
|
||||
mock_parse.return_value = (None, [tool_call])
|
||||
result = await llm.call_with_tools(
|
||||
messages=[{"role": "user", "content": "recall the memory"}],
|
||||
tools=TOOLS,
|
||||
tool_choice={"type": "function", "function": {"name": "recall"}},
|
||||
max_retries=0,
|
||||
)
|
||||
sent_payload = mock_post.call_args.kwargs["json"]
|
||||
|
||||
assert len(result.tool_calls) == 1
|
||||
assert result.tool_calls[0].name == "recall"
|
||||
assert sent_payload["tool_choice"] == {"type": "function", "name": "recall"}
|
||||
|
||||
@@ -88,8 +88,16 @@ async def test_hierarchical_fields_categorization():
|
||||
assert "entities_allow_free_form" in configurable
|
||||
assert "entity_labels" in configurable
|
||||
|
||||
# Verify other configurable fields
|
||||
assert "retain_default_strategy" in configurable
|
||||
assert "retain_strategies" in configurable
|
||||
assert "max_observations_per_scope" in configurable
|
||||
assert "reflect_source_facts_max_tokens" in configurable
|
||||
assert "llm_gemini_safety_settings" in configurable
|
||||
assert "mcp_enabled_tools" in configurable
|
||||
|
||||
# Verify count is correct
|
||||
assert len(configurable) == 20
|
||||
assert len(configurable) == 21
|
||||
|
||||
# Verify credential fields (NEVER exposed)
|
||||
assert "llm_api_key" in credentials
|
||||
|
||||
@@ -262,6 +262,12 @@ const sidebars: SidebarsConfig = {
|
||||
label: 'AG2',
|
||||
customProps: { icon: '/img/icons/ag2.svg' },
|
||||
},
|
||||
{
|
||||
type: 'doc',
|
||||
id: 'sdks/integrations/llamaindex',
|
||||
label: 'LlamaIndex',
|
||||
customProps: { icon: '/img/icons/llamaindex.png' },
|
||||
},
|
||||
{
|
||||
type: 'doc',
|
||||
id: 'sdks/integrations/skills',
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
---
|
||||
sidebar_position: 6
|
||||
---
|
||||
|
||||
# OpenAI Codex CLI
|
||||
|
||||
Persistent memory for [OpenAI Codex CLI](https://github.com/openai/codex) using [Hindsight](https://vectorize.io/hindsight). Three Python hook scripts automatically recall relevant context before each prompt and retain conversations after each turn — no changes to your Codex workflow required.
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# 1. Clone the Hindsight repo and install the plugin
|
||||
git clone https://github.com/vectorize-io/hindsight.git
|
||||
cd hindsight/hindsight-integrations/codex
|
||||
./install.sh
|
||||
|
||||
# 2. Configure your Hindsight connection
|
||||
cat > ~/.hindsight/codex.json << 'EOF'
|
||||
{
|
||||
"hindsightApiUrl": "https://api.hindsight.vectorize.io",
|
||||
"hindsightApiToken": "hsk_your_token_here",
|
||||
"bankId": "codex"
|
||||
}
|
||||
EOF
|
||||
|
||||
# 3. Start Codex — memory is live
|
||||
codex
|
||||
```
|
||||
|
||||
For a local Hindsight instance, set `hindsightApiUrl` to `http://localhost:9077` and omit `hindsightApiToken`.
|
||||
|
||||
## Features
|
||||
|
||||
- **Auto-recall** — on every user prompt, queries Hindsight for relevant memories and injects them as `additionalContext` (invisible to the transcript, visible to Codex)
|
||||
- **Auto-retain** — after each Codex response, stores the conversation transcript to Hindsight for future recall
|
||||
- **Dynamic bank IDs** — supports per-project memory isolation based on the working directory
|
||||
- **Session-level upsert** — uses the session ID as the document ID so re-running the same session updates rather than duplicates stored content
|
||||
- **Zero dependencies** — pure Python stdlib, no pip install required
|
||||
|
||||
## Architecture
|
||||
|
||||
The plugin uses three Codex hook events:
|
||||
|
||||
| Hook | Event | Purpose |
|
||||
|------|-------|---------|
|
||||
| `session_start.py` | `SessionStart` | Warm up — verify Hindsight is reachable |
|
||||
| `recall.py` | `UserPromptSubmit` | **Auto-recall** — query memories, inject as `additionalContext` |
|
||||
| `retain.py` | `Stop` | **Auto-retain** — extract transcript, POST to Hindsight (async) |
|
||||
|
||||
On `UserPromptSubmit`, the hook reads the prompt, queries Hindsight for the most relevant memories, and outputs a `hookSpecificOutput.additionalContext` block. Codex prepends this to the conversation before sending it to the model:
|
||||
|
||||
```
|
||||
<hindsight_memories>
|
||||
Relevant memories from past conversations...
|
||||
Current time - 2026-03-27 09:14
|
||||
|
||||
- Project uses FastAPI with asyncpg — not SQLAlchemy [world] (2026-03-26)
|
||||
- Preferred testing framework: pytest with pytest-asyncio [experience] (2026-03-26)
|
||||
</hindsight_memories>
|
||||
```
|
||||
|
||||
On `Stop`, the hook reads the session transcript, strips previously injected memory tags (to prevent feedback loops), and POSTs the conversation to Hindsight asynchronously.
|
||||
|
||||
## Connection Modes
|
||||
|
||||
### 1. External API (recommended)
|
||||
|
||||
Connect to a running Hindsight server (cloud or self-hosted):
|
||||
|
||||
```json
|
||||
{
|
||||
"hindsightApiUrl": "https://api.hindsight.vectorize.io",
|
||||
"hindsightApiToken": "hsk_your_token"
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Local Daemon
|
||||
|
||||
Run `hindsight-embed` locally. The `session_start.py` hook will detect it on `apiPort` (default `9077`). The daemon is not auto-started by the Codex plugin — start it separately:
|
||||
|
||||
```bash
|
||||
uvx hindsight-embed
|
||||
```
|
||||
|
||||
Then leave `hindsightApiUrl` empty in your config and the plugin will connect to `http://localhost:9077`.
|
||||
|
||||
## Configuration
|
||||
|
||||
Settings are loaded from `~/.hindsight/codex.json`. Every setting can also be overridden via environment variable.
|
||||
|
||||
**Loading order** (later entries win):
|
||||
|
||||
1. Built-in defaults
|
||||
2. Plugin `settings.json` (at `~/.hindsight/codex/settings.json`)
|
||||
3. User config (`~/.hindsight/codex.json`)
|
||||
4. Environment variables
|
||||
|
||||
---
|
||||
|
||||
### Connection
|
||||
|
||||
| Setting | Env Var | Default | Description |
|
||||
|---------|---------|---------|-------------|
|
||||
| `hindsightApiUrl` | `HINDSIGHT_API_URL` | `""` | URL of the Hindsight API server. Required. |
|
||||
| `hindsightApiToken` | `HINDSIGHT_API_TOKEN` | `null` | API token for authentication. Required for Hindsight Cloud. |
|
||||
| `apiPort` | `HINDSIGHT_API_PORT` | `9077` | Port for the local `hindsight-embed` daemon. |
|
||||
|
||||
---
|
||||
|
||||
### Memory Bank
|
||||
|
||||
| Setting | Env Var | Default | Description |
|
||||
|---------|---------|---------|-------------|
|
||||
| `bankId` | `HINDSIGHT_BANK_ID` | `"codex"` | The bank to read from and write to. All sessions share this bank unless `dynamicBankId` is enabled. |
|
||||
| `bankMission` | `HINDSIGHT_BANK_MISSION` | coding assistant prompt | Describes the agent's purpose. Sent when creating or updating the bank. |
|
||||
| `retainMission` | — | extraction prompt | Instructions for Hindsight's fact extraction — what to extract from coding conversations. |
|
||||
| `dynamicBankId` | `HINDSIGHT_DYNAMIC_BANK_ID` | `false` | When `true`, derives a unique bank ID from `dynamicBankGranularity` fields — useful for per-project isolation. |
|
||||
| `dynamicBankGranularity` | — | `["agent", "project"]` | Which fields to combine for dynamic bank IDs. `"project"` = working directory, `"agent"` = agent name. |
|
||||
| `bankIdPrefix` | — | `""` | Prefix prepended to all bank IDs. |
|
||||
| `agentName` | `HINDSIGHT_AGENT_NAME` | `"codex"` | Agent name used in dynamic bank ID derivation. |
|
||||
|
||||
---
|
||||
|
||||
### Auto-Recall
|
||||
|
||||
| Setting | Env Var | Default | Description |
|
||||
|---------|---------|---------|-------------|
|
||||
| `autoRecall` | `HINDSIGHT_AUTO_RECALL` | `true` | Master switch for auto-recall. |
|
||||
| `recallBudget` | `HINDSIGHT_RECALL_BUDGET` | `"mid"` | Search depth: `"low"` (fast), `"mid"` (balanced), `"high"` (thorough). |
|
||||
| `recallMaxTokens` | `HINDSIGHT_RECALL_MAX_TOKENS` | `1024` | Max tokens in the recalled memory block. |
|
||||
| `recallTypes` | — | `["world", "experience"]` | Memory types to retrieve. |
|
||||
| `recallContextTurns` | `HINDSIGHT_RECALL_CONTEXT_TURNS` | `1` | Prior turns to include when building the recall query. `1` = latest prompt only. |
|
||||
| `recallMaxQueryChars` | `HINDSIGHT_RECALL_MAX_QUERY_CHARS` | `800` | Max characters in the query sent to Hindsight. |
|
||||
| `recallRoles` | — | `["user", "assistant"]` | Which roles to include when building a multi-turn query. |
|
||||
| `recallPromptPreamble` | — | built-in | Text placed above the recalled memories in the injected context block. |
|
||||
|
||||
---
|
||||
|
||||
### Auto-Retain
|
||||
|
||||
| Setting | Env Var | Default | Description |
|
||||
|---------|---------|---------|-------------|
|
||||
| `autoRetain` | `HINDSIGHT_AUTO_RETAIN` | `true` | Master switch for auto-retain. |
|
||||
| `retainMode` | `HINDSIGHT_RETAIN_MODE` | `"full-session"` | `"full-session"` sends the full transcript per session (upserted by session ID). `"chunked"` sends sliding windows every N turns. |
|
||||
| `retainEveryNTurns` | — | `10` | Retain fires every N turns. `1` = every turn. Higher values reduce API calls. |
|
||||
| `retainOverlapTurns` | — | `2` | Extra turns included from the previous chunk (chunked mode only). |
|
||||
| `retainRoles` | — | `["user", "assistant"]` | Which roles to include in the retained transcript. |
|
||||
| `retainTags` | — | `["{session_id}"]` | Tags attached to the stored document. `{session_id}` is replaced at runtime. |
|
||||
| `retainMetadata` | — | `{}` | Arbitrary key-value metadata attached to the stored document. |
|
||||
| `retainContext` | — | `"codex"` | Label identifying the source integration. Useful when multiple integrations write to the same bank. |
|
||||
|
||||
---
|
||||
|
||||
### Debug
|
||||
|
||||
| Setting | Env Var | Default | Description |
|
||||
|---------|---------|---------|-------------|
|
||||
| `debug` | `HINDSIGHT_DEBUG` | `false` | Enable verbose logging to stderr. All log lines are prefixed with `[Hindsight]`. |
|
||||
|
||||
## Per-Project Memory
|
||||
|
||||
To give each project its own isolated memory bank, enable dynamic bank IDs:
|
||||
|
||||
```json
|
||||
{
|
||||
"dynamicBankId": true,
|
||||
"dynamicBankGranularity": ["agent", "project"]
|
||||
}
|
||||
```
|
||||
|
||||
With this config, running Codex in `~/projects/api` and `~/projects/frontend` stores and recalls memories separately. Bank IDs are derived from the working directory path.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**Hooks not firing**: Check that `~/.codex/config.toml` contains `codex_hooks = true` under `[features]`. Re-run `install.sh` to write this automatically.
|
||||
|
||||
**No memories recalled**: Recall returns results only after something has been retained. Either complete one Codex session first, or seed your bank manually using the [cookbook example](https://github.com/vectorize-io/hindsight-cookbook/tree/main/applications/codex-memory).
|
||||
|
||||
**Memory not being stored**: `retainEveryNTurns` defaults to `10` — retain only fires every 10 turns. While testing, add `"retainEveryNTurns": 1` to `~/.hindsight/codex.json`.
|
||||
|
||||
**Debug mode**: Add `"debug": true` to `~/.hindsight/codex.json` to see what Hindsight is doing on each turn:
|
||||
|
||||
```
|
||||
[Hindsight] Recalling from bank 'codex', query length: 42
|
||||
[Hindsight] Injecting 3 memories
|
||||
[Hindsight] Retaining to bank 'codex', doc 'sess-abc123', 2 messages, 847 chars
|
||||
```
|
||||
|
||||
**High latency on recall**: Use `"recallBudget": "low"` or reduce `recallMaxTokens` to speed up recall queries.
|
||||
@@ -0,0 +1,256 @@
|
||||
---
|
||||
sidebar_position: 8
|
||||
---
|
||||
|
||||
# LlamaIndex
|
||||
|
||||
Persistent long-term memory for [LlamaIndex](https://docs.llamaindex.ai/) agents via Hindsight. Two packages are available:
|
||||
|
||||
- **`llama-index-tools-hindsight`** — Agent-driven memory tools (retain/recall/reflect)
|
||||
- **`llama-index-memory-hindsight`** — Automatic memory via LlamaIndex's `BaseMemory` interface
|
||||
|
||||
Both follow the LlamaIndex namespace package convention (`from llama_index.tools.hindsight import ...`).
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
# Tools only (agent-driven)
|
||||
pip install llama-index-tools-hindsight
|
||||
|
||||
# Memory only (automatic)
|
||||
pip install llama-index-memory-hindsight
|
||||
|
||||
# Both
|
||||
pip install llama-index-tools-hindsight llama-index-memory-hindsight
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Automatic Memory (BaseMemory)
|
||||
|
||||
The simplest way to add Hindsight memory to a LlamaIndex agent. Messages are automatically stored on each turn, and relevant memories are recalled and injected as context.
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
from hindsight_client import Hindsight
|
||||
from llama_index.memory.hindsight import HindsightMemory
|
||||
from llama_index.core.agent import ReActAgent
|
||||
from llama_index.llms.openai import OpenAI
|
||||
|
||||
async def main():
|
||||
client = Hindsight(base_url="http://localhost:8888")
|
||||
|
||||
memory = HindsightMemory.from_client(
|
||||
client=client,
|
||||
bank_id="user-123",
|
||||
mission="Track user preferences and project context",
|
||||
)
|
||||
|
||||
agent = ReActAgent(tools=tools, llm=OpenAI(model="gpt-4o"), memory=memory)
|
||||
response = await agent.run("Remember that I prefer dark mode")
|
||||
print(response)
|
||||
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
### How It Works
|
||||
|
||||
| Event | What Happens |
|
||||
|-------|-------------|
|
||||
| Agent receives input | `get(input)` recalls relevant memories from Hindsight, prepends as system message |
|
||||
| Agent produces output | `put(message)` retains the message to Hindsight for future recall |
|
||||
| New session starts | Previous memories are available via recall; local chat buffer starts empty |
|
||||
|
||||
### `HindsightMemory.from_client()`
|
||||
|
||||
| Parameter | Type | Default | Description |
|
||||
|-----------|------|---------|-------------|
|
||||
| `client` | `Hindsight` | *required* | Hindsight client instance |
|
||||
| `bank_id` | `str` | *required* | Memory bank ID |
|
||||
| `mission` | `str` | `None` | Bank mission — auto-creates bank on first use |
|
||||
| `context` | `str` | `"llamaindex"` | Source label for retain operations |
|
||||
| `budget` | `str` | `"mid"` | Recall budget level |
|
||||
| `max_tokens` | `int` | `4096` | Max recall tokens |
|
||||
| `tags` | `list[str]` | `None` | Tags for retain operations |
|
||||
| `recall_tags` | `list[str]` | `None` | Tags to filter recall |
|
||||
| `recall_tags_match` | `str` | `"any"` | Tag matching mode |
|
||||
| `system_prompt` | `str` | *(built-in)* | Template for memory system message. Must contain `{memories}` |
|
||||
| `chat_history_limit` | `int` | `100` | Max messages in local buffer |
|
||||
|
||||
Also available: `HindsightMemory.from_url(hindsight_api_url, bank_id, ...)` for creating without a pre-built client.
|
||||
|
||||
---
|
||||
|
||||
## Agent-Driven Tools (BaseToolSpec)
|
||||
|
||||
For explicit control, expose retain/recall/reflect as tools the agent can choose to call.
|
||||
|
||||
### Quick Start: Tool Spec
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
from hindsight_client import Hindsight
|
||||
from llama_index.tools.hindsight import HindsightToolSpec
|
||||
from llama_index.llms.openai import OpenAI
|
||||
from llama_index.core.agent import ReActAgent
|
||||
|
||||
async def main():
|
||||
client = Hindsight(base_url="http://localhost:8888")
|
||||
|
||||
spec = HindsightToolSpec(
|
||||
client=client,
|
||||
bank_id="user-123",
|
||||
mission="Track user preferences",
|
||||
)
|
||||
tools = spec.to_tool_list()
|
||||
|
||||
agent = ReActAgent(tools=tools, llm=OpenAI(model="gpt-4o"))
|
||||
response = await agent.run("Remember that I prefer dark mode")
|
||||
print(response)
|
||||
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
### Quick Start: Factory Function
|
||||
|
||||
```python
|
||||
from llama_index.tools.hindsight import create_hindsight_tools
|
||||
|
||||
tools = create_hindsight_tools(
|
||||
client=client,
|
||||
bank_id="user-123",
|
||||
mission="Track user preferences",
|
||||
)
|
||||
```
|
||||
|
||||
### Selecting Tools
|
||||
|
||||
```python
|
||||
# Via to_tool_list()
|
||||
tools = spec.to_tool_list(spec_functions=["recall_memory", "reflect_on_memory"])
|
||||
|
||||
# Via factory flags
|
||||
tools = create_hindsight_tools(
|
||||
client=client,
|
||||
bank_id="user-123",
|
||||
include_retain=True,
|
||||
include_recall=True,
|
||||
include_reflect=False,
|
||||
)
|
||||
```
|
||||
|
||||
### Configuration
|
||||
|
||||
Set defaults via `configure()`, override per-call:
|
||||
|
||||
```python
|
||||
from llama_index.tools.hindsight import configure
|
||||
|
||||
configure(
|
||||
hindsight_api_url="http://localhost:8888",
|
||||
api_key="your-api-key", # or set HINDSIGHT_API_KEY env var
|
||||
budget="mid",
|
||||
tags=["source:llamaindex"],
|
||||
context="my-app",
|
||||
mission="Track user preferences",
|
||||
)
|
||||
|
||||
# Now create tools without passing client/url
|
||||
tools = create_hindsight_tools(bank_id="user-123")
|
||||
```
|
||||
|
||||
### `HindsightToolSpec()`
|
||||
|
||||
| Parameter | Type | Default | Description |
|
||||
|-----------|------|---------|-------------|
|
||||
| `bank_id` | `str` | *required* | Hindsight memory bank to operate on |
|
||||
| `client` | `Hindsight` | `None` | Pre-configured Hindsight client |
|
||||
| `hindsight_api_url` | `str` | `None` | API URL (used if no client provided) |
|
||||
| `api_key` | `str` | `None` | API key (used if no client provided) |
|
||||
| `budget` | `str` | `None` → `"mid"` | Recall/reflect budget: `low`, `mid`, `high` |
|
||||
| `max_tokens` | `int` | `None` → `4096` | Max tokens for recall results |
|
||||
| `tags` | `list[str]` | `None` | Tags applied when storing memories |
|
||||
| `recall_tags` | `list[str]` | `None` | Tags to filter recall results |
|
||||
| `recall_tags_match` | `str` | `None` → `"any"` | Tag matching: `any`, `all`, `any_strict`, `all_strict` |
|
||||
| `retain_metadata` | `dict[str, str]` | `None` | Default metadata for retain operations |
|
||||
| `retain_document_id` | `str` | `None` | Document ID for retain. Auto-generates `{session}-{timestamp}` if not set |
|
||||
| `retain_context` | `str` | `"llamaindex"` | Source label for retain operations |
|
||||
| `recall_types` | `list[str]` | `None` | Fact types: `world`, `experience`, `opinion`, `observation` |
|
||||
| `recall_include_entities` | `bool` | `False` | Include entity info in recall results |
|
||||
| `reflect_context` | `str` | `None` | Additional context for reflect |
|
||||
| `reflect_max_tokens` | `int` | `None` | Max tokens for reflect (defaults to `max_tokens`) |
|
||||
| `reflect_response_schema` | `dict` | `None` | JSON schema to constrain reflect output |
|
||||
| `reflect_tags` | `list[str]` | `None` | Tags for reflect (defaults to `recall_tags`) |
|
||||
| `reflect_tags_match` | `str` | `None` | Tag matching for reflect (defaults to `recall_tags_match`) |
|
||||
| `mission` | `str` | `None` | Bank mission — auto-creates bank on first use |
|
||||
|
||||
---
|
||||
|
||||
## Production Patterns
|
||||
|
||||
### Bank Mission
|
||||
|
||||
Set a mission to give the memory engine context for fact extraction:
|
||||
|
||||
```python
|
||||
# Tools
|
||||
spec = HindsightToolSpec(
|
||||
client=client,
|
||||
bank_id="user-123",
|
||||
mission="Track user coding preferences, project context, and technical decisions",
|
||||
)
|
||||
|
||||
# Memory
|
||||
memory = HindsightMemory.from_client(
|
||||
client=client,
|
||||
bank_id="user-123",
|
||||
mission="Track user coding preferences, project context, and technical decisions",
|
||||
)
|
||||
```
|
||||
|
||||
The bank is created automatically on first use. If it already exists, creation is silently skipped.
|
||||
|
||||
### Memory Scoping with Tags
|
||||
|
||||
```python
|
||||
spec = HindsightToolSpec(
|
||||
client=client,
|
||||
bank_id="user-123",
|
||||
tags=["source:chat", "session:abc"], # applied to all retains
|
||||
recall_tags=["source:chat"], # filter recalls to chat memories
|
||||
recall_tags_match="any",
|
||||
)
|
||||
```
|
||||
|
||||
### Error Handling
|
||||
|
||||
Both packages handle errors gracefully — operations are logged and return friendly messages instead of raising exceptions. Agents continue functioning even if memory is unavailable.
|
||||
|
||||
### Combining Tools + Memory
|
||||
|
||||
Use both packages together for maximum flexibility:
|
||||
|
||||
```python
|
||||
from llama_index.tools.hindsight import create_hindsight_tools
|
||||
from llama_index.memory.hindsight import HindsightMemory
|
||||
|
||||
# Automatic memory for context enrichment
|
||||
memory = HindsightMemory.from_client(client=client, bank_id="user-123")
|
||||
|
||||
# Explicit tools for agent-driven reflect
|
||||
tools = create_hindsight_tools(
|
||||
client=client,
|
||||
bank_id="user-123",
|
||||
include_retain=False, # memory handles retain automatically
|
||||
include_recall=False, # memory handles recall automatically
|
||||
include_reflect=True, # agent can still explicitly reflect
|
||||
)
|
||||
|
||||
agent = ReActAgent(tools=tools, llm=llm, memory=memory)
|
||||
```
|
||||
|
||||
## Requirements
|
||||
|
||||
- Python 3.10+
|
||||
- `llama-index-core >= 0.11.0`
|
||||
- `hindsight-client >= 0.4.0`
|
||||
@@ -331,6 +331,22 @@
|
||||
"icon": "/img/icons/ag2.svg"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "doc",
|
||||
"id": "sdks/integrations/llamaindex",
|
||||
"label": "LlamaIndex",
|
||||
"customProps": {
|
||||
"icon": "/img/icons/llamaindex.png"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "doc",
|
||||
"id": "sdks/integrations/codex",
|
||||
"label": "OpenAI Codex CLI",
|
||||
"customProps": {
|
||||
"icon": "/img/icons/openai.png"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "doc",
|
||||
"id": "sdks/integrations/skills",
|
||||
|
||||
@@ -878,6 +878,7 @@ Observations are consolidated knowledge synthesized from facts.
|
||||
| `HINDSIGHT_API_CONSOLIDATION_SOURCE_FACTS_MAX_TOKENS` | Total token budget for source facts included with observations in the consolidation prompt. `-1` = unlimited. Configurable per bank. | `-1` |
|
||||
| `HINDSIGHT_API_CONSOLIDATION_SOURCE_FACTS_MAX_TOKENS_PER_OBSERVATION` | Per-observation token cap for source facts in the consolidation prompt. Each observation independently gets at most this many tokens of source facts. `-1` = unlimited. Configurable per bank. | `256` |
|
||||
| `HINDSIGHT_API_OBSERVATIONS_MISSION` | What this bank should synthesise into durable observations. Replaces the built-in consolidation rules — leave unset to use the server default. | - |
|
||||
| `HINDSIGHT_API_MAX_OBSERVATIONS_PER_SCOPE` | Maximum number of observations allowed per tag scope. When the limit is reached, consolidation will only update or delete existing observations — no new ones are created. Applies per tag scope (e.g., per-tag when using `per_tag` observation scopes). Observations with no tags are not subject to this limit. `-1` = unlimited. Configurable per bank. | `-1` |
|
||||
|
||||
#### Customizing observations: when to use what
|
||||
|
||||
@@ -1154,7 +1155,7 @@ Configuration fields are categorized for security:
|
||||
|
||||
1. **Configurable Fields** - Safe behavioral settings that can be customized per-bank:
|
||||
- Retention: `retain_chunk_size`, `retain_extraction_mode`, `retain_mission`, `retain_custom_instructions`
|
||||
- Observations: `enable_observations`, `observations_mission`
|
||||
- Observations: `enable_observations`, `observations_mission`, `max_observations_per_scope`
|
||||
- MCP access control: `mcp_enabled_tools`
|
||||
|
||||
2. **Credential Fields** - NEVER exposed or configurable via API:
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
---
|
||||
sidebar_position: 6
|
||||
---
|
||||
|
||||
# OpenAI Codex CLI
|
||||
|
||||
Persistent memory for [OpenAI Codex CLI](https://github.com/openai/codex) using [Hindsight](https://vectorize.io/hindsight). Three Python hook scripts automatically recall relevant context before each prompt and retain conversations after each turn — no changes to your Codex workflow required.
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# 1. Clone the Hindsight repo and install the plugin
|
||||
git clone https://github.com/vectorize-io/hindsight.git
|
||||
cd hindsight/hindsight-integrations/codex
|
||||
./install.sh
|
||||
|
||||
# 2. Configure your Hindsight connection
|
||||
cat > ~/.hindsight/codex.json << 'EOF'
|
||||
{
|
||||
"hindsightApiUrl": "https://api.hindsight.vectorize.io",
|
||||
"hindsightApiToken": "hsk_your_token_here",
|
||||
"bankId": "codex"
|
||||
}
|
||||
EOF
|
||||
|
||||
# 3. Start Codex — memory is live
|
||||
codex
|
||||
```
|
||||
|
||||
For a local Hindsight instance, set `hindsightApiUrl` to `http://localhost:9077` and omit `hindsightApiToken`.
|
||||
|
||||
## Features
|
||||
|
||||
- **Auto-recall** — on every user prompt, queries Hindsight for relevant memories and injects them as `additionalContext` (invisible to the transcript, visible to Codex)
|
||||
- **Auto-retain** — after each Codex response, stores the conversation transcript to Hindsight for future recall
|
||||
- **Dynamic bank IDs** — supports per-project memory isolation based on the working directory
|
||||
- **Session-level upsert** — uses the session ID as the document ID so re-running the same session updates rather than duplicates stored content
|
||||
- **Zero dependencies** — pure Python stdlib, no pip install required
|
||||
|
||||
## Architecture
|
||||
|
||||
The plugin uses three Codex hook events:
|
||||
|
||||
| Hook | Event | Purpose |
|
||||
|------|-------|---------|
|
||||
| `session_start.py` | `SessionStart` | Warm up — verify Hindsight is reachable |
|
||||
| `recall.py` | `UserPromptSubmit` | **Auto-recall** — query memories, inject as `additionalContext` |
|
||||
| `retain.py` | `Stop` | **Auto-retain** — extract transcript, POST to Hindsight (async) |
|
||||
|
||||
On `UserPromptSubmit`, the hook reads the prompt, queries Hindsight for the most relevant memories, and outputs a `hookSpecificOutput.additionalContext` block. Codex prepends this to the conversation before sending it to the model:
|
||||
|
||||
```
|
||||
<hindsight_memories>
|
||||
Relevant memories from past conversations...
|
||||
Current time - 2026-03-27 09:14
|
||||
|
||||
- Project uses FastAPI with asyncpg — not SQLAlchemy [world] (2026-03-26)
|
||||
- Preferred testing framework: pytest with pytest-asyncio [experience] (2026-03-26)
|
||||
</hindsight_memories>
|
||||
```
|
||||
|
||||
On `Stop`, the hook reads the session transcript, strips previously injected memory tags (to prevent feedback loops), and POSTs the conversation to Hindsight asynchronously.
|
||||
|
||||
## Connection Modes
|
||||
|
||||
### 1. External API (recommended)
|
||||
|
||||
Connect to a running Hindsight server (cloud or self-hosted):
|
||||
|
||||
```json
|
||||
{
|
||||
"hindsightApiUrl": "https://api.hindsight.vectorize.io",
|
||||
"hindsightApiToken": "hsk_your_token"
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Local Daemon
|
||||
|
||||
Run `hindsight-embed` locally. The `session_start.py` hook will detect it on `apiPort` (default `9077`). The daemon is not auto-started by the Codex plugin — start it separately:
|
||||
|
||||
```bash
|
||||
uvx hindsight-embed
|
||||
```
|
||||
|
||||
Then leave `hindsightApiUrl` empty in your config and the plugin will connect to `http://localhost:9077`.
|
||||
|
||||
## Configuration
|
||||
|
||||
Settings are loaded from `~/.hindsight/codex.json`. Every setting can also be overridden via environment variable.
|
||||
|
||||
**Loading order** (later entries win):
|
||||
|
||||
1. Built-in defaults
|
||||
2. Plugin `settings.json` (at `~/.hindsight/codex/settings.json`)
|
||||
3. User config (`~/.hindsight/codex.json`)
|
||||
4. Environment variables
|
||||
|
||||
---
|
||||
|
||||
### Connection
|
||||
|
||||
| Setting | Env Var | Default | Description |
|
||||
|---------|---------|---------|-------------|
|
||||
| `hindsightApiUrl` | `HINDSIGHT_API_URL` | `""` | URL of the Hindsight API server. Required. |
|
||||
| `hindsightApiToken` | `HINDSIGHT_API_TOKEN` | `null` | API token for authentication. Required for Hindsight Cloud. |
|
||||
| `apiPort` | `HINDSIGHT_API_PORT` | `9077` | Port for the local `hindsight-embed` daemon. |
|
||||
|
||||
---
|
||||
|
||||
### Memory Bank
|
||||
|
||||
| Setting | Env Var | Default | Description |
|
||||
|---------|---------|---------|-------------|
|
||||
| `bankId` | `HINDSIGHT_BANK_ID` | `"codex"` | The bank to read from and write to. All sessions share this bank unless `dynamicBankId` is enabled. |
|
||||
| `bankMission` | `HINDSIGHT_BANK_MISSION` | coding assistant prompt | Describes the agent's purpose. Sent when creating or updating the bank. |
|
||||
| `retainMission` | — | extraction prompt | Instructions for Hindsight's fact extraction — what to extract from coding conversations. |
|
||||
| `dynamicBankId` | `HINDSIGHT_DYNAMIC_BANK_ID` | `false` | When `true`, derives a unique bank ID from `dynamicBankGranularity` fields — useful for per-project isolation. |
|
||||
| `dynamicBankGranularity` | — | `["agent", "project"]` | Which fields to combine for dynamic bank IDs. `"project"` = working directory, `"agent"` = agent name. |
|
||||
| `bankIdPrefix` | — | `""` | Prefix prepended to all bank IDs. |
|
||||
| `agentName` | `HINDSIGHT_AGENT_NAME` | `"codex"` | Agent name used in dynamic bank ID derivation. |
|
||||
|
||||
---
|
||||
|
||||
### Auto-Recall
|
||||
|
||||
| Setting | Env Var | Default | Description |
|
||||
|---------|---------|---------|-------------|
|
||||
| `autoRecall` | `HINDSIGHT_AUTO_RECALL` | `true` | Master switch for auto-recall. |
|
||||
| `recallBudget` | `HINDSIGHT_RECALL_BUDGET` | `"mid"` | Search depth: `"low"` (fast), `"mid"` (balanced), `"high"` (thorough). |
|
||||
| `recallMaxTokens` | `HINDSIGHT_RECALL_MAX_TOKENS` | `1024` | Max tokens in the recalled memory block. |
|
||||
| `recallTypes` | — | `["world", "experience"]` | Memory types to retrieve. |
|
||||
| `recallContextTurns` | `HINDSIGHT_RECALL_CONTEXT_TURNS` | `1` | Prior turns to include when building the recall query. `1` = latest prompt only. |
|
||||
| `recallMaxQueryChars` | `HINDSIGHT_RECALL_MAX_QUERY_CHARS` | `800` | Max characters in the query sent to Hindsight. |
|
||||
| `recallRoles` | — | `["user", "assistant"]` | Which roles to include when building a multi-turn query. |
|
||||
| `recallPromptPreamble` | — | built-in | Text placed above the recalled memories in the injected context block. |
|
||||
|
||||
---
|
||||
|
||||
### Auto-Retain
|
||||
|
||||
| Setting | Env Var | Default | Description |
|
||||
|---------|---------|---------|-------------|
|
||||
| `autoRetain` | `HINDSIGHT_AUTO_RETAIN` | `true` | Master switch for auto-retain. |
|
||||
| `retainMode` | `HINDSIGHT_RETAIN_MODE` | `"full-session"` | `"full-session"` sends the full transcript per session (upserted by session ID). `"chunked"` sends sliding windows every N turns. |
|
||||
| `retainEveryNTurns` | — | `10` | Retain fires every N turns. `1` = every turn. Higher values reduce API calls. |
|
||||
| `retainOverlapTurns` | — | `2` | Extra turns included from the previous chunk (chunked mode only). |
|
||||
| `retainRoles` | — | `["user", "assistant"]` | Which roles to include in the retained transcript. |
|
||||
| `retainTags` | — | `["{session_id}"]` | Tags attached to the stored document. `{session_id}` is replaced at runtime. |
|
||||
| `retainMetadata` | — | `{}` | Arbitrary key-value metadata attached to the stored document. |
|
||||
| `retainContext` | — | `"codex"` | Label identifying the source integration. Useful when multiple integrations write to the same bank. |
|
||||
|
||||
---
|
||||
|
||||
### Debug
|
||||
|
||||
| Setting | Env Var | Default | Description |
|
||||
|---------|---------|---------|-------------|
|
||||
| `debug` | `HINDSIGHT_DEBUG` | `false` | Enable verbose logging to stderr. All log lines are prefixed with `[Hindsight]`. |
|
||||
|
||||
## Per-Project Memory
|
||||
|
||||
To give each project its own isolated memory bank, enable dynamic bank IDs:
|
||||
|
||||
```json
|
||||
{
|
||||
"dynamicBankId": true,
|
||||
"dynamicBankGranularity": ["agent", "project"]
|
||||
}
|
||||
```
|
||||
|
||||
With this config, running Codex in `~/projects/api` and `~/projects/frontend` stores and recalls memories separately. Bank IDs are derived from the working directory path.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**Hooks not firing**: Check that `~/.codex/config.toml` contains `codex_hooks = true` under `[features]`. Re-run `install.sh` to write this automatically.
|
||||
|
||||
**No memories recalled**: Recall returns results only after something has been retained. Either complete one Codex session first, or seed your bank manually using the [cookbook example](https://github.com/vectorize-io/hindsight-cookbook/tree/main/applications/codex-memory).
|
||||
|
||||
**Memory not being stored**: `retainEveryNTurns` defaults to `10` — retain only fires every 10 turns. While testing, add `"retainEveryNTurns": 1` to `~/.hindsight/codex.json`.
|
||||
|
||||
**Debug mode**: Add `"debug": true` to `~/.hindsight/codex.json` to see what Hindsight is doing on each turn:
|
||||
|
||||
```
|
||||
[Hindsight] Recalling from bank 'codex', query length: 42
|
||||
[Hindsight] Injecting 3 memories
|
||||
[Hindsight] Retaining to bank 'codex', doc 'sess-abc123', 2 messages, 847 chars
|
||||
```
|
||||
|
||||
**High latency on recall**: Use `"recallBudget": "low"` or reduce `recallMaxTokens` to speed up recall queries.
|
||||
@@ -0,0 +1,256 @@
|
||||
---
|
||||
sidebar_position: 8
|
||||
---
|
||||
|
||||
# LlamaIndex
|
||||
|
||||
Persistent long-term memory for [LlamaIndex](https://docs.llamaindex.ai/) agents via Hindsight. Two packages are available:
|
||||
|
||||
- **`llama-index-tools-hindsight`** — Agent-driven memory tools (retain/recall/reflect)
|
||||
- **`llama-index-memory-hindsight`** — Automatic memory via LlamaIndex's `BaseMemory` interface
|
||||
|
||||
Both follow the LlamaIndex namespace package convention (`from llama_index.tools.hindsight import ...`).
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
# Tools only (agent-driven)
|
||||
pip install llama-index-tools-hindsight
|
||||
|
||||
# Memory only (automatic)
|
||||
pip install llama-index-memory-hindsight
|
||||
|
||||
# Both
|
||||
pip install llama-index-tools-hindsight llama-index-memory-hindsight
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Automatic Memory (BaseMemory)
|
||||
|
||||
The simplest way to add Hindsight memory to a LlamaIndex agent. Messages are automatically stored on each turn, and relevant memories are recalled and injected as context.
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
from hindsight_client import Hindsight
|
||||
from llama_index.memory.hindsight import HindsightMemory
|
||||
from llama_index.core.agent import ReActAgent
|
||||
from llama_index.llms.openai import OpenAI
|
||||
|
||||
async def main():
|
||||
client = Hindsight(base_url="http://localhost:8888")
|
||||
|
||||
memory = HindsightMemory.from_client(
|
||||
client=client,
|
||||
bank_id="user-123",
|
||||
mission="Track user preferences and project context",
|
||||
)
|
||||
|
||||
agent = ReActAgent(tools=tools, llm=OpenAI(model="gpt-4o"), memory=memory)
|
||||
response = await agent.run("Remember that I prefer dark mode")
|
||||
print(response)
|
||||
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
### How It Works
|
||||
|
||||
| Event | What Happens |
|
||||
|-------|-------------|
|
||||
| Agent receives input | `get(input)` recalls relevant memories from Hindsight, prepends as system message |
|
||||
| Agent produces output | `put(message)` retains the message to Hindsight for future recall |
|
||||
| New session starts | Previous memories are available via recall; local chat buffer starts empty |
|
||||
|
||||
### `HindsightMemory.from_client()`
|
||||
|
||||
| Parameter | Type | Default | Description |
|
||||
|-----------|------|---------|-------------|
|
||||
| `client` | `Hindsight` | *required* | Hindsight client instance |
|
||||
| `bank_id` | `str` | *required* | Memory bank ID |
|
||||
| `mission` | `str` | `None` | Bank mission — auto-creates bank on first use |
|
||||
| `context` | `str` | `"llamaindex"` | Source label for retain operations |
|
||||
| `budget` | `str` | `"mid"` | Recall budget level |
|
||||
| `max_tokens` | `int` | `4096` | Max recall tokens |
|
||||
| `tags` | `list[str]` | `None` | Tags for retain operations |
|
||||
| `recall_tags` | `list[str]` | `None` | Tags to filter recall |
|
||||
| `recall_tags_match` | `str` | `"any"` | Tag matching mode |
|
||||
| `system_prompt` | `str` | *(built-in)* | Template for memory system message. Must contain `{memories}` |
|
||||
| `chat_history_limit` | `int` | `100` | Max messages in local buffer |
|
||||
|
||||
Also available: `HindsightMemory.from_url(hindsight_api_url, bank_id, ...)` for creating without a pre-built client.
|
||||
|
||||
---
|
||||
|
||||
## Agent-Driven Tools (BaseToolSpec)
|
||||
|
||||
For explicit control, expose retain/recall/reflect as tools the agent can choose to call.
|
||||
|
||||
### Quick Start: Tool Spec
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
from hindsight_client import Hindsight
|
||||
from llama_index.tools.hindsight import HindsightToolSpec
|
||||
from llama_index.llms.openai import OpenAI
|
||||
from llama_index.core.agent import ReActAgent
|
||||
|
||||
async def main():
|
||||
client = Hindsight(base_url="http://localhost:8888")
|
||||
|
||||
spec = HindsightToolSpec(
|
||||
client=client,
|
||||
bank_id="user-123",
|
||||
mission="Track user preferences",
|
||||
)
|
||||
tools = spec.to_tool_list()
|
||||
|
||||
agent = ReActAgent(tools=tools, llm=OpenAI(model="gpt-4o"))
|
||||
response = await agent.run("Remember that I prefer dark mode")
|
||||
print(response)
|
||||
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
### Quick Start: Factory Function
|
||||
|
||||
```python
|
||||
from llama_index.tools.hindsight import create_hindsight_tools
|
||||
|
||||
tools = create_hindsight_tools(
|
||||
client=client,
|
||||
bank_id="user-123",
|
||||
mission="Track user preferences",
|
||||
)
|
||||
```
|
||||
|
||||
### Selecting Tools
|
||||
|
||||
```python
|
||||
# Via to_tool_list()
|
||||
tools = spec.to_tool_list(spec_functions=["recall_memory", "reflect_on_memory"])
|
||||
|
||||
# Via factory flags
|
||||
tools = create_hindsight_tools(
|
||||
client=client,
|
||||
bank_id="user-123",
|
||||
include_retain=True,
|
||||
include_recall=True,
|
||||
include_reflect=False,
|
||||
)
|
||||
```
|
||||
|
||||
### Configuration
|
||||
|
||||
Set defaults via `configure()`, override per-call:
|
||||
|
||||
```python
|
||||
from llama_index.tools.hindsight import configure
|
||||
|
||||
configure(
|
||||
hindsight_api_url="http://localhost:8888",
|
||||
api_key="your-api-key", # or set HINDSIGHT_API_KEY env var
|
||||
budget="mid",
|
||||
tags=["source:llamaindex"],
|
||||
context="my-app",
|
||||
mission="Track user preferences",
|
||||
)
|
||||
|
||||
# Now create tools without passing client/url
|
||||
tools = create_hindsight_tools(bank_id="user-123")
|
||||
```
|
||||
|
||||
### `HindsightToolSpec()`
|
||||
|
||||
| Parameter | Type | Default | Description |
|
||||
|-----------|------|---------|-------------|
|
||||
| `bank_id` | `str` | *required* | Hindsight memory bank to operate on |
|
||||
| `client` | `Hindsight` | `None` | Pre-configured Hindsight client |
|
||||
| `hindsight_api_url` | `str` | `None` | API URL (used if no client provided) |
|
||||
| `api_key` | `str` | `None` | API key (used if no client provided) |
|
||||
| `budget` | `str` | `None` → `"mid"` | Recall/reflect budget: `low`, `mid`, `high` |
|
||||
| `max_tokens` | `int` | `None` → `4096` | Max tokens for recall results |
|
||||
| `tags` | `list[str]` | `None` | Tags applied when storing memories |
|
||||
| `recall_tags` | `list[str]` | `None` | Tags to filter recall results |
|
||||
| `recall_tags_match` | `str` | `None` → `"any"` | Tag matching: `any`, `all`, `any_strict`, `all_strict` |
|
||||
| `retain_metadata` | `dict[str, str]` | `None` | Default metadata for retain operations |
|
||||
| `retain_document_id` | `str` | `None` | Document ID for retain. Auto-generates `{session}-{timestamp}` if not set |
|
||||
| `retain_context` | `str` | `"llamaindex"` | Source label for retain operations |
|
||||
| `recall_types` | `list[str]` | `None` | Fact types: `world`, `experience`, `opinion`, `observation` |
|
||||
| `recall_include_entities` | `bool` | `False` | Include entity info in recall results |
|
||||
| `reflect_context` | `str` | `None` | Additional context for reflect |
|
||||
| `reflect_max_tokens` | `int` | `None` | Max tokens for reflect (defaults to `max_tokens`) |
|
||||
| `reflect_response_schema` | `dict` | `None` | JSON schema to constrain reflect output |
|
||||
| `reflect_tags` | `list[str]` | `None` | Tags for reflect (defaults to `recall_tags`) |
|
||||
| `reflect_tags_match` | `str` | `None` | Tag matching for reflect (defaults to `recall_tags_match`) |
|
||||
| `mission` | `str` | `None` | Bank mission — auto-creates bank on first use |
|
||||
|
||||
---
|
||||
|
||||
## Production Patterns
|
||||
|
||||
### Bank Mission
|
||||
|
||||
Set a mission to give the memory engine context for fact extraction:
|
||||
|
||||
```python
|
||||
# Tools
|
||||
spec = HindsightToolSpec(
|
||||
client=client,
|
||||
bank_id="user-123",
|
||||
mission="Track user coding preferences, project context, and technical decisions",
|
||||
)
|
||||
|
||||
# Memory
|
||||
memory = HindsightMemory.from_client(
|
||||
client=client,
|
||||
bank_id="user-123",
|
||||
mission="Track user coding preferences, project context, and technical decisions",
|
||||
)
|
||||
```
|
||||
|
||||
The bank is created automatically on first use. If it already exists, creation is silently skipped.
|
||||
|
||||
### Memory Scoping with Tags
|
||||
|
||||
```python
|
||||
spec = HindsightToolSpec(
|
||||
client=client,
|
||||
bank_id="user-123",
|
||||
tags=["source:chat", "session:abc"], # applied to all retains
|
||||
recall_tags=["source:chat"], # filter recalls to chat memories
|
||||
recall_tags_match="any",
|
||||
)
|
||||
```
|
||||
|
||||
### Error Handling
|
||||
|
||||
Both packages handle errors gracefully — operations are logged and return friendly messages instead of raising exceptions. Agents continue functioning even if memory is unavailable.
|
||||
|
||||
### Combining Tools + Memory
|
||||
|
||||
Use both packages together for maximum flexibility:
|
||||
|
||||
```python
|
||||
from llama_index.tools.hindsight import create_hindsight_tools
|
||||
from llama_index.memory.hindsight import HindsightMemory
|
||||
|
||||
# Automatic memory for context enrichment
|
||||
memory = HindsightMemory.from_client(client=client, bank_id="user-123")
|
||||
|
||||
# Explicit tools for agent-driven reflect
|
||||
tools = create_hindsight_tools(
|
||||
client=client,
|
||||
bank_id="user-123",
|
||||
include_retain=False, # memory handles retain automatically
|
||||
include_recall=False, # memory handles recall automatically
|
||||
include_reflect=True, # agent can still explicitly reflect
|
||||
)
|
||||
|
||||
agent = ReActAgent(tools=tools, llm=llm, memory=memory)
|
||||
```
|
||||
|
||||
## Requirements
|
||||
|
||||
- Python 3.10+
|
||||
- `llama-index-core >= 0.11.0`
|
||||
- `hindsight-client >= 0.4.0`
|
||||
Reference in New Issue
Block a user