feat: per-user memory isolation via template scopes#568
feat: per-user memory isolation via template scopes#568eightHundreds wants to merge 6 commits intoCortexReach:masterfrom
Conversation
Add support for template variables in `scopes.default` (e.g. `user:${accountId}`)
and wildcard patterns in `agentAccess` (e.g. `user:*`) to enable per-user memory
isolation when multiple users interact with the same bot.
Key changes:
- Template utilities: hasTemplateVars, resolveTemplateScope, matchesWildcardScope,
inferWildcardFromTemplate in src/scopes.ts
- Hook-layer template resolution via resolveHookDefaultScope() in index.ts
- SQL wildcard support: scopeFilterToSqlCondition with proper LIKE escaping
- Application-layer wildcard matching: scopeFilterIncludes replaces includes()
- Smart extractor dedup narrowed to [defaultScope] instead of accessibleScopes
- 30 new tests covering template/wildcard utilities and integration scenarios
Design: scope system stays purely static (Plan B), template resolution is a
hook-layer concern. No auto-wildcard injection into accessible scopes.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The project already has jiti as a devDependency. No need to bump ^2.6.0 to ^2.6.1 in this PR. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
8852a81 to
911031c
Compare
|
Cross-user memory bleed in shared bots is a real problem and per-user scope Must fix
Worth discussing
Fix the three blockers and resolve the scopeFilter question, then this is ready for |
AliceLJY
left a comment
There was a problem hiding this comment.
Thorough review of all changed files. This is well-designed.
Architecture — Plan B (scope system stays static, template resolution at hook layer) is the right call. Clean separation between config-time scope validation and runtime context binding.
Security — checked:
- SQL wildcard injection:
escapeSqlLiteral+ LIKE meta-character escaping for%and_✅ - Overly broad wildcard patterns:
inferWildcardFromTemplatecorrectly rejects compound templates likeagent:${agentId}:user:${accountId}that would produceagent:*✅ - Failed template resolution: returns
undefined+ skips write (not empty-string write to bogus scope) ✅
Backward compatibility — static scopes.default works exactly as before. All existing scope-filter codepaths upgraded to wildcard-aware variants (scopeFilterToSqlCondition, scopeFilterIncludes) with consistent semantics.
Key design decisions I agree with:
- Dedup scope narrowed to
[defaultScope]instead ofaccessibleScopes— prevents cross-user dedup matching in multi-tenant setups - No auto-wildcard in
getAccessibleScopes— wildcards only via explicitagentAccessconfig getDefaultScope()returns"global"for template defaults when no agentId — safe fallback
Tests — 30 unit tests + integration tests with mock plugin harness. Coverage looks adequate.
One note: manual Feishu bot test is still unchecked in the test plan. Would be good to confirm multi-user isolation works end-to-end before merge.
LGTM — approve.
Summary
Implements per-user memory isolation (#555) to prevent cross-user memory bleed when multiple users DM the same bot (e.g., Feishu).
scopes.default(user:${accountId},${channelId}, etc.) — resolved at hook layer, scope system stays purely staticagentAccess(user:*) for explicit multi-user read access_%pattern)[defaultScope]instead ofaccessibleScopesConfiguration Example
{ "scopes": { "default": "user:${accountId}", "agentAccess": { "bot-1": ["global", "user:*"] } } }Each user's memories are written to their own scope (e.g.,
user:alice), while the bot can read across all user scopes via theuser:*wildcard.Key Design Decisions
resolveHookDefaultScope)getAccessibleScopesdoes NOT auto-append wildcards — wildcards only via explicitagentAccessconfigscopes.defaultworks exactly as before, no breaking changesFiles Changed
src/scopes.ts— template/wildcard utilities, wildcard-awareisAccessible, safegetDefaultScopeindex.ts—resolveHookDefaultScope()for hook-layer template resolutionsrc/store.ts—scopeFilterToSqlCondition(SQL LIKE with escaping),scopeFilterIncludes(app-layer wildcard matching)test/scope-template-wildcard.test.mjs— 30 new testsTest plan
🤖 Generated with Claude Code