mirror of
https://github.com/vectorize-io/hindsight.git
synced 2026-09-14 19:31:49 +08:00
41d71a9818
* 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.
158 lines
5.6 KiB
JavaScript
158 lines
5.6 KiB
JavaScript
#!/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: { refreshCron: '0 3 * * *' } },
|
|
);
|
|
|
|
// This mental model checks daily at 03:00 UTC and refreshes when scoped memories changed
|
|
console.log(`Operation ID: ${result2.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.
|
|
const result3 = await client.createMentalModel(
|
|
BANK_ID,
|
|
'Current Projects',
|
|
'Which projects is the user currently working on?',
|
|
{
|
|
tags: ['projects', 'mental-model'],
|
|
trigger: { tagsMatch: 'any' },
|
|
},
|
|
);
|
|
|
|
console.log(`Operation ID: ${result3.operation_id}`);
|
|
// [/docs:create-mental-model-tags-match]
|
|
|
|
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:clear-mental-model]
|
|
// Clear a mental model's content, then refresh for a full re-synthesis
|
|
await client.clearMentalModel(BANK_ID, mentalModelId);
|
|
|
|
// Trigger a fresh full rebuild
|
|
const fullRefreshResult = await client.refreshMentalModel(BANK_ID, mentalModelId);
|
|
|
|
console.log(`Full refresh operation ID: ${fullRefreshResult.operation_id}`);
|
|
// [/docs:clear-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');
|