mirror of
https://github.com/vectorize-io/hindsight.git
synced 2026-09-14 19:31:49 +08:00
perf/profiling-env
4 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
99a319513f |
fix(knowledge-pages): move a page's scope onto its mental model, and stop trigger patches resetting it (#3687) (#3755)
* docs(knowledge-pages): say that a page's tags are a filter, and let the dialog widen it (#3687) A page's `tags` are the scope it is synthesized from, not labels on it, and a tagged page matches with `all_strict` by default: a memory must carry EVERY tag and untagged memories are excluded outright. Nothing said so. The create-page API documented `tags` as "tags that scope which memories the page is built from" with no match mode, and `type:<x>` is documented as setting the page's rendered *type* — so a page created with `["type:runbook", "homelab", "infrastructure"]` silently required all three on every memory, matched nothing, and generated as "I don't have information about this" while a plain recall for the same query returned 81 results scoring 1.087. The server default stays. What changes is that you can now see it and move it: - The control-plane create/edit dialog states the rule under the tags field and, once there is a tag to widen, offers "Also build from untagged memories" -> `trigger.tags_match: "all"`. The edit dialog prefills from the page's stored mode and only sends `tags_match` when the checkbox actually moved, so a page on `any`/`any_strict`/`exact` survives a rename untouched. - `CreatePageRequest.tags`, `UpdateNodeRequest.tags` and `MentalModelTrigger.tags_match` carry the rule in the OpenAPI spec, so it reaches Swagger UI and the generated SDKs; both hand-written wrappers (TS `HindsightClient`, Python `Hindsight`) get it in parity. - A "Tags Are a Filter" section in the API docs works the reported page through, with the three ways to scope one and the `PATCH {"tags": []}` repair. Also corrects a stale claim in the same three places: a supplied `trigger` has merged over the page defaults since #3506, but the docs and both wrapper docstrings still said it REPLACES them and told you to repeat the fields you wanted to keep. Under that text the fix above reads as destructive. * fix(knowledge-pages): move a page's scope onto its mental model, and stop trigger patches resetting it (#3687) Two changes, one subject: where a knowledge page's retrieval scope is edited, and whether editing it destroys the rest of the page's configuration. 1. The page dialogs no longer take tags. `tags` on a page are the scope it is synthesized FROM, and a tagged page matches with `all_strict`: every tag required, untagged memories excluded. The create dialog offered a bare text input for them next to a hint that a `type:<x>` tag "sets the page's type" — so tags typed there to describe a topic silently became a hard filter that matched nothing, and the page generated as "I don't have information about this". A partial copy of the scope controls was the problem, so the copy is gone rather than extended: the page dialogs now take a name and a source query, and the page links to its backing mental model, which already owns the whole scope (tags, tags_match, tag_groups, fact types, schedule). Pages are created reading the whole bank; narrowing one is now a deliberate step taken where the match mode is visible next to the tags it governs. The edit dialog no longer sends `tags` at all. That is load-bearing: the PATCH applies only the keys present, so a dialog that no longer shows the field must not send it, or every rename would clear the page's scope. 2. `PATCH /mental-models/{id}` patches its trigger instead of replacing it. Which matters much more now that it is where pages send people. The route dumped the whole request model and the engine wrote that dict wholesale, so setting one field stamped `MentalModelTrigger`'s own defaults over every field left unset: a page edited there lost `mode: delta`, its observation-only `fact_types`, and `exclude_mental_models`, quietly becoming a from-scratch rebuild that also reflected over its sibling pages. #3506 fixed exactly this, but only on the two page routes. The MCP `update_mental_model` tool drives the same endpoint with a one-key dict (`{"refresh_after_consolidation": ...}`), so it was wiping triggers too. `_merge_page_trigger` is renamed `_merge_trigger` and is now the shared merge for any mental model; a full trigger still replaces, which keeps bank-template import declarative. Verified against a live API: a page created through the UI, opened via its new "Advanced options" link and saved from the mental-model editor, keeps `mode: delta`, `fact_types: [observation]`, `exclude_mental_models` and `refresh_after_consolidation`. Four of the five new regression tests fail without the engine change; the fifth is the guard that a complete trigger still replaces. * fix(knowledge-pages): open the backing mental model in place, and name it in the link (#3687) The link opened a page's scope editor by navigating to the Mental Models tab. Changing a page's scope is part of working on the page, so sending the reader to another tab — losing the page they were reading, and their place in the tree — was the wrong trade for a shareable URL. It now opens the same detail modal in place, on its configuration, and hands off to the very same `UpdateMentalModelDialog` the Mental Models tab uses (exported for it, not copied). Saving re-pulls the tree and the open page, since tags and trigger drive the chips and the freshness line. The label says "Mental model options" rather than "Advanced options": what opens is that model, and the page's scope living on a mental model is exactly the thing a reader needs told. The ?mentalModel= deep link added for the navigation is removed with it — nothing links there now, and a query param nothing produces is a trap for the next reader. * refactor(knowledge-pages): name the options i18n key for what it opens (#3687) The key was still `advancedOptions` after the label became "Mental model options", and the refresh callback asserted `currentBank!` where guarding is free. |
||
|
|
a5d00660b5 |
fix(knowledge-base): answer page staleness per scope, not from the bank watermark (#3291) (#3589)
The knowledge tree, the mental-model list and the control-plane stats card derived staleness from one bank-wide write watermark: any write flagged every page that had not read it. Only an in-scope write can move a page's own watermark, so a page whose scope went quiet stayed flagged indefinitely while the refresh gate — which does check each page's own scope — correctly refused to refresh it. The reporter measured 24 of 29 pages flagged for hours against 0 of 29 by the exact check. The approximation existed because the exact check cost a scan of the bank's memories per page: memory_units had no index involving updated_at, so `WHERE bank_id = $1 AND updated_at > $2` could not stop early on the common negative answer. Add (bank_id, updated_at DESC) and that cost goes from 10.7ms (tagged page, fresh) and 29.8ms (untagged model) to 0.046ms and 0.021ms on a 400k-row table. With the index the exact answer is affordable, so those surfaces now ask it, batched: any_memory_updated_since_batch answers a whole bank in one query per scope shape (30 pages in 0.2-0.5ms). Reflect's search_mental_models, which ran the scoped check per model inside every reflect call, uses the same batch. The watermark shortcut is kept for callers holding a live watermark, but the polling surfaces no longer pass the 60s-cached one — it can be older than a model's own last read and would prove a freshness that is not there. _bank_write_watermark had no callers left and is removed. Staleness still asks only what has been *written*; deletions leave no write behind and remain invisible, which the API and docs now state outright. |
||
|
|
0ca0e87a08 |
fix(control-plane): report mental-model freshness from the bank write watermark (#3156)
* fix(control-plane): report mental-model freshness from the bank write watermark The mental-models card compared each model's last_refreshed_at against the bank's last_consolidated_at, so any consolidation after a refresh — nearly always — reported every model as stale, and a bank that had never consolidated reported the opposite. Computing the real per-model answer on a list is the expensive fix: compute_mental_model_is_stale has no index to use (there is none on memory_units.updated_at), so it scans the bank's memories in full, per model — 10ms per model at 100k memories, 101ms at 500k, on a view that polls every 5s. Report a bank-wide watermark instead. MAX(updated_at) rides along on the aggregate _compute_bank_stats already runs and is served from the same cached payload as last_memory_write_at. A model refreshed at or after it is up to date, exactly; older only means something was written, possibly outside its tags, so the card says "may need refresh" rather than asserting stale. The exact answer stays on the single mental-model read, behind the dialog. The knowledge-base tree ran that same scan once per page and polls every 12s — already a full scan per page per tick in production. It now shares the watermark: one cached lookup for the whole tree. Fixes #3139 * perf(reflect): skip the per-model staleness scan below the bank watermark search_mental_models computed staleness with the exact scoped query for every model it returned — up to 5 full scans of the bank's memories per tool call, serially, on a held connection, and the agent can call the tool several times per reflect. get_bank_freshness already computes the bank's write watermark in the same scan it runs once per reflect, and was discarding it. Thread it through: a model refreshed at or after the newest write in the bank cannot be stale whatever its scope, so it skips the query entirely. Everything above the watermark still gets the exact tag-aware answer — the agent only trusts a model without a verifying recall() when is_stale is False, so guessing conservatively here would buy LLM turns to save a query. * chore(docs-skill): re-sync the generated reference copies generate-docs-skill.sh output drifted from the docs pages that landed on main (retain narrator guidance, configuration, coding-agents). Regenerated so verify-generated-files has nothing to report. |
||
|
|
4d22a882f5 |
docs(knowledge-pages): document Knowledge Pages and Mental Models, and manage them from the CLI (#3151)
* docs(knowledge-pages): document Knowledge Pages and Mental Models Knowledge Pages shipped in #2455 with no documentation at all — no architecture page, no API page, no mention in the sidebar. Mental models had an API page but nothing explaining what they are or why they are fast. Add both, as top-level entries under Architecture and API. - Architecture: how pages are mental models with a simplified, document-shaped configuration; the folder hierarchy; the `hindsight fs` filesystem projection; page-level search; and why a projected view over reconciled memory is not the same thing as a folder of raw files. - Architecture: mental models as standing answers built in the background, so an application reads the current version instead of paying for synthesis on the request path. - API: the full knowledge-base endpoint surface, the page defaults and what each one buys, staleness gating, what a refresh reads, and how delta mode edits a structured document instead of regenerating prose. The mental-model trigger table gains the seven settings it was missing. - FAQ: mental model vs knowledge page. Also corrects the neighbouring answer, which described mental models as built automatically during retain — that is observations. The API examples use the maintained clients like every other API page, so this adds the knowledge-base surface to the Python and TypeScript wrappers (kept at parity, with request-mapping tests on both sides) and runnable Python/Node/Go examples. * feat(cli): manage knowledge pages from the CLI The knowledge base was reachable from every client except the CLI, where the eight endpoints were listed as deliberate coverage skips ("managed in the control plane UI"). That left `hindsight fs` able to mirror pages read-only but nothing able to create, edit, search, or delete them — and it meant the API docs could not show a CLI tab alongside Python/Node/Go. Adds `hindsight knowledge-base` with tree, create-folder, create-page, get-page, search, update, delete, and export, removing the skips so cli-coverage-check enforces the surface from here on. `create-page` sends no trigger unless --mode or --fact-types is passed, so the server's page defaults stand; when either is given the whole trigger has to be restated, because a supplied trigger replaces the defaults rather than merging with them. Also adds the CLI tab to the Knowledge Pages API page and a Knowledge Base section to the CLI reference. |