mirror of
https://github.com/vectorize-io/hindsight.git
synced 2026-09-14 19:31:49 +08:00
fb94ce0341
* feat(mental-models): default list to metadata; MCP list returns metadata only Listing mental models defaulted to returning every model's full synthesized content (and reflect_response). That bloats a caller's context and lets a single list call pull an entire bank's synthesized knowledge in bulk, when the intended way to read a model's content is the single-model read. - MCP list_mental_models tool: returns metadata only (id, name, tags, staleness); the `detail` parameter is removed. An agent discovers models here and reads a specific model's content with get_mental_model. - HTTP GET .../mental-models: `detail` now defaults to `metadata` instead of `full`. Content stays available opt-in via `detail=content`/`full`, and when requested it is delivered and metered the same as a single-model read. - Engine list_mental_models is unchanged and still honors `detail` for internal callers (bank-template export/import need full content). - Regenerated OpenAPI + clients (Python/TypeScript/Go). Tests: the MCP tool is metadata-only with no `detail` param; the HTTP list defaults to metadata and returns content only when detail=content is passed; is_stale is still reported per model on the list. * fix(mental-models): follow through on the list default flip in every caller Flipping the list endpoint's `detail` default from `full` to `metadata` left the callers that were relying on the old default reading nulls. - Control plane: `MentalModelsView` now asks for `detail=content` — it renders the content preview, source query and trigger chips, and seeds the update dialog from the listed row, so metadata alone crashed the search filter (`m.source_query.toLowerCase()` on null) and would have clobbered every trigger setting on save. The search filter is null-guarded too. - CLI: `hindsight mental-model list` asks for `content` (`--verbose` → `full`), restoring the per-row preview and keeping `--output json` useful to scripts. - Docs: the detail-levels table said `full (default)` for both endpoints and showed a `detail` argument on the `list_mental_models` MCP tool that no longer exists; the three SDK list examples printed `source_query` off a default list. Added an upgrade note. - Wrapper clients: the Python docstring still promised a server-side `full` default; the TS one said nothing. - Dropped the "metered the same as a single-model read" claim from the endpoint docstring — a `detail=content` list still validates as one `LIST_MENTAL_MODELS` bank read, not one read per model. * fix(hindsight-all): let the facade ask for mental-model content `mental_models.list()` in both facade paths (the client wrapper and the embedded namespaces) forwarded no `detail`, so after the list default flipped to metadata a hindsight-all caller got content-free rows with no way to ask for more — the one wrapper where the capability was not just defaulted away but unreachable. Forwards `detail` like the TypeScript and Python wrappers do. --------- Co-authored-by: Nicolò Boschi <boschi1997@gmail.com>
186 lines
5.9 KiB
Python
186 lines
5.9 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Mental Models API examples for Hindsight.
|
|
Run: python examples/api/mental-models.py
|
|
"""
|
|
import os
|
|
import time
|
|
|
|
HINDSIGHT_URL = os.getenv("HINDSIGHT_API_URL", "http://localhost:8888")
|
|
BANK_ID = "mental-models-demo-bank"
|
|
|
|
# =============================================================================
|
|
# Setup (not shown in docs)
|
|
# =============================================================================
|
|
from hindsight_client import Hindsight
|
|
|
|
client = Hindsight(base_url=HINDSIGHT_URL)
|
|
|
|
# Create bank and seed some data
|
|
client.create_bank(bank_id=BANK_ID, name="Mental Models Demo")
|
|
client.retain(bank_id=BANK_ID, content="The team prefers async communication via Slack")
|
|
client.retain(bank_id=BANK_ID, content="For urgent issues, use the #incidents channel")
|
|
client.retain(bank_id=BANK_ID, content="Weekly syncs happen every Monday at 10am")
|
|
|
|
# Wait for data to be processed
|
|
time.sleep(2)
|
|
|
|
# =============================================================================
|
|
# Doc Examples
|
|
# =============================================================================
|
|
|
|
# [docs:create-mental-model]
|
|
# Create a mental model (runs reflect in background)
|
|
result = client.create_mental_model(
|
|
bank_id=BANK_ID,
|
|
name="Team Communication Preferences",
|
|
source_query="How does the team prefer to communicate?",
|
|
tags=["team", "communication"]
|
|
)
|
|
|
|
# Returns an operation_id - check operations endpoint for completion
|
|
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)
|
|
|
|
# [docs:create-mental-model-with-trigger]
|
|
# Create a mental model with automatic refresh enabled
|
|
result = client.create_mental_model(
|
|
bank_id=BANK_ID,
|
|
name="Project Status",
|
|
source_query="What is the current project status?",
|
|
trigger={"refresh_cron": "0 3 * * *"}
|
|
)
|
|
|
|
# This mental model checks daily at 03:00 UTC and refreshes when scoped memories changed
|
|
print(f"Operation ID: {result.operation_id}")
|
|
# [/docs:create-mental-model-with-trigger]
|
|
|
|
# [docs:create-mental-model-tags-match]
|
|
# Override how the model's tags filter source memories on refresh.
|
|
# A tagged model defaults to "all_strict" (a memory must carry EVERY tag);
|
|
# use "any" when your memories are tagged narrowly (one topic each), so the
|
|
# refresh reads any memory carrying at least one of the model's tags.
|
|
result = client.create_mental_model(
|
|
bank_id=BANK_ID,
|
|
name="Current Projects",
|
|
source_query="Which projects is the user currently working on?",
|
|
tags=["projects", "mental-model"],
|
|
trigger={"tags_match": "any"}
|
|
)
|
|
|
|
print(f"Operation ID: {result.operation_id}")
|
|
# [/docs:create-mental-model-tags-match]
|
|
|
|
# Wait for the mental model to be created
|
|
time.sleep(5)
|
|
|
|
# [docs:list-mental-models]
|
|
# List all mental models in a bank. The list returns metadata by default;
|
|
# detail="content" adds source_query/content/trigger.
|
|
mental_models = client.list_mental_models(bank_id=BANK_ID, detail="content")
|
|
|
|
for mental_model in mental_models.items:
|
|
print(f"- {mental_model.name}: {mental_model.source_query}")
|
|
# [/docs:list-mental-models]
|
|
|
|
# Get the mental model ID for subsequent examples
|
|
mental_model_id = mental_models.items[0].id if mental_models.items else None
|
|
|
|
if mental_model_id:
|
|
# [docs:get-mental-model]
|
|
# Get a specific mental model
|
|
mental_model = client.get_mental_model(
|
|
bank_id=BANK_ID,
|
|
mental_model_id=mental_model_id
|
|
)
|
|
|
|
print(f"Name: {mental_model.name}")
|
|
print(f"Content: {mental_model.content}")
|
|
print(f"Last refreshed: {mental_model.last_refreshed_at}")
|
|
# [/docs:get-mental-model]
|
|
|
|
|
|
# [docs:refresh-mental-model]
|
|
# Refresh a mental model to update with current knowledge
|
|
result = client.refresh_mental_model(
|
|
bank_id=BANK_ID,
|
|
mental_model_id=mental_model_id
|
|
)
|
|
|
|
print(f"Refresh operation ID: {result.operation_id}")
|
|
# [/docs:refresh-mental-model]
|
|
|
|
|
|
# [docs:clear-mental-model]
|
|
# Clear a mental model's content, then refresh for a full re-synthesis
|
|
client.clear_mental_model(
|
|
bank_id=BANK_ID,
|
|
mental_model_id=mental_model_id
|
|
)
|
|
|
|
# Trigger a fresh full rebuild
|
|
result = client.refresh_mental_model(
|
|
bank_id=BANK_ID,
|
|
mental_model_id=mental_model_id
|
|
)
|
|
|
|
print(f"Full refresh operation ID: {result.operation_id}")
|
|
# [/docs:clear-mental-model]
|
|
|
|
|
|
# [docs:update-mental-model]
|
|
# Update a mental model's metadata
|
|
updated = client.update_mental_model(
|
|
bank_id=BANK_ID,
|
|
mental_model_id=mental_model_id,
|
|
name="Updated Team Communication Preferences",
|
|
trigger={"refresh_after_consolidation": True} # Enable auto-refresh
|
|
)
|
|
|
|
print(f"Updated name: {updated.name}")
|
|
# [/docs:update-mental-model]
|
|
|
|
|
|
# [docs:get-mental-model-history]
|
|
# Get the change history of a mental model
|
|
history = client.get_mental_model_history(
|
|
bank_id=BANK_ID,
|
|
mental_model_id=mental_model_id
|
|
)
|
|
|
|
for entry in history:
|
|
print(f"Changed at: {entry['changed_at']}")
|
|
print(f"Previous content: {entry['previous_content']}")
|
|
# [/docs:get-mental-model-history]
|
|
|
|
# [docs:delete-mental-model]
|
|
# Delete a mental model
|
|
client.delete_mental_model(
|
|
bank_id=BANK_ID,
|
|
mental_model_id=mental_model_id
|
|
)
|
|
# [/docs:delete-mental-model]
|
|
|
|
|
|
# =============================================================================
|
|
# Cleanup (not shown in docs)
|
|
# =============================================================================
|
|
client.delete_bank(bank_id=BANK_ID)
|
|
|
|
print("mental-models.py: All examples passed")
|