feat(provider): split memory search scope_filter → scope_all + scope_any (bifrost 0.8.0/wire v0.6)

Repin bifrost 0.7.0→0.8.0 and reimplement the memory store's search scope
filter to the v0.6 split (#11): scope_all (AND/intersection) + scope_any
(OR/union over a list of conjunctive scopes), at parity with the v0.6
reference _matches_scope / _validate_scope. No-compat: scope_filter removed.

scope_any is the union-visibility primitive that resolves the #295/#297
silent-zero AND foot-gun — a subset-scoped chunk now recalls via an OR
member. End-to-end cold recall now gated only on Worldtree emitting
scope_any on its recall path (#297, upstream).

- store: search(scope_all, scope_any); _scope_subset + _matches_scope + _validate_scope
- contract v1.2: search FN sig, INV-005 recomposed, PRE-003 both fields, scope_any_union test
- tests: scope_any union, scope_all∧scope_any compose, both-empty match-all; parity vs real 0.8.0 dispatch (433 green)
- #17 contract: sync stale scope_filter/_scope_matches-AND refs to scope_all/scope_any
- runbook + persistent-memory updated; provider bounced onto 0.8.0 (fresh empty db)

v0.17.6
This commit is contained in:
vh
2026-06-16 23:09:31 -07:00
parent 43f2e148ad
commit 96d61a4bb1
8 changed files with 203 additions and 63 deletions
+18 -8
View File
@@ -107,7 +107,7 @@ PY
For a background-shell provider, that is the task output file; tail it:
```
[memory-provider] memory-call search REQUEST: scope_filter={...} top_k=... vec_dim=1024
[memory-provider] memory-call search REQUEST: scope_all={...} scope_any=[...] top_k=... vec_dim=1024
[memory-provider] memory-call search RESPONSE: N hit(s) [{'chunk_id':..., 'score':..., 'scope':...}]
```
@@ -117,14 +117,15 @@ delete — promoted failure-surfaces may be wanted corpus.
## Reading the result
The `search REQUEST` `scope_filter` vs the `search RESPONSE` hit count is the
whole diagnosis surface:
The `search REQUEST` `scope_all`/`scope_any` vs the `search RESPONSE` hit count is
the whole diagnosis surface:
- **0 hits** → the filter didn't match any stored chunk. Compare the filter's
axes against the stored `scope`. INV-005 requires **every** filter axis to
match, so an extra axis on the filter that the chunks don't carry (e.g.
`agent_self`) zeroes the result even when `end_user` matches. That's a
**scope-build / persist-recall-symmetry** question (Worldtree-side).
- **0 hits** → the composed v0.6 filter matched no stored chunk. `scope_all` axes
are AND-matched — an extra axis the chunks don't carry (e.g. `agent_self`) zeroes
the result even when `end_user` matches. `scope_any` is the OR/union escape hatch:
a subset-scoped chunk recalls if its scope ⊇ **any one** element. So 0 hits with a
populated store now means Worldtree sent an over-specified `scope_all` instead of a
`scope_any` union — a **scope-build** question (Worldtree-side, post-v0.6).
- **Hit present but the model says "no memory"** → we returned it; Worldtree
dropped it downstream of search → **recall-assembly / injection**
(Worldtree-side).
@@ -144,10 +145,19 @@ RESPONSE: 0 hit(s)
ANSWER: "I don't have access to your past preferences..."
```
(That capture is the **pre-v0.6 wire** — a single AND-only `scope_filter`.)
Root cause: the recall filter carried `agent_self` but the stored chunks are
`{end_user: smoke-user}` only → the `agent_self` axis excluded all of them.
Branch (a), scope asymmetry — fed to #297.
**Resolution (bifrost 0.8.0 / wire v0.6, #11):** the single `scope_filter` is split
into `scope_all` (AND) + `scope_any` (OR/union). Worldtree can now send the visible
scopes as a `scope_any` union (e.g. `[{end_user: smoke-user}, {end_user: smoke-user,
agent_self: ...}]`), so the subset-scoped chunk recalls via the matching OR member.
Our store implements this at parity with the v0.6 reference; **end-to-end cold recall
now waits only on Worldtree emitting `scope_any`** on the recall path (#297).
## Notes / foot-guns
- **HTTP, not HTTPS.** The spec requires `endpoint_url` be HTTPS; dev is relaxed