Repository navigation
Add reach-confined forget by id (forget_within) - #248
Merged
Merged
Conversation
Adds a forget_within method to MemoryEngine that removes only the named ids lying within a given reach, leaving ids outside it untouched and reading no node the reach does not admit. A default implementation reads the ids back through get and forgets what returns, so hosts confining a caller to its own subtree can forget by id without touching another tree. Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
Expose forget_within_by_get and forget_within_ids from the explore module so callers confined to a reach can forget by id without scanning outside it. The ForgetTarget::Ids docs now point at forget_within rather than the get-then-forget dance, since an engine may look for ids in every node it holds. Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
Adds a forget_within path that removes items by id only within the scopes a reach admits, so a confined caller never lists or reads outside its reach. The existing everywhere-by-id logic is factored into a shared helper that both paths use. Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
Extend the paging test engine to record forget targets and return a report, then add tests for forget_within covering blank and duplicate id rejection, forgetting only ids inside the reach, skipping the engine call when nothing is in reach, batching reads by MAX_GET_IDS, and rejecting an empty id list before any read. Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
Extend the namespaces conformance case with checks that a forget by id confined to a reach removes only the ids inside it, leaving items beyond the reach untouched. Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
Clone the expected item ids before collecting them into a set so the conformance check compiles without moving values it still needs. Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
Register the new forget-within test module in the cortex engine so its cases run as part of the existing test suite. Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
A forget by ids under a scope now routes through forget_within with the scope's reach instead of forgetting across the whole tree, so ids outside the scope are no longer affected. Unscoped forgets still use the plain forget by id, and tests cover both paths. Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
The scoped forget test now builds its scope by setting the reach field on a default ToolScope instead of calling with_reach, matching the current scope type. Store calls were also reformatted to satisfy rustfmt. Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
Adds `forget_within(ids, reach)` to the engine trait docs, which removes only the ids inside a reach and reads no node outside it while looking, unlike `ForgetTarget::Ids` which is unscoped. The Cortex integration notes and the namespace and memory-v2 specs now describe the default get-then-forget behaviour and the conformance coverage for a forget by id within a reach. Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
Note Currently processing new changes in this PR. This may take a few minutes, please wait... ⚙️ Run configuration
📒 Files selected for processing (16)
Comment |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds
MemoryEngine::forget_within(ids, reach), a forget by id that only looks for the ids inside a reach.ForgetTarget::Idsis unscoped by design. On the CortexDB engine it lists every scope under the TinyMemory root and reads the ids' labels in each one. A host that confines a caller to its own subtree already narrows the ids withgetbefore forgetting. The forget itself still swept the whole tree, though, so on a shared hosted engine it read other users' scopes with the caller's credential. OpenHuman's SaaS memory isolation test (tinyhumansai/openhuman#7271) found this. Nothing leaked back to the caller and nothing outside its tree was deleted, but the lookup read scopes it had no business reading.forget_withinis a new default trait method. The default,forget_within_by_get, runsgetwith the reach in batches ofMAX_GET_IDS, then callsforget(Ids)on the ids it found.forget_within_idsvalidates and dedups the ids (no ids or a blank id isInvalidRequest). Both are exported.forget_within. It looks the ids up only inall_scopes_for(reach), the scopes a read with that reach uses. A reach without descendants needs no listing at all. A subtree reach lists only under its own node. The listing must be complete, as it is for an unscoped forget by id.memory_forgetby ids now callsforget_withinwith the scope's reach when the scope has one. An unscopedToolScopekeeps usingForgetTarget::Ids.Related issue
None. Found by tinyhumansai/openhuman#7271 ("Finding: forget-by-id sweeps the whole tree").
API or behavior changes
The change is additive, so it needs a minor bump.
MemoryEnginegainsforget_within, which has a default. The api crate gainsforget_within_by_getandforget_within_ids.ForgetTargetis unchanged, and so is the behaviour offorget(ForgetTarget::Ids). Existing engine impls keep compiling. A wrapper engine that forwards calls should also forwardforget_within. If it doesn't, it gets the get-then-forget default, which is still correct but sweeps again through the inner engine's unscoped forget by id.Validation
cargo fmt --all -- --check: okcargo clippy --all-targets --all-features -- -D warnings: okcargo build --all-targets --all-features: okcargo test --all-features: all pass (869 integrations lib tests, 153 tools, 93 api, plus the integration and doc tests)RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --all-features: okTests
cortex/engine/mod_forget_within_tests.rsruns on both wires (Direct and hosted doubles). The setup has three trees:user:ann,user:ann/agent:scoutanduser:anna.annastarts withann.scope/prefixand forget bodies) is at or belowapp:tinymemory/user:ann. Nothing namesuser:anna, and anna's item survives.every_scope()and filter byadmits. That fails withnamed "app:tinymemory" outside "app:tinymemory/user:ann"and "an exact reach needs no scope listing". Usingevery_scope()unfiltered fails the counts.namespacescheck: aforget_withinunder agent b's reach removes none of a, scout or job. Aforget_withinover agent a's subtree removes scout but not the root.explore/mod_tests.rs: default-impl tests for id validation and dedup, forgetting only ids in reach, sending no forget when nothing is in reach,MAX_GET_IDSbatching, and refusing before reading.tools/write/mod_tests.rs: a scopedmemory_forgetgoes throughforget_withinwith the scope's reach, and an unscoped one throughForgetTarget::Ids.Not covered: a subtree listing still sends
prefix=<node path>with no trailing/. On a backend that matches string prefixes, the listing response may therefore include a sibling whose name starts the same way (user:annaunderuser:ann). Those names are dropped client-side byReach::admitsand never read. This is the same listing every subtreelist/fetchalready sends, so it's a separate, wider question and is left unchanged here.Documentation
docs/specs/memory-v2.md: the trait listing, "Get and forget by id", the Scope paragraph and the tools section.docs/architecture/namespaces.md: the reach table and the confined-caller guidance.docs/architecture/api.md: the trait method table.crates/tinymemory-integrations/src/cortex/README.md: the Forget bullet.Checklist
#[allow(...)],#[ignore], or relaxed lints.envcontents in the diff or the descriptionSummary by CodeRabbit