Compare commits

..
Author SHA1 Message Date
Nicolò Boschi f0c5153861 chore: apply ruff formatting to generate_changelog.py 2026-03-18 17:51:45 +01:00
Nicolò Boschi ef24828356 fix: add agno and hermes integration docs to version-0.4 for production build 2026-03-18 17:45:26 +01:00
Nicolò Boschi db7a0ad3a5 feat: independent versioning for integrations
- Add per-integration changelog pages at /changelog/integrations/<name>
- Move main changelog to changelog/index.md (URL unchanged)
- Add --integration flag to generate-changelog for LLM-based per-integration changelog generation
- Add scripts/release-integration.sh <name> <version> for cutting integration releases
- Add .github/workflows/release-integration.yml to publish on integrations/** tags
- Remove integrations from main release.sh and release.yml cycle
2026-03-18 17:36:52 +01:00
100 changed files with 487 additions and 14817 deletions
-4
View File
@@ -1775,9 +1775,6 @@ jobs:
- name: Run generate-clients
run: ./scripts/generate-clients.sh
- name: Run generate-docs-skill
run: ./scripts/generate-docs-skill.sh
- name: Run lint
run: ./scripts/hooks/lint.sh
@@ -1792,7 +1789,6 @@ jobs:
echo "Please run the following commands locally and commit the changes:"
echo " ./scripts/generate-openapi.sh"
echo " ./scripts/generate-clients.sh"
echo " ./scripts/generate-docs-skill.sh"
echo " ./scripts/hooks/lint.sh"
echo ""
git diff --stat
@@ -669,25 +669,6 @@ class ReflectRequest(BaseModel):
description="Compound tag filter using boolean groups. Groups in the list are AND-ed. "
"Each group is a leaf {tags, match} or compound {and: [...]}, {or: [...]}, {not: ...}.",
)
fact_types: list[Literal["world", "experience", "observation"]] | None = Field(
default=None,
description="Filter which fact types are retrieved during reflect. None means all types (world, experience, observation).",
)
exclude_mental_models: bool = Field(
default=False,
description="If true, exclude all mental models from the reflect loop (skip search_mental_models tool).",
)
exclude_mental_model_ids: list[str] | None = Field(
default=None,
description="Exclude specific mental models by ID from the reflect loop.",
)
@field_validator("fact_types")
@classmethod
def validate_reflect_fact_types(cls, v: list[str] | None) -> list[str] | None:
if v is not None and len(v) == 0:
raise ValueError("fact_types must not be empty. Use null to include all fact types.")
return v
@model_validator(mode="after")
def validate_tags_exclusive(self) -> "ReflectRequest":
@@ -1454,25 +1435,6 @@ class MentalModelTrigger(BaseModel):
default=False,
description="If true, refresh this mental model after observations consolidation (real-time mode)",
)
fact_types: list[Literal["world", "experience", "observation"]] | None = Field(
default=None,
description="Filter which fact types are retrieved during reflect. None means all types (world, experience, observation).",
)
exclude_mental_models: bool = Field(
default=False,
description="If true, exclude all mental models from the reflect loop (skip search_mental_models tool).",
)
exclude_mental_model_ids: list[str] | None = Field(
default=None,
description="Exclude specific mental models by ID from the reflect loop.",
)
@field_validator("fact_types")
@classmethod
def validate_fact_types(cls, v: list[str] | None) -> list[str] | None:
if v is not None and len(v) == 0:
raise ValueError("fact_types must not be empty. Use null to include all fact types.")
return v
class MentalModelResponse(BaseModel):
@@ -2543,9 +2505,6 @@ def _register_routes(app: FastAPI):
tags=request.tags,
tags_match=request.tags_match,
tag_groups=request.tag_groups,
fact_types=request.fact_types,
exclude_mental_models=request.exclude_mental_models,
exclude_mental_model_ids=request.exclude_mental_model_ids,
)
# Build based_on (memories + mental_models + directives) if facts are requested
@@ -477,42 +477,19 @@ class EntityResolver:
id_by_name: dict[str, str] = {row["name_lower"]: row["id"] for row in inserted_rows}
# Fallback SELECT for names that conflicted (another worker won the race).
#
# IMPORTANT: we must let PostgreSQL do the lowercasing on BOTH sides of the
# comparison. Python's str.lower() and PostgreSQL's LOWER() differ for some
# Unicode characters — most notably Turkish İ (U+0130):
# Python: 'İstanbul'.lower() == 'i\u0307stanbul' (i + combining dot, 2 chars)
# PostgreSQL: LOWER('İstanbul') == 'istanbul' (plain i, 1 char)
# Passing a Python-lowercased name to "LOWER(canonical_name) = ANY($2::text[])"
# would fail to match the stored entity, leaving entity_id as None and causing
# a NOT NULL constraint violation on unit_entities.entity_id.
#
# Fix: pass the original (mixed-case) input names and use
# "LOWER(canonical_name) = ANY(SELECT LOWER(n) FROM unnest($2) AS n)" so
# PostgreSQL lowercases both sides identically. The query also returns the
# original input_name so we can index id_by_name by Python's lower() of that
# name, which is what the assignment loop below uses as its lookup key.
missing_original = [g.name for name_lower, g in sorted_groups if name_lower not in id_by_name]
if missing_original:
missing = [n for n, _ in sorted_groups if n not in id_by_name]
if missing:
existing_rows = await conn.fetch(
f"""
SELECT e.id, LOWER(e.canonical_name) AS name_lower, inputs.input_name
FROM {fq_table("entities")} e
JOIN (
SELECT LOWER(n) AS input_name_lower, n AS input_name
FROM unnest($2::text[]) AS n
) AS inputs ON LOWER(e.canonical_name) = inputs.input_name_lower
WHERE e.bank_id = $1
SELECT id, LOWER(canonical_name) AS name_lower
FROM {fq_table("entities")}
WHERE bank_id = $1 AND LOWER(canonical_name) = ANY($2::text[])
""",
bank_id,
missing_original,
missing,
)
for row in existing_rows:
id_by_name[row["name_lower"]] = row["id"]
# Also index by Python's lower() of the original input name so the
# assignment loop (which uses Python-lowercased keys) finds it even
# when Python and PostgreSQL produce different lowercase strings.
id_by_name[row["input_name"].lower()] = row["id"]
# Assign entity IDs back and queue one stat per original mention so that
# flush_pending_stats() increments mention_count by the true mention count,
@@ -868,23 +868,14 @@ class MemoryEngine(MemoryEngineInterface):
tags = mental_model.get("tags")
tags_match = "all_strict" if tags else "any"
# Read reflect options from trigger (if stored)
trigger_data = mental_model.get("trigger") or {}
fact_types = trigger_data.get("fact_types")
exclude_mental_models = trigger_data.get("exclude_mental_models", False)
stored_exclude_ids: list[str] = trigger_data.get("exclude_mental_model_ids") or []
# Run reflect to generate new content, excluding the mental model being refreshed
# Always add self to excluded IDs to prevent circular reference
reflect_result = await self.reflect_async(
bank_id=bank_id,
query=source_query,
request_context=internal_context,
tags=tags,
tags_match=tags_match,
fact_types=fact_types,
exclude_mental_models=exclude_mental_models,
exclude_mental_model_ids=list({*stored_exclude_ids, mental_model_id}),
exclude_mental_model_ids=[mental_model_id],
)
generated_content = reflect_result.text or "No content generated"
@@ -5122,8 +5113,6 @@ class MemoryEngine(MemoryEngineInterface):
tags_match: TagsMatch = "any",
tag_groups: list[TagGroup] | None = None,
exclude_mental_model_ids: list[str] | None = None,
fact_types: list[str] | None = None,
exclude_mental_models: bool = False,
_skip_span: bool = False,
) -> ReflectResult:
"""
@@ -5244,11 +5233,6 @@ class MemoryEngine(MemoryEngineInterface):
pending_consolidation=pending_consolidation,
)
# Determine which tools to enable based on fact_types and exclude_mental_models
include_observations = fact_types is None or "observation" in fact_types
recall_fact_types = [ft for ft in (fact_types or ["world", "experience"]) if ft in ("world", "experience")]
include_recall = bool(recall_fact_types)
async def recall_fn(q: str, max_tokens: int = 4096, max_chunk_tokens: int = 1000) -> dict[str, Any]:
return await tool_recall(
self,
@@ -5260,7 +5244,6 @@ class MemoryEngine(MemoryEngineInterface):
tags_match=tags_match,
tag_groups=tag_groups,
max_chunk_tokens=max_chunk_tokens,
fact_types=recall_fact_types if fact_types is not None else None,
)
async def expand_fn(memory_ids: list[str], depth: str) -> dict[str, Any]:
@@ -5283,17 +5266,15 @@ class MemoryEngine(MemoryEngineInterface):
if directives:
logger.info(f"[REFLECT {reflect_id}] Loaded {len(directives)} directives")
# Check if the bank has any mental models (skip check if all mental models are excluded)
has_mental_models = False
if not exclude_mental_models:
async with pool.acquire() as conn:
mental_model_count = await conn.fetchval(
f"SELECT COUNT(*) FROM {fq_table('mental_models')} WHERE bank_id = $1",
bank_id,
)
has_mental_models = mental_model_count > 0
if has_mental_models:
logger.info(f"[REFLECT {reflect_id}] Bank has {mental_model_count} mental models")
# Check if the bank has any mental models
async with pool.acquire() as conn:
mental_model_count = await conn.fetchval(
f"SELECT COUNT(*) FROM {fq_table('mental_models')} WHERE bank_id = $1",
bank_id,
)
has_mental_models = mental_model_count > 0
if has_mental_models:
logger.info(f"[REFLECT {reflect_id}] Bank has {mental_model_count} mental models")
# Run the agent with parent span for reflect operation (skip if called from another operation)
if not _skip_span:
@@ -5318,8 +5299,6 @@ class MemoryEngine(MemoryEngineInterface):
response_schema=response_schema,
directives=directives,
has_mental_models=has_mental_models,
include_observations=include_observations,
include_recall=include_recall,
budget=effective_budget,
max_context_tokens=max_context_tokens,
)
@@ -6451,12 +6430,6 @@ class MemoryEngine(MemoryEngineInterface):
tags = mental_model.get("tags")
tags_match = "all_strict" if tags else "any"
# Read reflect options from trigger (if stored)
trigger_data = mental_model.get("trigger") or {}
fact_types = trigger_data.get("fact_types")
exclude_mental_models = trigger_data.get("exclude_mental_models", False)
stored_exclude_ids: list[str] = trigger_data.get("exclude_mental_model_ids") or []
# Run reflect with the source query, excluding the mental model being refreshed
# Skip creating a nested "hindsight.reflect" span since we already have "hindsight.mental_model_refresh"
reflect_result = await self.reflect_async(
@@ -6465,9 +6438,7 @@ class MemoryEngine(MemoryEngineInterface):
request_context=request_context,
tags=tags,
tags_match=tags_match,
fact_types=fact_types,
exclude_mental_models=exclude_mental_models,
exclude_mental_model_ids=list({*stored_exclude_ids, mental_model_id}),
exclude_mental_model_ids=[mental_model_id],
_skip_span=True,
)
@@ -316,8 +316,6 @@ async def run_reflect_agent(
response_schema: dict | None = None,
directives: list[dict[str, Any]] | None = None,
has_mental_models: bool = False,
include_observations: bool = True,
include_recall: bool = True,
budget: str | None = None,
max_context_tokens: int = 100_000,
) -> ReflectAgentResult:
@@ -357,14 +355,7 @@ async def run_reflect_agent(
directive_rules = _extract_directive_rules(directives) if directives else None
# Get tools for this agent (with directive compliance field if directives exist)
tools = get_reflect_tools(
directive_rules=directive_rules,
include_mental_models=has_mental_models,
include_observations=include_observations,
include_recall=include_recall,
)
# Build set of enabled tool names to guard against LLM hallucinating disabled tool calls
enabled_tools: frozenset[str] = frozenset(t["function"]["name"] for t in tools if t.get("type") == "function")
tools = get_reflect_tools(directive_rules=directive_rules)
# Build initial messages (directives are injected into system prompt at START and END)
system_prompt = build_system_prompt_for_tools(
@@ -547,18 +538,19 @@ async def run_reflect_agent(
llm_start = time.time()
# Determine tool_choice for this iteration.
# Force the full hierarchical retrieval path (only for enabled tools) before allowing auto.
# Build the forced sequence from the tools that are actually enabled.
forced_sequence = []
if has_mental_models:
forced_sequence.append("search_mental_models")
if include_observations:
forced_sequence.append("search_observations")
if include_recall:
forced_sequence.append("recall")
if iteration < len(forced_sequence):
iter_tool_choice: str | dict = {"type": "function", "function": {"name": forced_sequence[iteration]}}
# Force the full hierarchical retrieval path before allowing auto:
# With mental models:
# 0 → search_mental_models, 1 → search_observations, 2 → recall, 3+ → auto
# Without mental models:
# 0 → search_observations, 1 → recall, 2+ → auto
if iteration == 0 and has_mental_models:
iter_tool_choice: str | dict = {"type": "function", "function": {"name": "search_mental_models"}}
elif iteration == 0:
iter_tool_choice = {"type": "function", "function": {"name": "search_observations"}}
elif iteration == 1 and has_mental_models:
iter_tool_choice = {"type": "function", "function": {"name": "search_observations"}}
elif iteration == 1 or (iteration == 2 and has_mental_models):
iter_tool_choice = {"type": "function", "function": {"name": "recall"}}
else:
iter_tool_choice = "auto"
@@ -777,17 +769,7 @@ async def run_reflect_agent(
# Execute other tools in parallel (exclude done tool in all its format variants)
other_tools = [tc for tc in result.tool_calls if not _is_done_tool(tc.name)]
if other_tools:
# Partition into enabled vs hallucinated (not in enabled_tools set)
allowed_tools = []
hallucinated_tools = []
for tc in other_tools:
norm = _normalize_tool_name(tc.name)
if enabled_tools is not None and norm not in enabled_tools and norm not in ("done", "expand"):
hallucinated_tools.append(tc)
else:
allowed_tools.append(tc)
# Build assistant message with all tool calls (LLM requires them for history)
# Add assistant message with tool calls
messages.append(
{
"role": "assistant",
@@ -795,23 +777,6 @@ async def run_reflect_agent(
}
)
# Immediately reject hallucinated tool calls without adding to trace
for tc in hallucinated_tools:
messages.append(
{
"role": "tool",
"tool_call_id": tc.id,
"name": tc.name,
"content": json.dumps(
{
"error": f"Tool '{_normalize_tool_name(tc.name)}' is not available. Use only the tools provided to you."
}
),
}
)
other_tools = allowed_tools
# Execute tools in parallel
tool_tasks = [
_execute_tool_with_timing(
@@ -820,7 +785,6 @@ async def run_reflect_agent(
search_observations_fn,
recall_fn,
expand_fn,
enabled_tools=enabled_tools,
)
for tc in other_tools
]
@@ -1010,7 +974,6 @@ async def _execute_tool_with_timing(
search_observations_fn: Callable[[str, int], Awaitable[dict[str, Any]]],
recall_fn: Callable[[str, int, int], Awaitable[dict[str, Any]]],
expand_fn: Callable[[list[str], str], Awaitable[dict[str, Any]]],
enabled_tools: frozenset[str] | None = None,
) -> tuple[dict[str, Any], int]:
"""Execute a tool call and return result with timing."""
from hindsight_api.tracing import get_tracer
@@ -1044,7 +1007,6 @@ async def _execute_tool_with_timing(
search_observations_fn,
recall_fn,
expand_fn,
enabled_tools=enabled_tools,
)
# Set success attributes
@@ -1084,16 +1046,11 @@ async def _execute_tool(
search_observations_fn: Callable[[str, int], Awaitable[dict[str, Any]]],
recall_fn: Callable[[str, int, int], Awaitable[dict[str, Any]]],
expand_fn: Callable[[list[str], str], Awaitable[dict[str, Any]]],
enabled_tools: frozenset[str] | None = None,
) -> dict[str, Any]:
"""Execute a single tool by name."""
# Normalize tool name for various LLM output formats
tool_name = _normalize_tool_name(tool_name)
# Guard against LLMs hallucinating calls to tools that were not provided
if enabled_tools is not None and tool_name not in enabled_tools and tool_name not in ("done", "expand"):
return {"error": f"Tool '{tool_name}' is not available. Use only the tools provided to you."}
if tool_name == "search_mental_models":
query = args.get("query")
if not query:
@@ -200,7 +200,6 @@ async def tool_recall(
tag_groups: "list | None" = None,
connection_budget: int = 1,
max_chunk_tokens: int = 1000,
fact_types: list[str] | None = None,
) -> dict[str, Any]:
"""
Search memories using TEMPR retrieval.
@@ -218,18 +217,15 @@ async def tool_recall(
tags_match: How to match tags - "any" (OR), "all" (AND), or "exact"
connection_budget: Max DB connections for this recall (default 1 for internal ops)
max_chunk_tokens: Maximum tokens for raw source chunk text (default 1000, always included)
fact_types: Optional filter for fact types to retrieve. Defaults to ["experience", "world"].
Returns:
Dict with list of matching memories including raw chunk text
"""
# Only world/experience are valid for raw recall (observation is handled by search_observations)
recall_fact_type = [ft for ft in (fact_types or ["experience", "world"]) if ft in ("world", "experience")]
include_chunks = True
result = await memory_engine.recall_async(
bank_id=bank_id,
query=query,
fact_type=recall_fact_type,
fact_type=["experience", "world"],
max_tokens=max_tokens,
enable_trace=False,
request_context=request_context,
@@ -227,12 +227,7 @@ def _build_done_tool_with_directives(directive_rules: list[str]) -> dict:
}
def get_reflect_tools(
directive_rules: list[str] | None = None,
include_mental_models: bool = True,
include_observations: bool = True,
include_recall: bool = True,
) -> list[dict]:
def get_reflect_tools(directive_rules: list[str] | None = None) -> list[dict]:
"""
Get the list of tools for the reflect agent.
@@ -244,23 +239,16 @@ def get_reflect_tools(
Args:
directive_rules: Optional list of directive rule strings. If provided,
the done() tool will require directive compliance confirmation.
include_mental_models: Whether to include the search_mental_models tool.
include_observations: Whether to include the search_observations tool.
include_recall: Whether to include the recall tool.
Returns:
List of tool definitions in OpenAI format
"""
tools = []
if include_mental_models:
tools.append(TOOL_SEARCH_MENTAL_MODELS)
if include_observations:
tools.append(TOOL_SEARCH_OBSERVATIONS)
if include_recall:
tools.append(TOOL_RECALL)
tools.append(TOOL_EXPAND)
tools = [
TOOL_SEARCH_MENTAL_MODELS,
TOOL_SEARCH_OBSERVATIONS,
TOOL_RECALL,
TOOL_EXPAND,
]
# Use directive-aware done tool if directives are present
if directive_rules:
@@ -1083,16 +1083,30 @@ async def _extract_facts_from_chunk(
logger.warning(f"Skipping fact {i}: missing 'what' field")
continue
# Critical field: fact_type — "assistant" maps to "experience", everything else is "world".
# If fact_type is unexpected, fall back to fact_kind before defaulting to "world".
raw_fact_type = llm_fact.get("fact_type")
if raw_fact_type == "assistant":
# Critical field: fact_type
# LLM uses "assistant" but we convert to "experience" for storage
original_fact_type = llm_fact.get("fact_type")
fact_type = original_fact_type
# Convert "assistant" → "experience" for storage
if fact_type == "assistant":
fact_type = "experience"
elif raw_fact_type == "world":
fact_type = "world"
else:
raw_fact_kind = llm_fact.get("fact_kind")
fact_type = "experience" if raw_fact_kind == "assistant" else "world"
# Validate fact_type (after conversion)
if fact_type not in ["world", "experience", "opinion"]:
# Try to fix common mistakes - check if they swapped fact_type and fact_kind
fact_kind = llm_fact.get("fact_kind")
if fact_kind == "assistant":
fact_type = "experience"
elif fact_kind in ["world", "experience", "opinion"]:
fact_type = fact_kind
else:
# Default to 'world' if we can't determine
fact_type = "world"
logger.warning(
f"Fact {i}: defaulting to fact_type='world' "
f"(original fact_type={original_fact_type!r}, fact_kind={fact_kind!r})"
)
# Get fact_kind for temporal handling (but don't store it)
fact_kind = llm_fact.get("fact_kind", "conversation")
@@ -1740,17 +1754,23 @@ async def extract_facts_from_contents_batch_api(
who = get_value("who")
why = get_value("why")
# Critical field: fact_type — only "assistant" maps to "experience", everything else is "world"
# Critical field: fact_type — "assistant" maps to "experience", everything else is "world".
# If fact_type is unexpected, fall back to fact_kind before defaulting to "world".
raw_fact_type = llm_fact.get("fact_type")
if raw_fact_type == "assistant":
# Critical field: fact_type
original_fact_type = llm_fact.get("fact_type")
fact_type = original_fact_type
# Convert "assistant" → "experience"
if fact_type == "assistant":
fact_type = "experience"
elif raw_fact_type == "world":
fact_type = "world"
else:
raw_fact_kind = llm_fact.get("fact_kind")
fact_type = "experience" if raw_fact_kind == "assistant" else "world"
# Validate fact_type
if fact_type not in ["world", "experience", "opinion"]:
fact_kind = llm_fact.get("fact_kind")
if fact_kind == "assistant":
fact_type = "experience"
elif fact_kind in ["world", "experience", "opinion"]:
fact_type = fact_kind
else:
fact_type = "world"
# Build combined fact text
combined_parts = [what]
@@ -1913,7 +1933,7 @@ async def extract_facts_from_contents_batch_api(
for fact_from_llm in chunk_facts:
extracted_fact = ExtractedFactType(
fact_text=fact_from_llm.fact,
fact_type="experience" if fact_from_llm.fact_type == "assistant" else "world",
fact_type=fact_from_llm.fact_type,
entities=[e.text for e in (fact_from_llm.entities or [])],
occurred_start=_parse_datetime(fact_from_llm.occurred_start) if fact_from_llm.occurred_start else None,
occurred_end=_parse_datetime(fact_from_llm.occurred_end) if fact_from_llm.occurred_end else None,
@@ -2090,7 +2110,7 @@ async def extract_facts_from_contents(
# mentioned_at is always the event_date (when the conversation/document occurred)
extracted_fact = ExtractedFactType(
fact_text=fact_from_llm.fact,
fact_type="experience" if fact_from_llm.fact_type == "assistant" else "world",
fact_type=fact_from_llm.fact_type,
entities=[e.text for e in (fact_from_llm.entities or [])],
# occurred_start/end: from LLM only, leave None if not provided
occurred_start=_parse_datetime(fact_from_llm.occurred_start)
+6 -20
View File
@@ -48,8 +48,7 @@ def pg0_db_url(db_url, tmp_path_factory, worker_id):
Session-scoped fixture that ensures pg0 is running, migrations are applied,
and returns the database URL.
If HINDSIGHT_API_DATABASE_URL is a plain postgresql:// URL, uses it directly.
If HINDSIGHT_API_DATABASE_URL is a pg0:// URL, resolves it to a real URL first.
If HINDSIGHT_API_DATABASE_URL is set, uses that directly (no pg0 management).
Otherwise, starts pg0 once for the entire test session.
Uses filelock to ensure only one pytest-xdist worker starts pg0.
@@ -59,22 +58,9 @@ def pg0_db_url(db_url, tmp_path_factory, worker_id):
Note: We don't stop pg0 at the end because pytest-xdist runs workers in separate
processes that share the same pg0 instance. pg0 will persist for the next test run.
"""
from hindsight_api.pg0 import parse_pg0_url as _parse_pg0_url
# Determine pg0 instance name/port from db_url (if it's a pg0:// URL) or use defaults
if db_url and not _parse_pg0_url(db_url)[0]:
# Plain postgresql:// URL - use it directly but still run migrations
from hindsight_api.migrations import run_migrations
run_migrations(db_url)
return db_url
if db_url:
_, pg0_name, pg0_port = _parse_pg0_url(db_url)
pg0_instance_name = pg0_name or DEFAULT_PG0_INSTANCE_NAME
pg0_instance_port = pg0_port or DEFAULT_PG0_PORT
else:
pg0_instance_name = DEFAULT_PG0_INSTANCE_NAME
pg0_instance_port = DEFAULT_PG0_PORT
# Use provided database URL directly
return db_url
# Get shared temp dir for coordination between xdist workers
if worker_id == "master":
@@ -85,8 +71,8 @@ def pg0_db_url(db_url, tmp_path_factory, worker_id):
root_tmp_dir = tmp_path_factory.getbasetemp().parent
# Use a lock file to ensure only one worker starts pg0
lock_file = root_tmp_dir / f"pg0_setup_{pg0_instance_name}.lock"
url_file = root_tmp_dir / f"pg0_url_{pg0_instance_name}.txt"
lock_file = root_tmp_dir / "pg0_setup.lock"
url_file = root_tmp_dir / "pg0_url.txt"
with filelock.FileLock(str(lock_file)):
if url_file.exists():
@@ -94,7 +80,7 @@ def pg0_db_url(db_url, tmp_path_factory, worker_id):
url = url_file.read_text().strip()
else:
# First worker - start pg0
pg0 = EmbeddedPostgres(name=pg0_instance_name, port=pg0_instance_port)
pg0 = EmbeddedPostgres(name=DEFAULT_PG0_INSTANCE_NAME, port=DEFAULT_PG0_PORT)
# Run ensure_running in a new event loop
loop = asyncio.new_event_loop()
@@ -1,70 +0,0 @@
"""
Tests for EntityResolver edge cases.
"""
import uuid
from datetime import datetime, timezone
import asyncpg
import pytest
from hindsight_api.engine.entity_resolver import EntityResolver
from hindsight_api.pg0 import resolve_database_url
@pytest.mark.asyncio
async def test_resolve_entities_batch_handles_unicode_lower_conflicts(pg0_db_url):
"""
Existing entities with PostgreSQL/Python lowercase mismatches should resolve
to the conflicted row instead of leaving a missing entity_id.
"""
resolved_url = await resolve_database_url(pg0_db_url)
pool = await asyncpg.create_pool(resolved_url, min_size=1, max_size=2, command_timeout=30)
bank_id = f"test-entity-resolver-{uuid.uuid4().hex[:8]}"
event_date = datetime(2024, 1, 15, tzinfo=timezone.utc)
resolver = EntityResolver(pool=pool, entity_lookup="full")
try:
async with pool.acquire() as conn:
existing_entity_id = await conn.fetchval(
"""
INSERT INTO entities (bank_id, canonical_name, first_seen, last_seen, mention_count)
VALUES ($1, $2, $3, $3, 1)
RETURNING id
""",
bank_id,
"İstanbul",
event_date,
)
resolved_ids = await resolver.resolve_entities_batch(
bank_id=bank_id,
entities_data=[
{
"text": "istanbul",
"nearby_entities": [],
"event_date": event_date,
}
],
context="unicode case mismatch",
unit_event_date=event_date,
conn=conn,
)
entity_rows = await conn.fetch(
"""
SELECT id, canonical_name
FROM entities
WHERE bank_id = $1
ORDER BY canonical_name
""",
bank_id,
)
assert resolved_ids == [existing_entity_id]
assert len(entity_rows) == 1
assert entity_rows[0]["id"] == existing_entity_id
assert entity_rows[0]["canonical_name"] == "İstanbul"
finally:
await pool.execute("DELETE FROM entities WHERE bank_id = $1", bank_id)
await pool.close()
@@ -485,206 +485,3 @@ class TestReflectUsesMentalModels:
# Cleanup
await memory.delete_bank(bank_id, request_context=request_context)
class TestMentalModelReflectOptions:
"""Tests for fact_types and exclude_mental_models options stored in the trigger field."""
@pytest.mark.asyncio
async def test_trigger_stores_fact_types(self, memory: MemoryEngine, request_context):
"""Trigger field persists fact_types and returns them via get_mental_model."""
bank_id = f"test-mm-trigger-ft-{uuid.uuid4().hex[:8]}"
await memory.get_bank_profile(bank_id=bank_id, request_context=request_context)
mm = await memory.create_mental_model(
bank_id=bank_id,
name="Observations only",
source_query="Summarize observations",
content="content",
trigger={"refresh_after_consolidation": False, "fact_types": ["observation"]},
request_context=request_context,
)
fetched = await memory.get_mental_model(bank_id=bank_id, mental_model_id=mm["id"], request_context=request_context)
assert fetched["trigger"]["fact_types"] == ["observation"]
assert fetched["trigger"]["refresh_after_consolidation"] is False
await memory.delete_bank(bank_id, request_context=request_context)
@pytest.mark.asyncio
async def test_trigger_stores_exclude_mental_models(self, memory: MemoryEngine, request_context):
"""Trigger field persists exclude_mental_models flag."""
bank_id = f"test-mm-trigger-em-{uuid.uuid4().hex[:8]}"
await memory.get_bank_profile(bank_id=bank_id, request_context=request_context)
mm = await memory.create_mental_model(
bank_id=bank_id,
name="No mental models",
source_query="Summarize raw facts",
content="content",
trigger={"refresh_after_consolidation": False, "exclude_mental_models": True},
request_context=request_context,
)
fetched = await memory.get_mental_model(bank_id=bank_id, mental_model_id=mm["id"], request_context=request_context)
assert fetched["trigger"]["exclude_mental_models"] is True
await memory.delete_bank(bank_id, request_context=request_context)
@pytest.mark.asyncio
async def test_trigger_stores_exclude_mental_model_ids(self, memory: MemoryEngine, request_context):
"""Trigger field persists exclude_mental_model_ids list."""
bank_id = f"test-mm-trigger-eid-{uuid.uuid4().hex[:8]}"
await memory.get_bank_profile(bank_id=bank_id, request_context=request_context)
excluded_ids = ["mm-abc", "mm-xyz"]
mm = await memory.create_mental_model(
bank_id=bank_id,
name="Exclude some models",
source_query="Summarize",
content="content",
trigger={"refresh_after_consolidation": False, "exclude_mental_model_ids": excluded_ids},
request_context=request_context,
)
fetched = await memory.get_mental_model(bank_id=bank_id, mental_model_id=mm["id"], request_context=request_context)
assert fetched["trigger"]["exclude_mental_model_ids"] == excluded_ids
await memory.delete_bank(bank_id, request_context=request_context)
@pytest.mark.asyncio
async def test_update_trigger_reflect_options(self, memory: MemoryEngine, request_context):
"""update_mental_model persists updated trigger reflect options."""
bank_id = f"test-mm-trigger-upd-{uuid.uuid4().hex[:8]}"
await memory.get_bank_profile(bank_id=bank_id, request_context=request_context)
mm = await memory.create_mental_model(
bank_id=bank_id,
name="Initially no filter",
source_query="Summarize",
content="content",
trigger={"refresh_after_consolidation": False},
request_context=request_context,
)
updated = await memory.update_mental_model(
bank_id=bank_id,
mental_model_id=mm["id"],
trigger={
"refresh_after_consolidation": True,
"fact_types": ["world", "experience"],
"exclude_mental_models": False,
"exclude_mental_model_ids": ["mm-skip"],
},
request_context=request_context,
)
assert updated["trigger"]["refresh_after_consolidation"] is True
assert updated["trigger"]["fact_types"] == ["world", "experience"]
assert updated["trigger"]["exclude_mental_models"] is False
assert updated["trigger"]["exclude_mental_model_ids"] == ["mm-skip"]
await memory.delete_bank(bank_id, request_context=request_context)
class TestReflectFactTypeFiltering:
"""Tests for fact_types and exclude_mental_models filtering in reflect_async."""
@pytest.mark.asyncio
async def test_exclude_mental_models_skips_search_mental_models_tool(
self, memory: MemoryEngine, request_context
):
"""When exclude_mental_models=True, search_mental_models is never called."""
bank_id = f"test-reflect-exmm-{uuid.uuid4().hex[:8]}"
await memory.get_bank_profile(bank_id=bank_id, request_context=request_context)
# Create a mental model so the bank has one
await memory.create_mental_model(
bank_id=bank_id,
name="Existing Model",
source_query="Q",
content="Some content about the team",
request_context=request_context,
)
result = await memory.reflect_async(
bank_id=bank_id,
query="Tell me about the team",
request_context=request_context,
exclude_mental_models=True,
)
tool_names = [tc.tool for tc in result.tool_trace]
assert "search_mental_models" not in tool_names, (
f"search_mental_models should be excluded but found in: {tool_names}"
)
await memory.delete_bank(bank_id, request_context=request_context)
@pytest.mark.asyncio
async def test_exclude_observations_via_fact_types(self, memory: MemoryEngine, request_context):
"""When fact_types excludes observation, search_observations is never called."""
bank_id = f"test-reflect-exobs-{uuid.uuid4().hex[:8]}"
await memory.get_bank_profile(bank_id=bank_id, request_context=request_context)
result = await memory.reflect_async(
bank_id=bank_id,
query="Tell me something",
request_context=request_context,
fact_types=["world", "experience"],
)
tool_names = [tc.tool for tc in result.tool_trace]
assert "search_observations" not in tool_names, (
f"search_observations should be excluded but found in: {tool_names}"
)
await memory.delete_bank(bank_id, request_context=request_context)
@pytest.mark.asyncio
async def test_observation_only_fact_types_skips_recall(self, memory: MemoryEngine, request_context):
"""When fact_types=['observation'], recall is never called."""
bank_id = f"test-reflect-obsonly-{uuid.uuid4().hex[:8]}"
await memory.get_bank_profile(bank_id=bank_id, request_context=request_context)
result = await memory.reflect_async(
bank_id=bank_id,
query="Tell me something",
request_context=request_context,
fact_types=["observation"],
)
tool_names = [tc.tool for tc in result.tool_trace]
assert "recall" not in tool_names, f"recall should be excluded but found in: {tool_names}"
await memory.delete_bank(bank_id, request_context=request_context)
class TestReflectRequestValidation:
"""Tests for ReflectRequest and MentalModelTrigger validation via the HTTP API."""
@pytest.mark.asyncio
async def test_reflect_empty_fact_types_rejected(self, api_client, test_bank_id):
"""Passing fact_types=[] to reflect must return 422."""
await api_client.get(f"/v1/default/banks/{test_bank_id}/profile")
response = await api_client.post(
f"/v1/default/banks/{test_bank_id}/reflect",
json={"query": "test", "fact_types": []},
)
assert response.status_code == 422
@pytest.mark.asyncio
async def test_create_mental_model_empty_fact_types_rejected(self, api_client, test_bank_id):
"""Passing fact_types=[] inside trigger must return 422."""
await api_client.get(f"/v1/default/banks/{test_bank_id}/profile")
response = await api_client.post(
f"/v1/default/banks/{test_bank_id}/mental-models",
json={
"name": "Test",
"source_query": "Q",
"trigger": {"refresh_after_consolidation": False, "fact_types": []},
},
)
assert response.status_code == 422
-7
View File
@@ -601,13 +601,6 @@ impl ApiClient {
})
}
pub fn get_mental_model_history(&self, bank_id: &str, mental_model_id: &str, _verbose: bool) -> Result<serde_json::Value> {
self.runtime.block_on(async {
let response = self.client.get_mental_model_history(bank_id, mental_model_id, None).await?;
Ok(response.into_inner())
})
}
// --- Directive Methods ---
pub fn list_directives(&self, bank_id: &str, _verbose: bool) -> Result<types::DirectiveListResponse> {
+1 -29
View File
@@ -717,13 +717,6 @@ pub fn set_config(
llm_model: Option<String>,
llm_api_key: Option<String>,
llm_base_url: Option<String>,
retain_mission: Option<String>,
retain_extraction_mode: Option<String>,
observations_mission: Option<String>,
reflect_mission: Option<String>,
disposition_skepticism: Option<i64>,
disposition_literalism: Option<i64>,
disposition_empathy: Option<i64>,
verbose: bool,
output_format: OutputFormat,
) -> Result<()> {
@@ -743,30 +736,9 @@ pub fn set_config(
if let Some(base_url) = llm_base_url {
updates.insert("llm_base_url".to_string(), serde_json::Value::String(base_url));
}
if let Some(mission) = retain_mission {
updates.insert("retain_mission".to_string(), serde_json::Value::String(mission));
}
if let Some(mode) = retain_extraction_mode {
updates.insert("retain_extraction_mode".to_string(), serde_json::Value::String(mode));
}
if let Some(mission) = observations_mission {
updates.insert("observations_mission".to_string(), serde_json::Value::String(mission));
}
if let Some(mission) = reflect_mission {
updates.insert("reflect_mission".to_string(), serde_json::Value::String(mission));
}
if let Some(skepticism) = disposition_skepticism {
updates.insert("disposition_skepticism".to_string(), serde_json::Value::Number(skepticism.into()));
}
if let Some(literalism) = disposition_literalism {
updates.insert("disposition_literalism".to_string(), serde_json::Value::Number(literalism.into()));
}
if let Some(empathy) = disposition_empathy {
updates.insert("disposition_empathy".to_string(), serde_json::Value::Number(empathy.into()));
}
if updates.is_empty() {
return Err(anyhow!("No config updates provided. Use --llm-provider, --llm-model, --retain-mission, --observations-mission, or other flags".to_string()));
return Err(anyhow!("No config updates provided. Use --llm-provider, --llm-model, --llm-api-key, or --llm-base-url".to_string()));
}
let spinner = if output_format == OutputFormat::Pretty {
+3 -4
View File
@@ -149,12 +149,11 @@ pub fn update(
directive_id: &str,
name: Option<String>,
content: Option<String>,
is_active: Option<bool>,
verbose: bool,
output_format: OutputFormat,
) -> Result<()> {
if name.is_none() && content.is_none() && is_active.is_none() {
anyhow::bail!("At least one of --name, --content, or --is-active must be provided");
if name.is_none() && content.is_none() {
anyhow::bail!("At least one of --name or --content must be provided");
}
let spinner = if output_format == OutputFormat::Pretty {
@@ -166,7 +165,7 @@ pub fn update(
let request = types::UpdateDirectiveRequest {
name,
content,
is_active,
is_active: None,
priority: None,
tags: None,
};
-3
View File
@@ -363,9 +363,6 @@ impl App {
tags: None,
tags_match: TagsMatch::Any,
tag_groups: None,
fact_types: None,
exclude_mental_models: false,
exclude_mental_model_ids: None,
};
let result = client.reflect(&bank_id, &request, false)
+6 -33
View File
@@ -9,7 +9,7 @@ use crate::output::{self, OutputFormat};
use crate::ui;
// Import types from generated client
use hindsight_client::types::{Budget, ChunkIncludeOptions, FactsIncludeOptions, IncludeOptions, ReflectIncludeOptions, TagsMatch};
use hindsight_client::types::{Budget, ChunkIncludeOptions, IncludeOptions, TagsMatch};
use serde::Deserialize;
use serde_json;
@@ -43,16 +43,6 @@ fn parse_budget(budget: &str) -> Budget {
}
}
// Helper function to parse tags_match string to TagsMatch enum
fn parse_tags_match(tags_match: &Option<String>) -> TagsMatch {
match tags_match.as_deref().unwrap_or("any").to_lowercase().as_str() {
"all" => TagsMatch::All,
"any_strict" => TagsMatch::AnyStrict,
"all_strict" => TagsMatch::AllStrict,
_ => TagsMatch::Any,
}
}
/// List memory units with pagination and optional filters
pub fn list(
client: &ApiClient,
@@ -260,8 +250,6 @@ pub fn recall(
trace: bool,
include_chunks: bool,
chunk_max_tokens: i64,
tags: Vec<String>,
tags_match: Option<String>,
verbose: bool,
output_format: OutputFormat,
) -> Result<()> {
@@ -292,8 +280,8 @@ pub fn recall(
trace,
query_timestamp: None,
include,
tags: if tags.is_empty() { None } else { Some(tags) },
tags_match: parse_tags_match(&tags_match),
tags: None,
tags_match: TagsMatch::Any,
tag_groups: None,
};
@@ -324,9 +312,6 @@ pub fn reflect(
context: Option<String>,
max_tokens: Option<i64>,
schema_path: Option<PathBuf>,
tags: Vec<String>,
tags_match: Option<String>,
include_facts: bool,
verbose: bool,
output_format: OutputFormat,
) -> Result<()> {
@@ -347,28 +332,16 @@ pub fn reflect(
None
};
let include = if include_facts {
Some(ReflectIncludeOptions {
facts: Some(FactsIncludeOptions(serde_json::Map::new())),
tool_calls: None,
})
} else {
None
};
let request = ReflectRequest {
query,
budget: Some(parse_budget(&budget)),
context,
max_tokens: max_tokens.unwrap_or(4096),
include,
include: None,
response_schema,
tags: if tags.is_empty() { None } else { Some(tags) },
tags_match: parse_tags_match(&tags_match),
tags: None,
tags_match: TagsMatch::Any,
tag_groups: None,
fact_types: None,
exclude_mental_models: false,
exclude_mental_model_ids: None,
};
let response = client.reflect(agent_id, &request, verbose);
@@ -272,55 +272,6 @@ pub fn refresh(
}
}
/// Get the change history of a mental model
pub fn history(
client: &ApiClient,
bank_id: &str,
mental_model_id: &str,
verbose: bool,
output_format: OutputFormat,
) -> Result<()> {
let spinner = if output_format == OutputFormat::Pretty {
Some(ui::create_spinner("Fetching mental model history..."))
} else {
None
};
let response = client.get_mental_model_history(bank_id, mental_model_id, verbose);
if let Some(mut sp) = spinner {
sp.finish();
}
match response {
Ok(history) => {
if output_format == OutputFormat::Pretty {
ui::print_section_header(&format!("History: {}", mental_model_id));
if let Some(entries) = history.as_array() {
if entries.is_empty() {
println!(" {}", ui::dim("No history entries found."));
} else {
for entry in entries {
let changed_at = entry.get("changed_at").and_then(|v| v.as_str()).unwrap_or("unknown");
let previous = entry.get("previous_content").and_then(|v| v.as_str()).unwrap_or("(none)");
println!(" {} {}", ui::dim("Changed at:"), changed_at);
let preview: String = previous.chars().take(80).collect();
let ellipsis = if previous.len() > 80 { "..." } else { "" };
println!(" {} {}{}", ui::dim("Previous:"), ui::dim(&preview), ellipsis);
println!();
}
}
}
} else {
output::print_output(&history, output_format)?;
}
Ok(())
}
Err(e) => Err(e),
}
}
// Helper function to print mental model details
fn print_mental_model_detail(mental_model: &types::MentalModelResponse) {
ui::print_section_header(&mental_model.name);
+8 -72
View File
@@ -310,34 +310,6 @@ enum BankCommands {
/// LLM base URL override
#[arg(long)]
llm_base_url: Option<String>,
/// Retain mission: what to focus on during fact extraction
#[arg(long)]
retain_mission: Option<String>,
/// Retain extraction mode (concise, verbose, custom)
#[arg(long)]
retain_extraction_mode: Option<String>,
/// Observations mission: what to synthesize into durable observations
#[arg(long)]
observations_mission: Option<String>,
/// Reflect mission: first-person identity for reflect operations
#[arg(long)]
reflect_mission: Option<String>,
/// Disposition skepticism trait (1-5)
#[arg(long, value_parser = clap::value_parser!(i64).range(1..=5))]
disposition_skepticism: Option<i64>,
/// Disposition literalism trait (1-5)
#[arg(long, value_parser = clap::value_parser!(i64).range(1..=5))]
disposition_literalism: Option<i64>,
/// Disposition empathy trait (1-5)
#[arg(long, value_parser = clap::value_parser!(i64).range(1..=5))]
disposition_empathy: Option<i64>,
},
/// Reset bank configuration to defaults (remove all overrides)
@@ -415,14 +387,6 @@ enum MemoryCommands {
/// Maximum tokens for chunks (only used with --include-chunks)
#[arg(long, default_value = "8192")]
chunk_max_tokens: i64,
/// Filter by tags (comma-separated, e.g. user:alice,team)
#[arg(long, value_delimiter = ',')]
tags: Vec<String>,
/// Tag matching mode: any, all, any_strict, all_strict (default: any)
#[arg(long)]
tags_match: Option<String>,
},
/// Generate answers using bank identity (reflect/reasoning)
@@ -448,18 +412,6 @@ enum MemoryCommands {
/// Path to JSON schema file for structured output
#[arg(short = 's', long)]
schema: Option<PathBuf>,
/// Filter by tags (comma-separated, e.g. user:alice,team)
#[arg(long, value_delimiter = ',')]
tags: Vec<String>,
/// Tag matching mode: any, all, any_strict, all_strict (default: any)
#[arg(long)]
tags_match: Option<String>,
/// Include source facts (based_on) in the response
#[arg(long)]
include_facts: bool,
},
/// Store (retain) a single memory
@@ -726,15 +678,6 @@ enum MentalModelCommands {
/// Mental model ID
mental_model_id: String,
},
/// Get the change history of a mental model
History {
/// Bank ID
bank_id: String,
/// Mental model ID
mental_model_id: String,
},
}
#[derive(Subcommand)]
@@ -781,10 +724,6 @@ enum DirectiveCommands {
/// New content
#[arg(long)]
content: Option<String>,
/// Enable or disable the directive
#[arg(long)]
is_active: Option<bool>,
},
/// Delete a directive
@@ -882,8 +821,8 @@ fn run() -> Result<()> {
BankCommands::Config { bank_id, overrides_only } => {
commands::bank::config(&client, &bank_id, overrides_only, verbose, output_format)
}
BankCommands::SetConfig { bank_id, llm_provider, llm_model, llm_api_key, llm_base_url, retain_mission, retain_extraction_mode, observations_mission, reflect_mission, disposition_skepticism, disposition_literalism, disposition_empathy } => {
commands::bank::set_config(&client, &bank_id, llm_provider, llm_model, llm_api_key, llm_base_url, retain_mission, retain_extraction_mode, observations_mission, reflect_mission, disposition_skepticism, disposition_literalism, disposition_empathy, verbose, output_format)
BankCommands::SetConfig { bank_id, llm_provider, llm_model, llm_api_key, llm_base_url } => {
commands::bank::set_config(&client, &bank_id, llm_provider, llm_model, llm_api_key, llm_base_url, verbose, output_format)
}
BankCommands::ResetConfig { bank_id, yes } => {
commands::bank::reset_config(&client, &bank_id, yes, verbose, output_format)
@@ -898,11 +837,11 @@ fn run() -> Result<()> {
MemoryCommands::Get { bank_id, memory_id } => {
commands::memory::get(&client, &bank_id, &memory_id, verbose, output_format)
}
MemoryCommands::Recall { bank_id, query, fact_type, budget, max_tokens, trace, include_chunks, chunk_max_tokens, tags, tags_match } => {
commands::memory::recall(&client, &bank_id, query, fact_type, budget, max_tokens, trace, include_chunks, chunk_max_tokens, tags, tags_match, verbose, output_format)
MemoryCommands::Recall { bank_id, query, fact_type, budget, max_tokens, trace, include_chunks, chunk_max_tokens } => {
commands::memory::recall(&client, &bank_id, query, fact_type, budget, max_tokens, trace, include_chunks, chunk_max_tokens, verbose, output_format)
}
MemoryCommands::Reflect { bank_id, query, budget, context, max_tokens, schema, tags, tags_match, include_facts } => {
commands::memory::reflect(&client, &bank_id, query, budget, context, max_tokens, schema, tags, tags_match, include_facts, verbose, output_format)
MemoryCommands::Reflect { bank_id, query, budget, context, max_tokens, schema } => {
commands::memory::reflect(&client, &bank_id, query, budget, context, max_tokens, schema, verbose, output_format)
}
MemoryCommands::Retain { bank_id, content, doc_id, context, r#async } => {
commands::memory::retain(&client, &bank_id, content, doc_id, context, r#async, verbose, output_format)
@@ -991,9 +930,6 @@ fn run() -> Result<()> {
MentalModelCommands::Refresh { bank_id, mental_model_id } => {
commands::mental_model::refresh(&client, &bank_id, &mental_model_id, verbose, output_format)
}
MentalModelCommands::History { bank_id, mental_model_id } => {
commands::mental_model::history(&client, &bank_id, &mental_model_id, verbose, output_format)
}
},
// Directive commands
@@ -1007,8 +943,8 @@ fn run() -> Result<()> {
DirectiveCommands::Create { bank_id, name, content } => {
commands::directive::create(&client, &bank_id, &name, &content, verbose, output_format)
}
DirectiveCommands::Update { bank_id, directive_id, name, content, is_active } => {
commands::directive::update(&client, &bank_id, &directive_id, name, content, is_active, verbose, output_format)
DirectiveCommands::Update { bank_id, directive_id, name, content } => {
commands::directive::update(&client, &bank_id, &directive_id, name, content, verbose, output_format)
}
DirectiveCommands::Delete { bank_id, directive_id, yes } => {
commands::directive::delete(&client, &bank_id, &directive_id, yes, verbose, output_format)
-68
View File
@@ -4074,13 +4074,6 @@ components:
id: id
trigger:
refresh_after_consolidation: false
fact_types:
- world
- world
exclude_mental_model_ids:
- exclude_mental_model_ids
- exclude_mental_model_ids
exclude_mental_models: false
last_refreshed_at: last_refreshed_at
content: content
tags:
@@ -4096,13 +4089,6 @@ components:
id: id
trigger:
refresh_after_consolidation: false
fact_types:
- world
- world
exclude_mental_model_ids:
- exclude_mental_model_ids
- exclude_mental_model_ids
exclude_mental_models: false
last_refreshed_at: last_refreshed_at
content: content
tags:
@@ -4129,13 +4115,6 @@ components:
id: id
trigger:
refresh_after_consolidation: false
fact_types:
- world
- world
exclude_mental_model_ids:
- exclude_mental_model_ids
- exclude_mental_model_ids
exclude_mental_models: false
last_refreshed_at: last_refreshed_at
content: content
tags:
@@ -4190,13 +4169,6 @@ components:
description: Trigger settings for a mental model.
example:
refresh_after_consolidation: false
fact_types:
- world
- world
exclude_mental_model_ids:
- exclude_mental_model_ids
- exclude_mental_model_ids
exclude_mental_models: false
properties:
refresh_after_consolidation:
default: false
@@ -4204,26 +4176,6 @@ components:
\ (real-time mode)"
title: Refresh After Consolidation
type: boolean
fact_types:
items:
enum:
- world
- experience
- observation
type: string
nullable: true
type: array
exclude_mental_models:
default: false
description: "If true, exclude all mental models from the reflect loop (skip\
\ search_mental_models tool)."
title: Exclude Mental Models
type: boolean
exclude_mental_model_ids:
items:
type: string
nullable: true
type: array
title: MentalModelTrigger
OperationResponse:
description: Response model for a single async operation.
@@ -4732,26 +4684,6 @@ components:
$ref: '#/components/schemas/RecallRequest_tag_groups_inner'
nullable: true
type: array
fact_types:
items:
enum:
- world
- experience
- observation
type: string
nullable: true
type: array
exclude_mental_models:
default: false
description: "If true, exclude all mental models from the reflect loop (skip\
\ search_mental_models tool)."
title: Exclude Mental Models
type: boolean
exclude_mental_model_ids:
items:
type: string
nullable: true
type: array
required:
- query
title: ReflectRequest
@@ -21,10 +21,6 @@ var _ MappedNullable = &MentalModelTrigger{}
type MentalModelTrigger struct {
// If true, refresh this mental model after observations consolidation (real-time mode)
RefreshAfterConsolidation *bool `json:"refresh_after_consolidation,omitempty"`
FactTypes []string `json:"fact_types,omitempty"`
// If true, exclude all mental models from the reflect loop (skip search_mental_models tool).
ExcludeMentalModels *bool `json:"exclude_mental_models,omitempty"`
ExcludeMentalModelIds []string `json:"exclude_mental_model_ids,omitempty"`
}
// NewMentalModelTrigger instantiates a new MentalModelTrigger object
@@ -35,8 +31,6 @@ func NewMentalModelTrigger() *MentalModelTrigger {
this := MentalModelTrigger{}
var refreshAfterConsolidation bool = false
this.RefreshAfterConsolidation = &refreshAfterConsolidation
var excludeMentalModels bool = false
this.ExcludeMentalModels = &excludeMentalModels
return &this
}
@@ -47,8 +41,6 @@ func NewMentalModelTriggerWithDefaults() *MentalModelTrigger {
this := MentalModelTrigger{}
var refreshAfterConsolidation bool = false
this.RefreshAfterConsolidation = &refreshAfterConsolidation
var excludeMentalModels bool = false
this.ExcludeMentalModels = &excludeMentalModels
return &this
}
@@ -84,104 +76,6 @@ func (o *MentalModelTrigger) SetRefreshAfterConsolidation(v bool) {
o.RefreshAfterConsolidation = &v
}
// GetFactTypes returns the FactTypes field value if set, zero value otherwise (both if not set or set to explicit null).
func (o *MentalModelTrigger) GetFactTypes() []string {
if o == nil {
var ret []string
return ret
}
return o.FactTypes
}
// GetFactTypesOk returns a tuple with the FactTypes field value if set, nil otherwise
// and a boolean to check if the value has been set.
// NOTE: If the value is an explicit nil, `nil, true` will be returned
func (o *MentalModelTrigger) GetFactTypesOk() ([]string, bool) {
if o == nil || IsNil(o.FactTypes) {
return nil, false
}
return o.FactTypes, true
}
// HasFactTypes returns a boolean if a field has been set.
func (o *MentalModelTrigger) HasFactTypes() bool {
if o != nil && !IsNil(o.FactTypes) {
return true
}
return false
}
// SetFactTypes gets a reference to the given []string and assigns it to the FactTypes field.
func (o *MentalModelTrigger) SetFactTypes(v []string) {
o.FactTypes = v
}
// GetExcludeMentalModels returns the ExcludeMentalModels field value if set, zero value otherwise.
func (o *MentalModelTrigger) GetExcludeMentalModels() bool {
if o == nil || IsNil(o.ExcludeMentalModels) {
var ret bool
return ret
}
return *o.ExcludeMentalModels
}
// GetExcludeMentalModelsOk returns a tuple with the ExcludeMentalModels field value if set, nil otherwise
// and a boolean to check if the value has been set.
func (o *MentalModelTrigger) GetExcludeMentalModelsOk() (*bool, bool) {
if o == nil || IsNil(o.ExcludeMentalModels) {
return nil, false
}
return o.ExcludeMentalModels, true
}
// HasExcludeMentalModels returns a boolean if a field has been set.
func (o *MentalModelTrigger) HasExcludeMentalModels() bool {
if o != nil && !IsNil(o.ExcludeMentalModels) {
return true
}
return false
}
// SetExcludeMentalModels gets a reference to the given bool and assigns it to the ExcludeMentalModels field.
func (o *MentalModelTrigger) SetExcludeMentalModels(v bool) {
o.ExcludeMentalModels = &v
}
// GetExcludeMentalModelIds returns the ExcludeMentalModelIds field value if set, zero value otherwise (both if not set or set to explicit null).
func (o *MentalModelTrigger) GetExcludeMentalModelIds() []string {
if o == nil {
var ret []string
return ret
}
return o.ExcludeMentalModelIds
}
// GetExcludeMentalModelIdsOk returns a tuple with the ExcludeMentalModelIds field value if set, nil otherwise
// and a boolean to check if the value has been set.
// NOTE: If the value is an explicit nil, `nil, true` will be returned
func (o *MentalModelTrigger) GetExcludeMentalModelIdsOk() ([]string, bool) {
if o == nil || IsNil(o.ExcludeMentalModelIds) {
return nil, false
}
return o.ExcludeMentalModelIds, true
}
// HasExcludeMentalModelIds returns a boolean if a field has been set.
func (o *MentalModelTrigger) HasExcludeMentalModelIds() bool {
if o != nil && !IsNil(o.ExcludeMentalModelIds) {
return true
}
return false
}
// SetExcludeMentalModelIds gets a reference to the given []string and assigns it to the ExcludeMentalModelIds field.
func (o *MentalModelTrigger) SetExcludeMentalModelIds(v []string) {
o.ExcludeMentalModelIds = v
}
func (o MentalModelTrigger) MarshalJSON() ([]byte, error) {
toSerialize,err := o.ToMap()
if err != nil {
@@ -195,15 +89,6 @@ func (o MentalModelTrigger) ToMap() (map[string]interface{}, error) {
if !IsNil(o.RefreshAfterConsolidation) {
toSerialize["refresh_after_consolidation"] = o.RefreshAfterConsolidation
}
if o.FactTypes != nil {
toSerialize["fact_types"] = o.FactTypes
}
if !IsNil(o.ExcludeMentalModels) {
toSerialize["exclude_mental_models"] = o.ExcludeMentalModels
}
if o.ExcludeMentalModelIds != nil {
toSerialize["exclude_mental_model_ids"] = o.ExcludeMentalModelIds
}
return toSerialize, nil
}
@@ -33,10 +33,6 @@ type ReflectRequest struct {
// How to match tags: 'any' (OR, includes untagged), 'all' (AND, includes untagged), 'any_strict' (OR, excludes untagged), 'all_strict' (AND, excludes untagged).
TagsMatch *string `json:"tags_match,omitempty"`
TagGroups []RecallRequestTagGroupsInner `json:"tag_groups,omitempty"`
FactTypes []string `json:"fact_types,omitempty"`
// If true, exclude all mental models from the reflect loop (skip search_mental_models tool).
ExcludeMentalModels *bool `json:"exclude_mental_models,omitempty"`
ExcludeMentalModelIds []string `json:"exclude_mental_model_ids,omitempty"`
}
type _ReflectRequest ReflectRequest
@@ -52,8 +48,6 @@ func NewReflectRequest(query string) *ReflectRequest {
this.MaxTokens = &maxTokens
var tagsMatch string = "any"
this.TagsMatch = &tagsMatch
var excludeMentalModels bool = false
this.ExcludeMentalModels = &excludeMentalModels
return &this
}
@@ -66,8 +60,6 @@ func NewReflectRequestWithDefaults() *ReflectRequest {
this.MaxTokens = &maxTokens
var tagsMatch string = "any"
this.TagsMatch = &tagsMatch
var excludeMentalModels bool = false
this.ExcludeMentalModels = &excludeMentalModels
return &this
}
@@ -364,104 +356,6 @@ func (o *ReflectRequest) SetTagGroups(v []RecallRequestTagGroupsInner) {
o.TagGroups = v
}
// GetFactTypes returns the FactTypes field value if set, zero value otherwise (both if not set or set to explicit null).
func (o *ReflectRequest) GetFactTypes() []string {
if o == nil {
var ret []string
return ret
}
return o.FactTypes
}
// GetFactTypesOk returns a tuple with the FactTypes field value if set, nil otherwise
// and a boolean to check if the value has been set.
// NOTE: If the value is an explicit nil, `nil, true` will be returned
func (o *ReflectRequest) GetFactTypesOk() ([]string, bool) {
if o == nil || IsNil(o.FactTypes) {
return nil, false
}
return o.FactTypes, true
}
// HasFactTypes returns a boolean if a field has been set.
func (o *ReflectRequest) HasFactTypes() bool {
if o != nil && !IsNil(o.FactTypes) {
return true
}
return false
}
// SetFactTypes gets a reference to the given []string and assigns it to the FactTypes field.
func (o *ReflectRequest) SetFactTypes(v []string) {
o.FactTypes = v
}
// GetExcludeMentalModels returns the ExcludeMentalModels field value if set, zero value otherwise.
func (o *ReflectRequest) GetExcludeMentalModels() bool {
if o == nil || IsNil(o.ExcludeMentalModels) {
var ret bool
return ret
}
return *o.ExcludeMentalModels
}
// GetExcludeMentalModelsOk returns a tuple with the ExcludeMentalModels field value if set, nil otherwise
// and a boolean to check if the value has been set.
func (o *ReflectRequest) GetExcludeMentalModelsOk() (*bool, bool) {
if o == nil || IsNil(o.ExcludeMentalModels) {
return nil, false
}
return o.ExcludeMentalModels, true
}
// HasExcludeMentalModels returns a boolean if a field has been set.
func (o *ReflectRequest) HasExcludeMentalModels() bool {
if o != nil && !IsNil(o.ExcludeMentalModels) {
return true
}
return false
}
// SetExcludeMentalModels gets a reference to the given bool and assigns it to the ExcludeMentalModels field.
func (o *ReflectRequest) SetExcludeMentalModels(v bool) {
o.ExcludeMentalModels = &v
}
// GetExcludeMentalModelIds returns the ExcludeMentalModelIds field value if set, zero value otherwise (both if not set or set to explicit null).
func (o *ReflectRequest) GetExcludeMentalModelIds() []string {
if o == nil {
var ret []string
return ret
}
return o.ExcludeMentalModelIds
}
// GetExcludeMentalModelIdsOk returns a tuple with the ExcludeMentalModelIds field value if set, nil otherwise
// and a boolean to check if the value has been set.
// NOTE: If the value is an explicit nil, `nil, true` will be returned
func (o *ReflectRequest) GetExcludeMentalModelIdsOk() ([]string, bool) {
if o == nil || IsNil(o.ExcludeMentalModelIds) {
return nil, false
}
return o.ExcludeMentalModelIds, true
}
// HasExcludeMentalModelIds returns a boolean if a field has been set.
func (o *ReflectRequest) HasExcludeMentalModelIds() bool {
if o != nil && !IsNil(o.ExcludeMentalModelIds) {
return true
}
return false
}
// SetExcludeMentalModelIds gets a reference to the given []string and assigns it to the ExcludeMentalModelIds field.
func (o *ReflectRequest) SetExcludeMentalModelIds(v []string) {
o.ExcludeMentalModelIds = v
}
func (o ReflectRequest) MarshalJSON() ([]byte, error) {
toSerialize,err := o.ToMap()
if err != nil {
@@ -497,15 +391,6 @@ func (o ReflectRequest) ToMap() (map[string]interface{}, error) {
if o.TagGroups != nil {
toSerialize["tag_groups"] = o.TagGroups
}
if o.FactTypes != nil {
toSerialize["fact_types"] = o.FactTypes
}
if !IsNil(o.ExcludeMentalModels) {
toSerialize["exclude_mental_models"] = o.ExcludeMentalModels
}
if o.ExcludeMentalModelIds != nil {
toSerialize["exclude_mental_model_ids"] = o.ExcludeMentalModelIds
}
return toSerialize, nil
}
@@ -792,7 +792,6 @@ class Hindsight:
tags: list[str] | None = None,
max_tokens: int | None = None,
trigger: dict[str, Any] | None = None,
id: str | None = None,
):
"""
Create a mental model (runs reflect in background).
@@ -804,7 +803,6 @@ class Hindsight:
tags: Optional tags for filtering during retrieval
max_tokens: Optional maximum tokens for the mental model content
trigger: Optional trigger settings (e.g., {"refresh_after_consolidation": True})
id: Optional custom ID for the mental model (alphanumeric lowercase with hyphens)
Returns:
CreateMentalModelResponse with operation_id
@@ -816,7 +814,6 @@ class Hindsight:
trigger_obj = mental_model_trigger.MentalModelTrigger(**trigger)
request_obj = create_mental_model_request.CreateMentalModelRequest(
id=id,
name=name,
source_query=source_query,
tags=tags,
@@ -17,7 +17,7 @@ import pprint
import re # noqa: F401
import json
from pydantic import BaseModel, ConfigDict, Field, StrictBool, StrictStr, field_validator
from pydantic import BaseModel, ConfigDict, Field, StrictBool
from typing import Any, ClassVar, Dict, List, Optional
from typing import Optional, Set
from typing_extensions import Self
@@ -27,21 +27,7 @@ class MentalModelTrigger(BaseModel):
Trigger settings for a mental model.
""" # noqa: E501
refresh_after_consolidation: Optional[StrictBool] = Field(default=False, description="If true, refresh this mental model after observations consolidation (real-time mode)")
fact_types: Optional[List[StrictStr]] = None
exclude_mental_models: Optional[StrictBool] = Field(default=False, description="If true, exclude all mental models from the reflect loop (skip search_mental_models tool).")
exclude_mental_model_ids: Optional[List[StrictStr]] = None
__properties: ClassVar[List[str]] = ["refresh_after_consolidation", "fact_types", "exclude_mental_models", "exclude_mental_model_ids"]
@field_validator('fact_types')
def fact_types_validate_enum(cls, value):
"""Validates the enum"""
if value is None:
return value
for i in value:
if i not in set(['world', 'experience', 'observation']):
raise ValueError("each list item must be one of ('world', 'experience', 'observation')")
return value
__properties: ClassVar[List[str]] = ["refresh_after_consolidation"]
model_config = ConfigDict(
populate_by_name=True,
@@ -82,16 +68,6 @@ class MentalModelTrigger(BaseModel):
exclude=excluded_fields,
exclude_none=True,
)
# set to None if fact_types (nullable) is None
# and model_fields_set contains the field
if self.fact_types is None and "fact_types" in self.model_fields_set:
_dict['fact_types'] = None
# set to None if exclude_mental_model_ids (nullable) is None
# and model_fields_set contains the field
if self.exclude_mental_model_ids is None and "exclude_mental_model_ids" in self.model_fields_set:
_dict['exclude_mental_model_ids'] = None
return _dict
@classmethod
@@ -104,10 +80,7 @@ class MentalModelTrigger(BaseModel):
return cls.model_validate(obj)
_obj = cls.model_validate({
"refresh_after_consolidation": obj.get("refresh_after_consolidation") if obj.get("refresh_after_consolidation") is not None else False,
"fact_types": obj.get("fact_types"),
"exclude_mental_models": obj.get("exclude_mental_models") if obj.get("exclude_mental_models") is not None else False,
"exclude_mental_model_ids": obj.get("exclude_mental_model_ids")
"refresh_after_consolidation": obj.get("refresh_after_consolidation") if obj.get("refresh_after_consolidation") is not None else False
})
return _obj
@@ -17,7 +17,7 @@ import pprint
import re # noqa: F401
import json
from pydantic import BaseModel, ConfigDict, Field, StrictBool, StrictInt, StrictStr, field_validator
from pydantic import BaseModel, ConfigDict, Field, StrictInt, StrictStr, field_validator
from typing import Any, ClassVar, Dict, List, Optional
from hindsight_client_api.models.budget import Budget
from hindsight_client_api.models.recall_request_tag_groups_inner import RecallRequestTagGroupsInner
@@ -38,10 +38,7 @@ class ReflectRequest(BaseModel):
tags: Optional[List[StrictStr]] = None
tags_match: Optional[StrictStr] = Field(default='any', description="How to match tags: 'any' (OR, includes untagged), 'all' (AND, includes untagged), 'any_strict' (OR, excludes untagged), 'all_strict' (AND, excludes untagged).")
tag_groups: Optional[List[RecallRequestTagGroupsInner]] = None
fact_types: Optional[List[StrictStr]] = None
exclude_mental_models: Optional[StrictBool] = Field(default=False, description="If true, exclude all mental models from the reflect loop (skip search_mental_models tool).")
exclude_mental_model_ids: Optional[List[StrictStr]] = None
__properties: ClassVar[List[str]] = ["query", "budget", "context", "max_tokens", "include", "response_schema", "tags", "tags_match", "tag_groups", "fact_types", "exclude_mental_models", "exclude_mental_model_ids"]
__properties: ClassVar[List[str]] = ["query", "budget", "context", "max_tokens", "include", "response_schema", "tags", "tags_match", "tag_groups"]
@field_validator('tags_match')
def tags_match_validate_enum(cls, value):
@@ -53,17 +50,6 @@ class ReflectRequest(BaseModel):
raise ValueError("must be one of enum values ('any', 'all', 'any_strict', 'all_strict')")
return value
@field_validator('fact_types')
def fact_types_validate_enum(cls, value):
"""Validates the enum"""
if value is None:
return value
for i in value:
if i not in set(['world', 'experience', 'observation']):
raise ValueError("each list item must be one of ('world', 'experience', 'observation')")
return value
model_config = ConfigDict(
populate_by_name=True,
validate_assignment=True,
@@ -133,16 +119,6 @@ class ReflectRequest(BaseModel):
if self.tag_groups is None and "tag_groups" in self.model_fields_set:
_dict['tag_groups'] = None
# set to None if fact_types (nullable) is None
# and model_fields_set contains the field
if self.fact_types is None and "fact_types" in self.model_fields_set:
_dict['fact_types'] = None
# set to None if exclude_mental_model_ids (nullable) is None
# and model_fields_set contains the field
if self.exclude_mental_model_ids is None and "exclude_mental_model_ids" in self.model_fields_set:
_dict['exclude_mental_model_ids'] = None
return _dict
@classmethod
@@ -163,10 +139,7 @@ class ReflectRequest(BaseModel):
"response_schema": obj.get("response_schema"),
"tags": obj.get("tags"),
"tags_match": obj.get("tags_match") if obj.get("tags_match") is not None else 'any',
"tag_groups": [RecallRequestTagGroupsInner.from_dict(_item) for _item in obj["tag_groups"]] if obj.get("tag_groups") is not None else None,
"fact_types": obj.get("fact_types"),
"exclude_mental_models": obj.get("exclude_mental_models") if obj.get("exclude_mental_models") is not None else False,
"exclude_mental_model_ids": obj.get("exclude_mental_model_ids")
"tag_groups": [RecallRequestTagGroupsInner.from_dict(_item) for _item in obj["tag_groups"]] if obj.get("tag_groups") is not None else None
})
return _obj
@@ -1331,24 +1331,6 @@ export type MentalModelTrigger = {
* If true, refresh this mental model after observations consolidation (real-time mode)
*/
refresh_after_consolidation?: boolean;
/**
* Fact Types
*
* Filter which fact types are retrieved during reflect. None means all types (world, experience, observation).
*/
fact_types?: Array<"world" | "experience" | "observation"> | null;
/**
* Exclude Mental Models
*
* If true, exclude all mental models from the reflect loop (skip search_mental_models tool).
*/
exclude_mental_models?: boolean;
/**
* Exclude Mental Model Ids
*
* Exclude specific mental models by ID from the reflect loop.
*/
exclude_mental_model_ids?: Array<string> | null;
};
/**
@@ -1843,24 +1825,6 @@ export type ReflectRequest = {
tag_groups?: Array<
TagGroupLeaf | TagGroupAnd | TagGroupOr | TagGroupNot
> | null;
/**
* Fact Types
*
* Filter which fact types are retrieved during reflect. None means all types (world, experience, observation).
*/
fact_types?: Array<"world" | "experience" | "observation"> | null;
/**
* Exclude Mental Models
*
* If true, exclude all mental models from the reflect loop (skip search_mental_models tool).
*/
exclude_mental_models?: boolean;
/**
* Exclude Mental Model Ids
*
* Exclude specific mental models by ID from the reflect loop.
*/
exclude_mental_model_ids?: Array<string> | null;
};
/**
-14
View File
@@ -628,7 +628,6 @@ export class HindsightClient {
name: string,
sourceQuery: string,
options?: {
id?: string;
tags?: string[];
maxTokens?: number;
trigger?: { refreshAfterConsolidation?: boolean };
@@ -638,7 +637,6 @@ export class HindsightClient {
client: this.client,
path: { bank_id: bankId },
body: {
id: options?.id,
name,
source_query: sourceQuery,
tags: options?.tags,
@@ -728,18 +726,6 @@ export class HindsightClient {
throw new Error(`deleteMentalModel failed: ${JSON.stringify(response.error)}`);
}
}
/**
* Get the change history of a mental model.
*/
async getMentalModelHistory(bankId: string, mentalModelId: string): Promise<any> {
const response = await sdk.getMentalModelHistory({
client: this.client,
path: { bank_id: bankId, mental_model_id: mentalModelId },
});
return this.validateResponse(response, 'getMentalModelHistory');
}
}
// Re-export types for convenience
@@ -14,9 +14,6 @@ export async function POST(request: NextRequest) {
tags,
tags_match,
max_tokens,
fact_types,
exclude_mental_models,
exclude_mental_model_ids,
} = body;
const requestBody: any = {
@@ -25,9 +22,6 @@ export async function POST(request: NextRequest) {
tags,
tags_match,
max_tokens: max_tokens || undefined,
fact_types: fact_types || undefined,
exclude_mental_models: exclude_mental_models || undefined,
exclude_mental_model_ids: exclude_mental_model_ids || undefined,
};
// Add include options if specified
@@ -1,113 +0,0 @@
"use client";
import { cn } from "@/lib/utils";
export type FactType = "world" | "experience" | "observation";
export const ALL_FACT_TYPES: FactType[] = ["world", "experience", "observation"];
const FACT_TYPE_CONFIG: Record<
FactType,
{ label: string; active: string; inactive: string; dot: string }
> = {
world: {
label: "World",
active: "bg-blue-500/15 text-blue-700 border-blue-400 dark:text-blue-300 dark:border-blue-500",
inactive:
"border-border text-muted-foreground hover:border-blue-300 hover:text-blue-600 dark:hover:text-blue-400",
dot: "bg-blue-500",
},
experience: {
label: "Experience",
active:
"bg-emerald-500/15 text-emerald-700 border-emerald-400 dark:text-emerald-300 dark:border-emerald-500",
inactive:
"border-border text-muted-foreground hover:border-emerald-300 hover:text-emerald-600 dark:hover:text-emerald-400",
dot: "bg-emerald-500",
},
observation: {
label: "Observation",
active:
"bg-amber-500/15 text-amber-700 border-amber-400 dark:text-amber-300 dark:border-amber-500",
inactive:
"border-border text-muted-foreground hover:border-amber-300 hover:text-amber-600 dark:hover:text-amber-400",
dot: "bg-amber-500",
},
};
function FactTypePill({
ft,
active,
onToggle,
}: {
ft: FactType;
active: boolean;
onToggle: () => void;
}) {
const cfg = FACT_TYPE_CONFIG[ft];
return (
<button
type="button"
onClick={onToggle}
className={cn(
"inline-flex items-center gap-1.5 rounded-full border px-3 py-1 text-xs font-medium transition-all",
active ? cfg.active : cfg.inactive
)}
>
<span
className={cn("h-1.5 w-1.5 rounded-full", active ? cfg.dot : "bg-muted-foreground/50")}
/>
{cfg.label}
</button>
);
}
/**
* Inline pill-toggle fact-type filter for filter bars.
* An empty selection means "all types included".
*/
export function FactTypeFilter({
value,
onChange,
label = "Fact types:",
}: {
value: FactType[];
onChange: (next: FactType[]) => void;
label?: string;
}) {
const toggle = (ft: FactType) =>
onChange(value.includes(ft) ? value.filter((f) => f !== ft) : [...value, ft]);
return (
<div className="flex items-center gap-2">
{label && <span className="text-sm font-medium text-muted-foreground">{label}</span>}
<div className="flex gap-1.5">
{ALL_FACT_TYPES.map((ft) => (
<FactTypePill key={ft} ft={ft} active={value.includes(ft)} onToggle={() => toggle(ft)} />
))}
</div>
</div>
);
}
/**
* Pill-toggle group for use inside forms/dialogs.
*/
export function FactTypeCheckboxGroup({
value,
onChange,
}: {
value: FactType[];
onChange: (next: FactType[]) => void;
}) {
const toggle = (ft: FactType) =>
onChange(value.includes(ft) ? value.filter((f) => f !== ft) : [...value, ft]);
return (
<div className="flex flex-wrap gap-1.5">
{ALL_FACT_TYPES.map((ft) => (
<FactTypePill key={ft} ft={ft} active={value.includes(ft)} onToggle={() => toggle(ft)} />
))}
</div>
);
}
@@ -8,8 +8,6 @@ import { useBank } from "@/lib/bank-context";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Checkbox } from "@/components/ui/checkbox";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { FactType, FactTypeCheckboxGroup } from "@/components/fact-type-filter";
import { toast } from "sonner";
import { Card, CardContent } from "@/components/ui/card";
import {
@@ -88,9 +86,6 @@ interface MentalModel {
max_tokens: number;
trigger: {
refresh_after_consolidation: boolean;
fact_types?: Array<"world" | "experience" | "observation">;
exclude_mental_models?: boolean;
exclude_mental_model_ids?: string[];
};
last_refreshed_at: string;
created_at: string;
@@ -598,9 +593,6 @@ function CreateMentalModelDialog({
maxTokens: "2048",
tags: "",
autoRefresh: false,
factTypes: [] as Array<"world" | "experience" | "observation">,
excludeMentalModels: false,
excludeMentalModelIds: "",
});
const handleCreate = async () => {
@@ -616,23 +608,13 @@ function CreateMentalModelDialog({
const maxTokens = parseInt(form.maxTokens) || 2048;
// Submit mental model creation - content will be generated in background
const excludeIds = form.excludeMentalModelIds
.split(",")
.map((s) => s.trim())
.filter((s) => s.length > 0);
await client.createMentalModel(currentBank, {
id: form.id.trim() || undefined,
name: form.name.trim(),
source_query: form.sourceQuery.trim(),
tags: tags.length > 0 ? tags : undefined,
max_tokens: maxTokens,
trigger: {
refresh_after_consolidation: form.autoRefresh,
fact_types: form.factTypes.length > 0 ? form.factTypes : undefined,
exclude_mental_models: form.excludeMentalModels || undefined,
exclude_mental_model_ids: excludeIds.length > 0 ? excludeIds : undefined,
},
trigger: { refresh_after_consolidation: form.autoRefresh },
});
setForm({
@@ -642,9 +624,6 @@ function CreateMentalModelDialog({
maxTokens: "2048",
tags: "",
autoRefresh: false,
factTypes: [],
excludeMentalModels: false,
excludeMentalModelIds: "",
});
onCreated();
} catch (error) {
@@ -666,9 +645,6 @@ function CreateMentalModelDialog({
maxTokens: "2048",
tags: "",
autoRefresh: false,
factTypes: [],
excludeMentalModels: false,
excludeMentalModelIds: "",
});
onClose();
}
@@ -683,111 +659,80 @@ function CreateMentalModelDialog({
</DialogDescription>
</DialogHeader>
<Tabs defaultValue="general" className="py-2">
<TabsList className="w-full">
<TabsTrigger value="general" className="flex-1">
General
</TabsTrigger>
<TabsTrigger value="options" className="flex-1">
Options
</TabsTrigger>
</TabsList>
<TabsContent value="general" className="space-y-4 pt-4">
<div className="space-y-2">
<label className="text-sm font-medium text-foreground">ID</label>
<Input
value={form.id}
onChange={(e) => setForm({ ...form, id: e.target.value })}
placeholder="e.g., team-communication"
/>
</div>
<div className="space-y-2">
<label className="text-sm font-medium text-foreground">Name *</label>
<Input
value={form.name}
onChange={(e) => setForm({ ...form, name: e.target.value })}
placeholder="e.g., Team Communication Preferences"
/>
</div>
<div className="space-y-2">
<label className="text-sm font-medium text-foreground">Source Query *</label>
<Input
value={form.sourceQuery}
onChange={(e) => setForm({ ...form, sourceQuery: e.target.value })}
placeholder="e.g., How does the team prefer to communicate?"
/>
</div>
<div className="space-y-2">
<label className="text-sm font-medium text-foreground">Max Tokens</label>
<Input
type="number"
value={form.maxTokens}
onChange={(e) => setForm({ ...form, maxTokens: e.target.value })}
placeholder="2048"
min="256"
max="8192"
/>
</div>
</TabsContent>
<TabsContent value="options" className="space-y-4 pt-4">
<div className="space-y-2">
<label className="text-sm font-medium text-foreground">Tags</label>
<Input
value={form.tags}
onChange={(e) => setForm({ ...form, tags: e.target.value })}
placeholder="e.g., project-x, team-alpha (comma-separated)"
/>
</div>
<div className="flex items-center space-x-2">
<Checkbox
id="auto-refresh"
checked={form.autoRefresh}
onCheckedChange={(checked) => setForm({ ...form, autoRefresh: checked === true })}
/>
<label
htmlFor="auto-refresh"
className="text-sm font-medium text-foreground cursor-pointer"
>
Auto-refresh after consolidation
</label>
</div>
<div className="space-y-3">
<label className="text-sm font-medium text-foreground">Fact Types</label>
<FactTypeCheckboxGroup
value={form.factTypes}
onChange={(v) => setForm({ ...form, factTypes: v as FactType[] })}
/>
<p className="text-xs text-muted-foreground">Leave empty to include all types.</p>
</div>
<div className="flex items-center space-x-2">
<Checkbox
id="exclude-mental-models"
checked={form.excludeMentalModels}
onCheckedChange={(checked) =>
setForm({ ...form, excludeMentalModels: checked === true })
}
/>
<label
htmlFor="exclude-mental-models"
className="text-sm font-medium text-foreground cursor-pointer"
>
Exclude all mental models
</label>
</div>
<div className="space-y-2">
<label className="text-sm font-medium text-foreground">
Exclude Mental Model IDs
</label>
<Input
value={form.excludeMentalModelIds}
onChange={(e) => setForm({ ...form, excludeMentalModelIds: e.target.value })}
placeholder="e.g., model-a, model-b (comma-separated)"
/>
</div>
</TabsContent>
</Tabs>
<div className="space-y-4 py-4">
<div className="space-y-2">
<label className="text-sm font-medium text-foreground">
ID <span className="text-muted-foreground font-normal">(optional)</span>
</label>
<Input
value={form.id}
onChange={(e) => setForm({ ...form, id: e.target.value })}
placeholder="e.g., team-communication"
/>
<p className="text-xs text-muted-foreground">
Custom ID for the mental model. If not provided, a UUID will be generated.
</p>
</div>
<div className="space-y-2">
<label className="text-sm font-medium text-foreground">Name *</label>
<Input
value={form.name}
onChange={(e) => setForm({ ...form, name: e.target.value })}
placeholder="e.g., Team Communication Preferences"
/>
</div>
<div className="space-y-2">
<label className="text-sm font-medium text-foreground">Source Query *</label>
<Input
value={form.sourceQuery}
onChange={(e) => setForm({ ...form, sourceQuery: e.target.value })}
placeholder="e.g., How does the team prefer to communicate?"
/>
<p className="text-xs text-muted-foreground">
This query will be run to generate the initial content, and re-run when you refresh.
</p>
</div>
<div className="space-y-2">
<label className="text-sm font-medium text-foreground">Max Tokens</label>
<Input
type="number"
value={form.maxTokens}
onChange={(e) => setForm({ ...form, maxTokens: e.target.value })}
placeholder="2048"
min="256"
max="8192"
/>
<p className="text-xs text-muted-foreground">
Maximum tokens for the generated response (256-8192).
</p>
</div>
<div className="space-y-2">
<label className="text-sm font-medium text-foreground">
Tags <span className="text-muted-foreground font-normal">(optional)</span>
</label>
<Input
value={form.tags}
onChange={(e) => setForm({ ...form, tags: e.target.value })}
placeholder="e.g., project-x, team-alpha (comma-separated)"
/>
</div>
<div className="flex items-center space-x-2">
<Checkbox
id="auto-refresh"
checked={form.autoRefresh}
onCheckedChange={(checked) => setForm({ ...form, autoRefresh: checked === true })}
/>
<label
htmlFor="auto-refresh"
className="text-sm font-medium text-foreground cursor-pointer"
>
Auto-refresh after consolidation
</label>
</div>
<p className="text-xs text-muted-foreground -mt-2 ml-6">
Automatically refresh this mental model when memories are consolidated.
</p>
</div>
<DialogFooter>
<Button variant="outline" onClick={onClose} disabled={creating}>
@@ -831,12 +776,6 @@ function UpdateMentalModelDialog({
maxTokens: String(mentalModel.max_tokens || 2048),
tags: mentalModel.tags.join(", "),
autoRefresh: mentalModel.trigger?.refresh_after_consolidation || false,
factTypes:
(mentalModel.trigger?.fact_types as
| Array<"world" | "experience" | "observation">
| undefined) || [],
excludeMentalModels: mentalModel.trigger?.exclude_mental_models || false,
excludeMentalModelIds: (mentalModel.trigger?.exclude_mental_model_ids || []).join(", "),
});
// Reset form when mental model changes or dialog opens
@@ -848,12 +787,6 @@ function UpdateMentalModelDialog({
maxTokens: String(mentalModel.max_tokens || 2048),
tags: mentalModel.tags.join(", "),
autoRefresh: mentalModel.trigger?.refresh_after_consolidation || false,
factTypes:
(mentalModel.trigger?.fact_types as
| Array<"world" | "experience" | "observation">
| undefined) || [],
excludeMentalModels: mentalModel.trigger?.exclude_mental_models || false,
excludeMentalModelIds: (mentalModel.trigger?.exclude_mental_model_ids || []).join(", "),
});
}
}, [open, mentalModel]);
@@ -870,22 +803,12 @@ function UpdateMentalModelDialog({
const maxTokens = parseInt(form.maxTokens) || 2048;
const excludeIds = form.excludeMentalModelIds
.split(",")
.map((s) => s.trim())
.filter((s) => s.length > 0);
const updated = await client.updateMentalModel(currentBank, mentalModel.id, {
name: form.name.trim(),
source_query: form.sourceQuery.trim(),
tags: tags.length > 0 ? tags : undefined,
max_tokens: maxTokens,
trigger: {
refresh_after_consolidation: form.autoRefresh,
fact_types: form.factTypes.length > 0 ? form.factTypes : undefined,
exclude_mental_models: form.excludeMentalModels || undefined,
exclude_mental_model_ids: excludeIds.length > 0 ? excludeIds : undefined,
},
trigger: { refresh_after_consolidation: form.autoRefresh },
});
onUpdated(updated);
@@ -907,107 +830,72 @@ function UpdateMentalModelDialog({
</DialogDescription>
</DialogHeader>
<Tabs defaultValue="general" className="py-2">
<TabsList className="w-full">
<TabsTrigger value="general" className="flex-1">
General
</TabsTrigger>
<TabsTrigger value="options" className="flex-1">
Options
</TabsTrigger>
</TabsList>
<TabsContent value="general" className="space-y-4 pt-4">
<div className="space-y-2">
<label className="text-sm font-medium text-muted-foreground">ID</label>
<Input value={mentalModel.id} disabled className="bg-muted" />
</div>
<div className="space-y-2">
<label className="text-sm font-medium text-foreground">Name *</label>
<Input
value={form.name}
onChange={(e) => setForm({ ...form, name: e.target.value })}
placeholder="e.g., Team Communication Preferences"
/>
</div>
<div className="space-y-2">
<label className="text-sm font-medium text-foreground">Source Query *</label>
<Input
value={form.sourceQuery}
onChange={(e) => setForm({ ...form, sourceQuery: e.target.value })}
placeholder="e.g., How does the team prefer to communicate?"
/>
</div>
<div className="space-y-2">
<label className="text-sm font-medium text-foreground">Max Tokens</label>
<Input
type="number"
value={form.maxTokens}
onChange={(e) => setForm({ ...form, maxTokens: e.target.value })}
placeholder="2048"
min="256"
max="8192"
/>
</div>
</TabsContent>
<TabsContent value="options" className="space-y-4 pt-4">
<div className="space-y-2">
<label className="text-sm font-medium text-foreground">Tags</label>
<Input
value={form.tags}
onChange={(e) => setForm({ ...form, tags: e.target.value })}
placeholder="e.g., project-x, team-alpha (comma-separated)"
/>
</div>
<div className="flex items-center space-x-2">
<Checkbox
id="update-auto-refresh"
checked={form.autoRefresh}
onCheckedChange={(checked) => setForm({ ...form, autoRefresh: checked === true })}
/>
<label
htmlFor="update-auto-refresh"
className="text-sm font-medium text-foreground cursor-pointer"
>
Auto-refresh after consolidation
</label>
</div>
<div className="space-y-3">
<label className="text-sm font-medium text-foreground">Fact Types</label>
<FactTypeCheckboxGroup
value={form.factTypes}
onChange={(v) => setForm({ ...form, factTypes: v as FactType[] })}
/>
<p className="text-xs text-muted-foreground">Leave empty to include all types.</p>
</div>
<div className="flex items-center space-x-2">
<Checkbox
id="update-exclude-mental-models"
checked={form.excludeMentalModels}
onCheckedChange={(checked) =>
setForm({ ...form, excludeMentalModels: checked === true })
}
/>
<label
htmlFor="update-exclude-mental-models"
className="text-sm font-medium text-foreground cursor-pointer"
>
Exclude all mental models
</label>
</div>
<div className="space-y-2">
<label className="text-sm font-medium text-foreground">
Exclude Mental Model IDs
</label>
<Input
value={form.excludeMentalModelIds}
onChange={(e) => setForm({ ...form, excludeMentalModelIds: e.target.value })}
placeholder="e.g., model-a, model-b (comma-separated)"
/>
</div>
</TabsContent>
</Tabs>
<div className="space-y-4 py-4">
<div className="space-y-2">
<label className="text-sm font-medium text-muted-foreground">ID</label>
<Input value={mentalModel.id} disabled className="bg-muted" />
<p className="text-xs text-muted-foreground">ID cannot be changed after creation.</p>
</div>
<div className="space-y-2">
<label className="text-sm font-medium text-foreground">Name *</label>
<Input
value={form.name}
onChange={(e) => setForm({ ...form, name: e.target.value })}
placeholder="e.g., Team Communication Preferences"
/>
</div>
<div className="space-y-2">
<label className="text-sm font-medium text-foreground">Source Query *</label>
<Input
value={form.sourceQuery}
onChange={(e) => setForm({ ...form, sourceQuery: e.target.value })}
placeholder="e.g., How does the team prefer to communicate?"
/>
<p className="text-xs text-muted-foreground">
This query will be run to generate the initial content, and re-run when you refresh.
</p>
</div>
<div className="space-y-2">
<label className="text-sm font-medium text-foreground">Max Tokens</label>
<Input
type="number"
value={form.maxTokens}
onChange={(e) => setForm({ ...form, maxTokens: e.target.value })}
placeholder="2048"
min="256"
max="8192"
/>
<p className="text-xs text-muted-foreground">
Maximum tokens for the generated response (256-8192).
</p>
</div>
<div className="space-y-2">
<label className="text-sm font-medium text-foreground">
Tags <span className="text-muted-foreground font-normal">(optional)</span>
</label>
<Input
value={form.tags}
onChange={(e) => setForm({ ...form, tags: e.target.value })}
placeholder="e.g., project-x, team-alpha (comma-separated)"
/>
</div>
<div className="flex items-center space-x-2">
<Checkbox
id="update-auto-refresh"
checked={form.autoRefresh}
onCheckedChange={(checked) => setForm({ ...form, autoRefresh: checked === true })}
/>
<label
htmlFor="update-auto-refresh"
className="text-sm font-medium text-foreground cursor-pointer"
>
Auto-refresh after consolidation
</label>
</div>
<p className="text-xs text-muted-foreground -mt-2 ml-6">
Automatically refresh this mental model when memories are consolidated.
</p>
</div>
<DialogFooter>
<Button variant="outline" onClick={onClose} disabled={updating}>
@@ -14,7 +14,6 @@ import {
SelectValue,
} from "@/components/ui/select";
import { Checkbox } from "@/components/ui/checkbox";
import { FactType, FactTypeFilter } from "@/components/fact-type-filter";
import { Label } from "@/components/ui/label";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import {
@@ -34,6 +33,7 @@ import JsonView from "react18-json-view";
import "react18-json-view/src/style.css";
import { MemoryDetailPanel } from "./memory-detail-panel";
type FactType = "world" | "experience" | "observation";
type Budget = "low" | "mid" | "high";
type TagsMatch = "any" | "all" | "any_strict" | "all_strict";
type ViewMode = "results" | "trace" | "json";
@@ -157,6 +157,10 @@ export function SearchDebugView() {
}
};
const toggleFactType = (ft: FactType) => {
setFactTypes((prev) => (prev.includes(ft) ? prev.filter((t) => t !== ft) : [...prev, ft]));
};
if (!currentBank) {
return (
<Card className="border-dashed">
@@ -193,7 +197,28 @@ export function SearchDebugView() {
{/* Filters */}
<div className="flex flex-wrap items-center gap-6 mt-4 pt-4 border-t">
<FactTypeFilter value={factTypes} onChange={setFactTypes} label="Types:" />
{/* Fact Types */}
<div className="flex items-center gap-4">
<span className="text-sm font-medium text-muted-foreground">Types:</span>
<div className="flex gap-3">
{(["world", "experience"] as FactType[]).map((ft) => (
<label key={ft} className="flex items-center gap-2 cursor-pointer">
<Checkbox
checked={factTypes.includes(ft)}
onCheckedChange={() => toggleFactType(ft)}
/>
<span className="text-sm capitalize">{ft}</span>
</label>
))}
<label className="flex items-center gap-2 cursor-pointer">
<Checkbox
checked={factTypes.includes("observation")}
onCheckedChange={() => toggleFactType("observation")}
/>
<span className="text-sm">Observations</span>
</label>
</div>
</div>
<div className="h-6 w-px bg-border" />
@@ -13,7 +13,6 @@ import {
SelectValue,
} from "@/components/ui/select";
import { Checkbox } from "@/components/ui/checkbox";
import { FactType, FactTypeFilter } from "@/components/fact-type-filter";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import {
Sparkles,
@@ -52,9 +51,6 @@ export function ThinkView() {
const [loading, setLoading] = useState(false);
const [tags, setTags] = useState("");
const [tagsMatch, setTagsMatch] = useState<TagsMatch>("any");
const [factTypes, setFactTypes] = useState<FactType[]>([]);
const [excludeMentalModels, setExcludeMentalModels] = useState(false);
const [excludeMentalModelIds, setExcludeMentalModelIds] = useState("");
const [feedback, setFeedback] = useState("");
const [feedbackSubmitting, setFeedbackSubmitting] = useState(false);
const [feedbackSubmitted, setFeedbackSubmitted] = useState(false);
@@ -155,11 +151,6 @@ export function ThinkView() {
.map((t) => t.trim())
.filter((t) => t.length > 0);
const excludeIds = excludeMentalModelIds
.split(",")
.map((s) => s.trim())
.filter((s) => s.length > 0);
const data: any = await client.reflect({
bank_id: currentBank,
query,
@@ -168,9 +159,6 @@ export function ThinkView() {
include_facts: includeFacts,
include_tool_calls: includeToolCalls,
...(parsedTags.length > 0 && { tags: parsedTags, tags_match: tagsMatch }),
...(factTypes.length > 0 && { fact_types: factTypes }),
...(excludeMentalModels && { exclude_mental_models: true }),
...(excludeIds.length > 0 && { exclude_mental_model_ids: excludeIds }),
});
setResult(data);
} catch (error) {
@@ -287,29 +275,6 @@ export function ThinkView() {
</SelectContent>
</Select>
</div>
{/* Fact Types & Mental Model Filters */}
<div className="flex flex-wrap items-center gap-6 mt-4 pt-4 border-t">
<FactTypeFilter value={factTypes} onChange={setFactTypes} />
<div className="h-6 w-px bg-border" />
<label className="flex items-center gap-2 cursor-pointer">
<Checkbox
checked={excludeMentalModels}
onCheckedChange={(c) => setExcludeMentalModels(c as boolean)}
/>
<span className="text-sm">Exclude mental models</span>
</label>
<div className="flex items-center gap-2">
<span className="text-sm text-muted-foreground">Exclude IDs:</span>
<Input
type="text"
value={excludeMentalModelIds}
onChange={(e) => setExcludeMentalModelIds(e.target.value)}
placeholder="model-a, model-b"
className="h-8 w-48"
/>
</div>
</div>
</CardContent>
</Card>
+5 -33
View File
@@ -47,12 +47,7 @@ export interface MentalModel {
content: string;
tags: string[];
max_tokens: number;
trigger: {
refresh_after_consolidation: boolean;
fact_types?: Array<"world" | "experience" | "observation">;
exclude_mental_models?: boolean;
exclude_mental_model_ids?: string[];
};
trigger: { refresh_after_consolidation: boolean };
last_refreshed_at: string;
created_at: string;
reflect_response?: any;
@@ -188,9 +183,6 @@ export class ControlPlaneClient {
include_tool_calls?: boolean;
tags?: string[];
tags_match?: "any" | "all" | "any_strict" | "all_strict";
fact_types?: Array<"world" | "experience" | "observation">;
exclude_mental_models?: boolean;
exclude_mental_model_ids?: string[];
}) {
return this.fetchApi("/api/reflect", {
method: "POST",
@@ -765,12 +757,7 @@ export class ControlPlaneClient {
content: string;
tags: string[];
max_tokens: number;
trigger: {
refresh_after_consolidation: boolean;
fact_types?: Array<"world" | "experience" | "observation">;
exclude_mental_models?: boolean;
exclude_mental_model_ids?: string[];
};
trigger: { refresh_after_consolidation: boolean };
last_refreshed_at: string;
created_at: string;
reflect_response?: {
@@ -793,12 +780,7 @@ export class ControlPlaneClient {
source_query: string;
tags?: string[];
max_tokens?: number;
trigger?: {
refresh_after_consolidation: boolean;
fact_types?: Array<"world" | "experience" | "observation">;
exclude_mental_models?: boolean;
exclude_mental_model_ids?: string[];
};
trigger?: { refresh_after_consolidation: boolean };
}
) {
return this.fetchApi<{
@@ -827,12 +809,7 @@ export class ControlPlaneClient {
source_query?: string;
max_tokens?: number;
tags?: string[];
trigger?: {
refresh_after_consolidation: boolean;
fact_types?: Array<"world" | "experience" | "observation">;
exclude_mental_models?: boolean;
exclude_mental_model_ids?: string[];
};
trigger?: { refresh_after_consolidation: boolean };
}
) {
return this.fetchApi<{
@@ -843,12 +820,7 @@ export class ControlPlaneClient {
content: string;
tags: string[];
max_tokens: number;
trigger: {
refresh_after_consolidation: boolean;
fact_types?: Array<"world" | "experience" | "observation">;
exclude_mental_models?: boolean;
exclude_mental_model_ids?: string[];
};
trigger: { refresh_after_consolidation: boolean };
last_refreshed_at: string;
created_at: string;
reflect_response?: {
@@ -1,251 +0,0 @@
---
title: "Give the Only Self-Improving AI Agent (Hermes) a Memory Upgrade It Deserves"
authors: [benfrank241]
date: 2026-03-17
tags: [hermes, agents, python, memory, tutorial, plugin]
image: /img/blog/hermes-agent-memory.png
---
![How to Add Persistent Memory to Hermes Agent](/img/blog/hermes-agent-memory.png)
[Hermes Agent](https://github.com/NousResearch/hermes-agent) is a self-improving AI agent with 40+ tools and a plugin system. Its built-in memory saves to local files. `hindsight-hermes` replaces it with structured fact extraction, entity resolution, and multi-strategy retrieval — via one pip install and three environment variables.
<!-- truncate -->
**TL;DR:**
- Hermes Agent's built-in memory is local file-based — no structure, no retrieval intelligence, no cross-machine sync
- `hindsight-hermes` is a pip-installable plugin that registers Hindsight retain/recall/reflect as native Hermes tools
- One `pip install`, three environment variables, disable the built-in `memory` tool, and you're done
- Works with [Hindsight Cloud](https://ui.hindsight.vectorize.io/signup) (zero infra) or self-hosted
## The problem: good memory, but it could go further
Hermes Agent has memory built in, and it's a reasonable design. The `memory` tool saves durable facts to `~/.hermes/` as persistent files, and the `session_search` tool lets the agent look back through past conversations. It works — the agent can store preferences, recall context, and carry knowledge across sessions.
But there's room to grow:
- **Structure.** Memories are stored as text. There's no entity resolution (connecting "Alice" with "my coworker Alice from engineering"), no relationship tracking, and no temporal awareness beyond session timestamps.
- **Retrieval.** Search is keyword-based. For simple lookups that's fine, but it struggles with questions that use different terminology than what was stored.
- **Locality.** Memories live on disk. Run Hermes on your laptop and your server — two separate brains with no way to share context.
- **Synthesis.** You can store and retrieve facts, but you can't ask "based on everything you know about this customer, what should we prioritize?" and get a reasoned answer.
Hindsight adds the layer on top: structured fact extraction, entity resolution, a knowledge graph, multi-strategy retrieval with cross-encoder reranking, and a `reflect` operation that synthesizes across all stored memories.
## Architecture: a plugin that registers three tools
```
User ──> Hermes Agent
├── Built-in tools (terminal, browser, files, ...)
└── [hindsight] plugin
├── hindsight_retain ──> Hindsight API ──> fact extraction,
├── hindsight_recall ──> │ entity resolution,
└── hindsight_reflect ──> │ knowledge graph,
└──> PostgreSQL + pgvector
```
`hindsight-hermes` hooks into Hermes's [plugin system](https://github.com/NousResearch/hermes-agent/blob/main/hermes_cli/plugins.py). When Hermes starts, it scans for packages with the `hermes_agent.plugins` entry point, finds `hindsight-hermes`, and calls its `register()` function. That registers three tools into Hermes's tool registry.
No forking Hermes. No patching config files. Just `pip install` and environment variables.
## Setting up Hindsight
You have two options: Hindsight Cloud (no setup) or self-hosted.
**Option A: Hindsight Cloud**
1. [Sign up at Hindsight Cloud](https://ui.hindsight.vectorize.io/signup)
2. Create a memory bank in the dashboard and copy your API key
3. Your base URL is `https://api.hindsight.vectorize.io`
**Option B: Self-hosted with Docker**
```bash
docker run --rm -it --pull always -p 8888:8888 -p 9999:9999 \
-e HINDSIGHT_API_LLM_API_KEY=YOUR_OPENAI_KEY \
-v $HOME/.hindsight-docker:/home/hindsight/.pg0 \
ghcr.io/vectorize-io/hindsight:latest
```
Wait for the health check:
```bash
curl http://localhost:8888/health
# {"status":"healthy","database":"connected"}
```
The `-v` flag persists data across container restarts. Port 8888 is the API; port 9999 is the admin UI for browsing memories.
**Option C: Self-hosted with pip**
```bash
pip install hindsight-all
export HINDSIGHT_API_LLM_API_KEY=YOUR_OPENAI_KEY
hindsight-api
```
## Install hindsight-hermes
One pip install. The package auto-registers as a Hermes plugin via Python entry points — no config files, no manual plugin setup. The only requirement is that it's installed in the **same Python environment** as Hermes.
```bash
uv pip install hindsight-hermes --python $HOME/.hermes/hermes-agent/venv/bin/python
```
That's it. When Hermes starts, it discovers the package automatically and registers the three memory tools.
You can verify it's registered:
```bash
python -c "
import importlib.metadata
eps = importlib.metadata.entry_points(group='hermes_agent.plugins')
for ep in eps:
print(f'{ep.name}: {ep.value}')
"
# Expected: hindsight: hindsight_hermes
```
## Configuration
Set these environment variables before launching Hermes:
```bash
# Required — where Hindsight is running
export HINDSIGHT_API_URL=http://localhost:8888
# Required — the memory bank (an isolated "brain" for this agent)
export HINDSIGHT_BANK_ID=my-agent
# Optional — only needed for Hindsight Cloud (https://api.hindsight.vectorize.io)
export HINDSIGHT_API_KEY=hsk_your-key-here
# Optional — recall budget: low (fast), mid (default), high (thorough)
export HINDSIGHT_BUDGET=mid
```
If neither `HINDSIGHT_API_URL` nor `HINDSIGHT_API_KEY` is set, the plugin silently skips registration — Hermes starts normally without the Hindsight tools.
### Disable Hermes's built-in memory
This is the step people miss. Hermes has its own `memory` tool that saves to `~/.hermes/`. If both are active, **the LLM defaults to the built-in one** — it's a single tool it already recognizes. Your Hindsight tools will sit unused.
```bash
hermes tools disable memory
```
This persists across sessions. Re-enable later with `hermes tools enable memory`.
## Using the memory tools
Launch Hermes:
```bash
hermes
```
Type `/tools` to verify. You should see the `[hindsight]` toolset:
```
[hindsight]
* hindsight_recall - Search long-term memory for relevant information.
* hindsight_reflect - Synthesize a thoughtful answer from long-term memories.
* hindsight_retain - Store information to long-term memory for later retrieval.
```
### Retain — store memories
Tell Hermes something to remember:
```
● Remember that my favourite programming language is Rust and I prefer dark mode.
```
You should see `⚡ hindsight` in the response — that confirms it called `hindsight_retain`, not the built-in memory tool.
Under the hood, Hindsight extracts structured facts ("User's favourite programming language is Rust"), resolves entities, generates embeddings, and indexes everything. You don't manage any of that.
### Recall — search memories
```
● What do you know about my programming preferences?
```
Recall runs four retrieval strategies in parallel — semantic search, BM25 keyword matching, entity graph traversal, and temporal filtering — then reranks results with a cross-encoder. This is what makes it work better than string matching over flat files.
### Reflect — synthesize across memories
```
● Based on what you know about me, suggest a colour scheme for my IDE.
```
Reflect doesn't return raw facts. It traverses the knowledge graph, reasons across everything in the bank, and produces a synthesized answer. Slower than recall, but far more useful for open-ended questions.
### Verify via the API
Confirm memories are stored by querying Hindsight directly:
```bash
curl -s http://localhost:8888/v1/default/banks/my-agent/memories/recall \
-H "Content-Type: application/json" \
-d '{"query": "programming preferences", "budget": "low"}' | python3 -m json.tool
```
```json
{
"results": [
{
"text": "User's favourite programming language is Rust.",
"type": "world",
"entities": ["user"]
},
{
"text": "User prefers dark mode in all editors.",
"type": "world",
"entities": ["user"]
}
]
}
```
## Pitfalls and edge cases
1. **Plugin not in `/tools`.** The most common cause: `hindsight-hermes` is installed in a different Python environment than Hermes. Entry points are per-environment. Run `python -c "import importlib.metadata; print(list(importlib.metadata.entry_points(group='hermes_agent.plugins')))"` from the Hermes venv to verify.
2. **LLM picks built-in memory.** Even with the plugin loaded, if both `memory` and `hindsight_retain` exist, the LLM chooses `memory`. Run `hermes tools disable memory`.
3. **Retain is asynchronous.** The API returns immediately; fact extraction happens in the background. If you retain and immediately recall in the same turn, the new facts may not be indexed yet. Design so recall happens on subsequent turns.
4. **Env vars are read once at startup.** Changing `HINDSIGHT_API_URL` or `HINDSIGHT_BANK_ID` after launch has no effect. Restart Hermes to pick up changes.
## Tradeoffs: Hindsight plugin vs. alternatives
| | **Hindsight plugin** | **Built-in memory** |
|---|---|---|
| **Storage** | PostgreSQL + pgvector | Local files (~/.hermes/) |
| **Structure** | Facts, entities, relationships | Raw text |
| **Retrieval** | Semantic + BM25 + graph, reranked | Basic search |
| **Synthesis** | reflect tool | None |
| **Cross-machine** | Yes | No |
| **Setup** | pip install + env vars | Built-in |
**Use the built-in memory** when you want zero setup and basic persistence.
**Use the Hindsight plugin** when you want structured retrieval, entity resolution, and memory that persists across machines.
## Recap
`hindsight-hermes` gives Hermes Agent persistent, structured long-term memory via a pip-installable plugin. No code changes, no config patches.
Hermes's plugin system uses standard Python entry points, so any pip package can register tools. `hindsight-hermes` injects retain, recall, and reflect — backed by Hindsight's multi-strategy retrieval, entity resolution, and knowledge graph.
The key practical detail: disable Hermes's built-in `memory` tool. Otherwise the LLM prefers it and your Hindsight tools go unused.
## Next steps
- **Build up memory over time.** Use Hermes normally — it will retain what matters and recall it when relevant.
- **Try reflect for synthesis.** Ask open-ended questions: "Based on everything you know about me, what kind of projects would I enjoy?"
- **Use per-user banks.** Set `HINDSIGHT_BANK_ID` per user for isolated memory per person.
- **Explore the MCP alternative.** Hermes supports MCP servers natively. You can connect Hindsight's MCP server directly (`http://localhost:8888/mcp`) instead of the plugin — no `hindsight-hermes` package needed. The tradeoff is that the plugin registers tools with Hermes-native schemas, while MCP tools need discovery.
- **Use manual registration for more control.** If you want to set tags, recall filters, or skip the plugin system, `hindsight-hermes` exposes `register_tools()` and `memory_instructions()` functions for direct use.
- **Read the docs.** Full integration reference at [hindsight.vectorize.io/sdks/integrations/hermes](https://hindsight.vectorize.io/sdks/integrations/hermes).
@@ -13,7 +13,6 @@ import CodeSnippet from '@site/src/components/CodeSnippet';
{/* Import raw source files */}
import documentsPy from '!!raw-loader!@site/examples/api/documents.py';
import documentsMjs from '!!raw-loader!@site/examples/api/documents.mjs';
import documentsGo from '!!raw-loader!@site/examples/api/documents.go';
:::tip Prerequisites
Make sure you've completed the [Quick Start](./quickstart) and understand [how retain works](./retain).
@@ -61,9 +60,6 @@ hindsight memory retain my-bank "Meeting notes content..." --doc-id notes-2024-0
hindsight memory retain-files my-bank docs/
```
</TabItem>
<TabItem value="go" label="Go">
<CodeSnippet code={documentsGo} section="document-retain" language="go" />
</TabItem>
</Tabs>
@@ -88,9 +84,6 @@ hindsight memory retain my-bank "Project deadline: March 31" --doc-id project-pl
hindsight memory retain my-bank "Project deadline: April 15 (extended)" --doc-id project-plan
```
</TabItem>
<TabItem value="go" label="Go">
<CodeSnippet code={documentsGo} section="document-update" language="go" />
</TabItem>
</Tabs>
@@ -111,9 +104,6 @@ Retrieve a document's original text and metadata. This is useful for expanding d
hindsight document get my-bank meeting-2024-03-15
```
</TabItem>
<TabItem value="go" label="Go">
<CodeSnippet code={documentsGo} section="document-get" language="go" />
</TabItem>
</Tabs>
@@ -138,9 +128,6 @@ hindsight document update-tags my-bank meeting-2024-03-15 --tags team-a --tags t
hindsight document update-tags my-bank meeting-2024-03-15
```
</TabItem>
<TabItem value="go" label="Go">
<CodeSnippet code={documentsGo} section="document-update" language="go" />
</TabItem>
</Tabs>
@@ -165,9 +152,6 @@ Remove a document and all its associated memories:
hindsight document delete my-bank meeting-2024-03-15
```
</TabItem>
<TabItem value="go" label="Go">
<CodeSnippet code={documentsGo} section="document-delete" language="go" />
</TabItem>
</Tabs>
@@ -199,9 +183,6 @@ hindsight document list my-bank --q report
hindsight document list my-bank --tags team-a --tags team-b
```
</TabItem>
<TabItem value="go" label="Go">
<CodeSnippet code={documentsGo} section="document-list" language="go" />
</TabItem>
</Tabs>
@@ -13,7 +13,6 @@ import CodeSnippet from '@site/src/components/CodeSnippet';
{/* Import raw source files */}
import mainMethodsPy from '!!raw-loader!@site/examples/api/main-methods.py';
import mainMethodsMjs from '!!raw-loader!@site/examples/api/main-methods.mjs';
import mainMethodsGo from '!!raw-loader!@site/examples/api/main-methods.go';
:::tip Prerequisites
Make sure you've [installed Hindsight](../installation) and completed the [Quick Start](./quickstart).
@@ -34,18 +33,15 @@ Store conversations, documents, and facts into a memory bank.
```bash
# Store a single fact
hindsight memory retain my-bank "Alice joined Google in March 2024 as a Senior ML Engineer"
hindsight retain my-bank "Alice joined Google in March 2024 as a Senior ML Engineer"
# Store from a file
hindsight memory retain-files my-bank conversation.txt --context "Daily standup"
hindsight retain my-bank --file conversation.txt --context "Daily standup"
# Store multiple files
hindsight memory retain-files my-bank docs/
hindsight retain my-bank --files docs/*.md
```
</TabItem>
<TabItem value="go" label="Go">
<CodeSnippet code={mainMethodsGo} section="main-retain" language="go" />
</TabItem>
</Tabs>
@@ -70,21 +66,18 @@ Search for relevant memories using multi-strategy retrieval.
```bash
# Basic search
hindsight memory recall my-bank "What does Alice do at Google?"
hindsight recall my-bank "What does Alice do at Google?"
# Search with options
hindsight memory recall my-bank "What happened last spring?" \
hindsight recall my-bank "What happened last spring?" \
--budget high \
--max-tokens 8192 \
--fact-type world,experience
--fact-type world
# Verbose output
hindsight memory recall my-bank "Tell me about Alice" -v
# Verbose output (shows weights and sources)
hindsight recall my-bank "Tell me about Alice" -v
```
</TabItem>
<TabItem value="go" label="Go">
<CodeSnippet code={mainMethodsGo} section="main-recall" language="go" />
</TabItem>
</Tabs>
@@ -109,15 +102,15 @@ Generate disposition-aware responses using memories and observations.
```bash
# Basic reflect
hindsight memory reflect my-bank "Should we adopt TypeScript for our backend?"
hindsight reflect my-bank "Should we adopt TypeScript for our backend?"
# Verbose output (shows sources and observations)
hindsight reflect my-bank "What are Alice's strengths for the team lead role?" -v
# With higher reasoning budget
hindsight memory reflect my-bank "Analyze our tech stack" --budget high
hindsight reflect my-bank "Analyze our tech stack" --budget high
```
</TabItem>
<TabItem value="go" label="Go">
<CodeSnippet code={mainMethodsGo} section="main-reflect" language="go" />
</TabItem>
</Tabs>
@@ -13,12 +13,8 @@ import CodeSnippet from '@site/src/components/CodeSnippet';
{/* Import raw source files */}
import memoryBanksPy from '!!raw-loader!@site/examples/api/memory-banks.py';
import memoryBanksMjs from '!!raw-loader!@site/examples/api/memory-banks.mjs';
import memoryBanksSh from '!!raw-loader!@site/examples/api/memory-banks.sh';
import memoryBanksGo from '!!raw-loader!@site/examples/api/memory-banks.go';
import directivesPy from '!!raw-loader!@site/examples/api/directives.py';
import directivesMjs from '!!raw-loader!@site/examples/api/directives.mjs';
import directivesSh from '!!raw-loader!@site/examples/api/directives.sh';
import directivesGo from '!!raw-loader!@site/examples/api/directives.go';
## What is a Memory Bank?
@@ -48,10 +44,11 @@ Make sure you've completed the [Quick Start](./quickstart) to install the client
<CodeSnippet code={memoryBanksMjs} section="create-bank" language="javascript" />
</TabItem>
<TabItem value="cli" label="CLI">
<CodeSnippet code={memoryBanksSh} section="create-bank" language="bash" />
</TabItem>
<TabItem value="go" label="Go">
<CodeSnippet code={memoryBanksGo} section="create-bank" language="go" />
```bash
hindsight bank create my-bank
```
</TabItem>
</Tabs>
@@ -208,12 +205,6 @@ How skeptical vs trusting the bank is when evaluating claims during `reflect`. S
<TabItem value="node" label="Node.js">
<CodeSnippet code={memoryBanksMjs} section="bank-with-disposition" language="javascript" />
</TabItem>
<TabItem value="cli" label="CLI">
<CodeSnippet code={memoryBanksSh} section="bank-with-disposition" language="bash" />
</TabItem>
<TabItem value="go" label="Go">
<CodeSnippet code={memoryBanksGo} section="bank-with-disposition" language="go" />
</TabItem>
</Tabs>
| Value | Behaviour |
@@ -284,12 +275,6 @@ Bank configuration fields (retain mission, extraction mode, observations mission
<TabItem value="node" label="Node.js">
<CodeSnippet code={memoryBanksMjs} section="update-bank-config" language="javascript" />
</TabItem>
<TabItem value="cli" label="CLI">
<CodeSnippet code={memoryBanksSh} section="update-bank-config" language="bash" />
</TabItem>
<TabItem value="go" label="Go">
<CodeSnippet code={memoryBanksGo} section="update-bank-config" language="go" />
</TabItem>
</Tabs>
You can update any subset of fields — only the keys you provide are changed.
@@ -303,12 +288,6 @@ You can update any subset of fields — only the keys you provide are changed.
<TabItem value="node" label="Node.js">
<CodeSnippet code={memoryBanksMjs} section="get-bank-config" language="javascript" />
</TabItem>
<TabItem value="cli" label="CLI">
<CodeSnippet code={memoryBanksSh} section="get-bank-config" language="bash" />
</TabItem>
<TabItem value="go" label="Go">
<CodeSnippet code={memoryBanksGo} section="get-bank-config" language="go" />
</TabItem>
</Tabs>
The response distinguishes:
@@ -324,12 +303,6 @@ The response distinguishes:
<TabItem value="node" label="Node.js">
<CodeSnippet code={memoryBanksMjs} section="reset-bank-config" language="javascript" />
</TabItem>
<TabItem value="cli" label="CLI">
<CodeSnippet code={memoryBanksSh} section="reset-bank-config" language="bash" />
</TabItem>
<TabItem value="go" label="Go">
<CodeSnippet code={memoryBanksGo} section="reset-bank-config" language="go" />
</TabItem>
</Tabs>
This removes all bank-level overrides. The bank reverts to server-wide defaults (set via environment variables).
@@ -364,12 +337,6 @@ Use directives for rules that must never be violated:
<TabItem value="node" label="Node.js">
<CodeSnippet code={directivesMjs} section="create-directive" language="javascript" />
</TabItem>
<TabItem value="cli" label="CLI">
<CodeSnippet code={directivesSh} section="create-directive" language="bash" />
</TabItem>
<TabItem value="go" label="Go">
<CodeSnippet code={directivesGo} section="create-directive" language="go" />
</TabItem>
</Tabs>
### Listing Directives
@@ -381,12 +348,6 @@ Use directives for rules that must never be violated:
<TabItem value="node" label="Node.js">
<CodeSnippet code={directivesMjs} section="list-directives" language="javascript" />
</TabItem>
<TabItem value="cli" label="CLI">
<CodeSnippet code={directivesSh} section="list-directives" language="bash" />
</TabItem>
<TabItem value="go" label="Go">
<CodeSnippet code={directivesGo} section="list-directives" language="go" />
</TabItem>
</Tabs>
### Updating Directives
@@ -398,12 +359,6 @@ Use directives for rules that must never be violated:
<TabItem value="node" label="Node.js">
<CodeSnippet code={directivesMjs} section="update-directive" language="javascript" />
</TabItem>
<TabItem value="cli" label="CLI">
<CodeSnippet code={directivesSh} section="update-directive" language="bash" />
</TabItem>
<TabItem value="go" label="Go">
<CodeSnippet code={directivesGo} section="update-directive" language="go" />
</TabItem>
</Tabs>
### Deleting Directives
@@ -415,12 +370,6 @@ Use directives for rules that must never be violated:
<TabItem value="node" label="Node.js">
<CodeSnippet code={directivesMjs} section="delete-directive" language="javascript" />
</TabItem>
<TabItem value="cli" label="CLI">
<CodeSnippet code={directivesSh} section="delete-directive" language="bash" />
</TabItem>
<TabItem value="go" label="Go">
<CodeSnippet code={directivesGo} section="delete-directive" language="go" />
</TabItem>
</Tabs>
### Directives vs Disposition
@@ -12,9 +12,6 @@ import CodeSnippet from '@site/src/components/CodeSnippet';
{/* Import raw source files */}
import mentalModelsPy from '!!raw-loader!@site/examples/api/mental-models.py';
import mentalModelsMjs from '!!raw-loader!@site/examples/api/mental-models.mjs';
import mentalModelsSh from '!!raw-loader!@site/examples/api/mental-models.sh';
import mentalModelsGo from '!!raw-loader!@site/examples/api/mental-models.go';
## What Are Mental Models?
@@ -59,14 +56,22 @@ Creating a mental model runs a reflect operation in the background and saves the
<TabItem value="python" label="Python">
<CodeSnippet code={mentalModelsPy} section="create-mental-model" language="python" />
</TabItem>
<TabItem value="node" label="Node.js">
<CodeSnippet code={mentalModelsMjs} section="create-mental-model" language="javascript" />
</TabItem>
<TabItem value="cli" label="CLI">
<CodeSnippet code={mentalModelsSh} section="create-mental-model" language="bash" />
</TabItem>
<TabItem value="go" label="Go">
<CodeSnippet code={mentalModelsGo} section="create-mental-model" language="go" />
```bash
# Create a mental model (async operation)
curl -X POST "http://localhost:8888/v1/default/banks/my-bank/mental-models" \
-H "Content-Type: application/json" \
-d '{
"name": "Team Communication Preferences",
"source_query": "How does the team prefer to communicate?",
"tags": ["team"]
}'
# Response: {"operation_id": "op-123"}
# Use the operations endpoint to check completion
```
</TabItem>
</Tabs>
@@ -76,38 +81,12 @@ Creating a mental model runs a reflect operation in the background and saves the
|-----------|------|----------|-------------|
| `name` | string | Yes | Human-readable name for the mental model |
| `source_query` | string | Yes | The query to run to generate content |
| `id` | string | No | Custom ID for the mental model (alphanumeric lowercase with hyphens). Auto-generated if omitted. |
| `tags` | list | No | Tags for filtering during retrieval |
| `max_tokens` | int | No | Maximum tokens for the mental model content |
| `trigger` | object | No | Trigger settings (see [Automatic Refresh](#automatic-refresh)) |
---
## Create with Custom ID
Assign a stable, human-readable ID to a mental model so you can retrieve or update it by name instead of relying on the auto-generated UUID:
<Tabs>
<TabItem value="python" label="Python">
<CodeSnippet code={mentalModelsPy} section="create-mental-model-with-id" language="python" />
</TabItem>
<TabItem value="node" label="Node.js">
<CodeSnippet code={mentalModelsMjs} section="create-mental-model-with-id" language="javascript" />
</TabItem>
<TabItem value="cli" label="CLI">
<CodeSnippet code={mentalModelsSh} section="create-mental-model-with-id" language="bash" />
</TabItem>
<TabItem value="go" label="Go">
<CodeSnippet code={mentalModelsGo} section="create-mental-model-with-id" language="go" />
</TabItem>
</Tabs>
:::tip
Custom IDs must be lowercase alphanumeric and may contain hyphens (e.g. `team-policies`, `q4-status`). If a mental model with that ID already exists, the request is rejected.
:::
---
## Automatic Refresh
Mental models can be configured to **automatically refresh** when observations are updated. This keeps them in sync with the latest knowledge without manual intervention.
@@ -124,14 +103,19 @@ When `refresh_after_consolidation` is enabled, the mental model will be re-gener
<TabItem value="python" label="Python">
<CodeSnippet code={mentalModelsPy} section="create-mental-model-with-trigger" language="python" />
</TabItem>
<TabItem value="node" label="Node.js">
<CodeSnippet code={mentalModelsMjs} section="create-mental-model-with-trigger" language="javascript" />
</TabItem>
<TabItem value="cli" label="CLI">
<CodeSnippet code={mentalModelsSh} section="create-mental-model-with-trigger" language="bash" />
</TabItem>
<TabItem value="go" label="Go">
<CodeSnippet code={mentalModelsGo} section="create-mental-model-with-trigger" language="go" />
```bash
# Create a mental model with automatic refresh enabled
curl -X POST "http://localhost:8888/v1/default/banks/my-bank/mental-models" \
-H "Content-Type: application/json" \
-d '{
"name": "Project Status",
"source_query": "What is the current project status?",
"trigger": {"refresh_after_consolidation": true}
}'
```
</TabItem>
</Tabs>
@@ -156,14 +140,12 @@ Enable automatic refresh for mental models that need to stay current. Disable it
<TabItem value="python" label="Python">
<CodeSnippet code={mentalModelsPy} section="list-mental-models" language="python" />
</TabItem>
<TabItem value="node" label="Node.js">
<CodeSnippet code={mentalModelsMjs} section="list-mental-models" language="javascript" />
</TabItem>
<TabItem value="cli" label="CLI">
<CodeSnippet code={mentalModelsSh} section="list-mental-models" language="bash" />
</TabItem>
<TabItem value="go" label="Go">
<CodeSnippet code={mentalModelsGo} section="list-mental-models" language="go" />
```bash
curl "http://localhost:8888/v1/default/banks/my-bank/mental-models"
```
</TabItem>
</Tabs>
@@ -175,14 +157,12 @@ Enable automatic refresh for mental models that need to stay current. Disable it
<TabItem value="python" label="Python">
<CodeSnippet code={mentalModelsPy} section="get-mental-model" language="python" />
</TabItem>
<TabItem value="node" label="Node.js">
<CodeSnippet code={mentalModelsMjs} section="get-mental-model" language="javascript" />
</TabItem>
<TabItem value="cli" label="CLI">
<CodeSnippet code={mentalModelsSh} section="get-mental-model" language="bash" />
</TabItem>
<TabItem value="go" label="Go">
<CodeSnippet code={mentalModelsGo} section="get-mental-model" language="go" />
```bash
curl "http://localhost:8888/v1/default/banks/my-bank/mental-models/{mental_model_id}"
```
</TabItem>
</Tabs>
@@ -210,14 +190,12 @@ Re-run the source query to update the mental model with current knowledge:
<TabItem value="python" label="Python">
<CodeSnippet code={mentalModelsPy} section="refresh-mental-model" language="python" />
</TabItem>
<TabItem value="node" label="Node.js">
<CodeSnippet code={mentalModelsMjs} section="refresh-mental-model" language="javascript" />
</TabItem>
<TabItem value="cli" label="CLI">
<CodeSnippet code={mentalModelsSh} section="refresh-mental-model" language="bash" />
</TabItem>
<TabItem value="go" label="Go">
<CodeSnippet code={mentalModelsGo} section="refresh-mental-model" language="go" />
```bash
curl -X POST "http://localhost:8888/v1/default/banks/my-bank/mental-models/{mental_model_id}/refresh"
```
</TabItem>
</Tabs>
@@ -236,14 +214,14 @@ Update the mental model's name:
<TabItem value="python" label="Python">
<CodeSnippet code={mentalModelsPy} section="update-mental-model" language="python" />
</TabItem>
<TabItem value="node" label="Node.js">
<CodeSnippet code={mentalModelsMjs} section="update-mental-model" language="javascript" />
</TabItem>
<TabItem value="cli" label="CLI">
<CodeSnippet code={mentalModelsSh} section="update-mental-model" language="bash" />
</TabItem>
<TabItem value="go" label="Go">
<CodeSnippet code={mentalModelsGo} section="update-mental-model" language="go" />
```bash
curl -X PATCH "http://localhost:8888/v1/default/banks/my-bank/mental-models/{mental_model_id}" \
-H "Content-Type: application/json" \
-d '{"name": "Updated Team Communication Preferences"}'
```
</TabItem>
</Tabs>
@@ -255,14 +233,12 @@ Update the mental model's name:
<TabItem value="python" label="Python">
<CodeSnippet code={mentalModelsPy} section="delete-mental-model" language="python" />
</TabItem>
<TabItem value="node" label="Node.js">
<CodeSnippet code={mentalModelsMjs} section="delete-mental-model" language="javascript" />
</TabItem>
<TabItem value="cli" label="CLI">
<CodeSnippet code={mentalModelsSh} section="delete-mental-model" language="bash" />
</TabItem>
<TabItem value="go" label="Go">
<CodeSnippet code={mentalModelsGo} section="delete-mental-model" language="go" />
```bash
curl -X DELETE "http://localhost:8888/v1/default/banks/my-bank/mental-models/{mental_model_id}"
```
</TabItem>
</Tabs>
@@ -304,15 +280,6 @@ Every time a mental model's content changes (via refresh or manual update), the
<TabItem value="python" label="Python">
<CodeSnippet code={mentalModelsPy} section="get-mental-model-history" language="python" />
</TabItem>
<TabItem value="node" label="Node.js">
<CodeSnippet code={mentalModelsMjs} section="get-mental-model-history" language="javascript" />
</TabItem>
<TabItem value="cli" label="CLI">
<CodeSnippet code={mentalModelsSh} section="get-mental-model-history" language="bash" />
</TabItem>
<TabItem value="go" label="Go">
<CodeSnippet code={mentalModelsGo} section="get-mental-model-history" language="go" />
</TabItem>
</Tabs>
### Response
@@ -15,7 +15,6 @@ import {ClientsGrid, IntegrationsGrid} from '@site/src/components/SupportedGrids
import quickstartPy from '!!raw-loader!@site/examples/api/quickstart.py';
import quickstartMjs from '!!raw-loader!@site/examples/api/quickstart.mjs';
import quickstartSh from '!!raw-loader!@site/examples/api/quickstart.sh';
import quickstartGo from '!!raw-loader!@site/examples/api/quickstart.go';
## Clients
@@ -91,15 +90,6 @@ curl -fsSL https://hindsight.vectorize.io/get-cli | bash
<CodeSnippet code={quickstartSh} section="quickstart-full" language="bash" />
</TabItem>
<TabItem value="go" label="Go">
```bash
go get github.com/vectorize-io/hindsight/hindsight-clients/go
```
<CodeSnippet code={quickstartGo} section="quickstart-full" language="go" />
</TabItem>
</Tabs>
@@ -16,7 +16,6 @@ import CodeSnippet from '@site/src/components/CodeSnippet';
import recallPy from '!!raw-loader!@site/examples/api/recall.py';
import recallMjs from '!!raw-loader!@site/examples/api/recall.mjs';
import recallSh from '!!raw-loader!@site/examples/api/recall.sh';
import recallGo from '!!raw-loader!@site/examples/api/recall.go';
:::info How Recall Works
Learn about the four retrieval strategies (semantic, keyword, graph, temporal) and RRF fusion in the [Recall Architecture](/developer/retrieval) guide.
@@ -38,9 +37,6 @@ Make sure you've completed the [Quick Start](./quickstart) to install the client
<TabItem value="cli" label="CLI">
<CodeSnippet code={recallSh} section="recall-basic" language="bash" />
</TabItem>
<TabItem value="go" label="Go">
<CodeSnippet code={recallGo} section="recall-basic" language="go" />
</TabItem>
</Tabs>
---
@@ -63,19 +59,9 @@ Each type runs the full four-strategy retrieval pipeline independently, so narro
<CodeSnippet code={recallPy} section="recall-experience-only" language="python" />
<CodeSnippet code={recallPy} section="recall-observations-only" language="python" />
</TabItem>
<TabItem value="node" label="Node.js">
<CodeSnippet code={recallMjs} section="recall-world-only" language="javascript" />
<CodeSnippet code={recallMjs} section="recall-experience-only" language="javascript" />
<CodeSnippet code={recallMjs} section="recall-observations-only" language="javascript" />
</TabItem>
<TabItem value="cli" label="CLI">
<CodeSnippet code={recallSh} section="recall-fact-type" language="bash" />
</TabItem>
<TabItem value="go" label="Go">
<CodeSnippet code={recallGo} section="recall-world-only" language="go" />
<CodeSnippet code={recallGo} section="recall-experience-only" language="go" />
<CodeSnippet code={recallGo} section="recall-observations-only" language="go" />
</TabItem>
</Tabs>
:::tip About Observations
@@ -93,12 +79,6 @@ Controls retrieval depth and breadth. Accepted values are `low`, `mid` (default)
<TabItem value="node" label="Node.js">
<CodeSnippet code={recallMjs} section="recall-budget-levels" language="javascript" />
</TabItem>
<TabItem value="cli" label="CLI">
<CodeSnippet code={recallSh} section="recall-budget-levels" language="bash" />
</TabItem>
<TabItem value="go" label="Go">
<CodeSnippet code={recallGo} section="recall-budget-levels" language="go" />
</TabItem>
</Tabs>
### max_tokens
@@ -109,15 +89,6 @@ The maximum number of tokens the returned facts can collectively occupy. Default
<TabItem value="python" label="Python">
<CodeSnippet code={recallPy} section="recall-token-budget" language="python" />
</TabItem>
<TabItem value="node" label="Node.js">
<CodeSnippet code={recallMjs} section="recall-token-budget" language="javascript" />
</TabItem>
<TabItem value="cli" label="CLI">
<CodeSnippet code={recallSh} section="recall-token-budget" language="bash" />
</TabItem>
<TabItem value="go" label="Go">
<CodeSnippet code={recallGo} section="recall-token-budget" language="go" />
</TabItem>
</Tabs>
### query_timestamp
@@ -147,12 +118,6 @@ When enabled and `types` includes `observation`, each observation result is acco
<TabItem value="node" label="Node.js">
<CodeSnippet code={recallMjs} section="recall-source-facts" language="javascript" />
</TabItem>
<TabItem value="cli" label="CLI">
<CodeSnippet code={recallSh} section="recall-source-facts" language="bash" />
</TabItem>
<TabItem value="go" label="Go">
<CodeSnippet code={recallGo} section="recall-source-facts" language="go" />
</TabItem>
</Tabs>
#### entities
@@ -187,20 +152,7 @@ Consider a bank with these four memories:
Returns memories that have **at least one** matching tag, plus untagged memories.
<Tabs>
<TabItem value="python" label="Python">
<CodeSnippet code={recallPy} section="recall-tags-any" language="python" />
</TabItem>
<TabItem value="node" label="Node.js">
<CodeSnippet code={recallMjs} section="recall-tags-any" language="javascript" />
</TabItem>
<TabItem value="cli" label="CLI">
<CodeSnippet code={recallSh} section="recall-tags-any" language="bash" />
</TabItem>
<TabItem value="go" label="Go">
<CodeSnippet code={recallGo} section="recall-with-tags" language="go" />
</TabItem>
</Tabs>
Use this for **shared global knowledge + user-specific** patterns, where untagged memories represent information everyone should see.
@@ -208,20 +160,7 @@ Use this for **shared global knowledge + user-specific** patterns, where untagge
Same as `any` but untagged memories are excluded.
<Tabs>
<TabItem value="python" label="Python">
<CodeSnippet code={recallPy} section="recall-tags-any-strict" language="python" />
</TabItem>
<TabItem value="node" label="Node.js">
<CodeSnippet code={recallMjs} section="recall-tags-any-strict" language="javascript" />
</TabItem>
<TabItem value="cli" label="CLI">
<CodeSnippet code={recallSh} section="recall-tags-any-strict" language="bash" />
</TabItem>
<TabItem value="go" label="Go">
<CodeSnippet code={recallGo} section="recall-tags-strict" language="go" />
</TabItem>
</Tabs>
Use this when memories are **fully partitioned by tags** and untagged memories should never be visible.
@@ -229,20 +168,7 @@ Use this when memories are **fully partitioned by tags** and untagged memories s
Returns memories that have **every** specified tag, plus untagged memories.
<Tabs>
<TabItem value="python" label="Python">
<CodeSnippet code={recallPy} section="recall-tags-all-mode" language="python" />
</TabItem>
<TabItem value="node" label="Node.js">
<CodeSnippet code={recallMjs} section="recall-tags-all-mode" language="javascript" />
</TabItem>
<TabItem value="cli" label="CLI">
<CodeSnippet code={recallSh} section="recall-tags-all-mode" language="bash" />
</TabItem>
<TabItem value="go" label="Go">
<CodeSnippet code={recallGo} section="recall-tags-all-mode" language="go" />
</TabItem>
</Tabs>
Use this when memories must belong to a **specific intersection** of scopes (e.g., only memories relevant to both a user and a project), while still surfacing shared global knowledge.
@@ -250,20 +176,7 @@ Use this when memories must belong to a **specific intersection** of scopes (e.g
Returns memories that have **every** specified tag, and excludes untagged memories.
<Tabs>
<TabItem value="python" label="Python">
<CodeSnippet code={recallPy} section="recall-tags-all-strict" language="python" />
</TabItem>
<TabItem value="node" label="Node.js">
<CodeSnippet code={recallMjs} section="recall-tags-all-strict" language="javascript" />
</TabItem>
<TabItem value="cli" label="CLI">
<CodeSnippet code={recallSh} section="recall-tags-all-strict" language="bash" />
</TabItem>
<TabItem value="go" label="Go">
<CodeSnippet code={recallGo} section="recall-tags-all" language="go" />
</TabItem>
</Tabs>
Use this for strict scope enforcement where a memory must explicitly belong to **all** specified contexts.
@@ -16,7 +16,6 @@ import CodeSnippet from '@site/src/components/CodeSnippet';
import reflectPy from '!!raw-loader!@site/examples/api/reflect.py';
import reflectMjs from '!!raw-loader!@site/examples/api/reflect.mjs';
import reflectSh from '!!raw-loader!@site/examples/api/reflect.sh';
import reflectGo from '!!raw-loader!@site/examples/api/reflect.go';
:::info How Reflect Works
Learn about disposition-driven reasoning in the [Reflect Architecture](/developer/reflect) guide.
@@ -38,9 +37,6 @@ Make sure you've completed the [Quick Start](./quickstart) to install the client
<TabItem value="cli" label="CLI">
<CodeSnippet code={reflectSh} section="reflect-basic" language="bash" />
</TabItem>
<TabItem value="go" label="Go">
<CodeSnippet code={reflectGo} section="reflect-basic" language="go" />
</TabItem>
</Tabs>
---
@@ -62,12 +58,6 @@ Controls how thoroughly the agent explores the memory bank before answering. Acc
<TabItem value="node" label="Node.js">
<CodeSnippet code={reflectMjs} section="reflect-with-params" language="javascript" />
</TabItem>
<TabItem value="cli" label="CLI">
<CodeSnippet code={reflectSh} section="reflect-with-params" language="bash" />
</TabItem>
<TabItem value="go" label="Go">
<CodeSnippet code={reflectGo} section="reflect-with-params" language="go" />
</TabItem>
</Tabs>
### max_tokens
@@ -88,9 +78,6 @@ An optional JSON Schema object. When provided, the LLM generates a response that
<TabItem value="cli" label="CLI">
<CodeSnippet code={reflectSh} section="reflect-structured-output" language="bash" />
</TabItem>
<TabItem value="go" label="Go">
<CodeSnippet code={reflectGo} section="reflect-structured-output" language="go" />
</TabItem>
</Tabs>
### tags
@@ -101,15 +88,6 @@ Filters which memories the agent can access during reflection. Works identically
<TabItem value="python" label="Python">
<CodeSnippet code={reflectPy} section="reflect-with-tags" language="python" />
</TabItem>
<TabItem value="node" label="Node.js">
<CodeSnippet code={reflectMjs} section="reflect-with-tags" language="javascript" />
</TabItem>
<TabItem value="cli" label="CLI">
<CodeSnippet code={reflectSh} section="reflect-with-tags" language="bash" />
</TabItem>
<TabItem value="go" label="Go">
<CodeSnippet code={reflectGo} section="reflect-with-tags" language="go" />
</TabItem>
</Tabs>
### include
@@ -124,15 +102,6 @@ When enabled, the response includes a `based_on` object listing the memories, me
<TabItem value="python" label="Python">
<CodeSnippet code={reflectPy} section="reflect-sources" language="python" />
</TabItem>
<TabItem value="node" label="Node.js">
<CodeSnippet code={reflectMjs} section="reflect-sources" language="javascript" />
</TabItem>
<TabItem value="cli" label="CLI">
<CodeSnippet code={reflectSh} section="reflect-sources" language="bash" />
</TabItem>
<TabItem value="go" label="Go">
<CodeSnippet code={reflectGo} section="reflect-sources" language="go" />
</TabItem>
</Tabs>
#### include.tool_calls
+6 -40
View File
@@ -16,7 +16,6 @@ import CodeSnippet from '@site/src/components/CodeSnippet';
import retainPy from '!!raw-loader!@site/examples/api/retain.py';
import retainMjs from '!!raw-loader!@site/examples/api/retain.mjs';
import retainSh from '!!raw-loader!@site/examples/api/retain.sh';
import retainGo from '!!raw-loader!@site/examples/api/retain.go';
:::info How Retain Works
Learn about fact extraction, entity resolution, and graph construction in the [Retain Architecture](/developer/retain) guide.
@@ -40,9 +39,6 @@ A single retain call accepts one or more **items**. Each item is a piece of raw
<TabItem value="cli" label="CLI">
<CodeSnippet code={retainSh} section="retain-basic" language="bash" />
</TabItem>
<TabItem value="go" label="Go">
<CodeSnippet code={retainGo} section="retain-basic" language="go" />
</TabItem>
</Tabs>
### Retaining a Conversation
@@ -56,12 +52,6 @@ A full conversation should be retained as a single item. The LLM can parse any f
<TabItem value="node" label="Node.js">
<CodeSnippet code={retainMjs} section="retain-conversation" language="javascript" />
</TabItem>
<TabItem value="cli" label="CLI">
<CodeSnippet code={retainSh} section="retain-conversation" language="bash" />
</TabItem>
<TabItem value="go" label="Go">
<CodeSnippet code={retainGo} section="retain-conversation" language="go" />
</TabItem>
</Tabs>
When the conversation grows — a new message arrives — just retain again with the full updated content and the same `document_id`. Hindsight will delete the previous version and reprocess from scratch, so memories always reflect the latest state of the conversation.
@@ -102,9 +92,6 @@ Providing context consistently is one of the highest-leverage things you can do
<TabItem value="cli" label="CLI">
<CodeSnippet code={retainSh} section="retain-with-context" language="bash" />
</TabItem>
<TabItem value="go" label="Go">
<CodeSnippet code={retainGo} section="retain-with-context" language="go" />
</TabItem>
</Tabs>
### metadata
@@ -216,12 +203,6 @@ Multiple items can be submitted in a single request. Batch ingestion is the reco
<TabItem value="node" label="Node.js">
<CodeSnippet code={retainMjs} section="retain-batch" language="javascript" />
</TabItem>
<TabItem value="cli" label="CLI">
<CodeSnippet code={retainSh} section="retain-batch" language="bash" />
</TabItem>
<TabItem value="go" label="Go">
<CodeSnippet code={retainGo} section="retain-batch" language="go" />
</TabItem>
</Tabs>
@@ -234,18 +215,18 @@ Upload files directly — Hindsight converts them to text and extracts memories
**Supported formats:** PDF, DOCX, DOC, PPTX, PPT, XLSX, XLS, images (JPG, PNG, GIF, etc. — OCR), audio (MP3, WAV, FLAC, etc. — transcription), HTML, and plain text formats (TXT, MD, CSV, JSON, YAML, etc.)
<Tabs>
<TabItem value="cli" label="CLI">
<CodeSnippet code={retainSh} section="retain-files" language="bash" />
</TabItem>
<TabItem value="curl" label="HTTP">
<CodeSnippet code={retainSh} section="retain-files-curl" language="bash" />
</TabItem>
<TabItem value="python" label="Python">
<CodeSnippet code={retainPy} section="retain-files" language="python" />
</TabItem>
<TabItem value="node" label="Node.js">
<CodeSnippet code={retainMjs} section="retain-files" language="javascript" />
</TabItem>
<TabItem value="cli" label="CLI">
<CodeSnippet code={retainSh} section="retain-files" language="bash" />
</TabItem>
<TabItem value="go" label="Go">
<CodeSnippet code={retainGo} section="retain-files" language="go" />
</TabItem>
</Tabs>
The file retain endpoint always returns asynchronously. The response contains `operation_ids` — one per uploaded file — which you can poll via `GET /v1/default/banks/{bank_id}/operations` to track progress.
@@ -256,15 +237,6 @@ Upload up to 10 files per request (max 100 MB total). Each file becomes a separa
<TabItem value="python" label="Python">
<CodeSnippet code={retainPy} section="retain-files-batch" language="python" />
</TabItem>
<TabItem value="node" label="Node.js">
<CodeSnippet code={retainMjs} section="retain-files-batch" language="javascript" />
</TabItem>
<TabItem value="cli" label="CLI">
<CodeSnippet code={retainSh} section="retain-files" language="bash" />
</TabItem>
<TabItem value="go" label="Go">
<CodeSnippet code={retainGo} section="retain-files" language="go" />
</TabItem>
</Tabs>
:::info File Storage
@@ -284,12 +256,6 @@ For large batches, use async ingestion to avoid blocking your application:
<TabItem value="node" label="Node.js">
<CodeSnippet code={retainMjs} section="retain-async" language="javascript" />
</TabItem>
<TabItem value="cli" label="CLI">
<CodeSnippet code={retainSh} section="retain-async" language="bash" />
</TabItem>
<TabItem value="go" label="Go">
<CodeSnippet code={retainGo} section="retain-async" language="go" />
</TabItem>
</Tabs>
When `async: true`, the call returns immediately with an `operation_id`. Processing runs in the background via the worker service. No `usage` metrics are returned for async operations.
-86
View File
@@ -1,86 +0,0 @@
package main
import (
"context"
"fmt"
"net/http"
"os"
hindsight "github.com/vectorize-io/hindsight/hindsight-clients/go"
)
const bankID = "directives-example-bank"
func main() {
apiURL := os.Getenv("HINDSIGHT_API_URL")
if apiURL == "" {
apiURL = "http://localhost:8888"
}
cfg := hindsight.NewConfiguration()
cfg.Servers = hindsight.ServerConfigurations{{URL: apiURL}}
client := hindsight.NewAPIClient(cfg)
ctx := context.Background()
// =============================================================================
// Setup (not shown in docs)
// =============================================================================
client.BanksAPI.CreateOrUpdateBank(ctx, bankID).
CreateBankRequest(hindsight.CreateBankRequest{
Name: *hindsight.NewNullableString(hindsight.PtrString("Test Bank")),
}).Execute()
// =============================================================================
// Doc Examples
// =============================================================================
// [docs:create-directive]
// Create a directive (hard rule for reflect)
directive, _, _ := client.DirectivesAPI.CreateDirective(ctx, bankID).
CreateDirectiveRequest(hindsight.CreateDirectiveRequest{
Name: "Formal Language",
Content: "Always respond in formal English, avoiding slang and colloquialisms.",
}).Execute()
fmt.Printf("Created directive: %s\n", directive.GetId())
// [/docs:create-directive]
directiveID := directive.GetId()
// [docs:list-directives]
// List all directives in a bank
directives, _, _ := client.DirectivesAPI.ListDirectives(ctx, bankID).Execute()
for _, d := range directives.GetItems() {
content := d.GetContent()
if len(content) > 50 {
content = content[:50]
}
fmt.Printf("- %s: %s...\n", d.GetName(), content)
}
// [/docs:list-directives]
// [docs:update-directive]
// Update a directive (e.g., disable without deleting)
isActiveFalse := false
updated, _, _ := client.DirectivesAPI.UpdateDirective(ctx, bankID, directiveID).
UpdateDirectiveRequest(hindsight.UpdateDirectiveRequest{
IsActive: *hindsight.NewNullableBool(&isActiveFalse),
}).Execute()
fmt.Printf("Directive active: %v\n", updated.GetIsActive())
// [/docs:update-directive]
// [docs:delete-directive]
// Delete a directive
client.DirectivesAPI.DeleteDirective(ctx, bankID, directiveID).Execute()
// [/docs:delete-directive]
// =============================================================================
// Cleanup (not shown in docs)
// =============================================================================
req, _ := http.NewRequest("DELETE", fmt.Sprintf("%s/v1/default/banks/%s", apiURL, bankID), nil)
http.DefaultClient.Do(req)
fmt.Println("directives.go: All examples passed")
}
-51
View File
@@ -1,51 +0,0 @@
#!/bin/bash
# Directives API examples for Hindsight CLI
# Run: bash examples/api/directives.sh
set -e
HINDSIGHT_URL="${HINDSIGHT_API_URL:-http://localhost:8888}"
BANK_ID="directives-example-bank"
# =============================================================================
# Setup (not shown in docs)
# =============================================================================
hindsight bank create "$BANK_ID" --name "Test Bank"
# =============================================================================
# Doc Examples
# =============================================================================
# [docs:create-directive]
# Create a directive (hard rule for reflect)
hindsight directive create "$BANK_ID" \
"Formal Language" \
"Always respond in formal English, avoiding slang and colloquialisms."
# [/docs:create-directive]
# Get the directive ID for subsequent operations
DIRECTIVE_ID=$(hindsight directive list "$BANK_ID" -o json | python3 -c "import sys,json; items=json.load(sys.stdin).get('items',[]); print(items[0]['id'] if items else '')" 2>/dev/null || echo "")
# [docs:list-directives]
# List all directives in a bank
hindsight directive list "$BANK_ID"
# [/docs:list-directives]
if [ -n "$DIRECTIVE_ID" ]; then
# [docs:update-directive]
# Update a directive (e.g., disable without deleting)
hindsight directive update "$BANK_ID" "$DIRECTIVE_ID" --is-active false
# [/docs:update-directive]
# [docs:delete-directive]
# Delete a directive
hindsight directive delete "$BANK_ID" "$DIRECTIVE_ID" -y
# [/docs:delete-directive]
fi
# =============================================================================
# Cleanup (not shown in docs)
# =============================================================================
hindsight bank delete "$BANK_ID" -y
echo "directives.sh: All examples passed"
-94
View File
@@ -1,94 +0,0 @@
package main
import (
"context"
"fmt"
"log"
"net/http"
"os"
hindsight "github.com/vectorize-io/hindsight/hindsight-clients/go"
)
func main() {
apiURL := os.Getenv("HINDSIGHT_API_URL")
if apiURL == "" {
apiURL = "http://localhost:8888"
}
cfg := hindsight.NewConfiguration()
cfg.Servers = hindsight.ServerConfigurations{{URL: apiURL}}
client := hindsight.NewAPIClient(cfg)
ctx := context.Background()
// [docs:document-retain]
// Retain with document ID
docID := "meeting-2024-03-15"
client.MemoryAPI.RetainMemories(ctx, "my-bank").
RetainRequest(hindsight.RetainRequest{
Items: []hindsight.MemoryItem{
{
Content: "Alice presented the Q4 roadmap...",
DocumentId: *hindsight.NewNullableString(&docID),
},
},
}).Execute()
// [/docs:document-retain]
// [docs:document-update]
// Original
planDoc := "project-plan"
client.MemoryAPI.RetainMemories(ctx, "my-bank").
RetainRequest(hindsight.RetainRequest{
Items: []hindsight.MemoryItem{
{
Content: "Project deadline: March 31",
DocumentId: *hindsight.NewNullableString(&planDoc),
},
},
}).Execute()
// Update (deletes old facts, creates new ones)
client.MemoryAPI.RetainMemories(ctx, "my-bank").
RetainRequest(hindsight.RetainRequest{
Items: []hindsight.MemoryItem{
{
Content: "Project deadline: April 15 (extended)",
DocumentId: *hindsight.NewNullableString(&planDoc),
},
},
}).Execute()
// [/docs:document-update]
// [docs:document-get]
doc, _, err := client.DocumentsAPI.GetDocument(ctx, "my-bank", "meeting-2024-03-15").Execute()
if err != nil {
log.Fatalf("Failed to get document: %v", err)
}
fmt.Printf("Document ID: %s\n", doc.GetId())
fmt.Printf("Memory units: %d\n", doc.GetMemoryUnitCount())
// [/docs:document-get]
// [docs:document-delete]
client.DocumentsAPI.DeleteDocument(ctx, "my-bank", "meeting-2024-03-15").Execute()
// [/docs:document-delete]
// [docs:document-list]
// List all documents
docs, _, err := client.DocumentsAPI.ListDocuments(ctx, "my-bank").Execute()
if err != nil {
log.Fatalf("Failed to list documents: %v", err)
}
for _, d := range docs.Items {
id, _ := d["id"].(string)
memCount, _ := d["memory_unit_count"].(float64)
fmt.Printf("%s: %d memories\n", id, int(memCount))
}
// [/docs:document-list]
// Cleanup (not shown in docs)
req, _ := http.NewRequest("DELETE", fmt.Sprintf("%s/v1/default/banks/my-bank", apiURL), nil)
http.DefaultClient.Do(req)
fmt.Println("documents.go: All examples passed")
}
@@ -1,60 +0,0 @@
package main
import (
"context"
"fmt"
"net/http"
"os"
hindsight "github.com/vectorize-io/hindsight/hindsight-clients/go"
)
func main() {
apiURL := os.Getenv("HINDSIGHT_API_URL")
if apiURL == "" {
apiURL = "http://localhost:8888"
}
cfg := hindsight.NewConfiguration()
cfg.Servers = hindsight.ServerConfigurations{{URL: apiURL}}
client := hindsight.NewAPIClient(cfg)
ctx := context.Background()
// [docs:main-retain]
// Store a fact or conversation into a memory bank
client.MemoryAPI.RetainMemories(ctx, "my-bank").
RetainRequest(hindsight.RetainRequest{
Items: []hindsight.MemoryItem{
{Content: "Alice joined Google in March 2024 as a Senior ML Engineer"},
},
}).Execute()
// [/docs:main-retain]
// [docs:main-recall]
// Search for memories using a natural language query
resp, _, _ := client.MemoryAPI.RecallMemories(ctx, "my-bank").
RecallRequest(hindsight.RecallRequest{
Query: "What does Alice do at Google?",
}).Execute()
for _, r := range resp.Results {
fmt.Println(r.Text)
}
// [/docs:main-recall]
// [docs:main-reflect]
// Generate a reasoned response using memories and bank disposition
answer, _, _ := client.MemoryAPI.Reflect(ctx, "my-bank").
ReflectRequest(hindsight.ReflectRequest{
Query: "Should we adopt TypeScript for our backend?",
}).Execute()
fmt.Println(answer.GetText())
// [/docs:main-reflect]
// Cleanup (not shown in docs)
req, _ := http.NewRequest("DELETE", fmt.Sprintf("%s/v1/default/banks/my-bank", apiURL), nil)
http.DefaultClient.Do(req)
fmt.Println("main-methods.go: All examples passed")
}
-114
View File
@@ -1,114 +0,0 @@
package main
import (
"context"
"fmt"
"net/http"
"os"
hindsight "github.com/vectorize-io/hindsight/hindsight-clients/go"
)
func main() {
apiURL := os.Getenv("HINDSIGHT_API_URL")
if apiURL == "" {
apiURL = "http://localhost:8888"
}
cfg := hindsight.NewConfiguration()
cfg.Servers = hindsight.ServerConfigurations{{URL: apiURL}}
client := hindsight.NewAPIClient(cfg)
ctx := context.Background()
// =============================================================================
// Doc Examples
// =============================================================================
// [docs:create-bank]
client.BanksAPI.CreateOrUpdateBank(ctx, "my-bank").
CreateBankRequest(hindsight.CreateBankRequest{}).Execute()
// [/docs:create-bank]
// [docs:bank-with-disposition]
client.BanksAPI.CreateOrUpdateBank(ctx, "architect-bank").
CreateBankRequest(hindsight.CreateBankRequest{
ReflectMission: *hindsight.NewNullableString(hindsight.PtrString(
"You're a senior software architect - keep track of system designs, " +
"technology decisions, and architectural patterns. Prefer simplicity over cutting-edge.",
)),
DispositionSkepticism: *hindsight.NewNullableInt32(hindsight.PtrInt32(4)),
DispositionLiteralism: *hindsight.NewNullableInt32(hindsight.PtrInt32(4)),
DispositionEmpathy: *hindsight.NewNullableInt32(hindsight.PtrInt32(2)),
}).Execute()
// [/docs:bank-with-disposition]
// [docs:bank-background]
client.BanksAPI.CreateOrUpdateBank(ctx, "my-bank").
CreateBankRequest(hindsight.CreateBankRequest{
ReflectMission: *hindsight.NewNullableString(hindsight.PtrString(
"I am a research assistant specializing in machine learning.",
)),
}).Execute()
// [/docs:bank-background]
// [docs:bank-mission]
client.BanksAPI.CreateOrUpdateBank(ctx, "my-bank").
CreateBankRequest(hindsight.CreateBankRequest{
ReflectMission: *hindsight.NewNullableString(hindsight.PtrString(
"You're a senior software architect - keep track of system designs, " +
"technology decisions, and architectural patterns.",
)),
}).Execute()
// [/docs:bank-mission]
// [docs:bank-support-agent]
client.BanksAPI.CreateOrUpdateBank(ctx, "support-bank").
CreateBankRequest(hindsight.CreateBankRequest{}).Execute()
client.BanksAPI.UpdateBankConfig(ctx, "support-bank").
BankConfigUpdate(hindsight.BankConfigUpdate{
Updates: map[string]interface{}{
"observations_mission": "I am a customer support agent. Track customer preferences, " +
"recurring issues, and resolution history to provide consistent, personalized support.",
},
}).Execute()
// [/docs:bank-support-agent]
// [docs:update-bank-config]
client.BanksAPI.UpdateBankConfig(ctx, "my-bank").
BankConfigUpdate(hindsight.BankConfigUpdate{
Updates: map[string]interface{}{
"retain_mission": "Always include technical decisions, API design choices, and architectural trade-offs. " +
"Ignore meeting logistics and social exchanges.",
"retain_extraction_mode": "verbose",
"observations_mission": "Observations are stable facts about people and projects. " +
"Always include preferences, skills, and recurring patterns. Ignore one-off events.",
"disposition_skepticism": 4,
"disposition_literalism": 4,
"disposition_empathy": 2,
},
}).Execute()
// [/docs:update-bank-config]
// [docs:get-bank-config]
// Returns resolved config (server defaults merged with bank overrides) and the raw overrides
result, _, _ := client.BanksAPI.GetBankConfig(ctx, "my-bank").Execute()
// result.Config — full resolved configuration
// result.Overrides — only fields overridden at the bank level
fmt.Println("Config keys:", len(result.GetConfig()))
// [/docs:get-bank-config]
// [docs:reset-bank-config]
// Remove all bank-level overrides, reverting to server defaults
client.BanksAPI.ResetBankConfig(ctx, "my-bank").Execute()
// [/docs:reset-bank-config]
// =============================================================================
// Cleanup (not shown in docs)
// =============================================================================
for _, bankID := range []string{"my-bank", "architect-bank", "support-bank"} {
req, _ := http.NewRequest("DELETE", fmt.Sprintf("%s/v1/default/banks/%s", apiURL, bankID), nil)
http.DefaultClient.Do(req)
}
fmt.Println("memory-banks.go: All examples passed")
}
@@ -1,71 +0,0 @@
#!/bin/bash
# Memory Banks API examples for Hindsight CLI
# Run: bash examples/api/memory-banks.sh
set -e
HINDSIGHT_URL="${HINDSIGHT_API_URL:-http://localhost:8888}"
# =============================================================================
# Doc Examples
# =============================================================================
# [docs:create-bank]
hindsight bank create my-bank
# [/docs:create-bank]
# [docs:bank-with-disposition]
hindsight bank create architect-bank \
--mission "You're a senior software architect - keep track of system designs, technology decisions, and architectural patterns. Prefer simplicity over cutting-edge." \
--skepticism 4 \
--literalism 4 \
--empathy 2
# [/docs:bank-with-disposition]
# [docs:bank-background]
hindsight bank create my-bank \
--mission "I am a research assistant specializing in machine learning."
# [/docs:bank-background]
# [docs:bank-mission]
hindsight bank create my-bank \
--mission "You're a senior software architect - keep track of system designs, technology decisions, and architectural patterns."
# [/docs:bank-mission]
# [docs:bank-support-agent]
hindsight bank create support-bank
hindsight bank set-config support-bank \
--observations-mission "I am a customer support agent. Track customer preferences, recurring issues, and resolution history."
# [/docs:bank-support-agent]
# [docs:update-bank-config]
hindsight bank set-config my-bank \
--retain-mission "Always include technical decisions, API design choices, and architectural trade-offs. Ignore meeting logistics and social exchanges." \
--retain-extraction-mode verbose \
--observations-mission "Observations are stable facts about people and projects. Always include preferences, skills, and recurring patterns. Ignore one-off events." \
--disposition-skepticism 4 \
--disposition-literalism 4 \
--disposition-empathy 2
# [/docs:update-bank-config]
# [docs:get-bank-config]
# Returns resolved config (server defaults merged with bank overrides)
hindsight bank config my-bank
# Show only bank-specific overrides
hindsight bank config my-bank --overrides-only
# [/docs:get-bank-config]
# [docs:reset-bank-config]
# Remove all bank-level overrides, reverting to server defaults
hindsight bank reset-config my-bank -y
# [/docs:reset-bank-config]
# =============================================================================
# Cleanup (not shown in docs)
# =============================================================================
for bank_id in my-bank architect-bank support-bank; do
curl -s -X DELETE "${HINDSIGHT_URL}/v1/default/banks/${bank_id}" > /dev/null
done
echo "memory-banks.sh: All examples passed"
@@ -1,173 +0,0 @@
package main
import (
"context"
"fmt"
"net/http"
"os"
"time"
hindsight "github.com/vectorize-io/hindsight/hindsight-clients/go"
)
const mmBankID = "mental-models-demo-bank"
func main() {
apiURL := os.Getenv("HINDSIGHT_API_URL")
if apiURL == "" {
apiURL = "http://localhost:8888"
}
cfg := hindsight.NewConfiguration()
cfg.Servers = hindsight.ServerConfigurations{{URL: apiURL}}
client := hindsight.NewAPIClient(cfg)
ctx := context.Background()
// =============================================================================
// Setup (not shown in docs)
// =============================================================================
client.BanksAPI.CreateOrUpdateBank(ctx, mmBankID).
CreateBankRequest(hindsight.CreateBankRequest{
Name: *hindsight.NewNullableString(hindsight.PtrString("Mental Models Demo")),
}).Execute()
for _, content := range []string{
"The team prefers async communication via Slack",
"For urgent issues, use the #incidents channel",
"Weekly syncs happen every Monday at 10am",
} {
client.MemoryAPI.RetainMemories(ctx, mmBankID).
RetainRequest(hindsight.RetainRequest{
Items: []hindsight.MemoryItem{{Content: content}},
}).Execute()
}
time.Sleep(2 * time.Second)
// =============================================================================
// Doc Examples
// =============================================================================
// [docs:create-mental-model]
// Create a mental model (runs reflect in background)
result, _, _ := client.MentalModelsAPI.CreateMentalModel(ctx, mmBankID).
CreateMentalModelRequest(hindsight.CreateMentalModelRequest{
Name: "Team Communication Preferences",
SourceQuery: "How does the team prefer to communicate?",
Tags: []string{"team", "communication"},
}).Execute()
// Returns an operation_id — check operations endpoint for completion
fmt.Printf("Operation ID: %s\n", result.GetOperationId())
// [/docs:create-mental-model]
// [docs:create-mental-model-with-id]
// Create a mental model with a specific custom ID
mmID := "communication-policy"
resultWithID, _, _ := client.MentalModelsAPI.CreateMentalModel(ctx, mmBankID).
CreateMentalModelRequest(hindsight.CreateMentalModelRequest{
Id: *hindsight.NewNullableString(&mmID),
Name: "Communication Policy",
SourceQuery: "What are the team's communication guidelines?",
}).Execute()
fmt.Printf("Created with custom ID: %s\n", resultWithID.GetOperationId())
// [/docs:create-mental-model-with-id]
time.Sleep(5 * time.Second)
// [docs:create-mental-model-with-trigger]
// Create a mental model with automatic refresh enabled
refreshTrue := true
result2, _, _ := client.MentalModelsAPI.CreateMentalModel(ctx, mmBankID).
CreateMentalModelRequest(hindsight.CreateMentalModelRequest{
Name: "Project Status",
SourceQuery: "What is the current project status?",
Trigger: &hindsight.MentalModelTrigger{
RefreshAfterConsolidation: &refreshTrue,
},
}).Execute()
// This mental model will automatically refresh when observations are updated
fmt.Printf("Operation ID: %s\n", result2.GetOperationId())
// [/docs:create-mental-model-with-trigger]
time.Sleep(5 * time.Second)
// [docs:list-mental-models]
// List all mental models in a bank
mentalModels, _, _ := client.MentalModelsAPI.ListMentalModels(ctx, mmBankID).Execute()
for _, mm := range mentalModels.GetItems() {
fmt.Printf("- %s: %s\n", mm.GetName(), mm.GetSourceQuery())
}
// [/docs:list-mental-models]
if len(mentalModels.GetItems()) == 0 {
fmt.Println("mental-models.go: All examples passed (no mental models created yet)")
cleanupMentalModels(client, ctx, apiURL)
return
}
mentalModelID := mentalModels.GetItems()[0].GetId()
// [docs:get-mental-model]
// Get a specific mental model
mentalModel, _, _ := client.MentalModelsAPI.GetMentalModel(ctx, mmBankID, mentalModelID).Execute()
fmt.Printf("Name: %s\n", mentalModel.GetName())
fmt.Printf("Content: %s\n", mentalModel.GetContent())
fmt.Printf("Last refreshed: %s\n", mentalModel.GetLastRefreshedAt())
// [/docs:get-mental-model]
// [docs:refresh-mental-model]
// Refresh a mental model to update with current knowledge
refreshResult, _, _ := client.MentalModelsAPI.RefreshMentalModel(ctx, mmBankID, mentalModelID).Execute()
fmt.Printf("Refresh operation ID: %s\n", refreshResult.GetOperationId())
// [/docs:refresh-mental-model]
// [docs:update-mental-model]
// Update a mental model's metadata
newName := "Updated Team Communication Preferences"
refreshAfter := true
updated, _, _ := client.MentalModelsAPI.UpdateMentalModel(ctx, mmBankID, mentalModelID).
UpdateMentalModelRequest(hindsight.UpdateMentalModelRequest{
Name: *hindsight.NewNullableString(&newName),
Trigger: *hindsight.NewNullableMentalModelTrigger(&hindsight.MentalModelTrigger{
RefreshAfterConsolidation: &refreshAfter,
}),
}).Execute()
fmt.Printf("Updated name: %s\n", updated.GetName())
// [/docs:update-mental-model]
// [docs:get-mental-model-history]
// Get the change history of a mental model
history, _, _ := client.MentalModelsAPI.GetMentalModelHistory(ctx, mmBankID, mentalModelID).Execute()
if entries, ok := history.([]interface{}); ok {
for _, entry := range entries {
if e, ok := entry.(map[string]interface{}); ok {
fmt.Printf("Changed at: %v\n", e["changed_at"])
fmt.Printf("Previous content: %v\n", e["previous_content"])
}
}
}
// [/docs:get-mental-model-history]
// [docs:delete-mental-model]
// Delete a mental model
client.MentalModelsAPI.DeleteMentalModel(ctx, mmBankID, mentalModelID).Execute()
// [/docs:delete-mental-model]
// =============================================================================
// Cleanup (not shown in docs)
// =============================================================================
cleanupMentalModels(client, ctx, apiURL)
fmt.Println("mental-models.go: All examples passed")
}
func cleanupMentalModels(client *hindsight.APIClient, ctx context.Context, apiURL string) {
req, _ := http.NewRequest("DELETE", fmt.Sprintf("%s/v1/default/banks/%s", apiURL, mmBankID), nil)
http.DefaultClient.Do(req)
}
@@ -1,129 +0,0 @@
#!/usr/bin/env node
/**
* Mental Models API examples for Hindsight (Node.js)
* Run: node examples/api/mental-models.mjs
*/
import { HindsightClient } from '@vectorize-io/hindsight-client';
const HINDSIGHT_URL = process.env.HINDSIGHT_API_URL || 'http://localhost:8888';
const BANK_ID = 'mental-models-demo-bank';
// =============================================================================
// Setup (not shown in docs)
// =============================================================================
const client = new HindsightClient({ baseUrl: HINDSIGHT_URL });
await client.createBank(BANK_ID, { name: 'Mental Models Demo' });
await client.retain(BANK_ID, 'The team prefers async communication via Slack');
await client.retain(BANK_ID, 'For urgent issues, use the #incidents channel');
await client.retain(BANK_ID, 'Weekly syncs happen every Monday at 10am');
await new Promise(r => setTimeout(r, 2000));
// =============================================================================
// Doc Examples
// =============================================================================
// [docs:create-mental-model]
// Create a mental model (runs reflect in background)
const result = await client.createMentalModel(
BANK_ID,
'Team Communication Preferences',
'How does the team prefer to communicate?',
{ tags: ['team', 'communication'] },
);
// Returns an operation_id — check operations endpoint for completion
console.log(`Operation ID: ${result.operation_id}`);
// [/docs:create-mental-model]
// [docs:create-mental-model-with-id]
// Create a mental model with a specific custom ID
const resultWithId = await client.createMentalModel(
BANK_ID,
'Communication Policy',
"What are the team's communication guidelines?",
{ id: 'communication-policy' },
);
console.log(`Created with custom ID: ${resultWithId.operation_id}`);
// [/docs:create-mental-model-with-id]
await new Promise(r => setTimeout(r, 5000));
// [docs:create-mental-model-with-trigger]
// Create a mental model with automatic refresh enabled
const result2 = await client.createMentalModel(
BANK_ID,
'Project Status',
'What is the current project status?',
{ trigger: { refreshAfterConsolidation: true } },
);
// This mental model will automatically refresh when observations are updated
console.log(`Operation ID: ${result2.operation_id}`);
// [/docs:create-mental-model-with-trigger]
await new Promise(r => setTimeout(r, 5000));
// [docs:list-mental-models]
// List all mental models in a bank
const mentalModels = await client.listMentalModels(BANK_ID);
for (const mm of mentalModels.items) {
console.log(`- ${mm.name}: ${mm.source_query}`);
}
// [/docs:list-mental-models]
const mentalModelId = mentalModels.items[0]?.id;
if (!mentalModelId) {
console.log('mental-models.mjs: All examples passed (no mental models created yet)');
await fetch(`${HINDSIGHT_URL}/v1/default/banks/${BANK_ID}`, { method: 'DELETE' });
process.exit(0);
}
// [docs:get-mental-model]
// Get a specific mental model
const mentalModel = await client.getMentalModel(BANK_ID, mentalModelId);
console.log(`Name: ${mentalModel.name}`);
console.log(`Content: ${mentalModel.content}`);
console.log(`Last refreshed: ${mentalModel.last_refreshed_at}`);
// [/docs:get-mental-model]
// [docs:refresh-mental-model]
// Refresh a mental model to update with current knowledge
const refreshResult = await client.refreshMentalModel(BANK_ID, mentalModelId);
console.log(`Refresh operation ID: ${refreshResult.operation_id}`);
// [/docs:refresh-mental-model]
// [docs:update-mental-model]
// Update a mental model's metadata
const updated = await client.updateMentalModel(BANK_ID, mentalModelId, {
name: 'Updated Team Communication Preferences',
trigger: { refresh_after_consolidation: true },
});
console.log(`Updated name: ${updated.name}`);
// [/docs:update-mental-model]
// [docs:get-mental-model-history]
// Get the change history of a mental model
const history = await client.getMentalModelHistory(BANK_ID, mentalModelId);
for (const entry of history) {
console.log(`Changed at: ${entry.changed_at}`);
console.log(`Previous content: ${entry.previous_content}`);
}
// [/docs:get-mental-model-history]
// [docs:delete-mental-model]
// Delete a mental model
await client.deleteMentalModel(BANK_ID, mentalModelId);
// [/docs:delete-mental-model]
// =============================================================================
// Cleanup (not shown in docs)
// =============================================================================
await client.deleteBank(BANK_ID);
console.log('mental-models.mjs: All examples passed');
@@ -42,18 +42,6 @@ result = client.create_mental_model(
print(f"Operation ID: {result.operation_id}")
# [/docs:create-mental-model]
# [docs:create-mental-model-with-id]
# Create a mental model with a specific custom ID
result_with_id = client.create_mental_model(
bank_id=BANK_ID,
name="Communication Policy",
source_query="What are the team's communication guidelines?",
id="communication-policy"
)
print(f"Created with custom ID: {result_with_id.operation_id}")
# [/docs:create-mental-model-with-id]
# Wait for the mental model to be created
time.sleep(5)
@@ -1,90 +0,0 @@
#!/bin/bash
# Mental Models API examples for Hindsight CLI
# Run: bash examples/api/mental-models.sh
set -e
HINDSIGHT_URL="${HINDSIGHT_API_URL:-http://localhost:8888}"
BANK_ID="mental-models-demo-bank"
# =============================================================================
# Setup (not shown in docs)
# =============================================================================
hindsight bank create "$BANK_ID" --name "Mental Models Demo"
hindsight memory retain "$BANK_ID" "The team prefers async communication via Slack"
hindsight memory retain "$BANK_ID" "For urgent issues, use the #incidents channel"
hindsight memory retain "$BANK_ID" "Weekly syncs happen every Monday at 10am"
sleep 2
# =============================================================================
# Doc Examples
# =============================================================================
# [docs:create-mental-model]
# Create a mental model (runs reflect in background)
hindsight mental-model create "$BANK_ID" \
"Team Communication Preferences" \
"How does the team prefer to communicate?"
# [/docs:create-mental-model]
# [docs:create-mental-model-with-id]
# Create a mental model with a specific custom ID
hindsight mental-model create "$BANK_ID" \
"Communication Policy" \
"What are the team's communication guidelines?" \
--id communication-policy
# [/docs:create-mental-model-with-id]
sleep 5
# [docs:create-mental-model-with-trigger]
# Create a mental model and get its ID for subsequent operations
hindsight mental-model create "$BANK_ID" \
"Project Status" \
"What is the current project status?"
# [/docs:create-mental-model-with-trigger]
sleep 5
# [docs:list-mental-models]
# List all mental models in a bank
hindsight mental-model list "$BANK_ID"
# [/docs:list-mental-models]
# Get the first mental model ID for subsequent examples
MENTAL_MODEL_ID=$(hindsight mental-model list "$BANK_ID" -o json | python3 -c "import sys,json; items=json.load(sys.stdin).get('items',[]); print(items[0]['id'] if items else '')" 2>/dev/null || echo "")
if [ -n "$MENTAL_MODEL_ID" ]; then
# [docs:get-mental-model]
# Get a specific mental model
hindsight mental-model get "$BANK_ID" "$MENTAL_MODEL_ID"
# [/docs:get-mental-model]
# [docs:refresh-mental-model]
# Refresh a mental model to update with current knowledge
hindsight mental-model refresh "$BANK_ID" "$MENTAL_MODEL_ID"
# [/docs:refresh-mental-model]
# [docs:update-mental-model]
# Update a mental model's metadata
hindsight mental-model update "$BANK_ID" "$MENTAL_MODEL_ID" \
--name "Updated Team Communication Preferences"
# [/docs:update-mental-model]
# [docs:get-mental-model-history]
# Get the change history of a mental model
hindsight mental-model history "$BANK_ID" "$MENTAL_MODEL_ID"
# [/docs:get-mental-model-history]
# [docs:delete-mental-model]
# Delete a mental model
hindsight mental-model delete "$BANK_ID" "$MENTAL_MODEL_ID" -y
# [/docs:delete-mental-model]
fi
# =============================================================================
# Cleanup (not shown in docs)
# =============================================================================
curl -s -X DELETE "${HINDSIGHT_URL}/v1/default/banks/${BANK_ID}" > /dev/null
echo "mental-models.sh: All examples passed"
-217
View File
@@ -1,217 +0,0 @@
package main
import (
"context"
"fmt"
"net/http"
"os"
hindsight "github.com/vectorize-io/hindsight/hindsight-clients/go"
)
func main() {
apiURL := os.Getenv("HINDSIGHT_API_URL")
if apiURL == "" {
apiURL = "http://localhost:8888"
}
cfg := hindsight.NewConfiguration()
cfg.Servers = hindsight.ServerConfigurations{{URL: apiURL}}
client := hindsight.NewAPIClient(cfg)
ctx := context.Background()
// =============================================================================
// Setup (not shown in docs)
// =============================================================================
for _, content := range []string{
"Alice works at Google as a software engineer",
"Alice loves hiking on weekends",
"Bob is a data scientist who works with Alice",
} {
client.MemoryAPI.RetainMemories(ctx, "my-bank").
RetainRequest(hindsight.RetainRequest{
Items: []hindsight.MemoryItem{{Content: content}},
}).Execute()
}
// =============================================================================
// Doc Examples
// =============================================================================
// [docs:recall-basic]
response, _, _ := client.MemoryAPI.RecallMemories(ctx, "my-bank").
RecallRequest(hindsight.RecallRequest{
Query: "What does Alice do?",
}).Execute()
// response.Results is a slice of RecallResult, each with:
// - Id: fact ID
// - Text: the extracted fact
// - Type: "world", "experience", or "observation"
// - Context: context label set during retain
// - Tags: []string of tags
// - Entities: []string of entity names linked to this fact
// - OccurredStart: ISO datetime of when the event started
// - OccurredEnd: ISO datetime of when the event ended
// - MentionedAt: ISO datetime of when the fact was retained
// - DocumentId: document this fact belongs to
for _, r := range response.GetResults() {
fmt.Println(r.GetText())
}
// [/docs:recall-basic]
// [docs:recall-with-options]
budgetHigh := hindsight.HIGH
maxTokens := int32(8000)
traceTrue := true
detailedResponse, _, _ := client.MemoryAPI.RecallMemories(ctx, "my-bank").
RecallRequest(hindsight.RecallRequest{
Query: "What does Alice do?",
Types: []string{"world", "experience"},
Budget: &budgetHigh,
MaxTokens: &maxTokens,
Trace: &traceTrue,
}).Execute()
for _, r := range detailedResponse.GetResults() {
fmt.Println("-", r.GetText())
}
// [/docs:recall-with-options]
// [docs:recall-world-only]
// Only world facts (objective information)
client.MemoryAPI.RecallMemories(ctx, "my-bank").
RecallRequest(hindsight.RecallRequest{
Query: "Where does Alice work?",
Types: []string{"world"},
}).Execute()
// [/docs:recall-world-only]
// [docs:recall-experience-only]
// Only experience (conversations and events)
client.MemoryAPI.RecallMemories(ctx, "my-bank").
RecallRequest(hindsight.RecallRequest{
Query: "What have I recommended?",
Types: []string{"experience"},
}).Execute()
// [/docs:recall-experience-only]
// [docs:recall-observations-only]
// Only observations (consolidated knowledge)
client.MemoryAPI.RecallMemories(ctx, "my-bank").
RecallRequest(hindsight.RecallRequest{
Query: "What patterns have I learned?",
Types: []string{"observation"},
}).Execute()
// [/docs:recall-observations-only]
// [docs:recall-source-facts]
// Recall observations and include their source facts
maxSFTokens := int32(4096)
sfOpts := hindsight.SourceFactsIncludeOptions{MaxTokens: &maxSFTokens}
obsResponse, _, _ := client.MemoryAPI.RecallMemories(ctx, "my-bank").
RecallRequest(hindsight.RecallRequest{
Query: "What patterns have I learned about Alice?",
Types: []string{"observation"},
Include: &hindsight.IncludeOptions{
SourceFacts: *hindsight.NewNullableSourceFactsIncludeOptions(&sfOpts),
},
}).Execute()
for _, obs := range obsResponse.GetResults() {
fmt.Printf("Observation: %s\n", obs.GetText())
for _, factID := range obs.GetSourceFactIds() {
if fact, ok := obsResponse.GetSourceFacts()[factID]; ok {
fmt.Printf(" - [%s] %s\n", fact.GetType(), fact.GetText())
}
}
}
// [/docs:recall-source-facts]
// [docs:recall-budget-levels]
budgetLow := hindsight.LOW
// Quick lookup
client.MemoryAPI.RecallMemories(ctx, "my-bank").
RecallRequest(hindsight.RecallRequest{
Query: "Alice's email",
Budget: &budgetLow,
}).Execute()
// Deep exploration
client.MemoryAPI.RecallMemories(ctx, "my-bank").
RecallRequest(hindsight.RecallRequest{
Query: "How are Alice and Bob connected?",
Budget: &budgetHigh,
}).Execute()
// [/docs:recall-budget-levels]
// [docs:recall-token-budget]
// Fill up to 4K tokens of context with relevant memories
mt4k := int32(4096)
client.MemoryAPI.RecallMemories(ctx, "my-bank").
RecallRequest(hindsight.RecallRequest{
Query: "What do I know about Alice?",
MaxTokens: &mt4k,
}).Execute()
// Smaller budget for quick lookups
mt500 := int32(500)
client.MemoryAPI.RecallMemories(ctx, "my-bank").
RecallRequest(hindsight.RecallRequest{
Query: "Alice's email",
MaxTokens: &mt500,
}).Execute()
// [/docs:recall-token-budget]
// [docs:recall-with-tags]
// Filter recall to only memories tagged for a specific user
tagsMatch := "any"
client.MemoryAPI.RecallMemories(ctx, "my-bank").
RecallRequest(hindsight.RecallRequest{
Query: "What feedback did the user give?",
Tags: []string{"user:alice"},
TagsMatch: &tagsMatch,
}).Execute()
// [/docs:recall-with-tags]
// [docs:recall-tags-strict]
// Strict mode: only return memories that have matching tags (exclude untagged)
tagsMatchStrict := "any_strict"
client.MemoryAPI.RecallMemories(ctx, "my-bank").
RecallRequest(hindsight.RecallRequest{
Query: "What did the user say?",
Tags: []string{"user:alice"},
TagsMatch: &tagsMatchStrict,
}).Execute()
// [/docs:recall-tags-strict]
// [docs:recall-tags-all]
// AND matching: require ALL specified tags to be present
tagsMatchAll := "all_strict"
client.MemoryAPI.RecallMemories(ctx, "my-bank").
RecallRequest(hindsight.RecallRequest{
Query: "What bugs were reported?",
Tags: []string{"user:alice", "bug-report"},
TagsMatch: &tagsMatchAll,
}).Execute()
// [/docs:recall-tags-all]
// [docs:recall-tags-all-mode]
// AND matching, includes untagged memories
tagsMatchAllMode := "all"
client.MemoryAPI.RecallMemories(ctx, "my-bank").
RecallRequest(hindsight.RecallRequest{
Query: "communication tools",
Tags: []string{"user:alice", "team"},
TagsMatch: &tagsMatchAllMode,
}).Execute()
// [/docs:recall-tags-all-mode]
// =============================================================================
// Cleanup (not shown in docs)
// =============================================================================
req, _ := http.NewRequest("DELETE", fmt.Sprintf("%s/v1/default/banks/my-bank", apiURL), nil)
http.DefaultClient.Do(req)
fmt.Println("recall.go: All examples passed")
}
-82
View File
@@ -61,88 +61,6 @@ for (const r of detailedResponse.results) {
// [/docs:recall-with-options]
// [docs:recall-world-only]
await client.recall('my-bank', 'query', { types: ['world'] });
// [/docs:recall-world-only]
// [docs:recall-experience-only]
await client.recall('my-bank', 'query', { types: ['experience'] });
// [/docs:recall-experience-only]
// [docs:recall-observations-only]
await client.recall('my-bank', 'query', { types: ['observation'] });
// [/docs:recall-observations-only]
// [docs:recall-token-budget]
// Fill up to 4K tokens of context with relevant memories
await client.recall('my-bank', 'What do I know about Alice?', { maxTokens: 4096 });
// Smaller budget for quick lookups
await client.recall('my-bank', "Alice's email", { maxTokens: 500 });
// [/docs:recall-token-budget]
// [docs:recall-with-tags]
// Filter recall to only memories tagged for a specific user
await client.recall('my-bank', 'What feedback did the user give?', {
tags: ['user:alice']
});
// [/docs:recall-with-tags]
// [docs:recall-tags-strict]
// Strict: only memories that have matching tags (excludes untagged)
await client.recall('my-bank', 'What did the user say?', {
tags: ['user:alice'],
tagsMatch: 'any_strict'
});
// [/docs:recall-tags-strict]
// [docs:recall-tags-all]
// AND matching: require ALL specified tags to be present
await client.recall('my-bank', 'What bugs were reported?', {
tags: ['user:alice', 'bug-report'],
tagsMatch: 'all_strict'
});
// [/docs:recall-tags-all]
// [docs:recall-tags-any]
await client.recall('my-bank', 'communication preferences', {
tags: ['user:alice'],
tagsMatch: 'any'
});
// [/docs:recall-tags-any]
// [docs:recall-tags-any-strict]
await client.recall('my-bank', 'communication preferences', {
tags: ['user:alice'],
tagsMatch: 'any_strict'
});
// [/docs:recall-tags-any-strict]
// [docs:recall-tags-all-mode]
await client.recall('my-bank', 'communication tools', {
tags: ['user:alice', 'team'],
tagsMatch: 'all'
});
// [/docs:recall-tags-all-mode]
// [docs:recall-tags-all-strict]
await client.recall('my-bank', 'communication tools', {
tags: ['user:alice', 'team'],
tagsMatch: 'all_strict'
});
// [/docs:recall-tags-all-strict]
// [docs:recall-source-facts]
// Recall observations and include their source facts
const obsResponse = await client.recall('my-bank', 'What patterns have I learned about Alice?', {
-70
View File
@@ -38,76 +38,6 @@ hindsight memory recall my-bank "query" --trace
# [/docs:recall-trace]
# [docs:recall-budget-levels]
# Quick lookup
hindsight memory recall my-bank "Alice's email" --budget low
# Deep exploration
hindsight memory recall my-bank "How are Alice and Bob connected?" --budget high
# [/docs:recall-budget-levels]
# [docs:recall-token-budget]
# Fill up to 4K tokens of context with relevant memories
hindsight memory recall my-bank "What do I know about Alice?" --max-tokens 4096
# Smaller budget for quick lookups
hindsight memory recall my-bank "Alice's email" --max-tokens 500
# [/docs:recall-token-budget]
# [docs:recall-source-facts]
# Recall observations with source facts
hindsight memory recall my-bank "What patterns have I learned about Alice?" \
--fact-type observation
# [/docs:recall-source-facts]
# [docs:recall-with-tags]
# Filter recall to only memories tagged for a specific user
hindsight memory recall my-bank "What feedback did the user give?" \
--tags "user:alice"
# [/docs:recall-with-tags]
# [docs:recall-tags-strict]
# Strict: only memories that have matching tags (excludes untagged)
hindsight memory recall my-bank "What did the user say?" \
--tags "user:alice" --tags-match any_strict
# [/docs:recall-tags-strict]
# [docs:recall-tags-all]
# AND matching: require ALL specified tags to be present
hindsight memory recall my-bank "What bugs were reported?" \
--tags "user:alice,bug-report" --tags-match all_strict
# [/docs:recall-tags-all]
# [docs:recall-tags-any]
hindsight memory recall my-bank "communication preferences" \
--tags "user:alice" --tags-match any
# [/docs:recall-tags-any]
# [docs:recall-tags-any-strict]
hindsight memory recall my-bank "communication preferences" \
--tags "user:alice" --tags-match any_strict
# [/docs:recall-tags-any-strict]
# [docs:recall-tags-all-mode]
hindsight memory recall my-bank "communication tools" \
--tags "user:alice,team" --tags-match all
# [/docs:recall-tags-all-mode]
# [docs:recall-tags-all-strict]
hindsight memory recall my-bank "communication tools" \
--tags "user:alice,team" --tags-match all_strict
# [/docs:recall-tags-all-strict]
# =============================================================================
# Cleanup (not shown in docs)
# =============================================================================
-155
View File
@@ -1,155 +0,0 @@
package main
import (
"context"
"fmt"
"net/http"
"os"
hindsight "github.com/vectorize-io/hindsight/hindsight-clients/go"
)
func main() {
apiURL := os.Getenv("HINDSIGHT_API_URL")
if apiURL == "" {
apiURL = "http://localhost:8888"
}
cfg := hindsight.NewConfiguration()
cfg.Servers = hindsight.ServerConfigurations{{URL: apiURL}}
client := hindsight.NewAPIClient(cfg)
ctx := context.Background()
// =============================================================================
// Setup (not shown in docs)
// =============================================================================
for _, content := range []string{
"Alice works at Google as a software engineer",
"Alice has been working there for 5 years",
"Alice recently got promoted to senior engineer",
} {
client.MemoryAPI.RetainMemories(ctx, "my-bank").
RetainRequest(hindsight.RetainRequest{
Items: []hindsight.MemoryItem{{Content: content}},
}).Execute()
}
// =============================================================================
// Doc Examples
// =============================================================================
// [docs:reflect-basic]
client.MemoryAPI.Reflect(ctx, "my-bank").
ReflectRequest(hindsight.ReflectRequest{
Query: "What should I know about Alice?",
}).Execute()
// [/docs:reflect-basic]
// [docs:reflect-with-params]
budgetMid := hindsight.MID
client.MemoryAPI.Reflect(ctx, "my-bank").
ReflectRequest(hindsight.ReflectRequest{
Query: "We're considering a hybrid work policy. What do you think about remote work?",
Budget: &budgetMid,
}).Execute()
// [/docs:reflect-with-params]
// [docs:reflect-with-context]
// Context is passed to the LLM to help it understand the situation
ctxText := "We're in a budget review meeting discussing Q4 spending"
client.MemoryAPI.Reflect(ctx, "my-bank").
ReflectRequest(hindsight.ReflectRequest{
Query: "What do you think about the proposal?",
Context: *hindsight.NewNullableString(&ctxText),
}).Execute()
// [/docs:reflect-with-context]
// [docs:reflect-disposition]
// Create a bank with specific disposition
skepticism := int32(5)
literalism := int32(4)
empathy := int32(2)
mission := "I am a risk-aware financial advisor"
client.BanksAPI.CreateOrUpdateBank(ctx, "cautious-advisor").
CreateBankRequest(hindsight.CreateBankRequest{
Name: *hindsight.NewNullableString(hindsight.PtrString("Cautious Advisor")),
ReflectMission: *hindsight.NewNullableString(&mission),
DispositionSkepticism: *hindsight.NewNullableInt32(&skepticism),
DispositionLiteralism: *hindsight.NewNullableInt32(&literalism),
DispositionEmpathy: *hindsight.NewNullableInt32(&empathy),
}).Execute()
// Reflect responses will reflect this disposition
client.MemoryAPI.Reflect(ctx, "cautious-advisor").
ReflectRequest(hindsight.ReflectRequest{
Query: "Should I invest in crypto?",
}).Execute()
// Response will likely emphasize risks and caution
// [/docs:reflect-disposition]
// [docs:reflect-sources]
// include.facts enables the based_on field in the response
sourcesResponse, _, _ := client.MemoryAPI.Reflect(ctx, "my-bank").
ReflectRequest(hindsight.ReflectRequest{
Query: "Tell me about Alice",
Include: &hindsight.ReflectIncludeOptions{
Facts: map[string]interface{}{}, // empty map enables fact inclusion
},
}).Execute()
fmt.Println("Response:", sourcesResponse.GetText())
fmt.Println("\nBased on:")
if basedOn := sourcesResponse.GetBasedOn(); basedOn.Memories != nil {
for _, fact := range basedOn.GetMemories() {
fmt.Printf(" - [%s] %s\n", fact.GetType(), fact.GetText())
}
}
// [/docs:reflect-sources]
// [docs:reflect-with-tags]
// Filter reflection to only consider memories for a specific user
tagsMatch := "any_strict"
client.MemoryAPI.Reflect(ctx, "my-bank").
ReflectRequest(hindsight.ReflectRequest{
Query: "What does this user think about our product?",
Tags: []string{"user:alice"},
TagsMatch: &tagsMatch,
}).Execute()
// [/docs:reflect-with-tags]
// [docs:reflect-structured-output]
// Define JSON schema for structured output
responseSchema := map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"recommendation": map[string]interface{}{"type": "string"},
"confidence": map[string]interface{}{"type": "string", "enum": []string{"low", "medium", "high"}},
"key_factors": map[string]interface{}{"type": "array", "items": map[string]interface{}{"type": "string"}},
"risks": map[string]interface{}{"type": "array", "items": map[string]interface{}{"type": "string"}},
},
"required": []string{"recommendation", "confidence", "key_factors"},
}
structuredResponse, _, _ := client.MemoryAPI.Reflect(ctx, "my-bank").
ReflectRequest(hindsight.ReflectRequest{
Query: "Should we hire Alice for the ML team lead position?",
ResponseSchema: responseSchema,
}).Execute()
// Access structured output
if out := structuredResponse.GetStructuredOutput(); out != nil {
fmt.Println("Recommendation:", out["recommendation"])
fmt.Println("Key factors:", out["key_factors"])
}
// [/docs:reflect-structured-output]
// =============================================================================
// Cleanup (not shown in docs)
// =============================================================================
for _, bankID := range []string{"my-bank", "cautious-advisor"} {
req, _ := http.NewRequest("DELETE", fmt.Sprintf("%s/v1/default/banks/%s", apiURL, bankID), nil)
http.DefaultClient.Do(req)
}
fmt.Println("reflect.go: All examples passed")
}
+2 -13
View File
@@ -60,27 +60,16 @@ const advisorResponse = await client.reflect('cautious-advisor', 'Should I inves
// [docs:reflect-sources]
const sourcesResponse = await client.reflect('my-bank', 'Tell me about Alice', {
includeFacts: true
});
const sourcesResponse = await client.reflect('my-bank', 'Tell me about Alice');
console.log('Response:', sourcesResponse.text);
console.log('\nBased on:');
for (const fact of (sourcesResponse.based_on?.memories || [])) {
for (const fact of sourcesResponse.based_on || []) {
console.log(` - [${fact.type}] ${fact.text}`);
}
// [/docs:reflect-sources]
// [docs:reflect-with-tags]
// Filter reflect to only use memories tagged for a specific user
await client.reflect('my-bank', 'What feedback did the user give?', {
tags: ['user:alice'],
tagsMatch: 'any_strict'
});
// [/docs:reflect-with-tags]
// [docs:reflect-structured-output]
// Define JSON schema directly
const responseSchema = {
+3 -23
View File
@@ -26,29 +26,9 @@ hindsight memory reflect my-bank "Should I learn Python?" --context "career advi
# [/docs:reflect-with-context]
# [docs:reflect-with-params]
hindsight memory reflect my-bank "Summarize my week" --budget high --max-tokens 8192
# [/docs:reflect-with-params]
# [docs:reflect-disposition]
hindsight bank set-config my-bank \
--disposition-skepticism 5 \
--disposition-literalism 4 \
--disposition-empathy 2
hindsight memory reflect my-bank "Should I invest in crypto?"
# [/docs:reflect-disposition]
# [docs:reflect-sources]
hindsight memory reflect my-bank "Tell me about Alice" --include-facts
# [/docs:reflect-sources]
# [docs:reflect-with-tags]
hindsight memory reflect my-bank "What feedback did the user give?" \
--tags "user:alice" --tags-match any_strict
# [/docs:reflect-with-tags]
# [docs:reflect-high-budget]
hindsight memory reflect my-bank "Summarize my week" --budget high
# [/docs:reflect-high-budget]
# [docs:reflect-structured-output]
-137
View File
@@ -1,137 +0,0 @@
package main
import (
"context"
"fmt"
"log"
"net/http"
"os"
hindsight "github.com/vectorize-io/hindsight/hindsight-clients/go"
)
func main() {
apiURL := os.Getenv("HINDSIGHT_API_URL")
if apiURL == "" {
apiURL = "http://localhost:8888"
}
cfg := hindsight.NewConfiguration()
cfg.Servers = hindsight.ServerConfigurations{{URL: apiURL}}
client := hindsight.NewAPIClient(cfg)
ctx := context.Background()
// =============================================================================
// Doc Examples
// =============================================================================
// [docs:retain-basic]
client.MemoryAPI.RetainMemories(ctx, "my-bank").
RetainRequest(hindsight.RetainRequest{
Items: []hindsight.MemoryItem{
{Content: "Alice works at Google as a software engineer"},
},
}).Execute()
// [/docs:retain-basic]
// [docs:retain-conversation]
// Retain an entire conversation as a single document.
conversation := "Alice (2024-03-15T09:00:00Z): Hi Bob! Did you end up going to the doctor last week?\n" +
"Bob (2024-03-15T09:01:00Z): Yes, finally. Turns out I have a mild peanut allergy.\n" +
"Alice (2024-03-15T09:02:00Z): Oh no! Are you okay?\n" +
"Bob (2024-03-15T09:03:00Z): Yeah, nothing serious. Just need to carry an antihistamine.\n" +
"Alice (2024-03-15T09:04:00Z): Good to know. We'll avoid peanuts at the team lunch."
docID := "chat-2024-03-15-alice-bob"
context_ := "team chat"
ts := "2024-03-15T09:04:00Z"
client.MemoryAPI.RetainMemories(ctx, "my-bank").
RetainRequest(hindsight.RetainRequest{
Items: []hindsight.MemoryItem{
{
Content: conversation,
Context: *hindsight.NewNullableString(&context_),
DocumentId: *hindsight.NewNullableString(&docID),
Timestamp: *hindsight.NewNullableTimestamp(&hindsight.Timestamp{
String: &ts,
}),
},
},
}).Execute()
// [/docs:retain-conversation]
// [docs:retain-with-context]
ctxLabel := "career update"
ts2 := "2024-03-15T10:00:00Z"
client.MemoryAPI.RetainMemories(ctx, "my-bank").
RetainRequest(hindsight.RetainRequest{
Items: []hindsight.MemoryItem{
{
Content: "Alice got promoted to senior engineer",
Context: *hindsight.NewNullableString(&ctxLabel),
Timestamp: *hindsight.NewNullableTimestamp(&hindsight.Timestamp{
String: &ts2,
}),
},
},
}).Execute()
// [/docs:retain-with-context]
// [docs:retain-batch]
doc1 := "conversation_001_msg_1"
doc2 := "conversation_001_msg_2"
doc3 := "conversation_001_msg_3"
ctx1 := "career"
ctx2 := "relationship"
client.MemoryAPI.RetainMemories(ctx, "my-bank").
RetainRequest(hindsight.RetainRequest{
Items: []hindsight.MemoryItem{
{Content: "Alice works at Google", Context: *hindsight.NewNullableString(&ctx1), DocumentId: *hindsight.NewNullableString(&doc1)},
{Content: "Bob is a data scientist at Meta", Context: *hindsight.NewNullableString(&ctx1), DocumentId: *hindsight.NewNullableString(&doc2)},
{Content: "Alice and Bob are friends", Context: *hindsight.NewNullableString(&ctx2), DocumentId: *hindsight.NewNullableString(&doc3)},
},
}).Execute()
// [/docs:retain-batch]
// [docs:retain-async]
// Start async ingestion (returns immediately)
asyncTrue := true
largeDoc1 := "large-doc-1"
largeDoc2 := "large-doc-2"
retainResp, _, _ := client.MemoryAPI.RetainMemories(ctx, "my-bank").
RetainRequest(hindsight.RetainRequest{
Items: []hindsight.MemoryItem{
{Content: "Large batch item 1", DocumentId: *hindsight.NewNullableString(&largeDoc1)},
{Content: "Large batch item 2", DocumentId: *hindsight.NewNullableString(&largeDoc2)},
},
Async: &asyncTrue,
}).Execute()
// Check if it was processed asynchronously
fmt.Println("Async:", retainResp.GetAsync())
// [/docs:retain-async]
// [docs:retain-files]
// Open a file and upload it — Hindsight converts it to text and extracts memories.
// Supports: PDF, DOCX, PPTX, XLSX, images (OCR), audio (transcription), and text formats.
f, err := os.Open("../../hindsight-docs/examples/api/sample.pdf")
if err != nil {
log.Fatalf("Failed to open file: %v", err)
}
defer f.Close()
fileResp, _, _ := client.FilesAPI.FileRetain(ctx, "my-bank").
Files([]*os.File{f}).
Request(`{"files_metadata": [{"context": "quarterly report"}]}`).
Execute()
fmt.Println("Operation IDs:", fileResp.GetOperationIds()) // Track processing via the operations endpoint
// [/docs:retain-files]
// =============================================================================
// Cleanup (not shown in docs)
// =============================================================================
req, _ := http.NewRequest("DELETE", fmt.Sprintf("%s/v1/default/banks/my-bank", apiURL), nil)
http.DefaultClient.Do(req)
fmt.Println("retain.go: All examples passed")
}
-15
View File
@@ -85,21 +85,6 @@ console.log(result.operation_ids); // Track processing via the operations endpo
// [/docs:retain-files]
// [docs:retain-files-batch]
// Upload multiple files with per-file metadata (up to 10 files per request)
const batchResult = await client.retainFiles('my-bank', [
new File([pdfBytes], 'report.pdf'),
new File([pdfBytes], 'notes.pdf'),
], {
filesMetadata: [
{ context: 'quarterly report', document_id: 'q1-report', tags: ['project:alpha'] },
{ context: 'meeting notes', document_id: 'q1-notes', tags: ['project:alpha'] },
]
});
console.log(batchResult.operation_ids); // One operation ID per file
// [/docs:retain-files-batch]
// =============================================================================
// Cleanup (not shown in docs)
// =============================================================================
-25
View File
@@ -25,37 +25,12 @@ hindsight memory retain my-bank "Alice works at Google as a software engineer"
# [/docs:retain-basic]
# [docs:retain-conversation]
# Retain an entire conversation as a single document.
CONVERSATION="Alice (2024-03-15T09:00:00Z): Hi Bob! Did you end up going to the doctor last week?
Bob (2024-03-15T09:01:00Z): Yes, finally. Turns out I have a mild peanut allergy.
Alice (2024-03-15T09:02:00Z): Oh no! Are you okay?
Bob (2024-03-15T09:03:00Z): Yeah, nothing serious. Just need to carry an antihistamine.
Alice (2024-03-15T09:04:00Z): Good to know. We'll avoid peanuts at the team lunch."
hindsight memory retain my-bank "$CONVERSATION" \
--context "team chat" \
--doc-id "chat-2024-03-15-alice-bob"
# [/docs:retain-conversation]
# [docs:retain-with-context]
hindsight memory retain my-bank "Alice got promoted" \
--context "career update"
# [/docs:retain-with-context]
# [docs:retain-batch]
# Batch ingestion via individual retain calls (CLI processes items one at a time)
hindsight memory retain my-bank "Alice works at Google" \
--context "career" --doc-id "conversation_001_msg_1"
hindsight memory retain my-bank "Bob is a data scientist at Meta" \
--context "career" --doc-id "conversation_001_msg_2"
hindsight memory retain my-bank "Alice and Bob are friends" \
--context "relationship" --doc-id "conversation_001_msg_3"
# [/docs:retain-batch]
# [docs:retain-async]
hindsight memory retain my-bank "Meeting notes" --async
# [/docs:retain-async]
+1 -1
View File
@@ -5,7 +5,7 @@
"scripts": {
"docusaurus": "docusaurus",
"start": "docusaurus start",
"build": "node scripts/check-code-parity.mjs && docusaurus build",
"build": "docusaurus build",
"swizzle": "docusaurus swizzle",
"deploy": "docusaurus deploy",
"clear": "docusaurus clear",
@@ -1,128 +0,0 @@
#!/usr/bin/env node
/**
* Validates that every "language" Tabs block in MDX docs has all 4 required variants:
* Python, Node.js, CLI, Go.
*
* A Tabs block is considered a "language" block if it contains at least one TabItem
* with value "python", "node", "cli", or "go".
*
* Run: node scripts/check-code-parity.mjs
*/
import { readFileSync, readdirSync, statSync } from 'node:fs';
import { join, relative } from 'node:path';
import { fileURLToPath } from 'node:url';
import { dirname } from 'node:path';
const __dirname = dirname(fileURLToPath(import.meta.url));
const docsRoot = join(__dirname, '..');
const REQUIRED_TABS = new Set(['python', 'node', 'cli', 'go']);
const IGNORED_PATHS = [
'node_modules',
'build',
'.docusaurus',
'versioned_docs', // skip versioned docs
];
/**
* Recursively find all .mdx files under a directory.
*/
function findMdxFiles(dir) {
const results = [];
for (const entry of readdirSync(dir)) {
if (IGNORED_PATHS.includes(entry)) continue;
const full = join(dir, entry);
const stat = statSync(full);
if (stat.isDirectory()) {
results.push(...findMdxFiles(full));
} else if (entry.endsWith('.mdx') || entry.endsWith('.md')) {
results.push(full);
}
}
return results;
}
/**
* Parse a single MDX file and return all violations.
* A violation is a Tabs block that has at least one language tab but is missing
* one or more of the 4 required language variants.
*/
function checkFile(filePath) {
const content = readFileSync(filePath, 'utf8');
const violations = [];
// Split content into Tabs blocks.
// Strategy: find <Tabs> ... </Tabs> sections and scan for TabItem values.
// We use a simple line-by-line state machine.
const lines = content.split('\n');
let inTabs = false;
let tabsStartLine = -1;
let currentTabValues = new Set();
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
if (!inTabs) {
// Look for opening <Tabs> tag (not <TabItem>)
if (/^\s*<Tabs[\s>]/.test(line) && !/<\/Tabs/.test(line)) {
inTabs = true;
tabsStartLine = i + 1; // 1-indexed
currentTabValues = new Set();
}
} else {
// Inside a Tabs block — look for </Tabs> or nested TabItem values
if (/^\s*<\/Tabs\s*>/.test(line)) {
// End of Tabs block — check if it's a language block
const hasLanguageTab = [...currentTabValues].some(v => REQUIRED_TABS.has(v));
if (hasLanguageTab) {
const missing = [...REQUIRED_TABS].filter(t => !currentTabValues.has(t));
if (missing.length > 0) {
violations.push({
line: tabsStartLine,
found: [...currentTabValues].filter(v => REQUIRED_TABS.has(v)),
missing,
});
}
}
inTabs = false;
currentTabValues = new Set();
} else {
// Look for TabItem value attributes
// Matches: <TabItem value="python" or <TabItem value='cli'
const match = line.match(/TabItem[^>]*value=["']([^"']+)["']/);
if (match) {
currentTabValues.add(match[1]);
}
}
}
}
return violations;
}
// ─── Main ────────────────────────────────────────────────────────────────────
const mdxFiles = findMdxFiles(docsRoot);
let totalViolations = 0;
for (const filePath of mdxFiles) {
const violations = checkFile(filePath);
if (violations.length > 0) {
const rel = relative(docsRoot, filePath);
for (const v of violations) {
console.error(
`[code-parity] ${rel}:${v.line} — Tabs block missing language tabs: ${v.missing.join(', ')} (found: ${v.found.join(', ')})`
);
}
totalViolations += violations.length;
}
}
if (totalViolations > 0) {
console.error(`\n[code-parity] ❌ Found ${totalViolations} Tabs block(s) missing required language variants.`);
console.error('[code-parity] Every Tabs block with language tabs must include: python, node, cli, go');
process.exit(1);
} else {
console.log(`[code-parity] ✅ All ${mdxFiles.length} docs files pass 4-tab parity check.`);
}
@@ -1,82 +0,0 @@
.banner {
width: 100%;
overflow: hidden;
border-bottom: 1px solid var(--ifm-color-emphasis-200);
background: var(--ifm-background-surface-color);
padding: 7px 0;
/* Color cascades down to all items; dark mode is handled by Docusaurus on the html element */
color: #555;
mask-image: linear-gradient(to right, transparent 0%, black 8%, black 92%, transparent 100%);
-webkit-mask-image: linear-gradient(to right, transparent 0%, black 8%, black 92%, transparent 100%);
}
[data-theme='dark'] .banner {
color: #e0e0e0;
border-color: var(--ifm-color-emphasis-300);
}
.track {
display: flex;
align-items: center;
white-space: nowrap;
width: max-content;
animation: scrollLeft 60s linear infinite;
}
@keyframes scrollLeft {
from { transform: translateX(0); }
to { transform: translateX(-50%); }
}
.itemLink {
text-decoration: none;
color: inherit;
}
.itemLink:hover .item {
color: #0074d9;
border-color: #0074d9;
background: rgba(0, 116, 217, 0.08);
}
[data-theme='dark'] .itemLink:hover .item {
color: #60aaff;
background: rgba(96, 170, 255, 0.12);
border-color: rgba(96, 170, 255, 0.5);
}
.item {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 4px 14px;
margin: 0 6px;
border-radius: 999px;
border: 1px solid var(--ifm-color-emphasis-300);
background: var(--ifm-background-color);
font-size: 0.78rem;
font-weight: 500;
color: inherit;
transition: color 0.15s, border-color 0.15s, background 0.15s;
user-select: none;
}
[data-theme='dark'] .item {
border-color: var(--ifm-color-emphasis-400);
background: var(--ifm-background-surface-color);
}
.itemIcon {
display: inline-flex;
align-items: center;
flex-shrink: 0;
opacity: 0.85;
}
.itemIcon img {
display: block;
}
.itemLabel {
line-height: 1;
}
@@ -1,80 +0,0 @@
import React from 'react';
import type {IconType} from 'react-icons';
import {SiPython, SiGo, SiOpenai, SiAnthropic, SiGooglegemini, SiOllama, SiVercel} from 'react-icons/si';
import {LuTerminal, LuZap, LuBrainCog, LuSparkles, LuGlobe} from 'react-icons/lu';
import styles from './IntegrationsBanner.module.css';
interface BannerItem {
label: string;
icon?: IconType;
imgSrc?: string;
href?: string;
}
const OpenAICompatibleIcon: IconType = ({size = 18, ...props}) => (
<span style={{position: 'relative', display: 'inline-flex'}}>
<SiOpenai size={size} {...props} />
<span style={{
position: 'absolute', bottom: -2, right: -5,
fontSize: Math.round((size as number) * 0.5), fontWeight: 900, lineHeight: 1,
color: 'currentColor',
}}>+</span>
</span>
);
const ITEMS: BannerItem[] = [
// Clients
{label: 'Python', icon: SiPython, href: '/sdks/python'},
{label: 'TypeScript', imgSrc: '/img/icons/typescript.png', href: '/sdks/nodejs'},
{label: 'Go', icon: SiGo, href: '/sdks/go'},
{label: 'CLI', icon: LuTerminal, href: '/sdks/cli'},
{label: 'HTTP', icon: LuGlobe, href: '/developer/api/quickstart'},
// Integrations
{label: 'MCP Server', imgSrc: '/img/icons/mcp.png', href: '/sdks/integrations/local-mcp'},
{label: 'LiteLLM', imgSrc: '/img/icons/litellm.png', href: '/sdks/integrations/litellm'},
{label: 'OpenClaw', imgSrc: '/img/icons/openclaw.png', href: '/sdks/integrations/openclaw'},
{label: 'Vercel AI', icon: SiVercel, href: '/sdks/integrations/ai-sdk'},
{label: 'Vercel Chat', icon: SiVercel, href: '/sdks/integrations/chat'},
{label: 'CrewAI', imgSrc: '/img/icons/crewai.png', href: '/sdks/integrations/crewai'},
{label: 'Pydantic AI', imgSrc: '/img/icons/pydanticai.png', href: '/sdks/integrations/pydantic-ai'},
{label: 'Skills', imgSrc: '/img/icons/skills.png', href: '/sdks/integrations/skills'},
{label: 'Agno', imgSrc: '/img/icons/agno.png', href: '/sdks/integrations/agno'},
{label: 'Hermes', imgSrc: '/img/icons/hermes.png', href: '/sdks/integrations/hermes'},
// LLM Providers
{label: 'OpenAI', icon: SiOpenai},
{label: 'Anthropic', icon: SiAnthropic},
{label: 'Gemini', icon: SiGooglegemini},
{label: 'Groq', icon: LuZap},
{label: 'Ollama', icon: SiOllama},
{label: 'LM Studio', icon: LuBrainCog},
{label: 'MiniMax', icon: LuSparkles},
{label: 'OpenAI Compat', icon: OpenAICompatibleIcon},
];
function BannerItemComponent({item}: {item: BannerItem}) {
const content = (
<span className={styles.item}>
<span className={styles.itemIcon}>
{item.icon && <item.icon size={18} />}
{item.imgSrc && <img src={item.imgSrc} alt={item.label} width={18} height={18} style={{objectFit: 'contain'}} />}
</span>
<span className={styles.itemLabel}>{item.label}</span>
</span>
);
return item.href
? <a href={item.href} className={styles.itemLink}>{content}</a>
: <span>{content}</span>;
}
export default function IntegrationsBanner(): JSX.Element {
const doubled = [...ITEMS, ...ITEMS];
return (
<div className={styles.banner}>
<div className={styles.track}>
{doubled.map((item, i) => (
<BannerItemComponent key={`${item.label}-${i}`} item={item} />
))}
</div>
</div>
);
}
@@ -38,8 +38,6 @@ export function IntegrationsGrid() {
{ label: 'CrewAI', imgSrc: '/img/icons/crewai.png', href: '/sdks/integrations/crewai' },
{ label: 'Pydantic AI', imgSrc: '/img/icons/pydanticai.png', href: '/sdks/integrations/pydantic-ai' },
{ label: 'Skills', imgSrc: '/img/icons/skills.png', href: '/sdks/integrations/skills' },
{ label: 'Agno', imgSrc: '/img/icons/agno.png', href: '/sdks/integrations/agno' },
{ label: 'Hermes', imgSrc: '/img/icons/hermes.png', href: '/sdks/integrations/hermes' },
]} />
);
}
-5
View File
@@ -1267,8 +1267,3 @@ html.docs-wrapper article h3 {
}
}
/* Remove right border from doc sidebar */
.theme-doc-sidebar-container {
border-right: none !important;
}
+3 -7
View File
@@ -1,15 +1,11 @@
import React, {type ReactNode} from 'react';
import NavbarLayout from '@theme/Navbar/Layout';
import NavbarContent from '@theme/Navbar/Content';
import IntegrationsBanner from '@site/src/components/IntegrationsBanner';
export default function Navbar(): ReactNode {
return (
<>
<NavbarLayout>
<NavbarContent />
</NavbarLayout>
<IntegrationsBanner />
</>
<NavbarLayout>
<NavbarContent />
</NavbarLayout>
);
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 398 KiB

-82
View File
@@ -6299,47 +6299,6 @@
"title": "Refresh After Consolidation",
"description": "If true, refresh this mental model after observations consolidation (real-time mode)",
"default": false
},
"fact_types": {
"anyOf": [
{
"items": {
"type": "string",
"enum": [
"world",
"experience",
"observation"
]
},
"type": "array"
},
{
"type": "null"
}
],
"title": "Fact Types",
"description": "Filter which fact types are retrieved during reflect. None means all types (world, experience, observation)."
},
"exclude_mental_models": {
"type": "boolean",
"title": "Exclude Mental Models",
"description": "If true, exclude all mental models from the reflect loop (skip search_mental_models tool).",
"default": false
},
"exclude_mental_model_ids": {
"anyOf": [
{
"items": {
"type": "string"
},
"type": "array"
},
{
"type": "null"
}
],
"title": "Exclude Mental Model Ids",
"description": "Exclude specific mental models by ID from the reflect loop."
}
},
"type": "object",
@@ -7340,47 +7299,6 @@
],
"title": "Tag Groups",
"description": "Compound tag filter using boolean groups. Groups in the list are AND-ed. Each group is a leaf {tags, match} or compound {and: [...]}, {or: [...]}, {not: ...}."
},
"fact_types": {
"anyOf": [
{
"items": {
"type": "string",
"enum": [
"world",
"experience",
"observation"
]
},
"type": "array"
},
{
"type": "null"
}
],
"title": "Fact Types",
"description": "Filter which fact types are retrieved during reflect. None means all types (world, experience, observation)."
},
"exclude_mental_models": {
"type": "boolean",
"title": "Exclude Mental Models",
"description": "If true, exclude all mental models from the reflect loop (skip search_mental_models tool).",
"default": false
},
"exclude_mental_model_ids": {
"anyOf": [
{
"items": {
"type": "string"
},
"type": "array"
},
{
"type": "null"
}
],
"title": "Exclude Mental Model Ids",
"description": "Exclude specific mental models by ID from the reflect loop."
}
},
"type": "object",
+2 -2
View File
@@ -13,7 +13,7 @@
},
"hindsight-clients/typescript": {
"name": "@vectorize-io/hindsight-client",
"version": "0.4.19",
"version": "0.4.18",
"license": "MIT",
"devDependencies": {
"@hey-api/openapi-ts": "0.88.0",
@@ -293,7 +293,7 @@
},
"hindsight-control-plane": {
"name": "@vectorize-io/hindsight-control-plane",
"version": "0.4.19",
"version": "0.4.18",
"license": "ISC",
"dependencies": {
"@radix-ui/react-alert-dialog": "^1.1.15",
-144
View File
@@ -175,47 +175,6 @@ for page in best-practices faq; do
done
done
# Process changelog — may be a single file or a directory
if [ -f "$PAGES_DIR/changelog.md" ] || [ -f "$PAGES_DIR/changelog.mdx" ]; then
for ext in md mdx; do
src="$PAGES_DIR/changelog.$ext"
if [ -f "$src" ]; then
dest="$REFS_DIR/changelog.md"
mkdir -p "$(dirname "$dest")"
if [[ "$src" == *.mdx ]]; then
convert_mdx_to_md "$src" "$dest"
else
cp "$src" "$dest"
fi
print_info "Included page: changelog.$ext"
fi
done
elif [ -d "$PAGES_DIR/changelog" ]; then
find "$PAGES_DIR/changelog" -type f \( -name "*.md" -o -name "*.mdx" \) | while read -r file; do
rel="${file#$PAGES_DIR/}"
dest="$REFS_DIR/$rel"
if [[ "$file" == *.mdx ]]; then
dest="${dest%.mdx}.md"
fi
mkdir -p "$(dirname "$dest")"
if [[ "$file" == *.mdx ]]; then
convert_mdx_to_md "$file" "$dest"
else
cp "$file" "$dest"
fi
print_info "Included changelog: ${file#$PAGES_DIR/changelog/}"
done
fi
# Copy OpenAPI spec into the skill
OPENAPI_SRC="$ROOT_DIR/hindsight-docs/static/openapi.json"
if [ -f "$OPENAPI_SRC" ]; then
cp "$OPENAPI_SRC" "$REFS_DIR/openapi.json"
print_info "Included: openapi.json"
else
print_warn "openapi.json not found at $OPENAPI_SRC — skipping"
fi
# Generate SKILL.md
print_info "Generating SKILL.md..."
cat > "$SKILL_DIR/SKILL.md" <<'EOF'
@@ -249,8 +208,6 @@ All documentation is in `references/` organized by category:
references/
├── best-practices.md # START HERE — missions, tags, formats, anti-patterns
├── faq.md # Common questions and decisions
├── changelog/ # Release history and version changes (index.md + integrations/)
├── openapi.json # Full OpenAPI spec — endpoint schemas, request/response models
├── developer/
│ ├── api/ # Core operations: retain, recall, reflect, memory banks
│ └── *.md # Architecture, configuration, deployment, performance
@@ -345,107 +302,6 @@ print_info "✓ Generated skill at: $SKILL_DIR"
print_info "✓ Documentation files: $(find "$REFS_DIR" -type f | wc -l | tr -d ' ')"
print_info "✓ SKILL.md created with search guidance"
# Rewrite Docusaurus absolute paths (e.g. /developer/foo) to relative paths
print_info "Rewriting Docusaurus absolute paths to relative paths..."
python3 - "$REFS_DIR" <<'PYTHON'
import sys
import re
import os
from pathlib import Path
refs_dir = Path(sys.argv[1]).resolve()
link_pattern = re.compile(r'\[([^\]]*)\]\((/[^)]*)\)')
SPECIAL_MAPPINGS = {
'/api-reference': 'openapi.json',
}
def try_resolve(url_path, refs_dir):
"""Try to find the file in refs_dir for a Docusaurus absolute path like /developer/foo."""
if url_path in SPECIAL_MAPPINGS:
candidate = refs_dir / SPECIAL_MAPPINGS[url_path]
return candidate if candidate.exists() else None
doc_path = url_path.lstrip('/')
for candidate in [
refs_dir / (doc_path + '.md'),
refs_dir / doc_path / 'index.md',
refs_dir / doc_path,
]:
if candidate.exists():
return candidate
return None
image_pattern = re.compile(r'!\[[^\]]*\]\([^)]*\)')
html_img_pattern = re.compile(r'<img\b[^>]*/?>', re.IGNORECASE)
changed = 0
for md_file in refs_dir.rglob("*.md"):
original_content = md_file.read_text()
# Strip images (markdown and HTML)
content = image_pattern.sub('', original_content)
content = html_img_pattern.sub('', content)
def rewrite(match):
text = match.group(1)
url = match.group(2)
anchor = ''
if '#' in url:
url, frag = url.split('#', 1)
anchor = '#' + frag
if not url or url == '/':
return text # strip link, keep text
resolved = try_resolve(url, refs_dir)
if resolved is None:
return text # strip unresolvable link, keep text
rel = os.path.relpath(resolved, md_file.parent)
return f'[{text}]({rel}{anchor})'
new_content = link_pattern.sub(rewrite, content)
if new_content != original_content:
md_file.write_text(new_content)
changed += 1
print(f"[INFO] Rewrote Docusaurus links in {changed} file(s)")
PYTHON
# Validate: no links point outside the skill directory
print_info "Validating links in generated skill files..."
python3 - "$SKILL_DIR" <<'PYTHON'
import sys
import re
from pathlib import Path
skill_dir = Path(sys.argv[1]).resolve()
errors = []
# Find all markdown links: [text](url) — exclude images too
link_pattern = re.compile(r'\[([^\]]*)\]\(([^)]+)\)')
for md_file in skill_dir.rglob("*.md"):
content = md_file.read_text()
for match in link_pattern.finditer(content):
url = match.group(2).split("#")[0].strip() # strip anchors
if not url:
continue
# Absolute URLs and anchors-only are fine
if url.startswith(("http://", "https://", "mailto:", "ftp://")):
continue
# Resolve relative to the file's directory
resolved = (md_file.parent / url).resolve()
if not str(resolved).startswith(str(skill_dir)):
errors.append(f" {md_file.relative_to(skill_dir)}: '{url}' -> {resolved}")
if errors:
print("ERROR: The following links point outside the skill directory.")
print("All links must be absolute URLs or relative paths within the skill.")
for e in errors:
print(e)
sys.exit(1)
print(f"[INFO] Link validation passed ({skill_dir})")
PYTHON
echo ""
print_info "Usage:"
echo " - Agents can use Glob to find files: references/developer/api/*.md"
-2
View File
@@ -28,8 +28,6 @@ All documentation is in `references/` organized by category:
references/
├── best-practices.md # START HERE — missions, tags, formats, anti-patterns
├── faq.md # Common questions and decisions
├── changelog/ # Release history and version changes (index.md + integrations/)
├── openapi.json # Full OpenAPI spec — endpoint schemas, request/response models
├── developer/
│ ├── api/ # Core operations: retain, recall, reflect, memory banks
│ └── *.md # Architecture, configuration, deployment, performance
@@ -1,608 +0,0 @@
---
hide_table_of_contents: true
---
# Changelog
This changelog highlights user-facing changes only. Internal maintenance, CI/CD, and infrastructure updates are omitted.
For full release details, see [GitHub Releases](https://github.com/vectorize-io/hindsight/releases).
## Integration Changelogs
| Integration | Package | Description |
|---|---|---|
| [LiteLLM](integrations/litellm.md) | `hindsight-litellm` | Universal LLM memory via LiteLLM (100+ providers) |
| [Pydantic AI](integrations/pydantic-ai.md) | `hindsight-pydantic-ai` | Persistent memory tools for Pydantic AI agents |
| [CrewAI](integrations/crewai.md) | `hindsight-crewai` | Persistent memory for CrewAI agents |
| [AI SDK](integrations/ai-sdk.md) | `@vectorize-io/hindsight-ai-sdk` | Memory integration for Vercel AI SDK |
| [Chat SDK](integrations/chat.md) | `@vectorize-io/hindsight-chat` | Memory integration for Vercel Chat SDK |
| [OpenClaw](integrations/openclaw.md) | `@vectorize-io/hindsight-openclaw` | Hindsight memory plugin for OpenClaw |
## [0.4.19](https://github.com/vectorize-io/hindsight/releases/tag/v0.4.19)
**Features**
- TypeScript client now works in Deno environments. ([`72c25c97`](https://github.com/vectorize-io/hindsight/commit/72c25c97))
- Added Agno integration to use Hindsight as a memory toolkit. ([`8c378b98`](https://github.com/vectorize-io/hindsight/commit/8c378b98))
- Added Hermes Agent integration (hindsight-hermes) for persistent memory. ([`ef90842f`](https://github.com/vectorize-io/hindsight/commit/ef90842f))
- Expanded retain behavior with new `verbatim` and `chunks` extraction modes and named retain strategies. ([`e4f8a157`](https://github.com/vectorize-io/hindsight/commit/e4f8a157))
**Improvements**
- Improved local reranker performance/efficiency with FP16 and bucketed batching, plus compatibility with Transformers 5.x. ([`e7da7d0e`](https://github.com/vectorize-io/hindsight/commit/e7da7d0e))
**Bug Fixes**
- Prevented silent memory loss when consolidation fails (failed consolidations are tracked and can be recovered). ([`28dac7c7`](https://github.com/vectorize-io/hindsight/commit/28dac7c7))
- Fixed Docker control-plane startup to respect the configured control-plane hostname. ([`8a64dc8d`](https://github.com/vectorize-io/hindsight/commit/8a64dc8d))
- Database cleanup migration now removes orphaned observation memory units to avoid inconsistent memory state. ([`f09ad9de`](https://github.com/vectorize-io/hindsight/commit/f09ad9de))
- Deleting a document now also deletes linked memory units to prevent leftover/stale memory entries. ([`f27bd953`](https://github.com/vectorize-io/hindsight/commit/f27bd953))
- Fixed MCP middleware to send an Accept header, preventing 406 response errors in some setups. ([`836fd81e`](https://github.com/vectorize-io/hindsight/commit/836fd81e))
- Improved compatibility with Gemini tool-calling by preserving thought signature metadata to avoid failures on gemini-3.1-flash-lite-preview. ([`21f9f46c`](https://github.com/vectorize-io/hindsight/commit/21f9f46c))
## [0.4.18](https://github.com/vectorize-io/hindsight/releases/tag/v0.4.18)
**Features**
- Add compound tag filtering using tag groups. ([`5de793ee`](https://github.com/vectorize-io/hindsight/commit/5de793ee))
- Publish new slim Python packages (hindsight-api-slim and hindsight-all-slim) for smaller installs. ([`15ea23d5`](https://github.com/vectorize-io/hindsight/commit/15ea23d5))
- Add MiniMax as a supported LLM provider. ([`2344484f`](https://github.com/vectorize-io/hindsight/commit/2344484f))
- Add Jina MLX reranker provider optimized for Apple Silicon. ([`1caf5ec9`](https://github.com/vectorize-io/hindsight/commit/1caf5ec9))
**Improvements**
- Allow configuring maximum recall query tokens via an environment variable. ([`66dedb8d`](https://github.com/vectorize-io/hindsight/commit/66dedb8d))
- Improve retrieval performance by switching to per-bank HNSW indexes. ([`43b3efc4`](https://github.com/vectorize-io/hindsight/commit/43b3efc4))
**Bug Fixes**
- Prevent reranking failures by truncating long documents that exceed LiteLLM reranker context limits. ([`eeb938fc`](https://github.com/vectorize-io/hindsight/commit/eeb938fc))
- Ensure recalled memories are injected as system context for OpenClaw. ([`b17f338e`](https://github.com/vectorize-io/hindsight/commit/b17f338e))
- Ensure embedded profiles are registered in CLI metadata when the daemon starts. ([`06b0f74a`](https://github.com/vectorize-io/hindsight/commit/06b0f74a))
- Cancel in-flight async operations when a bank is deleted to avoid dangling work. ([`0560f626`](https://github.com/vectorize-io/hindsight/commit/0560f626))
## [0.4.17](https://github.com/vectorize-io/hindsight/releases/tag/v0.4.17)
**Features**
- Added a manual retry option for failed asynchronous operations. ([`dcaacbe4`](https://github.com/vectorize-io/hindsight/commit/dcaacbe4))
- You can now change/update tags on an existing document. ([`1b4ad7f4`](https://github.com/vectorize-io/hindsight/commit/1b4ad7f4))
- Added history tracking and a diff view for mental model changes. ([`e2baca8b`](https://github.com/vectorize-io/hindsight/commit/e2baca8b))
- Added observation history tracking with a UI diff view to review changes over time. ([`576473b6`](https://github.com/vectorize-io/hindsight/commit/576473b6))
- File uploads can now choose a parser per request, with configurable fallback chains. ([`99220d05`](https://github.com/vectorize-io/hindsight/commit/99220d05))
- Added an extension hook that runs after file-to-Markdown conversion completes. ([`1d17dea2`](https://github.com/vectorize-io/hindsight/commit/1d17dea2))
**Improvements**
- Operations view now supports filtering by operation type and has more reliable auto-refresh behavior. ([`f7a60f89`](https://github.com/vectorize-io/hindsight/commit/f7a60f89))
- Added token limits for “source facts” used during consolidation and recall to better control context usage. ([`5d05962d`](https://github.com/vectorize-io/hindsight/commit/5d05962d))
- Improved bank selector usability by truncating very long bank names in the dropdown. ([`1e40cd22`](https://github.com/vectorize-io/hindsight/commit/1e40cd22))
**Bug Fixes**
- Fixed webhook schema issues affecting multi-tenant retain webhooks. ([`32a4882a`](https://github.com/vectorize-io/hindsight/commit/32a4882a))
- Fixed file ingestion failures by stripping null bytes from parsed file content before retaining. ([`cd3a6a22`](https://github.com/vectorize-io/hindsight/commit/cd3a6a22))
- Fixed tool selection handling for OpenAI-compatible providers when using named tool_choice. ([`1cdfb7c2`](https://github.com/vectorize-io/hindsight/commit/1cdfb7c2))
- Improved consolidation behavior to prioritize a banks mission over an ephemeral-state heuristic. ([`00ccf0b2`](https://github.com/vectorize-io/hindsight/commit/00ccf0b2))
- Fixed database migrations to correctly handle mental model embedding dimension changes. ([`7accac94`](https://github.com/vectorize-io/hindsight/commit/7accac94))
- Fixed file upload failures caused by an Iris parser httpx read timeout. ([`fa3501d4`](https://github.com/vectorize-io/hindsight/commit/fa3501d4))
- Improved reliability of running migrations by serializing Alembic upgrades within the process. ([`f88b50a4`](https://github.com/vectorize-io/hindsight/commit/f88b50a4))
- Fixed Google Cloud Storage authentication when using Workload Identity Federation credentials. ([`d2504ac5`](https://github.com/vectorize-io/hindsight/commit/d2504ac5))
- Fixed the bank selector to refresh the bank list when the dropdown is opened. ([`0ad8c2d0`](https://github.com/vectorize-io/hindsight/commit/0ad8c2d0))
## [0.4.16](https://github.com/vectorize-io/hindsight/releases/tag/v0.4.16)
**Features**
- Added Webhooks with `consolidation.completed` and `retain.completed` events. ([`abbf874d`](https://github.com/vectorize-io/hindsight/commit/abbf874d))
**Improvements**
- Improved OpenClaw recall/retention controls. ([`d425e93c`](https://github.com/vectorize-io/hindsight/commit/d425e93c))
- Improved search/reranking quality by switching combined scoring to multiplicative boosts. ([`aa8e5475`](https://github.com/vectorize-io/hindsight/commit/aa8e5475))
- Improved performance of observation recall by 40x on large banks. ([`ad2cf72a`](https://github.com/vectorize-io/hindsight/commit/ad2cf72a))
- Improved server shutdown behavior by capping graceful shutdown time and allowing a forced kill on a second Ctrl+C. ([`4c058b4b`](https://github.com/vectorize-io/hindsight/commit/4c058b4b))
**Bug Fixes**
- Fixed an async deadlock risk by running database schema migrations in a background thread during startup. ([`e0a2ac63`](https://github.com/vectorize-io/hindsight/commit/e0a2ac63))
- Fixed webhook delivery/outbox processing so transactions dont silently roll back due to using the wrong database schema name. ([`75b95106`](https://github.com/vectorize-io/hindsight/commit/75b95106))
- Fixed observation results to correctly resolve and return related chunks using source_memory_ids. ([`cb6d1c46`](https://github.com/vectorize-io/hindsight/commit/cb6d1c46))
- Fixed MCP bank-level tool filtering compatibility with FastMCP 3.x. ([`f17406fd`](https://github.com/vectorize-io/hindsight/commit/f17406fd))
- Fixed crashes when an LLM returns invalid JSON across all retries (now handled cleanly instead of raising a TypeError). ([`66423b85`](https://github.com/vectorize-io/hindsight/commit/66423b85))
- Fixed observations without source dates to preserve missing (None) temporal fields instead of incorrectly populating them. ([`891c33b1`](https://github.com/vectorize-io/hindsight/commit/891c33b1))
## [0.4.15](https://github.com/vectorize-io/hindsight/releases/tag/v0.4.15)
**Features**
- Added observation_scopes to control the granularity/visibility of observations. ([`55af4681`](https://github.com/vectorize-io/hindsight/commit/55af4681))
- List documents API now supports filtering by tags (and fixes the q parameter description). ([`1d70abfe`](https://github.com/vectorize-io/hindsight/commit/1d70abfe))
- Added PydanticAI integration for persistent agent memory. ([`cab5a40f`](https://github.com/vectorize-io/hindsight/commit/cab5a40f))
- Added richer entity label support (optional labels, free-form values, multi-value fields, and UI polish). ([`9b96becc`](https://github.com/vectorize-io/hindsight/commit/9b96becc))
- Added support for timestamp="unset" so content can be retained without a date. ([`f903948a`](https://github.com/vectorize-io/hindsight/commit/f903948a))
- OpenClaw can now automatically retain the last n+2 turns every n turns (default n=10). ([`ad1660b3`](https://github.com/vectorize-io/hindsight/commit/ad1660b3))
- Added configurable Gemini/Vertex AI safety settings for LLM calls. ([`73ef99e7`](https://github.com/vectorize-io/hindsight/commit/73ef99e7))
- Added extension hooks to customize root routing and error headers. ([`e407f4bc`](https://github.com/vectorize-io/hindsight/commit/e407f4bc))
**Improvements**
- Improved recall performance by fetching all recall chunks in a single query. ([`61bf428b`](https://github.com/vectorize-io/hindsight/commit/61bf428b))
- Improved recall/retain performance and scalability for large memory banks. ([`7942f181`](https://github.com/vectorize-io/hindsight/commit/7942f181))
**Bug Fixes**
- Fixed the TypeScript SDK to send null (not undefined) when includeEntities is false. ([`15f4b876`](https://github.com/vectorize-io/hindsight/commit/15f4b876))
- Prevented reflect from failing with context_length_exceeded on large memory banks. ([`77defd96`](https://github.com/vectorize-io/hindsight/commit/77defd96))
- Fixed a consolidation deadlock caused by retrying after zombie processing tasks. ([`c2876490`](https://github.com/vectorize-io/hindsight/commit/c2876490))
- Fixed observations count in the control plane that always showed 0. ([`eaeaa1f2`](https://github.com/vectorize-io/hindsight/commit/eaeaa1f2))
- Fixed ZeroEntropy rerank endpoint URL and ensured the MCP retain async_processing parameter is handled correctly. ([`f6f1a7d8`](https://github.com/vectorize-io/hindsight/commit/f6f1a7d8))
- Fixed JSON serialization issues and logging-related exception propagation when using the claude_code LLM provider. ([`ecb833f4`](https://github.com/vectorize-io/hindsight/commit/ecb833f4))
- Added bank-scoped request validation to prevent cross-bank/invalid bank operations. ([`5270aa5a`](https://github.com/vectorize-io/hindsight/commit/5270aa5a))
## [0.4.14](https://github.com/vectorize-io/hindsight/releases/tag/v0.4.14)
**Features**
- Add Chat SDK integration to give chatbots persistent memory. ([`fed987f9`](https://github.com/vectorize-io/hindsight/commit/fed987f9))
- Allow configuring which MCP tools are exposed per memory bank, and expand the MCP tool set with additional tools and parameters. ([`3ffec650`](https://github.com/vectorize-io/hindsight/commit/3ffec650))
- Enable the bank configuration API by default. ([`4d030707`](https://github.com/vectorize-io/hindsight/commit/4d030707))
- Support filtering graph-based memory retrieval by tags. ([`0bb5ca4c`](https://github.com/vectorize-io/hindsight/commit/0bb5ca4c))
- Add batch observations consolidation to process multiple observations more efficiently. ([`0aa7c2b3`](https://github.com/vectorize-io/hindsight/commit/0aa7c2b3))
- Add OpenClaw options to toggle autoRecall and exclude specific providers. ([`3f9eb27c`](https://github.com/vectorize-io/hindsight/commit/3f9eb27c))
- Add a ZeroEntropy reranker provider option. ([`17259675`](https://github.com/vectorize-io/hindsight/commit/17259675))
**Improvements**
- Increase customization options for reflect, retain, and consolidation behavior. ([`2a322732`](https://github.com/vectorize-io/hindsight/commit/2a322732))
- Include source document metadata in fact extraction results. ([`87219b73`](https://github.com/vectorize-io/hindsight/commit/87219b73))
**Bug Fixes**
- Raise a clear error when embedding dimensions exceed pgvector HNSW limits (instead of failing later at runtime). ([`8cd65b98`](https://github.com/vectorize-io/hindsight/commit/8cd65b98))
- Fix multi-tenant schema isolation issues in storage and the bank config API. ([`b180b3ad`](https://github.com/vectorize-io/hindsight/commit/b180b3ad))
- Ensure LiteLLM embedding calls use the correct float encoding format to prevent embedding failures. ([`58f2de70`](https://github.com/vectorize-io/hindsight/commit/58f2de70))
- Improve recall performance by reducing memory usage during retrieval. ([`9f0c031d`](https://github.com/vectorize-io/hindsight/commit/9f0c031d))
- Handle observation regeneration correctly when underlying memories are deleted. ([`ac9a94ad`](https://github.com/vectorize-io/hindsight/commit/ac9a94ad))
- Fix reflect retrieval to correctly populate dependencies and enforce full hierarchical retrieval. ([`8b1a4658`](https://github.com/vectorize-io/hindsight/commit/8b1a4658))
- Fix OpenClaw health checks by passing the auth token to the health endpoint. ([`40b02645`](https://github.com/vectorize-io/hindsight/commit/40b02645))
## [0.4.13](https://github.com/vectorize-io/hindsight/releases/tag/v0.4.13)
**Features**
- Switched the default OpenAI LLM to gpt-4o-mini. ([`325b5cc1`](https://github.com/vectorize-io/hindsight/commit/325b5cc1))
- Observation recall now includes the source facts behind recalled observations. ([`5569d4ad`](https://github.com/vectorize-io/hindsight/commit/5569d4ad))
- Added CrewAI integration to enable persistent memory. ([`41db2960`](https://github.com/vectorize-io/hindsight/commit/41db2960))
**Bug Fixes**
- Fixed npx hindsight-control-plane failing to run. ([`0758827d`](https://github.com/vectorize-io/hindsight/commit/0758827d))
- Improved MCP compatibility by aligning the local MCP implementation with the server and removing the deprecated stateless parameter. ([`ea8163c5`](https://github.com/vectorize-io/hindsight/commit/ea8163c5))
- Fixed Docker startup failures when using named Docker volumes. ([`ac739487`](https://github.com/vectorize-io/hindsight/commit/ac739487))
- Prevented reranker crashes when an upstream provider returns an error. ([`58c4d657`](https://github.com/vectorize-io/hindsight/commit/58c4d657))
- Improved accuracy of fact temporal ordering by reducing per-fact time offsets. ([`c3ef1555`](https://github.com/vectorize-io/hindsight/commit/c3ef1555))
- Client timeout settings are now properly respected. ([`dcaa9f14`](https://github.com/vectorize-io/hindsight/commit/dcaa9f14))
- Fixed documents not being tracked when fact extraction returns zero facts. ([`f78278ea`](https://github.com/vectorize-io/hindsight/commit/f78278ea))
## [0.4.12](https://github.com/vectorize-io/hindsight/releases/tag/v0.4.12)
**Features**
- Accept and ingest PDFs, images, and common Office documents as inputs. ([`224b7b74`](https://github.com/vectorize-io/hindsight/commit/224b7b74))
- Add the Iris file parser for improved document parsing support. ([`7eafba66`](https://github.com/vectorize-io/hindsight/commit/7eafba66))
- Add async Retain support via provider Batch APIs (e.g., OpenAI and Groq) for higher-throughput ingestion. ([`40d42c58`](https://github.com/vectorize-io/hindsight/commit/40d42c58))
- Allow Recall to return chunks only (no memories) by setting max_tokens=0. ([`7dad9da0`](https://github.com/vectorize-io/hindsight/commit/7dad9da0))
- Add a Go client SDK for the Hindsight API. ([`2a47389f`](https://github.com/vectorize-io/hindsight/commit/2a47389f))
- Add support for the pgvectorscale (DiskANN) vector index backend. ([`95c42204`](https://github.com/vectorize-io/hindsight/commit/95c42204))
- Add support for Azure pg_diskann vector indexing. ([`476726c2`](https://github.com/vectorize-io/hindsight/commit/476726c2))
**Improvements**
- Improve reliability of async batch Retain when ingesting large payloads. ([`aefb3fcf`](https://github.com/vectorize-io/hindsight/commit/aefb3fcf))
- Improve AI SDK tooling to make it easier to work with Hindsight programmatically. ([`d06a0259`](https://github.com/vectorize-io/hindsight/commit/d06a0259))
**Bug Fixes**
- Ensure document tags are preserved when using the async Retain flow. ([`b4b5c44a`](https://github.com/vectorize-io/hindsight/commit/b4b5c44a))
- Fix OpenClaw ingestion failures for very large content (E2BIG). ([`6bad6673`](https://github.com/vectorize-io/hindsight/commit/6bad6673))
- Harden OpenClaw behavior (safer shell usage, better HTTP mode handling, and more reliable initialization), including per-user banks support. ([`c4610130`](https://github.com/vectorize-io/hindsight/commit/c4610130))
- Improve Python client async API consistency and reduce connection drop issues via keepalive timeout fixes. ([`8114ef44`](https://github.com/vectorize-io/hindsight/commit/8114ef44))
## [0.4.11](https://github.com/vectorize-io/hindsight/releases/tag/v0.4.11)
**Features**
- Added support for LiteLLM SDK as an embeddings and reranking provider. ([`e408b7e`](https://github.com/vectorize-io/hindsight/commit/e408b7e))
- Expanded Postgres search support with additional text/vector extensions, including TimescaleDB pg_textsearch and vchord/pgvector options. ([`d871c30`](https://github.com/vectorize-io/hindsight/commit/d871c30))
- Added hierarchical configuration scopes (system, tenant, bank) for more flexible multi-tenant setup and overrides. ([`8d731f2`](https://github.com/vectorize-io/hindsight/commit/8d731f2))
- Added reverse proxy/base-path support for running Hindsight behind a proxy. ([`93ddd41`](https://github.com/vectorize-io/hindsight/commit/93ddd41))
- Added MCP tools to create, read, update, and delete mental models. ([`f641b30`](https://github.com/vectorize-io/hindsight/commit/f641b30))
- Added a "docs" skill for agents/tools to access documentation-oriented capabilities. ([`dd1e098`](https://github.com/vectorize-io/hindsight/commit/dd1e098))
- Added an OpenClaw configuration option to skip recall/retain for specific providers. ([`fb7be3e`](https://github.com/vectorize-io/hindsight/commit/fb7be3e))
**Improvements**
- Improved LiteLLM gateway model configuration for more reliable provider/model selection. ([`7d95a00`](https://github.com/vectorize-io/hindsight/commit/7d95a00))
- Exposed actual LLM token usage in retain results to improve cost/usage visibility. ([`83ca669`](https://github.com/vectorize-io/hindsight/commit/83ca669))
- Added user-initiated attribution to request context to improve async task and usage attribution. ([`90be7c6`](https://github.com/vectorize-io/hindsight/commit/90be7c6))
- Added OpenTelemetry tracing for improved request traceability and observability. ([`69dec8e`](https://github.com/vectorize-io/hindsight/commit/69dec8e))
- Helm chart: split TEI embedding and reranker into separate deployments for independent scaling and rollout. ([`43f9a8b`](https://github.com/vectorize-io/hindsight/commit/43f9a8b))
- Helm chart: added PodDisruptionBudgets and per-component affinity controls for more resilient scheduling. ([`9943957`](https://github.com/vectorize-io/hindsight/commit/9943957))
**Bug Fixes**
- Fixed a recursion issue in memory retention that could cause failures or runaway memory usage. ([`4f11210`](https://github.com/vectorize-io/hindsight/commit/4f11210))
- Fixed Reflect API serialization/schema issues for "based_on" so reflections are returned and stored correctly. ([`f9a8a8e`](https://github.com/vectorize-io/hindsight/commit/f9a8a8e))
- Improved MCP server compatibility by allowing extra tool arguments when appropriate and fixing bank ID resolution priority. ([`7ee229b`](https://github.com/vectorize-io/hindsight/commit/7ee229b))
- Added missing trust_code environment configuration support. ([`60574ee`](https://github.com/vectorize-io/hindsight/commit/60574ee))
- Hardened the MCP server with fixes to routing/validation and more accurate usage metering. ([`e798979`](https://github.com/vectorize-io/hindsight/commit/e798979))
- Fixed the slim Docker image to include tiktoken to prevent runtime tokenization errors. ([`6eec83b`](https://github.com/vectorize-io/hindsight/commit/6eec83b))
- Fixed MCP operations not being tracked correctly for usage metering. ([`888b50d`](https://github.com/vectorize-io/hindsight/commit/888b50d))
- Helm chart: fixed GKE deployments overriding the configured HINDSIGHT_API_PORT. ([`03f47e2`](https://github.com/vectorize-io/hindsight/commit/03f47e2))
## [0.4.10](https://github.com/vectorize-io/hindsight/releases/tag/v0.4.10)
**Features**
- Provided a slimmer Docker distribution to reduce image size and speed up pulls. ([`f648178`](https://github.com/vectorize-io/hindsight/commit/f648178))
- Added Markdown support in Reflect and Mental Models content. ([`c4ef090`](https://github.com/vectorize-io/hindsight/commit/c4ef090))
- Added built-in Supabase tenant extension for running Hindsight with Supabase-backed multi-tenancy. ([`e99ee0f`](https://github.com/vectorize-io/hindsight/commit/e99ee0f))
- Added TenantExtension authentication support to the MCP endpoint. ([`fedfb49`](https://github.com/vectorize-io/hindsight/commit/fedfb49))
**Improvements**
- Improved MCP tool availability/routing based on the endpoint being used. ([`d90588b`](https://github.com/vectorize-io/hindsight/commit/d90588b))
**Bug Fixes**
- Stopped logging database usernames and passwords to prevent credential leaks in logs. ([`c568094`](https://github.com/vectorize-io/hindsight/commit/c568094))
- Fixed OpenClaw sessions wiping memory on each new session. ([`981cf60`](https://github.com/vectorize-io/hindsight/commit/981cf60))
- Fixed hindsight-embed profiles not loading correctly. ([`0430588`](https://github.com/vectorize-io/hindsight/commit/0430588))
- Fixed tagged directives so they correctly apply to tagged mental models. ([`278718d`](https://github.com/vectorize-io/hindsight/commit/278718d))
- Fixed a cast error that could cause failures at runtime. ([`093ecff`](https://github.com/vectorize-io/hindsight/commit/093ecff))
**Other**
- Added a docker-compose example to simplify local deployment and testing. ([`5179d5f`](https://github.com/vectorize-io/hindsight/commit/5179d5f))
## [0.4.9](https://github.com/vectorize-io/hindsight/releases/tag/v0.4.9)
**Features**
- New AI SDK integration. ([`7e339e1`](https://github.com/vectorize-io/hindsight/commit/7e339e1))
- Add a Python SDK for running Hindsight in embedded mode (HindsightEmbedded). ([`d3302c9`](https://github.com/vectorize-io/hindsight/commit/d3302c9))
- Add streaming support to the hindsight-litellm wrappers. ([`665877b`](https://github.com/vectorize-io/hindsight/commit/665877b))
- Add OpenClaw support for connecting to an external Hindsight API and using dynamic per-channel memory banks. ([`6b34692`](https://github.com/vectorize-io/hindsight/commit/6b34692))
**Improvements**
- Improve the mental models experience in the control plane UI. ([`7097716`](https://github.com/vectorize-io/hindsight/commit/7097716))
- Reduce noisy Hugging Face logging output. ([`34d9188`](https://github.com/vectorize-io/hindsight/commit/34d9188))
**Bug Fixes**
- Improve recall endpoint reliability by handling timeouts correctly and rejecting overly long queries. ([`dd621a6`](https://github.com/vectorize-io/hindsight/commit/dd621a6))
- Improve /reflect behavior with Claude Code and Codex providers. ([`a43d208`](https://github.com/vectorize-io/hindsight/commit/a43d208))
- Fix OpenClaw shell argument escaping for more reliable command execution. ([`63e2964`](https://github.com/vectorize-io/hindsight/commit/63e2964))
## [0.4.8](https://github.com/vectorize-io/hindsight/releases/tag/v0.4.8)
**Features**
- Added profile support for `hindsight-embed`, enabling separate embedding configurations/workspaces. ([`6c7f057`](https://github.com/vectorize-io/hindsight/commit/6c7f057))
- Added support for additional LLM backends, including OpenAI Codex and Claude Code. ([`539190b`](https://github.com/vectorize-io/hindsight/commit/539190b))
**Improvements**
- Enhanced OpenClaw and `hindsight-embed` parameter/config options for easier configuration and better defaults. ([`749478d`](https://github.com/vectorize-io/hindsight/commit/749478d))
- Added OpenClaw plugin configuration options to select LLM provider and model. ([`8564135`](https://github.com/vectorize-io/hindsight/commit/8564135))
- Server now prints its version during startup to simplify debugging and support requests. ([`1499ce5`](https://github.com/vectorize-io/hindsight/commit/1499ce5))
- Improved tracing/debuggability by propagating request context through asynchronous background tasks. ([`44d9125`](https://github.com/vectorize-io/hindsight/commit/44d9125))
- Added stronger validation and context for mental model create/refresh operations to prevent invalid requests. ([`35127d5`](https://github.com/vectorize-io/hindsight/commit/35127d5))
**Bug Fixes**
- Improved embedding CLI experience with richer logs and isolated profiles to avoid cross-contamination between runs. ([`794a743`](https://github.com/vectorize-io/hindsight/commit/794a743))
- Operation validation now runs correctly in the worker process, preventing invalid background operations from slipping through. ([`96f0e54`](https://github.com/vectorize-io/hindsight/commit/96f0e54))
- Fixed unreliable behavior when using a custom PostgreSQL schema. ([`3825506`](https://github.com/vectorize-io/hindsight/commit/3825506))
## [0.4.7](https://github.com/vectorize-io/hindsight/releases/tag/v0.4.7)
**Features**
- Add extension hooks to validate and customize mental model operations. ([`9c3fda7`](https://github.com/vectorize-io/hindsight/commit/9c3fda7))
- Add support for using an external embedding API provider in OpenClaw plugin (with additional OpenClaw compatibility fixes). ([`4b57b82`](https://github.com/vectorize-io/hindsight/commit/4b57b82))
**Improvements**
- Speed up container startup by preloading the tiktoken encoding during Docker image builds. ([`039944c`](https://github.com/vectorize-io/hindsight/commit/039944c))
**Bug Fixes**
- Prevent PostgreSQL insert failures by stripping null bytes from text fields before saving. ([`ef9d3a1`](https://github.com/vectorize-io/hindsight/commit/ef9d3a1))
- Fix worker schema selection so it uses the correct default database schema. ([`d788a55`](https://github.com/vectorize-io/hindsight/commit/d788a55))
- Honor an already-set HINDSIGHT_API_DATABASE_URL instead of overwriting it in the hindsight-embed workflow. ([`f0cb192`](https://github.com/vectorize-io/hindsight/commit/f0cb192))
## [0.4.6](https://github.com/vectorize-io/hindsight/releases/tag/v0.4.6)
**Improvements**
- Improved OpenClaw configuration setup to make embedding integration easier to configure. ([`27498f9`](https://github.com/vectorize-io/hindsight/commit/27498f9))
**Bug Fixes**
- Fixed OpenClaw embedding version binding/versioning to prevent mismatches when using the embed integration. ([`1163b1f`](https://github.com/vectorize-io/hindsight/commit/1163b1f))
## [0.4.5](https://github.com/vectorize-io/hindsight/releases/tag/v0.4.5)
**Bug Fixes**
- Fixed occasional failures when retaining memories asynchronously with timestamps. ([`cbb8fc6`](https://github.com/vectorize-io/hindsight/commit/cbb8fc6))
## [0.4.4](https://github.com/vectorize-io/hindsight/releases/tag/v0.4.4)
**Bug Fixes**
- Fixed async “retain” operations failing when a timestamp is provided. ([`35f0984`](https://github.com/vectorize-io/hindsight/commit/35f0984))
- Corrected the OpenClaw daemon integration name to “openclaw” (previously “openclawd”). ([`b364bc3`](https://github.com/vectorize-io/hindsight/commit/b364bc3))
## [0.4.3](https://github.com/vectorize-io/hindsight/releases/tag/v0.4.3)
**Features**
- Add Vertex AI as a supported LLM provider. ([`c2ac7d0`](https://github.com/vectorize-io/hindsight/commit/c2ac7d0), [`49ae55a`](https://github.com/vectorize-io/hindsight/commit/49ae55a))
- Add Bearer token authentication for MCP and propagate tenant authentication across MCP requests. ([`0da77ce`](https://github.com/vectorize-io/hindsight/commit/0da77ce))
**Improvements**
- CLI: add a --wait flag for consolidate and a --date filter for listing documents. ([`ff20bf9`](https://github.com/vectorize-io/hindsight/commit/ff20bf9))
**Bug Fixes**
- Fix worker polling deadlocks to prevent background processing from stalling. ([`f4f86e3`](https://github.com/vectorize-io/hindsight/commit/f4f86e3))
- Improve reliability of Docker builds by retrying ML model downloads. ([`ecc590c`](https://github.com/vectorize-io/hindsight/commit/ecc590c))
- Fix tenant authentication handling for internal background tasks and ensure the control-plane forwards required auth to the dataplane. ([`03bf13e`](https://github.com/vectorize-io/hindsight/commit/03bf13e))
- Ensure tenant database migrations run at startup and workers use the correct tenant schema context. ([`657fe02`](https://github.com/vectorize-io/hindsight/commit/657fe02))
- Fix control-plane graph endpoint errors when upstream data is missing. ([`751f99a`](https://github.com/vectorize-io/hindsight/commit/751f99a))
**Other**
- Rename the default bot/user identity from "moltbot" to "openclaw". ([`728ce13`](https://github.com/vectorize-io/hindsight/commit/728ce13))
## [0.4.2](https://github.com/vectorize-io/hindsight/releases/tag/v0.4.2)
**Features**
- Added Clawdbot/Moltbot/OpenClaw integration. ([`12e9a3d`](https://github.com/vectorize-io/hindsight/commit/12e9a3d))
**Improvements**
- Added additional configuration options to control LLM retry behavior. ([`3f211f0`](https://github.com/vectorize-io/hindsight/commit/3f211f0))
- Added real-time logs showing a detailed timing breakdown during consolidation runs. ([`8781c9f`](https://github.com/vectorize-io/hindsight/commit/8781c9f))
**Bug Fixes**
- Fixed hindsight-embed crashing on macOS. ([`c16ccc2`](https://github.com/vectorize-io/hindsight/commit/c16ccc2))
## [0.4.1](https://github.com/vectorize-io/hindsight/releases/tag/v0.4.1)
**Features**
- Added support for using a non-default PostgreSQL schema by default. ([`2b72e1f`](https://github.com/vectorize-io/hindsight/commit/2b72e1f))
**Improvements**
- Improved memory consolidation performance (benchmarking and optimizations). ([`b43ef98`](https://github.com/vectorize-io/hindsight/commit/b43ef98))
**Bug Fixes**
- Fixed the /version endpoint returning an incorrect version. ([`cfcc23c`](https://github.com/vectorize-io/hindsight/commit/cfcc23c))
- Fixed mental model search failing due to UUID type mismatch after text-ID migration. ([`94cc0a1`](https://github.com/vectorize-io/hindsight/commit/94cc0a1))
- Added safer PyTorch device detection to prevent crashes on some environments. ([`67c4788`](https://github.com/vectorize-io/hindsight/commit/67c4788))
- Fixed Python packages exposing an incorrect __version__ value. ([`fccbdfe`](https://github.com/vectorize-io/hindsight/commit/fccbdfe))
## [0.4.0](https://github.com/vectorize-io/hindsight/releases/tag/v0.4.0)
**Observations**, **Mental Models**, new **Agentic Reflect** and Directives, read the announcement.
**Features**
- Added support for providing a custom prompt for memory extraction. ([`3172e99`](https://github.com/vectorize-io/hindsight/commit/3172e99))
- Expanded the LiteLLM integration with async retain/reflect support, cleaner API, and support for tags/mission (including passing API keys correctly). ([`1d4879a`](https://github.com/vectorize-io/hindsight/commit/1d4879a))
- Added a new worker service to run background tasks at scale. ([`4c79240`](https://github.com/vectorize-io/hindsight/commit/4c79240))
- MCP retain now supports timestamps. ([`b378f68`](https://github.com/vectorize-io/hindsight/commit/b378f68))
- Added support for installing skills via `npx add-skill`. ([`ec22317`](https://github.com/vectorize-io/hindsight/commit/ec22317))
**Improvements**
- CLI retain-files now accepts more file types. ([`1eeced3`](https://github.com/vectorize-io/hindsight/commit/1eeced3))
**Bug Fixes**
- Fixed a macOS crash in the embed daemon caused by an XPC connection issue. ([`e5fc6ee`](https://github.com/vectorize-io/hindsight/commit/e5fc6ee))
- Fixed occasional extraction in the wrong language. ([`87d4a36`](https://github.com/vectorize-io/hindsight/commit/87d4a36))
- Fixed PyTorch model initialization issues that could cause startup failures (meta tensor/init problems). ([`ddaa5f5`](https://github.com/vectorize-io/hindsight/commit/ddaa5f5))
**Features**
- Add memory tags so you can label and filter memories during recall/reflect. ([`20c8f8b`](https://github.com/vectorize-io/hindsight/commit/20c8f8b))
- Allow choosing different AI providers/models per operation. ([`e6709d5`](https://github.com/vectorize-io/hindsight/commit/e6709d5))
- Add Cohere support for embeddings and reranking. ([`4de0730`](https://github.com/vectorize-io/hindsight/commit/4de0730))
- Add configurable embedding dimensions and OpenAI embeddings support. ([`70de23e`](https://github.com/vectorize-io/hindsight/commit/70de23e))
- Support custom base URLs for OpenAI-style embeddings and Cohere endpoints. ([`fa53917`](https://github.com/vectorize-io/hindsight/commit/fa53917))
- Add LiteLLM gateway support for routing LLM/embedding requests. ([`d47c8a2`](https://github.com/vectorize-io/hindsight/commit/d47c8a2))
- Add multilingual content support to improve handling and retrieval across languages. ([`c65c6a9`](https://github.com/vectorize-io/hindsight/commit/c65c6a9))
- Add delete memory bank capability. ([`4b82d2d`](https://github.com/vectorize-io/hindsight/commit/4b82d2d))
- Add backup/restore tooling for memory banks. ([`67b273d`](https://github.com/vectorize-io/hindsight/commit/67b273d))
**Improvements**
- Add retention modes to control how memories are extracted and stored. ([`fb31a35`](https://github.com/vectorize-io/hindsight/commit/fb31a35))
- Add offline (optional) database migrations to support restricted/air-gapped deployments. ([`233bd2e`](https://github.com/vectorize-io/hindsight/commit/233bd2e))
- Add database connection configuration options for more flexible deployments. ([`33fac2c`](https://github.com/vectorize-io/hindsight/commit/33fac2c))
- Load .env automatically on startup to simplify configuration. ([`c06d9b4`](https://github.com/vectorize-io/hindsight/commit/c06d9b4))
- Expose an operation ID from retain requests so async/background processing can be tracked. ([`1dacd0e`](https://github.com/vectorize-io/hindsight/commit/1dacd0e))
- Add per-request LLM token usage metrics for monitoring and cost tracking. ([`29a542d`](https://github.com/vectorize-io/hindsight/commit/29a542d))
- Add LLM call latency metrics for performance monitoring. ([`5e1f13e`](https://github.com/vectorize-io/hindsight/commit/5e1f13e))
- Include tenant in metrics labels for better multi-tenant observability. ([`1ffc2a4`](https://github.com/vectorize-io/hindsight/commit/1ffc2a4))
- Add async processing option to MCP retain tool for background retention workflows. ([`37fc7fb`](https://github.com/vectorize-io/hindsight/commit/37fc7fb))
**Bug Fixes**
- Fix extension loading in multi-worker deployments so all workers load extensions correctly. ([`f5f3fca`](https://github.com/vectorize-io/hindsight/commit/f5f3fca))
- Improve recall performance by batching recall queries. ([`5991308`](https://github.com/vectorize-io/hindsight/commit/5991308))
- Improve retrieval quality and stability for large memory banks (graph/MPFP retrieval fixes). ([`6232e69`](https://github.com/vectorize-io/hindsight/commit/6232e69))
- Fix entities list being limited to 100 entities. ([`26bf571`](https://github.com/vectorize-io/hindsight/commit/26bf571))
- Fix UI only showing the first 1000 memories. ([`67c1a42`](https://github.com/vectorize-io/hindsight/commit/67c1a42))
- Fix duplicated causal relationships and improve token usage during processing. ([`49e233c`](https://github.com/vectorize-io/hindsight/commit/49e233c))
- Improve causal link detection accuracy. ([`2a00df0`](https://github.com/vectorize-io/hindsight/commit/2a00df0))
- Make retain max completion tokens configurable to prevent truncation issues. ([`7715a51`](https://github.com/vectorize-io/hindsight/commit/7715a51))
- Fix Python SDK not sending the Authorization header, preventing authenticated requests. ([`39e3f7c`](https://github.com/vectorize-io/hindsight/commit/39e3f7c))
- Fix stats endpoint missing tenant authentication in multi-tenant setups. ([`d6ff191`](https://github.com/vectorize-io/hindsight/commit/d6ff191))
- Fix embedding dimension handling for tenant schemas in multi-tenant databases. ([`6fe9314`](https://github.com/vectorize-io/hindsight/commit/6fe9314))
- Fix Groq free-tier compatibility so requests work correctly. ([`d899d18`](https://github.com/vectorize-io/hindsight/commit/d899d18))
- Fix security vulnerability (qs / CVE-2025-15284). ([`b3becb6`](https://github.com/vectorize-io/hindsight/commit/b3becb6))
- Restore MCP tools for listing and creating memory banks. ([`9fd5679`](https://github.com/vectorize-io/hindsight/commit/9fd5679))
## [0.2.0](https://github.com/vectorize-io/hindsight/releases/tag/v0.2.0)
**Features**
- Add additional model provider support, including Anthropic Claude and LM Studio. ([`787ed60`](https://github.com/vectorize-io/hindsight/commit/787ed60))
- Add multi-bank access and new MCP tools for interacting with multiple memory banks via MCP. ([`6b5f593`](https://github.com/vectorize-io/hindsight/commit/6b5f593))
- Allow supplying custom entities when retaining memories via the retain endpoint. ([`dd59bc8`](https://github.com/vectorize-io/hindsight/commit/dd59bc8))
- Enhance the /reflect endpoint with max_tokens control and optional structured output responses. ([`d49e820`](https://github.com/vectorize-io/hindsight/commit/d49e820))
**Improvements**
- Improve local LLM support for reasoning-capable models and streamline Docker startup for local deployments. ([`eea0f27`](https://github.com/vectorize-io/hindsight/commit/eea0f27))
- Support operation validator extensions and return proper HTTP errors when validation fails. ([`ce45d30`](https://github.com/vectorize-io/hindsight/commit/ce45d30))
- Add configurable observation thresholds to control when observations are created/updated. ([`54e2df0`](https://github.com/vectorize-io/hindsight/commit/54e2df0))
- Improve graph visualization to the control plane for exploring memory relationships. ([`1a62069`](https://github.com/vectorize-io/hindsight/commit/1a62069))
**Bug Fixes**
- Fix MCP server lifecycle handling so MCP lifespan is correctly tied to the FastAPI app lifespan. ([`6b78f7d`](https://github.com/vectorize-io/hindsight/commit/6b78f7d))
## [0.1.15](https://github.com/vectorize-io/hindsight/releases/tag/v0.1.15)
**Features**
- Add the ability to delete documents from the web UI. ([`f7ff32d`](https://github.com/vectorize-io/hindsight/commit/f7ff32d))
**Improvements**
- Improve the API health check endpoint and update the generated client APIs/types accordingly. ([`e06a612`](https://github.com/vectorize-io/hindsight/commit/e06a612))
## [0.1.14](https://github.com/vectorize-io/hindsight/releases/tag/v0.1.14)
**Bug Fixes**
- Fixes the embedded “get-skill” installer so installing skills works correctly. ([`0b352d1`](https://github.com/vectorize-io/hindsight/commit/0b352d1))
## [0.1.13](https://github.com/vectorize-io/hindsight/releases/tag/v0.1.13)
**Improvements**
- Improve reliability by surfacing task handler failures so retries can occur when processing fails. ([`904ea4d`](https://github.com/vectorize-io/hindsight/commit/904ea4d))
- Revamp the hindsight-embed component architecture, including a new daemon/client model and CLI updates for embedding workflows. ([`e6511e7`](https://github.com/vectorize-io/hindsight/commit/e6511e7))
**Bug Fixes**
- Fix memory retention so timestamps are correctly taken into account. ([`234d426`](https://github.com/vectorize-io/hindsight/commit/234d426))
## [0.1.12](https://github.com/vectorize-io/hindsight/releases/tag/v0.1.12)
**Features**
- Added an extensions system for plugging in new operations/skills (including built-in tenant support). ([`2a0c490`](https://github.com/vectorize-io/hindsight/commit/2a0c490))
- Introduced the hindsight-embed tool and a native agentic skill for embedding/agent workflows. ([`da44a5e`](https://github.com/vectorize-io/hindsight/commit/da44a5e))
**Improvements**
- Improved reliability when parsing LLM JSON by retrying on parse errors and adding clearer diagnostics. ([`a831a7b`](https://github.com/vectorize-io/hindsight/commit/a831a7b))
**Bug Fixes**
- Fixed structured-output support for Ollama-based LLM providers. ([`32bca12`](https://github.com/vectorize-io/hindsight/commit/32bca12))
- Adjusted LLM validation to cap max completion tokens at 100 to prevent validation failures. ([`b94b5cf`](https://github.com/vectorize-io/hindsight/commit/b94b5cf))
## [0.1.11](https://github.com/vectorize-io/hindsight/releases/tag/v0.1.11)
**Bug Fixes**
- Fixed the standalone Docker image and control plane standalone build process so standalone deployments build correctly. ([`2948cb6`](https://github.com/vectorize-io/hindsight/commit/2948cb6))
## [0.1.10](https://github.com/vectorize-io/hindsight/releases/tag/v0.1.10)
*This release contains internal maintenance and infrastructure changes only.*
## [0.1.9](https://github.com/vectorize-io/hindsight/releases/tag/v0.1.9)
**Features**
- Simplified local MCP installation and added a standalone UI option for easier setup. ([`1c6acc3`](https://github.com/vectorize-io/hindsight/commit/1c6acc3))
**Bug Fixes**
- Fixed the standalone Docker image so it builds and starts reliably. ([`b52eb90`](https://github.com/vectorize-io/hindsight/commit/b52eb90))
- Improved Docker runtime reliability by adding required system utilities (procps). ([`ae80876`](https://github.com/vectorize-io/hindsight/commit/ae80876))
## [0.1.8](https://github.com/vectorize-io/hindsight/releases/tag/v0.1.8)
**Bug Fixes**
- Fix bank list responses when a bank has no name. ([`04f01ab`](https://github.com/vectorize-io/hindsight/commit/04f01ab))
- Fix failures when retaining memories asynchronously. ([`63f5138`](https://github.com/vectorize-io/hindsight/commit/63f5138))
- Fix a race condition in the bank selector when switching banks. ([`e468a4e`](https://github.com/vectorize-io/hindsight/commit/e468a4e))
## [0.1.7](https://github.com/vectorize-io/hindsight/releases/tag/v0.1.7)
*This release contains internal maintenance and infrastructure changes only.*
## [0.1.6](https://github.com/vectorize-io/hindsight/releases/tag/v0.1.6)
**Features**
- Added support for the Gemini 3 Pro and GPT-5.2 models. ([`bb1f9cb`](https://github.com/vectorize-io/hindsight/commit/bb1f9cb))
- Added a local MCP server option for running/connecting to Hindsight via MCP without a separate remote service. ([`7dd6853`](https://github.com/vectorize-io/hindsight/commit/7dd6853))
**Improvements**
- Updated the Postgres/pg0 dependency to a newer 0.11.x series for improved compatibility and stability. ([`47be07f`](https://github.com/vectorize-io/hindsight/commit/47be07f))
## [0.1.5](https://github.com/vectorize-io/hindsight/releases/tag/v0.1.5)
**Features**
- Added LiteLLM integration so Hindsight can capture and manage memories from LiteLLM-based LLM calls. ([`dfccbf2`](https://github.com/vectorize-io/hindsight/commit/dfccbf2))
- Added an optional graph-based retriever (MPFP) to improve recall by leveraging relationships between memories. ([`7445cef`](https://github.com/vectorize-io/hindsight/commit/7445cef))
**Improvements**
- Switched the embedded Postgres layer to pg0-embedded for a smoother local/standalone experience. ([`94c2b85`](https://github.com/vectorize-io/hindsight/commit/94c2b85))
**Bug Fixes**
- Fixed repeated retries on 400 errors from the LLM, preventing unnecessary request loops and failures. ([`70983f5`](https://github.com/vectorize-io/hindsight/commit/70983f5))
- Fixed recall trace visualization in the control plane so search/recall debugging displays correctly. ([`922164e`](https://github.com/vectorize-io/hindsight/commit/922164e))
- Fixed the CLI installer to make installation more reliable. ([`158a6aa`](https://github.com/vectorize-io/hindsight/commit/158a6aa))
- Updated Next.js to patch security vulnerabilities (CVE-2025-55184, CVE-2025-55183). ([`f018cc5`](https://github.com/vectorize-io/hindsight/commit/f018cc5))
## [0.1.3](https://github.com/vectorize-io/hindsight/releases/tag/v0.1.3)
**Improvements**
- Improved CLI and UI branding/polish, including new banner/logo assets and updated interface styling. ([`fa554b8`](https://github.com/vectorize-io/hindsight/commit/fa554b8))
## [0.1.2](https://github.com/vectorize-io/hindsight/releases/tag/v0.1.2)
**Bug Fixes**
- Fixed the standalone Docker image so it builds/runs correctly. ([`1056a20`](https://github.com/vectorize-io/hindsight/commit/1056a20))
@@ -1,11 +0,0 @@
---
hide_table_of_contents: true
---
# AI SDK Integration Changelog
Changelog for [`@vectorize-io/hindsight-ai-sdk`](https://www.npmjs.com/package/@vectorize-io/hindsight-ai-sdk) — memory integration for Vercel AI SDK.
For the source code, see [`hindsight-integrations/ai-sdk`](https://github.com/vectorize-io/hindsight/tree/main/hindsight-integrations/ai-sdk).
← [Back to main changelog](../index.md)
@@ -1,11 +0,0 @@
---
hide_table_of_contents: true
---
# Chat SDK Integration Changelog
Changelog for [`@vectorize-io/hindsight-chat`](https://www.npmjs.com/package/@vectorize-io/hindsight-chat) — memory integration for Vercel Chat SDK.
For the source code, see [`hindsight-integrations/chat`](https://github.com/vectorize-io/hindsight/tree/main/hindsight-integrations/chat).
← [Back to main changelog](../index.md)
@@ -1,11 +0,0 @@
---
hide_table_of_contents: true
---
# CrewAI Integration Changelog
Changelog for [`hindsight-crewai`](https://pypi.org/project/hindsight-crewai/) — persistent memory for CrewAI agents.
For the source code, see [`hindsight-integrations/crewai`](https://github.com/vectorize-io/hindsight/tree/main/hindsight-integrations/crewai).
← [Back to main changelog](../index.md)
@@ -1,11 +0,0 @@
---
hide_table_of_contents: true
---
# LiteLLM Integration Changelog
Changelog for [`hindsight-litellm`](https://pypi.org/project/hindsight-litellm/) — universal LLM memory integration via LiteLLM.
For the source code, see [`hindsight-integrations/litellm`](https://github.com/vectorize-io/hindsight/tree/main/hindsight-integrations/litellm).
← [Back to main changelog](../index.md)
@@ -1,11 +0,0 @@
---
hide_table_of_contents: true
---
# OpenClaw Integration Changelog
Changelog for [`@vectorize-io/hindsight-openclaw`](https://www.npmjs.com/package/@vectorize-io/hindsight-openclaw) — Hindsight memory plugin for OpenClaw.
For the source code, see [`hindsight-integrations/openclaw`](https://github.com/vectorize-io/hindsight/tree/main/hindsight-integrations/openclaw).
← [Back to main changelog](../index.md)
@@ -1,11 +0,0 @@
---
hide_table_of_contents: true
---
# Pydantic AI Integration Changelog
Changelog for [`hindsight-pydantic-ai`](https://pypi.org/project/hindsight-pydantic-ai/) — persistent memory tools for Pydantic AI agents.
For the source code, see [`hindsight-integrations/pydantic-ai`](https://github.com/vectorize-io/hindsight/tree/main/hindsight-integrations/pydantic-ai).
← [Back to main changelog](../index.md)
@@ -79,12 +79,6 @@ hindsight memory retain my-bank "Meeting notes content..." --doc-id notes-2024-0
hindsight memory retain-files my-bank docs/
```
### Go
```go
# Section 'document-retain' not found in api/documents.go
```
## Update Documents
Re-retaining with the same document_id **replaces** the old content:
@@ -131,12 +125,6 @@ hindsight memory retain my-bank "Project deadline: March 31" --doc-id project-pl
hindsight memory retain my-bank "Project deadline: April 15 (extended)" --doc-id project-plan
```
### Go
```go
# Section 'document-update' not found in api/documents.go
```
## Get Document
Retrieve a document's original text and metadata. This is useful for expanding document context after a recall operation returns memories with document references.
@@ -191,12 +179,6 @@ console.log(`Created: ${doc.created_at}`);
hindsight document get my-bank meeting-2024-03-15
```
### Go
```go
# Section 'document-get' not found in api/documents.go
```
## Update Document
Update mutable fields on an existing document without re-processing the content. Currently supports updating `tags`.
@@ -243,12 +225,6 @@ hindsight document update-tags my-bank meeting-2024-03-15 --tags team-a --tags t
hindsight document update-tags my-bank meeting-2024-03-15
```
### Go
```go
# Section 'document-update' not found in api/documents.go
```
:::info Observations are re-consolidated
When tags change, any consolidated observations derived from the document's memories are invalidated and queued for re-consolidation under the new tags. Co-source memories from other documents that shared those observations are also reset.
## Delete Document
@@ -295,12 +271,6 @@ console.log(`Deleted ${deleteResult.memory_units_deleted} memories`);
hindsight document delete my-bank meeting-2024-03-15
```
### Go
```go
# Section 'document-delete' not found in api/documents.go
```
:::warning
Deleting a document permanently removes all memories extracted from it. This action cannot be undone.
## List Documents
@@ -403,12 +373,6 @@ hindsight document list my-bank --q report
hindsight document list my-bank --tags team-a --tags team-b
```
### Go
```go
# Section 'document-list' not found in api/documents.go
```
### Filtering Options
| Parameter | Description |
@@ -76,19 +76,13 @@ await client.retainBatch('my-bank', [
```bash
# Store a single fact
hindsight memory retain my-bank "Alice joined Google in March 2024 as a Senior ML Engineer"
hindsight retain my-bank "Alice joined Google in March 2024 as a Senior ML Engineer"
# Store from a file
hindsight memory retain-files my-bank conversation.txt --context "Daily standup"
hindsight retain my-bank --file conversation.txt --context "Daily standup"
# Store multiple files
hindsight memory retain-files my-bank docs/
```
### Go
```go
# Section 'main-retain' not found in api/main-methods.go
hindsight retain my-bank --files docs/*.md
```
**What happens:** Content is processed by an LLM to extract rich facts, identify entities, and build connections in a knowledge graph.
@@ -172,22 +166,16 @@ for (const [entityId, entity] of Object.entries(entityResults.entities || {})) {
```bash
# Basic search
hindsight memory recall my-bank "What does Alice do at Google?"
hindsight recall my-bank "What does Alice do at Google?"
# Search with options
hindsight memory recall my-bank "What happened last spring?" \
hindsight recall my-bank "What happened last spring?" \
--budget high \
--max-tokens 8192 \
--fact-type world,experience
--fact-type world
# Verbose output
hindsight memory recall my-bank "Tell me about Alice" -v
```
### Go
```go
# Section 'main-recall' not found in api/main-methods.go
# Verbose output (shows weights and sources)
hindsight recall my-bank "Tell me about Alice" -v
```
**What happens:** Four search strategies (semantic, keyword, graph, temporal) run in parallel, results are fused and reranked.
@@ -250,16 +238,13 @@ for (const fact of detailedResponse.based_on || []) {
```bash
# Basic reflect
hindsight memory reflect my-bank "Should we adopt TypeScript for our backend?"
hindsight reflect my-bank "Should we adopt TypeScript for our backend?"
# Verbose output (shows sources and observations)
hindsight reflect my-bank "What are Alice's strengths for the team lead role?" -v
# With higher reasoning budget
hindsight memory reflect my-bank "Analyze our tech stack" --budget high
```
### Go
```go
# Section 'main-reflect' not found in api/main-methods.go
hindsight reflect my-bank "Analyze our tech stack" --budget high
```
**What happens:** Memories and observations are recalled, bank disposition is applied, and the LLM reasons through the evidence to generate a response.
@@ -42,15 +42,9 @@ await client.createBank('my-bank');
hindsight bank create my-bank
```
### Go
```go
# Section 'create-bank' not found in api/memory-banks.go
```
## Bank Configuration
Each memory bank can be configured independently per operation. Configuration can be set via the [bank config API](#updating-configuration), the Control Plane UI, or [server-wide environment variables](../configuration.md).
Each memory bank can be configured independently per operation. Configuration can be set via the [bank config API](#updating-configuration), the [Control Plane UI](/), or [server-wide environment variables](/developer/configuration).
### retain_mission {#retain-configuration}
@@ -83,7 +77,7 @@ Maximum number of characters per chunk when splitting content for fact extractio
Default: `3000`
See [Retain configuration](../configuration.md#retain) for environment variable names and defaults.
See [Retain configuration](/developer/configuration#retain) for environment variable names and defaults.
### entity_labels {#entity-labels}
@@ -178,7 +172,7 @@ Total token budget for source facts included with observations in the consolidat
Per-observation token cap for source facts in the consolidation prompt. Each observation independently gets at most this many tokens of source facts, preventing a single observation with many source facts from consuming the entire budget. `-1` = unlimited. Leave unset to use the server default (`256`).
See [Observations configuration](../configuration.md#observations) for environment variable names and defaults.
See [Observations configuration](/developer/configuration#observations) for environment variable names and defaults.
### reflect_mission
@@ -220,22 +214,6 @@ await client.updateBankConfig('architect-bank', {
});
```
### CLI
```bash
hindsight bank create architect-bank \
--mission "You're a senior software architect - keep track of system designs, technology decisions, and architectural patterns. Prefer simplicity over cutting-edge." \
--skepticism 4 \
--literalism 4 \
--empathy 2
```
### Go
```go
# Section 'bank-with-disposition' not found in api/memory-banks.go
```
| Value | Behaviour |
|-------|-----------|
| `1` | Trusting — accepts information at face value |
@@ -322,24 +300,6 @@ await client.updateBankConfig('my-bank', {
});
```
### CLI
```bash
hindsight bank set-config my-bank \
--retain-mission "Always include technical decisions, API design choices, and architectural trade-offs. Ignore meeting logistics and social exchanges." \
--retain-extraction-mode verbose \
--observations-mission "Observations are stable facts about people and projects. Always include preferences, skills, and recurring patterns. Ignore one-off events." \
--disposition-skepticism 4 \
--disposition-literalism 4 \
--disposition-empathy 2
```
### Go
```go
# Section 'update-bank-config' not found in api/memory-banks.go
```
You can update any subset of fields — only the keys you provide are changed.
### Reading the Current Configuration
@@ -362,22 +322,6 @@ const { config, overrides } = await client.getBankConfig('my-bank');
// overrides — only fields overridden at the bank level
```
### CLI
```bash
# Returns resolved config (server defaults merged with bank overrides)
hindsight bank config my-bank
# Show only bank-specific overrides
hindsight bank config my-bank --overrides-only
```
### Go
```go
# Section 'get-bank-config' not found in api/memory-banks.go
```
The response distinguishes:
- **`config`** — the fully resolved configuration (server defaults merged with bank overrides)
- **`overrides`** — only the fields explicitly overridden for this bank
@@ -398,22 +342,9 @@ client.reset_bank_config("my-bank")
await client.resetBankConfig('my-bank');
```
### CLI
```bash
# Remove all bank-level overrides, reverting to server defaults
hindsight bank reset-config my-bank -y
```
### Go
```go
# Section 'reset-bank-config' not found in api/memory-banks.go
```
This removes all bank-level overrides. The bank reverts to server-wide defaults (set via environment variables).
You can also update configuration directly from the Control Plane UI — navigate to a bank and open the **Configuration** tab.
You can also update configuration directly from the [Control Plane UI](/) — navigate to a bank and open the **Configuration** tab.
---
@@ -460,21 +391,6 @@ const directive = await client.createDirective(
console.log(`Created directive: ${directive.id}`);
```
### CLI
```bash
# Create a directive (hard rule for reflect)
hindsight directive create "$BANK_ID" \
"Formal Language" \
"Always respond in formal English, avoiding slang and colloquialisms."
```
### Go
```go
# Section 'create-directive' not found in api/directives.go
```
### Listing Directives
### Python
@@ -498,19 +414,6 @@ for (const d of directives.items) {
}
```
### CLI
```bash
# List all directives in a bank
hindsight directive list "$BANK_ID"
```
### Go
```go
# Section 'list-directives' not found in api/directives.go
```
### Updating Directives
### Python
@@ -537,18 +440,6 @@ const updated = await client.updateDirective(BANK_ID, directiveId, {
console.log(`Directive active: ${updated.is_active}`);
```
### CLI
```bash
# Section 'update-directive' not found in api/directives.sh
```
### Go
```go
# Section 'update-directive' not found in api/directives.go
```
### Deleting Directives
### Python
@@ -568,18 +459,6 @@ client.delete_directive(
await client.deleteDirective(BANK_ID, directiveId);
```
### CLI
```bash
# Section 'delete-directive' not found in api/directives.sh
```
### Go
```go
# Section 'delete-directive' not found in api/directives.go
```
### Directives vs Disposition
| Aspect | Directives | Disposition |
@@ -59,34 +59,20 @@ result = client.create_mental_model(
print(f"Operation ID: {result.operation_id}")
```
### Node.js
```javascript
// Create a mental model (runs reflect in background)
const result = await client.createMentalModel(
BANK_ID,
'Team Communication Preferences',
'How does the team prefer to communicate?',
{ tags: ['team', 'communication'] },
);
// Returns an operation_id — check operations endpoint for completion
console.log(`Operation ID: ${result.operation_id}`);
```
### CLI
```bash
# Create a mental model (runs reflect in background)
hindsight mental-model create "$BANK_ID" \
"Team Communication Preferences" \
"How does the team prefer to communicate?"
```
# Create a mental model (async operation)
curl -X POST "http://localhost:8888/v1/default/banks/my-bank/mental-models" \
-H "Content-Type: application/json" \
-d '{
"name": "Team Communication Preferences",
"source_query": "How does the team prefer to communicate?",
"tags": ["team"]
}'
### Go
```go
# Section 'create-mental-model' not found in api/mental-models.go
# Response: {"operation_id": "op-123"}
# Use the operations endpoint to check completion
```
### Parameters
@@ -95,65 +81,12 @@ hindsight mental-model create "$BANK_ID" \
|-----------|------|----------|-------------|
| `name` | string | Yes | Human-readable name for the mental model |
| `source_query` | string | Yes | The query to run to generate content |
| `id` | string | No | Custom ID for the mental model (alphanumeric lowercase with hyphens). Auto-generated if omitted. |
| `tags` | list | No | Tags for filtering during retrieval |
| `max_tokens` | int | No | Maximum tokens for the mental model content |
| `trigger` | object | No | Trigger settings (see [Automatic Refresh](#automatic-refresh)) |
---
## Create with Custom ID
Assign a stable, human-readable ID to a mental model so you can retrieve or update it by name instead of relying on the auto-generated UUID:
### Python
```python
# Create a mental model with a specific custom ID
result_with_id = client.create_mental_model(
bank_id=BANK_ID,
name="Communication Policy",
source_query="What are the team's communication guidelines?",
id="communication-policy"
)
print(f"Created with custom ID: {result_with_id.operation_id}")
```
### Node.js
```javascript
// Create a mental model with a specific custom ID
const resultWithId = await client.createMentalModel(
BANK_ID,
'Communication Policy',
"What are the team's communication guidelines?",
{ id: 'communication-policy' },
);
console.log(`Created with custom ID: ${resultWithId.operation_id}`);
```
### CLI
```bash
# Create a mental model with a specific custom ID
hindsight mental-model create "$BANK_ID" \
"Communication Policy" \
"What are the team's communication guidelines?" \
--id communication-policy
```
### Go
```go
# Section 'create-mental-model-with-id' not found in api/mental-models.go
```
:::tip
Custom IDs must be lowercase alphanumeric and may contain hyphens (e.g. `team-policies`, `q4-status`). If a mental model with that ID already exists, the request is rejected.
---
## Automatic Refresh
Mental models can be configured to **automatically refresh** when observations are updated. This keeps them in sync with the latest knowledge without manual intervention.
@@ -181,34 +114,17 @@ result = client.create_mental_model(
print(f"Operation ID: {result.operation_id}")
```
### Node.js
```javascript
// Create a mental model with automatic refresh enabled
const result2 = await client.createMentalModel(
BANK_ID,
'Project Status',
'What is the current project status?',
{ trigger: { refreshAfterConsolidation: true } },
);
// This mental model will automatically refresh when observations are updated
console.log(`Operation ID: ${result2.operation_id}`);
```
### CLI
```bash
# Create a mental model and get its ID for subsequent operations
hindsight mental-model create "$BANK_ID" \
"Project Status" \
"What is the current project status?"
```
### Go
```go
# Section 'create-mental-model-with-trigger' not found in api/mental-models.go
# Create a mental model with automatic refresh enabled
curl -X POST "http://localhost:8888/v1/default/banks/my-bank/mental-models" \
-H "Content-Type: application/json" \
-d '{
"name": "Project Status",
"source_query": "What is the current project status?",
"trigger": {"refresh_after_consolidation": true}
}'
```
### When to Use Automatic Refresh
@@ -236,28 +152,10 @@ for mental_model in mental_models.items:
print(f"- {mental_model.name}: {mental_model.source_query}")
```
### Node.js
```javascript
// List all mental models in a bank
const mentalModels = await client.listMentalModels(BANK_ID);
for (const mm of mentalModels.items) {
console.log(`- ${mm.name}: ${mm.source_query}`);
}
```
### CLI
```bash
# List all mental models in a bank
hindsight mental-model list "$BANK_ID"
```
### Go
```go
# Section 'list-mental-models' not found in api/mental-models.go
curl "http://localhost:8888/v1/default/banks/my-bank/mental-models"
```
---
@@ -270,27 +168,10 @@ hindsight mental-model list "$BANK_ID"
# Section 'get-mental-model' not found in api/mental-models.py
```
### Node.js
```javascript
// Get a specific mental model
const mentalModel = await client.getMentalModel(BANK_ID, mentalModelId);
console.log(`Name: ${mentalModel.name}`);
console.log(`Content: ${mentalModel.content}`);
console.log(`Last refreshed: ${mentalModel.last_refreshed_at}`);
```
### CLI
```bash
# Section 'get-mental-model' not found in api/mental-models.sh
```
### Go
```go
# Section 'get-mental-model' not found in api/mental-models.go
curl "http://localhost:8888/v1/default/banks/my-bank/mental-models/{mental_model_id}"
```
### Response Fields
@@ -319,25 +200,10 @@ Re-run the source query to update the mental model with current knowledge:
# Section 'refresh-mental-model' not found in api/mental-models.py
```
### Node.js
```javascript
// Refresh a mental model to update with current knowledge
const refreshResult = await client.refreshMentalModel(BANK_ID, mentalModelId);
console.log(`Refresh operation ID: ${refreshResult.operation_id}`);
```
### CLI
```bash
# Section 'refresh-mental-model' not found in api/mental-models.sh
```
### Go
```go
# Section 'refresh-mental-model' not found in api/mental-models.go
curl -X POST "http://localhost:8888/v1/default/banks/my-bank/mental-models/{mental_model_id}/refresh"
```
Refreshing is useful when:
@@ -357,28 +223,12 @@ Update the mental model's name:
# Section 'update-mental-model' not found in api/mental-models.py
```
### Node.js
```javascript
// Update a mental model's metadata
const updated = await client.updateMentalModel(BANK_ID, mentalModelId, {
name: 'Updated Team Communication Preferences',
trigger: { refresh_after_consolidation: true },
});
console.log(`Updated name: ${updated.name}`);
```
### CLI
```bash
# Section 'update-mental-model' not found in api/mental-models.sh
```
### Go
```go
# Section 'update-mental-model' not found in api/mental-models.go
curl -X PATCH "http://localhost:8888/v1/default/banks/my-bank/mental-models/{mental_model_id}" \
-H "Content-Type: application/json" \
-d '{"name": "Updated Team Communication Preferences"}'
```
---
@@ -391,23 +241,10 @@ console.log(`Updated name: ${updated.name}`);
# Section 'delete-mental-model' not found in api/mental-models.py
```
### Node.js
```javascript
// Delete a mental model
await client.deleteMentalModel(BANK_ID, mentalModelId);
```
### CLI
```bash
# Section 'delete-mental-model' not found in api/mental-models.sh
```
### Go
```go
# Section 'delete-mental-model' not found in api/mental-models.go
curl -X DELETE "http://localhost:8888/v1/default/banks/my-bank/mental-models/{mental_model_id}"
```
---
@@ -450,30 +287,6 @@ Every time a mental model's content changes (via refresh or manual update), the
# Section 'get-mental-model-history' not found in api/mental-models.py
```
### Node.js
```javascript
// Get the change history of a mental model
const history = await client.getMentalModelHistory(BANK_ID, mentalModelId);
for (const entry of history) {
console.log(`Changed at: ${entry.changed_at}`);
console.log(`Previous content: ${entry.previous_content}`);
}
```
### CLI
```bash
# Section 'get-mental-model-history' not found in api/mental-models.sh
```
### Go
```go
# Section 'get-mental-model-history' not found in api/mental-models.go
```
### Response
The endpoint returns a list of history entries, most recent first:
@@ -503,5 +316,5 @@ History tracking is enabled by default. Set `HINDSIGHT_API_ENABLE_MENTAL_MODEL_H
## Next Steps
- [**Reflect**](./reflect) — How the agentic loop uses mental models
- [**Observations**](../observations.md) — How knowledge is consolidated
- [**Observations**](/developer/observations) — How knowledge is consolidated
- [**Operations**](./operations) — Track async mental model creation
@@ -41,7 +41,7 @@ docker run --rm -it --pull always -p 8888:8888 -p 9999:9999 \
> **💡 LLM Provider**
>
Hindsight requires an LLM with structured output support. Recommended: **Groq** with `gpt-oss-20b` for fast, cost-effective inference.
See [LLM Providers](../models.md#llm) for more details.
See [LLM Providers](/developer/models#llm) for more details.
---
## Use the Client
@@ -105,16 +105,6 @@ hindsight memory recall my-bank "What does Alice do?"
hindsight memory reflect my-bank "Tell me about Alice"
```
### Go
```bash
go get github.com/vectorize-io/hindsight/hindsight-clients/go
```
```go
# Section 'quickstart-full' not found in api/quickstart.go
```
---
## What's Happening
@@ -137,4 +127,4 @@ go get github.com/vectorize-io/hindsight/hindsight-clients/go
- [**Recall**](./recall) — Search and retrieval strategies
- [**Reflect**](./reflect) — Disposition-aware reasoning
- [**Memory Banks**](./memory-banks) — Configure disposition and mission
- [**Server Deployment**](../installation.md) — Docker Compose, Helm, and production setup
- [**Server Deployment**](/developer/installation) — Docker Compose, Helm, and production setup
@@ -8,7 +8,7 @@ When you **recall**, Hindsight runs four retrieval strategies in parallel — se
{/* Import raw source files */}
:::info How Recall Works
Learn about the four retrieval strategies (semantic, keyword, graph, temporal) and RRF fusion in the [Recall Architecture](../retrieval.md) guide.
Learn about the four retrieval strategies (semantic, keyword, graph, temporal) and RRF fusion in the [Recall Architecture](/developer/retrieval) guide.
> **💡 Prerequisites**
>
Make sure you've completed the [Quick Start](./quickstart) to install the client and start the server.
@@ -72,12 +72,6 @@ const response = await client.recall('my-bank', 'What does Alice do?');
hindsight memory recall my-bank "What does Alice do?"
```
### Go
```go
# Section 'recall-basic' not found in api/recall.go
```
---
## Parameters
@@ -119,36 +113,12 @@ observations = client.recall(
)
```
### Node.js
```javascript
await client.recall('my-bank', 'query', { types: ['world'] });
```
```javascript
await client.recall('my-bank', 'query', { types: ['experience'] });
```
```javascript
await client.recall('my-bank', 'query', { types: ['observation'] });
```
### CLI
```bash
hindsight memory recall my-bank "query" --fact-type world,observation
```
### Go
```go
# Section 'recall-world-only' not found in api/recall.go
```
```go
# Section 'recall-experience-only' not found in api/recall.go
```
```go
# Section 'recall-observations-only' not found in api/recall.go
```
> **💡 About Observations**
>
Observations are consolidated knowledge synthesized from multiple facts over time — patterns, preferences, and learnings the memory bank has built up. They are created automatically in the background after retain operations.
@@ -176,22 +146,6 @@ const quickResults = await client.recall('my-bank', "Alice's email", { budget: '
const deepResults = await client.recall('my-bank', 'How are Alice and Bob connected?', { budget: 'high' });
```
### CLI
```bash
# Quick lookup
hindsight memory recall my-bank "Alice's email" --budget low
# Deep exploration
hindsight memory recall my-bank "How are Alice and Bob connected?" --budget high
```
### Go
```go
# Section 'recall-budget-levels' not found in api/recall.go
```
### max_tokens
The maximum number of tokens the returned facts can collectively occupy. Defaults to `4096`. Only the `text` field of each fact is counted toward this budget — metadata, tags, entities, and other fields are not included. After reranking, facts are included in relevance order until this budget is exhausted — so you always get the most relevant memories that fit. Hindsight is designed for agents, which think in tokens rather than result counts: set `max_tokens` to however much of your context window you want to allocate to memories.
@@ -206,32 +160,6 @@ results = client.recall(bank_id="my-bank", query="What do I know about Alice?",
results = client.recall(bank_id="my-bank", query="Alice's email", max_tokens=500)
```
### Node.js
```javascript
// Fill up to 4K tokens of context with relevant memories
await client.recall('my-bank', 'What do I know about Alice?', { maxTokens: 4096 });
// Smaller budget for quick lookups
await client.recall('my-bank', "Alice's email", { maxTokens: 500 });
```
### CLI
```bash
# Fill up to 4K tokens of context with relevant memories
hindsight memory recall my-bank "What do I know about Alice?" --max-tokens 4096
# Smaller budget for quick lookups
hindsight memory recall my-bank "Alice's email" --max-tokens 500
```
### Go
```go
# Section 'recall-token-budget' not found in api/recall.go
```
### query_timestamp
An ISO 8601 datetime representing when the query is being asked, from the user's perspective. When provided, it is used as the anchor for resolving relative temporal expressions in the query — for example, if the query says "last month" and `query_timestamp` is `2023-05-30`, the temporal search window becomes approximately April 2023. Without it, the server's current time is used as the anchor. This field matters most for replaying historical conversations or building agents that need time-anchored recall.
@@ -294,20 +222,6 @@ for (const obs of obsResponse.results) {
}
```
### CLI
```bash
# Recall observations with source facts
hindsight memory recall my-bank "What patterns have I learned about Alice?" \
--fact-type observation
```
### Go
```go
# Section 'recall-source-facts' not found in api/recall.go
```
#### entities
Enabled by default. When active, each returned fact includes the canonical names of entities associated with it. Set to `null` to skip the entity JOIN query and reduce response size. The `max_tokens` sub-option (default `500`) is a future-facing guard for entity data.
@@ -340,8 +254,6 @@ Consider a bank with these four memories:
Returns memories that have **at least one** matching tag, plus untagged memories.
### Python
```python
response = client.recall(
bank_id="my-bank",
@@ -356,36 +268,12 @@ response = client.recall(
# [match] "Company policy: no meetings on Fridays" — untagged, included by default
```
### Node.js
```javascript
await client.recall('my-bank', 'communication preferences', {
tags: ['user:alice'],
tagsMatch: 'any'
});
```
### CLI
```bash
hindsight memory recall my-bank "communication preferences" \
--tags "user:alice" --tags-match any
```
### Go
```go
# Section 'recall-with-tags' not found in api/recall.go
```
Use this for **shared global knowledge + user-specific** patterns, where untagged memories represent information everyone should see.
#### `any_strict` — OR matching, excludes untagged
Same as `any` but untagged memories are excluded.
### Python
```python
response = client.recall(
bank_id="my-bank",
@@ -400,36 +288,12 @@ response = client.recall(
# [no match] "Company policy: no meetings on Fridays" — untagged, excluded
```
### Node.js
```javascript
await client.recall('my-bank', 'communication preferences', {
tags: ['user:alice'],
tagsMatch: 'any_strict'
});
```
### CLI
```bash
hindsight memory recall my-bank "communication preferences" \
--tags "user:alice" --tags-match any_strict
```
### Go
```go
# Section 'recall-tags-strict' not found in api/recall.go
```
Use this when memories are **fully partitioned by tags** and untagged memories should never be visible.
#### `all` — AND matching, includes untagged
Returns memories that have **every** specified tag, plus untagged memories.
### Python
```python
response = client.recall(
bank_id="my-bank",
@@ -444,36 +308,12 @@ response = client.recall(
# [match] "Company policy: no meetings on Fridays" — untagged, included by default
```
### Node.js
```javascript
await client.recall('my-bank', 'communication tools', {
tags: ['user:alice', 'team'],
tagsMatch: 'all'
});
```
### CLI
```bash
hindsight memory recall my-bank "communication tools" \
--tags "user:alice,team" --tags-match all
```
### Go
```go
# Section 'recall-tags-all-mode' not found in api/recall.go
```
Use this when memories must belong to a **specific intersection** of scopes (e.g., only memories relevant to both a user and a project), while still surfacing shared global knowledge.
#### `all_strict` — AND matching, excludes untagged
Returns memories that have **every** specified tag, and excludes untagged memories.
### Python
```python
response = client.recall(
bank_id="my-bank",
@@ -488,28 +328,6 @@ response = client.recall(
# [no match] "Company policy: no meetings on Fridays" — untagged, excluded
```
### Node.js
```javascript
await client.recall('my-bank', 'communication tools', {
tags: ['user:alice', 'team'],
tagsMatch: 'all_strict'
});
```
### CLI
```bash
hindsight memory recall my-bank "communication tools" \
--tags "user:alice,team" --tags-match all_strict
```
### Go
```go
# Section 'recall-tags-all' not found in api/recall.go
```
Use this for strict scope enforcement where a memory must explicitly belong to **all** specified contexts.
> **💡 Extra tags are fine**
@@ -8,7 +8,7 @@ When you call **reflect**, Hindsight runs an agentic loop that autonomously sear
{/* Import raw source files */}
:::info How Reflect Works
Learn about disposition-driven reasoning in the [Reflect Architecture](../reflect.md) guide.
Learn about disposition-driven reasoning in the [Reflect Architecture](/developer/reflect) guide.
> **💡 Prerequisites**
>
Make sure you've completed the [Quick Start](./quickstart) to install the client and start the server.
@@ -32,12 +32,6 @@ await client.reflect('my-bank', 'What should I know about Alice?');
hindsight memory reflect my-bank "What do you know about Alice?"
```
### Go
```go
# Section 'reflect-basic' not found in api/reflect.go
```
---
## Parameters
@@ -69,18 +63,6 @@ const response = await client.reflect('my-bank', 'What do you think about remote
});
```
### CLI
```bash
hindsight memory reflect my-bank "Summarize my week" --budget high --max-tokens 8192
```
### Go
```go
# Section 'reflect-with-params' not found in api/reflect.go
```
### max_tokens
Limits the length of the final generated response. Defaults to `4096`. This does not affect how much the agent can retrieve during the agentic loop — only the final answer length.
@@ -165,12 +147,6 @@ hindsight memory reflect hiring-team \
rm -f schema.json
```
### Go
```go
# Section 'reflect-structured-output' not found in api/reflect.go
```
### tags
Filters which memories the agent can access during reflection. Works identically to [recall tags](./recall#tags) — only memories matching the specified tags are considered. The `tags_match` parameter controls the matching logic (`any`, `all`, `any_strict`, `all_strict`) with the same semantics as recall.
@@ -187,29 +163,6 @@ response = client.reflect(
)
```
### Node.js
```javascript
// Filter reflect to only use memories tagged for a specific user
await client.reflect('my-bank', 'What feedback did the user give?', {
tags: ['user:alice'],
tagsMatch: 'any_strict'
});
```
### CLI
```bash
hindsight memory reflect my-bank "What feedback did the user give?" \
--tags "user:alice" --tags-match any_strict
```
### Go
```go
# Section 'reflect-with-tags' not found in api/reflect.go
```
### include
Controls optional supplementary data returned alongside the main response.
@@ -234,32 +187,6 @@ for fact in (response.based_on.memories if response.based_on else []):
print(f" - [{fact.type}] {fact.text}")
```
### Node.js
```javascript
const sourcesResponse = await client.reflect('my-bank', 'Tell me about Alice', {
includeFacts: true
});
console.log('Response:', sourcesResponse.text);
console.log('\nBased on:');
for (const fact of (sourcesResponse.based_on?.memories || [])) {
console.log(` - [${fact.type}] ${fact.text}`);
}
```
### CLI
```bash
hindsight memory reflect my-bank "Tell me about Alice" --include-facts
```
### Go
```go
# Section 'reflect-sources' not found in api/reflect.go
```
#### include.tool_calls
When enabled, the response includes a `trace` object with the full execution log of every tool call and LLM call made during the agentic loop, including inputs, outputs, and durations. Set `output: false` to include only tool inputs for a smaller payload. Useful for debugging why the agent reached a particular conclusion.
@@ -8,7 +8,7 @@ When you **retain** content, Hindsight doesn't just store the raw text—it inte
{/* Import raw source files */}
:::info How Retain Works
Learn about fact extraction, entity resolution, and graph construction in the [Retain Architecture](../retain.md) guide.
Learn about fact extraction, entity resolution, and graph construction in the [Retain Architecture](/developer/retain) guide.
> **💡 Prerequisites**
>
Make sure you've completed the [Quick Start](./quickstart) to install the client and start the server.
@@ -37,12 +37,6 @@ await client.retain('my-bank', 'Alice works at Google as a software engineer');
hindsight memory retain my-bank "Alice works at Google as a software engineer"
```
### Go
```go
# Section 'retain-basic' not found in api/retain.go
```
### Retaining a Conversation
A full conversation should be retained as a single item. The LLM can parse any format — plain text, JSON, Markdown, or any structured representation — as long as it clearly conveys who said what and when. The example below uses a simple `Name (timestamp): text` format.
@@ -91,27 +85,6 @@ await client.retain('my-bank', conversation, {
});
```
### CLI
```bash
# Retain an entire conversation as a single document.
CONVERSATION="Alice (2024-03-15T09:00:00Z): Hi Bob! Did you end up going to the doctor last week?
Bob (2024-03-15T09:01:00Z): Yes, finally. Turns out I have a mild peanut allergy.
Alice (2024-03-15T09:02:00Z): Oh no! Are you okay?
Bob (2024-03-15T09:03:00Z): Yeah, nothing serious. Just need to carry an antihistamine.
Alice (2024-03-15T09:04:00Z): Good to know. We'll avoid peanuts at the team lunch."
hindsight memory retain my-bank "$CONVERSATION" \
--context "team chat" \
--doc-id "chat-2024-03-15-alice-bob"
```
### Go
```go
# Section 'retain-conversation' not found in api/retain.go
```
When the conversation grows — a new message arrives — just retain again with the full updated content and the same `document_id`. Hindsight will delete the previous version and reprocess from scratch, so memories always reflect the latest state of the conversation.
---
@@ -167,12 +140,6 @@ hindsight memory retain my-bank "Alice got promoted" \
--context "career update"
```
### Go
```go
# Section 'retain-with-context' not found in api/retain.go
```
### metadata
Arbitrary key-value string pairs that provide context about this item. For example: `{"source": "slack", "channel": "engineering", "thread_id": "T123"}`. Metadata is included in the fact extraction prompt, so the LLM can use it as additional context when extracting facts — for instance, knowing the document title or source can improve accuracy. It is also stored on each memory unit and returned with every recalled memory, letting you do client-side filtering or static enrichment without extra lookups — for example, linking a memory back to its source URL, thread ID, or any application-specific identifier.
@@ -296,24 +263,6 @@ await client.retainBatch('my-bank', [
]);
```
### CLI
```bash
# Batch ingestion via individual retain calls (CLI processes items one at a time)
hindsight memory retain my-bank "Alice works at Google" \
--context "career" --doc-id "conversation_001_msg_1"
hindsight memory retain my-bank "Bob is a data scientist at Meta" \
--context "career" --doc-id "conversation_001_msg_2"
hindsight memory retain my-bank "Alice and Bob are friends" \
--context "relationship" --doc-id "conversation_001_msg_3"
```
### Go
```go
# Section 'retain-batch' not found in api/retain.go
```
---
## Files
@@ -322,6 +271,28 @@ Upload files directly — Hindsight converts them to text and extracts memories
**Supported formats:** PDF, DOCX, DOC, PPTX, PPT, XLSX, XLS, images (JPG, PNG, GIF, etc. — OCR), audio (MP3, WAV, FLAC, etc. — transcription), HTML, and plain text formats (TXT, MD, CSV, JSON, YAML, etc.)
### CLI
```bash
# Upload a single file (PDF, DOCX, PPTX, XLSX, images, audio, and more)
hindsight memory retain-files my-bank "$SAMPLE_FILE"
# Upload a directory of files
hindsight memory retain-files my-bank "$SCRIPT_DIR/"
# Queue files for background processing (returns immediately)
hindsight memory retain-files my-bank "$SCRIPT_DIR/" --async
```
### HTTP
```bash
# Via HTTP API (multipart/form-data)
curl -X POST "${HINDSIGHT_URL}/v1/default/banks/my-bank/files/retain" \
-F "files=@${SAMPLE_FILE};type=application/octet-stream" \
-F "request={\"files_metadata\": [{\"context\": \"quarterly report\"}]}"
```
### Python
```python
@@ -353,25 +324,6 @@ const result = await client.retainFiles('my-bank', [
console.log(result.operation_ids); // Track processing via the operations endpoint
```
### CLI
```bash
# Upload a single file (PDF, DOCX, PPTX, XLSX, images, audio, and more)
hindsight memory retain-files my-bank "$SAMPLE_FILE"
# Upload a directory of files
hindsight memory retain-files my-bank "$SCRIPT_DIR/"
# Queue files for background processing (returns immediately)
hindsight memory retain-files my-bank "$SCRIPT_DIR/" --async
```
### Go
```go
# Section 'retain-files' not found in api/retain.go
```
The file retain endpoint always returns asynchronously. The response contains `operation_ids` — one per uploaded file — which you can poll via `GET /v1/default/banks/{bank_id}/operations` to track progress.
Upload up to 10 files per request (max 100 MB total). Each file becomes a separate document with optional per-file metadata:
@@ -395,41 +347,6 @@ result = client.retain_files(
print(result.operation_ids) # One operation ID per file
```
### Node.js
```javascript
// Upload multiple files with per-file metadata (up to 10 files per request)
const batchResult = await client.retainFiles('my-bank', [
new File([pdfBytes], 'report.pdf'),
new File([pdfBytes], 'notes.pdf'),
], {
filesMetadata: [
{ context: 'quarterly report', document_id: 'q1-report', tags: ['project:alpha'] },
{ context: 'meeting notes', document_id: 'q1-notes', tags: ['project:alpha'] },
]
});
console.log(batchResult.operation_ids); // One operation ID per file
```
### CLI
```bash
# Upload a single file (PDF, DOCX, PPTX, XLSX, images, audio, and more)
hindsight memory retain-files my-bank "$SAMPLE_FILE"
# Upload a directory of files
hindsight memory retain-files my-bank "$SCRIPT_DIR/"
# Queue files for background processing (returns immediately)
hindsight memory retain-files my-bank "$SCRIPT_DIR/" --async
```
### Go
```go
# Section 'retain-files' not found in api/retain.go
```
:::info File Storage
Uploaded files are stored server-side (PostgreSQL by default, or S3/GCS/Azure for production). Configure storage via `HINDSIGHT_API_FILE_STORAGE_TYPE`. See [Configuration](../configuration#file-processing) for details.
---
@@ -467,18 +384,6 @@ await client.retainBatch('my-bank', [
});
```
### CLI
```bash
hindsight memory retain my-bank "Meeting notes" --async
```
### Go
```go
# Section 'retain-async' not found in api/retain.go
```
When `async: true`, the call returns immediately with an `operation_id`. Processing runs in the background via the worker service. No `usage` metrics are returned for async operations.
### Cut Costs 50% with Provider Batch APIs
@@ -574,7 +574,7 @@ Controls the retain (memory ingestion) pipeline.
| `HINDSIGHT_API_RETAIN_BATCH_ENABLED` | Use LLM Batch API for fact extraction (50% cost savings, only with async operations) | `false` |
| `HINDSIGHT_API_RETAIN_BATCH_POLL_INTERVAL_SECONDS` | Batch API polling interval in seconds | `60` |
> **Entity labels** (`entity_labels`) and **free-form entity extraction** (`entities_allow_free_form`) are configured per bank via the [bank config API](api/memory-banks.md#retain-configuration), not as global environment variables — each bank can have its own controlled vocabulary. See [Entity Labels](retain.md#entity-labels) for details.
> **Entity labels** (`entity_labels`) and **free-form entity extraction** (`entities_allow_free_form`) are configured per bank via the [bank config API](/developer/api/memory-banks#retain-configuration), not as global environment variables — each bank can have its own controlled vocabulary. See [Entity Labels](/developer/retain#entity-labels) for details.
#### Customizing retain: when to use what
@@ -646,6 +646,8 @@ client.retain(bank_id, items=[{"content": "...document text..."}], strategy="doc
If no `strategy` is specified in a retain call, `retain_default_strategy` is used. If neither is set, the bank/global config applies directly.
> **Note on chunk size and retrieval fairness**: When mixing strategies with very different chunk sizes in the same bank, `chunks` and `verbatim` memories participate only in semantic retrieval (not entity graph or temporal paths). Smaller chunk sizes (e.g., 800 chars) produce more targeted embeddings and are recommended for document strategies to keep scores comparable with LLM-extracted facts.
**`HINDSIGHT_API_RETAIN_EXTRACTION_MODE=chunks` — zero LLM cost**
Each chunk is stored as-is with no LLM call whatsoever. No entity extraction, no temporal indexing — only embeddings are generated for semantic search. User-provided entities passed via `RetainContent.entities` are the sole source of entity data. Use when ingestion speed and cost matter more than structured metadata.
@@ -122,22 +122,22 @@ These settings only affect the `reflect` operation, not `recall`.
## Next Steps
### Getting Started
- [**Quick Start**](api/quickstart.md) — Install and get up and running in 60 seconds
- [**RAG vs Hindsight**](rag-vs-hindsight.md) — See how Hindsight differs from traditional RAG with real examples
- [**Quick Start**](/developer/api/quickstart) — Install and get up and running in 60 seconds
- [**RAG vs Hindsight**](/developer/rag-vs-hindsight) — See how Hindsight differs from traditional RAG with real examples
### Core Concepts
- [**Retain**](retain.md) — How memories are stored with multi-dimensional facts
- [**Recall**](retrieval.md) — How TEMPR's 4-way search retrieves memories
- [**Reflect**](reflect.md) — How mission, directives, and disposition shape reasoning
- [**Retain**](/developer/retain) — How memories are stored with multi-dimensional facts
- [**Recall**](/developer/retrieval) — How TEMPR's 4-way search retrieves memories
- [**Reflect**](/developer/reflect) — How mission, directives, and disposition shape reasoning
### API Methods
- [**Retain**](api/retain.md) — Store information in memory banks
- [**Recall**](api/recall.md) — Search and retrieve memories
- [**Reflect**](api/reflect.md) — Agentic reasoning with memory
- [**Mental Models**](api/mental-models.md) — User-curated summaries for common queries
- [**Memory Banks**](api/memory-banks.md) — Configure mission, directives, and disposition
- [**Documents**](api/documents.md) — Manage document sources
- [**Operations**](api/operations.md) — Monitor async tasks
- [**Retain**](/developer/api/retain) — Store information in memory banks
- [**Recall**](/developer/api/recall) — Search and retrieve memories
- [**Reflect**](/developer/api/reflect) — Agentic reasoning with memory
- [**Mental Models**](/developer/api/mental-models) — User-curated summaries for common queries
- [**Memory Banks**](/developer/api/memory-banks) — Configure mission, directives, and disposition
- [**Documents**](/developer/api/documents) — Manage document sources
- [**Operations**](/developer/api/operations) — Monitor async tasks
### Deployment
- [**Server Setup**](installation.md) — Deploy with Docker Compose, Helm, or pip
- [**Server Setup**](/developer/installation) — Deploy with Docker Compose, Helm, or pip
@@ -32,7 +32,7 @@ See [Configuration](./configuration#llm-provider) for setup examples.
Not sure which model to use? The **[Model Leaderboard](https://benchmarks.hindsight.vectorize.io/)** benchmarks models across accuracy, speed, cost, and reliability for retain, reflect, and observation consolidation so you can pick the right trade-off for your use case.
[](https://benchmarks.hindsight.vectorize.io/)
[![Model Leaderboard](/img/leaderboard.png)](https://benchmarks.hindsight.vectorize.io/)
### Tested Models
@@ -120,7 +120,7 @@ observations = client.recall(
The reflect agent uses **hierarchical retrieval**:
1. **[Mental Models](api/mental-models.md)** — User-curated summaries (highest priority)
1. **[Mental Models](/developer/api/mental-models)** — User-curated summaries (highest priority)
2. **Observations** — Consolidated knowledge with freshness awareness
3. **Raw Facts** — Ground truth for verification
@@ -168,7 +168,7 @@ Leave it blank to use the server default — durable, specific facts that stay t
| *"Observations are stable facts about named individuals only"* | Person-centric knowledge, tied to specific people |
| *"Observations are recurring patterns in customer support interactions"* | Failure modes, common requests, pain points |
Set `observations_mission` via the [bank config API](api/memory-banks.md#observations-configuration) or the [`HINDSIGHT_API_OBSERVATIONS_MISSION`](configuration.md#observations) environment variable.
Set `observations_mission` via the [bank config API](/developer/api/memory-banks#observations-configuration) or the [`HINDSIGHT_API_OBSERVATIONS_MISSION`](/developer/configuration#observations) environment variable.
---
@@ -55,11 +55,11 @@ The agent:
The agent uses a smart retrieval hierarchy:
1. **[Mental Models](api/mental-models.md)** — User-curated summaries you've pre-computed for common queries
2. **[Observations](observations.md)** — Consolidated knowledge with freshness awareness
1. **[Mental Models](/developer/api/mental-models)** — User-curated summaries you've pre-computed for common queries
2. **[Observations](/developer/observations)** — Consolidated knowledge with freshness awareness
3. **Raw Facts** — Ground truth for verification when observations are stale
**Mental models** are saved reflect responses that you create for frequently asked questions. They're checked first because they represent explicitly curated knowledge. See the [Mental Models API](api/mental-models.md) for how to create and manage them.
**Mental models** are saved reflect responses that you create for frequently asked questions. They're checked first because they represent explicitly curated knowledge. See the [Mental Models API](/developer/api/mental-models) for how to create and manage them.
If an observation is marked as **stale**, the agent automatically verifies it against current facts.
@@ -132,7 +132,7 @@ The reflect mission frames how the agent reasons and responds:
- Keeps reasoning consistent across conversations
:::info Per-operation missions
The reflect mission only affects `reflect()`. To steer what gets extracted during `retain()`, use [`retain_mission`](api/memory-banks.md#retain-configuration). To control what gets synthesised into observations, use [`observations_mission`](api/memory-banks.md#observations-configuration).
The reflect mission only affects `reflect()`. To steer what gets extracted during `retain()`, use [`retain_mission`](/developer/api/memory-banks#retain-configuration). To control what gets synthesised into observations, use [`observations_mission`](/developer/api/memory-banks#observations-configuration).
---
## Disposition Shapes Reasoning
@@ -187,7 +187,7 @@ Use directives for constraints that must never be violated:
:::tip
Use disposition for personality and character. Use directives for compliance and guardrails.
See [Memory Banks: Directives](api/memory-banks.md#directives) for how to create and manage directives.
See [Memory Banks: Directives](/developer/api/memory-banks#directives) for how to create and manage directives.
---
@@ -94,7 +94,7 @@ If "Alice" appears with "Google" and "Stanford" multiple times, a new "Alice" me
You can define a controlled vocabulary of `key:value` classification labels (e.g. `pedagogy:scaffolding`, `engagement:active`) that are extracted at retain time and stored as entities. Because labels become entities, they automatically link related memories in the knowledge graph and improve both semantic and keyword retrieval. Labels can optionally also write to the memory unit's tags, enabling standard tag-based filtering during recall and reflect.
See [entity_labels in the bank config](api/memory-banks.md#entity-labels) for full configuration details.
See [entity_labels in the bank config](/developer/api/memory-banks#entity-labels) for full configuration details.
---
@@ -201,7 +201,7 @@ For finer control, you can also change the **extraction mode**:
| `verbose` | When you need richer facts with full context and relationships |
| `custom` | When you want to write your own extraction rules entirely |
Set `retain_mission` and `retain_extraction_mode` via the [bank config API](api/memory-banks.md#retain-configuration) or the [`HINDSIGHT_API_RETAIN_MISSION`](configuration.md#retain) environment variable.
Set `retain_mission` and `retain_extraction_mode` via the [bank config API](/developer/api/memory-banks#retain-configuration) or the [`HINDSIGHT_API_RETAIN_MISSION`](/developer/configuration#retain) environment variable.
---
+13 -13
View File
@@ -28,7 +28,7 @@ Hindsight is an agent memory system that provides long-term memory for AI agents
- **Supports temporal reasoning** with time-aware retrieval
- **Enables disposition-aware reflection** for nuanced reasoning
For a detailed comparison, see [RAG vs Memory](developer/rag-vs-hindsight.md).
For a detailed comparison, see [RAG vs Memory](/developer/rag-vs-hindsight).
---
@@ -65,7 +65,7 @@ Unlike vector databases (just search) or RAG systems (document retrieval), Hinds
<LLMProvidersGrid />
See [Models](developer/models.md) for the full list of supported providers, recommended models, and configuration examples.
See [Models](/developer/models) for the full list of supported providers, recommended models, and configuration examples.
---
@@ -73,9 +73,9 @@ See [Models](developer/models.md) for the full list of supported providers, reco
The **[Model Leaderboard](https://benchmarks.hindsight.vectorize.io/)** benchmarks models across accuracy, speed, cost, and reliability for retain, reflect, and observation consolidation — it's the best place to find the right trade-off for your use case.
[](https://benchmarks.hindsight.vectorize.io/)
[![Model Leaderboard](/img/leaderboard.png)](https://benchmarks.hindsight.vectorize.io/)
See [Models](developer/models.md) for the full list of supported and tested models, provider defaults, and configuration examples.
See [Models](/developer/models) for the full list of supported and tested models, provider defaults, and configuration examples.
---
@@ -86,7 +86,7 @@ No! You have two options:
1. **Hindsight Cloud** - Fully managed service at [ui.hindsight.vectorize.io](https://ui.hindsight.vectorize.io)
2. **Self-hosted** - Deploy on your own infrastructure using Docker or direct installation
See [Installation](developer/installation.md) for self-hosting instructions.
See [Installation](/developer/installation) for self-hosting instructions.
---
@@ -97,7 +97,7 @@ For running the Hindsight API server locally:
- 4GB RAM minimum (8GB recommended for production)
- LLM API key (OpenAI, Anthropic, etc.) or local LLM setup
See [Installation](developer/installation.md) for setup instructions.
See [Installation](/developer/installation) for setup instructions.
---
@@ -120,7 +120,7 @@ There are two approaches for multi-user applications:
- Filter by tags during recall/reflect for per-user queries
- **Advantage**: Enables both per-user AND cross-user queries (e.g., analyze specific users or aggregate across all users)
Choose per-user banks for simplicity and privacy, or single bank with tags if you need holistic reasoning across users. See [Memory Banks](developer/api/memory-banks.md) for management details.
Choose per-user banks for simplicity and privacy, or single bank with tags if you need holistic reasoning across users. See [Memory Banks](/developer/api/memory-banks) for management details.
---
@@ -132,7 +132,7 @@ Hindsight has three core operations:
- **Recall**: Search and retrieve raw memory data based on a query
- **Reflect**: Use an AI agent to answer a query using retrieved memories
See [Operations](developer/api/operations.md) for API details.
See [Operations](/developer/api/operations) for API details.
---
@@ -162,7 +162,7 @@ reflect("What should I order for Alice?")
→ "I'd recommend a vegetarian sushi platter — Alice loves sushi and prefers vegetarian options." # grounded answer
```
See [Recall](developer/api/recall.md) and [Reflect](developer/reflect.md) for full API details.
See [Recall](/developer/api/recall) and [Reflect](/developer/reflect) for full API details.
---
@@ -174,7 +174,7 @@ See [Recall](developer/api/recall.md) and [Reflect](developer/reflect.md) for fu
- Long-term behavioral patterns (e.g., "Customer is price-sensitive but values quality")
- Context for AI agent reasoning during **reflect** operations
Mental models are automatically built during retain and used by reflect to provide richer, more contextual responses. See [Mental Models](developer/api/mental-models.md).
Mental models are automatically built during retain and used by reflect to provide richer, more contextual responses. See [Mental Models](/developer/api/mental-models).
---
@@ -184,7 +184,7 @@ Typical latencies:
- **Without reranking**: 50-100ms
- **With reranking**: 200-500ms (depends on reranker model and installation)
See [Performance](developer/performance.md) for tuning options.
See [Performance](/developer/performance) for tuning options.
---
@@ -203,7 +203,7 @@ client.retain(bank_id="my-bank", items=[{
client.recall(bank_id="my-bank", query="...", tags=["user:alice"])
```
See [Tags](developer/api/retain.md#tags-and-document_tags) for full details including document-level tagging.
See [Tags](/developer/api/retain#tags-and-document_tags) for full details including document-level tagging.
**What about filtering by entities?**
@@ -227,7 +227,7 @@ If you need explicit tag-based filtering on entity-like values, use **entity lab
client.recall(bank_id="my-bank", query="...", tags=["user:alice"])
```
See [Entity Labels](developer/retain.md#entity-labels) for configuration details.
See [Entity Labels](/developer/retain#entity-labels) for configuration details.
**What about document `metadata`?**
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -4,7 +4,7 @@ sidebar_position: 4
# CLI Reference
The Hindsight CLI provides command-line access to memory operations and bank management. All commands follow the [OpenAPI specification](../openapi.json), so you can use `--help` on any command to see all available options.
The Hindsight CLI provides command-line access to memory operations and bank management. All commands follow the [OpenAPI specification](/api-reference), so you can use `--help` on any command to see all available options.
## Installation
@@ -244,4 +244,4 @@ hindsight-embed configure
- High-availability requirements
- Multi-tenant applications
For production deployments, use the [API Service](../developer/services.md) with external PostgreSQL instead.
For production deployments, use the [API Service](/developer/services) with external PostgreSQL instead.
@@ -1,186 +0,0 @@
---
sidebar_position: 9
---
# Agno
Persistent memory tools for [Agno](https://github.com/agno-agi/agno) agents via Hindsight. Give your agents long-term memory with retain, recall, and reflect — using Agno's native Toolkit pattern.
## Features
- **Native Toolkit** - Extends Agno's `Toolkit` base class, just like `Mem0Tools`
- **Memory Instructions** - Pre-recall memories for injection into `Agent(instructions=[...])`
- **Three Memory Tools** - Retain (store), Recall (search), Reflect (synthesize) — include any combination
- **Flexible Bank Resolution** - Static bank ID, `RunContext.user_id`, or custom resolver
- **Simple Configuration** - Configure once globally, or pass a client directly
## Installation
```bash
pip install hindsight-agno
```
## Quick Start
```python
from agno.agent import Agent
from agno.models.openai import OpenAIChat
from hindsight_agno import HindsightTools
agent = Agent(
model=OpenAIChat(id="gpt-4o-mini"),
tools=[HindsightTools(
bank_id="user-123",
hindsight_api_url="http://localhost:8888",
)],
)
agent.print_response("Remember that I prefer dark mode")
agent.print_response("What are my preferences?")
```
The agent now has three tools it can call:
- **`retain_memory`** — Store information to long-term memory
- **`recall_memory`** — Search long-term memory for relevant facts
- **`reflect_on_memory`** — Synthesize a reasoned answer from memories
## With Memory Instructions
Pre-recall relevant memories and inject them into the system prompt:
```python
from hindsight_agno import HindsightTools, memory_instructions
agent = Agent(
model=OpenAIChat(id="gpt-4o-mini"),
tools=[HindsightTools(
bank_id="user-123",
hindsight_api_url="http://localhost:8888",
)],
instructions=[memory_instructions(
bank_id="user-123",
hindsight_api_url="http://localhost:8888",
)],
)
```
## Selecting Tools
Include only the tools you need:
```python
tools = [HindsightTools(
bank_id="user-123",
hindsight_api_url="http://localhost:8888",
enable_retain=True,
enable_recall=True,
enable_reflect=False, # Omit reflect
)]
```
## Bank Resolution
The bank ID is resolved in order:
1. **`bank_resolver`** — Custom callable `(RunContext) -> str`
2. **`bank_id`** — Static bank ID passed to constructor
3. **`run_context.user_id`** — Automatic per-user banks
```python
# Per-user banks from RunContext
agent = Agent(
model=OpenAIChat(id="gpt-4o-mini"),
tools=[HindsightTools(hindsight_api_url="http://localhost:8888")],
user_id="user-123", # Used as bank_id
)
# Custom resolver
def resolve_bank(ctx):
return f"team-{ctx.user_id}"
agent = Agent(
model=OpenAIChat(id="gpt-4o-mini"),
tools=[HindsightTools(
bank_resolver=resolve_bank,
hindsight_api_url="http://localhost:8888",
)],
)
```
## Global Configuration
Instead of passing connection details to every toolkit, configure once:
```python
from hindsight_agno import configure, HindsightTools
configure(
hindsight_api_url="http://localhost:8888",
api_key="your-api-key", # Or set HINDSIGHT_API_KEY env var
budget="mid", # Recall budget: low/mid/high
max_tokens=4096, # Max tokens for recall results
tags=["env:prod"], # Tags for stored memories
recall_tags=["scope:global"], # Tags to filter recall
recall_tags_match="any", # Tag match mode: any/all/any_strict/all_strict
)
# Now create toolkit without passing connection details
tools = [HindsightTools(bank_id="user-123")]
```
## Configuration Reference
### `HindsightTools()`
| Parameter | Default | Description |
|---|---|---|
| `bank_id` | `None` | Static Hindsight memory bank ID |
| `bank_resolver` | `None` | Callable `(RunContext) -> str` for dynamic bank ID |
| `client` | `None` | Pre-configured Hindsight client |
| `hindsight_api_url` | `None` | API URL (used if no client provided) |
| `api_key` | `None` | API key (used if no client provided) |
| `budget` | `"mid"` | Recall/reflect budget level (low/mid/high) |
| `max_tokens` | `4096` | Maximum tokens for recall results |
| `tags` | `None` | Tags applied when storing memories |
| `recall_tags` | `None` | Tags to filter when searching |
| `recall_tags_match` | `"any"` | Tag matching mode |
| `enable_retain` | `True` | Include the retain (store) tool |
| `enable_recall` | `True` | Include the recall (search) tool |
| `enable_reflect` | `True` | Include the reflect (synthesize) tool |
### `memory_instructions()`
| Parameter | Default | Description |
|---|---|---|
| `bank_id` | *required* | Hindsight memory bank ID |
| `client` | `None` | Pre-configured Hindsight client |
| `hindsight_api_url` | `None` | API URL (used if no client provided) |
| `api_key` | `None` | API key (used if no client provided) |
| `query` | `"relevant context about the user"` | Recall query for memory injection |
| `budget` | `"low"` | Recall budget level |
| `max_results` | `5` | Maximum memories to inject |
| `max_tokens` | `4096` | Maximum tokens for recall results |
| `prefix` | `"Relevant memories:\n"` | Text prepended before memory list |
| `tags` | `None` | Tags to filter recall results |
| `tags_match` | `"any"` | Tag matching mode |
### `configure()`
| Parameter | Default | Description |
|---|---|---|
| `hindsight_api_url` | Production API | Hindsight API URL |
| `api_key` | `HINDSIGHT_API_KEY` env | API key for authentication |
| `budget` | `"mid"` | Default recall budget level |
| `max_tokens` | `4096` | Default max tokens for recall |
| `tags` | `None` | Default tags for retain operations |
| `recall_tags` | `None` | Default tags to filter recall |
| `recall_tags_match` | `"any"` | Default tag matching mode |
| `verbose` | `False` | Enable verbose logging |
## Requirements
- Python >= 3.10
- agno
- hindsight-client >= 0.4.0
- A running Hindsight API server
@@ -1,219 +0,0 @@
---
sidebar_position: 10
---
# Hermes Agent
Hindsight memory integration for [Hermes Agent](https://github.com/NousResearch/hermes-agent). Gives your Hermes agent persistent long-term memory via retain, recall, and reflect tools.
## What it does
This package registers three tools into Hermes via its plugin system:
- **`hindsight_retain`** — Stores information to long-term memory. Hermes calls this when the user shares facts, preferences, or anything worth remembering.
- **`hindsight_recall`** — Searches long-term memory for relevant information. Returns a numbered list of matching memories.
- **`hindsight_reflect`** — Synthesizes a thoughtful answer from stored memories. Use this when you want Hermes to reason over what it knows rather than return raw facts.
These tools appear under the `[hindsight]` toolset in Hermes's `/tools` list.
## Setup
### 1. Install hindsight-hermes into the Hermes venv
The package must be installed in the **same Python environment** that Hermes runs in, so the entry point is discoverable.
```bash
uv pip install hindsight-hermes --python $HOME/.hermes/hermes-agent/venv/bin/python
```
### 2. Set environment variables
The plugin reads its configuration from environment variables. Set these before launching Hermes:
```bash
# Required — tells the plugin where Hindsight is running
export HINDSIGHT_API_URL=http://localhost:8888
# Required — the memory bank to read/write. Think of this as a "brain" for one user or agent.
export HINDSIGHT_BANK_ID=my-agent
# Optional — only needed if using Hindsight Cloud (https://api.hindsight.vectorize.io)
export HINDSIGHT_API_KEY=your-api-key
# Optional — recall budget: low (fast), mid (default), high (thorough)
export HINDSIGHT_BUDGET=mid
```
If neither `HINDSIGHT_API_URL` nor `HINDSIGHT_API_KEY` is set, the plugin silently skips registration — Hermes starts normally without the Hindsight tools.
### 3. Disable Hermes's built-in memory tool
Hermes has its own `memory` tool that saves to local files (`~/.hermes/`). If both are active, the LLM tends to prefer the built-in one since it's familiar. Disable it so the LLM uses Hindsight instead:
```bash
hermes tools disable memory
```
This persists across sessions. You can re-enable it later with `hermes tools enable memory`.
### 4. Start Hindsight API
Follow the [Quick Start](../../developer/api/quickstart.md) guide to get the Hindsight API running, then come back here.
### 5. Launch Hermes
```bash
hermes
```
Verify the plugin loaded by typing `/tools` — you should see:
```
[hindsight]
* hindsight_recall - Search long-term memory for relevant information.
* hindsight_reflect - Synthesize a thoughtful answer from long-term memories.
* hindsight_retain - Store information to long-term memory for later retrieval.
```
### 6. Test it
**Store a memory:**
> Remember that my favourite colour is red
You should see `⚡ hindsight` in the response, confirming it called `hindsight_retain`.
**Recall a memory:**
> What's my favourite colour?
**Reflect on memories:**
> Based on what you know about me, suggest a colour scheme for my IDE
This calls `hindsight_reflect`, which synthesizes a response from all stored memories.
**Verify via API:**
```bash
curl -s http://localhost:8888/v1/default/banks/my-agent/memories/recall \
-H "Content-Type: application/json" \
-d '{"query": "favourite colour", "budget": "low"}' | python3 -m json.tool
```
## Troubleshooting
### Tools don't appear in `/tools`
1. **Check the plugin is installed in the right venv.** Run this from the Hermes venv:
```bash
python -c "from hindsight_hermes import register; print('OK')"
```
2. **Check the entry point is registered:**
```bash
python -c "
import importlib.metadata
eps = importlib.metadata.entry_points(group='hermes_agent.plugins')
print(list(eps))
"
```
You should see `EntryPoint(name='hindsight', value='hindsight_hermes', group='hermes_agent.plugins')`.
3. **Check env vars are set.** The plugin skips registration silently if `HINDSIGHT_API_URL` and `HINDSIGHT_API_KEY` are both unset.
### Hermes uses built-in memory instead of Hindsight
Run `hermes tools disable memory` and restart. The built-in `memory` tool and Hindsight tools have overlapping purposes — the LLM will prefer whichever it's more familiar with, which is usually the built-in one.
### Bank not found errors
The plugin auto-creates banks on first use. If you see bank errors, check that the Hindsight API is running and `HINDSIGHT_API_URL` is correct.
### Connection refused
Make sure the Hindsight API is running and listening on the URL you configured. Test with:
```bash
curl http://localhost:8888/health
```
## Manual registration (advanced)
If you don't want to use the plugin system, you can register tools directly in a Hermes startup script or custom agent:
```python
from hindsight_hermes import register_tools
register_tools(
bank_id="my-agent",
hindsight_api_url="http://localhost:8888",
budget="mid",
tags=["hermes"], # applied to all retained memories
recall_tags=["hermes"], # filter recall to only these tags
)
```
This imports `tools.registry` from Hermes at call time and registers the three tools directly. This approach gives you more control over parameters but requires Hermes to be importable.
## Memory instructions (system prompt injection)
Pre-recall memories at startup and inject them into the system prompt, so the agent starts every conversation with relevant context:
```python
from hindsight_hermes import memory_instructions
context = memory_instructions(
bank_id="my-agent",
hindsight_api_url="http://localhost:8888",
query="user preferences and important context",
budget="low",
max_results=5,
)
# Returns:
# Relevant memories:
# 1. User's favourite colour is red
# 2. User prefers dark mode
```
This never raises — if the API is down or no memories exist, it returns an empty string.
## Global configuration (advanced)
Instead of passing parameters to every call, configure once:
```python
from hindsight_hermes import configure
configure(
hindsight_api_url="http://localhost:8888",
api_key="your-key",
budget="mid",
tags=["hermes"],
)
```
Subsequent calls to `register_tools()` or `memory_instructions()` will use these defaults if no explicit values are provided.
## MCP alternative
Hermes also supports MCP servers natively. You can use Hindsight's MCP server directly instead of this plugin — no `hindsight-hermes` package needed:
```yaml
# In your Hermes config
mcp_servers:
- name: hindsight
url: http://localhost:8888/mcp
```
This exposes the same retain/recall/reflect operations through Hermes's MCP integration. The tradeoff is that MCP tools may have different naming and the LLM needs to discover them, whereas the plugin registers tools with Hermes-native schemas.
## Configuration reference
| Parameter | Env Var | Default | Description |
|-----------|---------|---------|-------------|
| `hindsight_api_url` | `HINDSIGHT_API_URL` | `https://api.hindsight.vectorize.io` | Hindsight API URL |
| `api_key` | `HINDSIGHT_API_KEY` | — | API key for authentication |
| `bank_id` | `HINDSIGHT_BANK_ID` | — | Memory bank ID |
| `budget` | `HINDSIGHT_BUDGET` | `mid` | Recall budget (low/mid/high) |
| `max_tokens` | — | `4096` | Max tokens for recall results |
| `tags` | — | — | Tags applied when storing memories |
| `recall_tags` | — | — | Tags to filter recall results |
| `recall_tags_match` | — | `any` | Tag matching mode (any/all/any_strict/all_strict) |
| `toolset` | — | `hindsight` | Hermes toolset group name |
@@ -132,11 +132,11 @@ The local server exposes the full tool set (29 tools in multi-bank mode, 26 in s
| `list_banks` | List all memory banks (multi-bank only) |
| `create_bank` | Create or configure a memory bank (multi-bank only) |
For detailed parameter documentation, see the [MCP Server reference](../../developer/mcp-server.md#available-tools).
For detailed parameter documentation, see the [MCP Server reference](/developer/mcp-server#available-tools).
## Environment Variables
All standard [Hindsight configuration variables](../../developer/configuration.md) are supported. Key ones for local use:
All standard [Hindsight configuration variables](/developer/configuration) are supported. Key ones for local use:
| Variable | Required | Default | Description |
|----------|----------|---------|-------------|