SIGN IN SIGN UP

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