SIGN IN SIGN UP

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.
N
Nicolò Boschi committed
99a319513f05d6e47f641b73584f9e9ea9cb484c
Parent: 92695c6
Committed by GitHub <noreply@github.com> on 8/24/2026, 12:06:12 PM