fix(coding-agents): let knowledge pages see the observations they ask for, and stop tagging initiative markers with a page id (#3664)
* fix(coding-agents): let knowledge pages see the observations they ask for, and stop tagging initiative markers with a page id (#3641) Two tag mistakes, one root: the plugin used `tags` for things tags cannot do. 1. Pages could never retrieve an observation. Every seeded page is tag-scoped to one `knowledge:<tier>` label, and the server defaults a tagged model to `all_strict`, which EXCLUDES untagged memories. Since #3564 this plugin retains with `observation_scopes: "shared"`, which consolidates into the single empty scope — so every observation in these banks is untagged. Pages asked for the `observation` fact type and could not match a single row of it, synthesizing from raw world/experience facts alone. The trigger now states `tags_match: "all"`: AND over the page's tier tag, but including untagged rows. A `knowledge:decision` fact still cannot reach the Component map (it is tagged and lacks that page's tag), while the untagged shared observations reach every page, where the `source_query` selects among them. `seedPages()` re-sends the trigger when the live page reports a different `tags_match`, so previously seeded banks are repaired too — the source query is unchanged on those banks, so it could not be the drift signal. 2. `relatedPageId:<pageId>` was never a tag. The tag vocabulary here is fixed and low-cardinality on purpose (matching is exact set-ops, no wildcards); one new tag value per initiative isolates nothing — the concrete page and the overview both filter on `knowledge:feature-work` — and lands on every fact extracted from the marker. It existed only to carry an id into the synthesis prompt. The id now rides on `metadata.relatedPageId` and, because reflect strips `metadata` from its search results while keeping `context`, on a `[[page:<id>]]` link in the marker's retain context. That is the channel the overview page actually reads back, so its cross-links keep working. Per-initiative pages remain scoped by their `source_query`, not by tags; giving them real tag isolation needs work facts to carry the initiative, which is a separate change. * test(coding-agents): guard the single observation scope over the whole source tree Forwarding `observationScopes` from every entrypoint (already guarded) is worthless if a write path skips the method that actually sends it. `retain()` is the only place `observation_scopes` reaches the wire, so a second `/memories` POST anywhere would silently consolidate under the server's `combined` default and split one repo's beliefs per tag combination again (#3564) — while writing perfectly good memories, so no existing test would fail. Asserts over the source tree, the way daemon.test.ts guards `ensureDaemon`: no module addresses the memories endpoint but the client, and that one call site sits inside retain() with the scoping on the item it posts. Both halves verified to fail when violated. * refactor(coding-agents): type the page PATCH body, and cover the server that reports no trigger Review follow-ups on this branch: - the PATCH payload was a `Record<string, unknown>`; its keys are known, so state them. - no test covered the case most banks are actually in today — a server older than #3572 reports no trigger on the tree, so the policy is unknowable and gets re-sent. It must be trigger-ONLY, or every deepen run rebuilds all five pages.
N
Nicolò Boschi committed
170a078578cee23a95cd5084c2581bb10765a61a
Parent: 7f7d9c1
Committed by GitHub <noreply@github.com>
on 8/20/2026, 8:35:51 AM