Files
Nicolò Boschi 41d71a9818 fix(#2808): make mental model tags_match configurable on all creation surfaces (MCP, TS client, CLI) (#2858)
* feat(mcp): let create_mental_model configure tags_match (#2808)

A tagged mental model with no explicit tags_match in its trigger JSON
refreshes under all_strict (a memory must carry every one of the model's
tags), while the staleness check and every recall/reflect path default to
any. Broadly-tagged models reading narrowly-tagged memories therefore get
marked stale and then refresh to empty content.

The HTTP API, generated SDK clients, and Control Plane UI already let users
set trigger.tags_match; the MCP create_mental_model tool did not. Add a
tags_match argument (validated against TagsMatch) to both MCP variants. It
is only written into the trigger when explicitly passed, so the resolved
all_strict default is preserved for existing callers.

Document the all_strict footgun and the tags_match override in the MCP and
mental-models API docs (regen skills/hindsight-docs mirror).

* fix(ts-client): expose tags_match/tag_groups on createMentalModel

The ergonomic TypeScript wrapper's createMentalModel accepted only
{ refreshAfterConsolidation } in its trigger option and dropped every other
trigger field, so a wrapper user could not set tags_match — the exact knob
needed to avoid the empty-refresh footgun in #2808. The low-level generated
sdk already accepts the full MentalModelTriggerInput; thread tagsMatch and
tagGroups through, mirroring how recall/reflect already expose them.

The Python client needs no change: its wrapper takes a pass-through
trigger dict and the generated MentalModelTriggerInput already validates
tags_match.

* test(ts-client): cover createMentalModel trigger mapping

Mock the generated sdk layer (no server needed) and assert the ergonomic
camelCase trigger options map onto the snake_case body: tagsMatch ->
tags_match, tagGroups -> tag_groups, refreshAfterConsolidation still maps,
and omitting trigger sends none (preserving the all_strict default). Locks
in the #2808 wrapper fix.

* docs(mental-models): add tags_match code snippet

Replace the static JSON block in the tags_match override section with a
live CodeSnippet pulled from the Python example, showing how to create a
model with trigger.tags_match="any" so a broadly-tagged model reads
narrowly-tagged memories on refresh (#2808).

* feat(cli): add --tags-match to mental-model create + all-language docs

The Rust CLI's `mental-model create` was the last creation surface with no
way to set tags_match, so a tagged model created via the CLI hit the same
empty-refresh footgun (#2808). Add a `--tags-match` flag (any/all/any_strict/
all_strict/exact) that is only sent when passed, preserving the server's
all_strict default; invalid values are rejected before the request.

Expand the mental-models docs "tags_match override" example from a single
Python snippet to a full Tabs block (Python / Node.js / CLI / Go), each
pulled from the runnable example files, and regen the skills mirror.
2026-07-21 12:04:23 +02:00

111 lines
4.1 KiB
Bash

#!/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]
# [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);
# pass --tags-match any when your memories are tagged narrowly (one topic
# each), so the refresh reads any memory carrying at least one of the tags.
hindsight mental-model create "$BANK_ID" \
"Current Projects" \
"Which projects is the user currently working on?" \
--tags projects,mental-model \
--tags-match any
# [/docs:create-mental-model-tags-match]
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:clear-mental-model]
# Clear a mental model's content, then refresh for a full re-synthesis
curl -s -X POST "${HINDSIGHT_URL}/v1/default/banks/${BANK_ID}/mental-models/${MENTAL_MODEL_ID}/clear"
# Trigger a fresh full rebuild
hindsight mental-model refresh "$BANK_ID" "$MENTAL_MODEL_ID"
# [/docs:clear-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"