feat: share memory and KG across users when workspace sharing is enabled

Memory (MEMORY.md, memory/*) and knowledge graph are now shared when
workspace sharing is active, matching the filesystem sharing behavior.
Previously memory was always per-user isolated even with shared workspace,
causing inconsistencies when collaborating on the same files.

Adds MemoryUserID(ctx) helper that returns empty userID (global scope)
when shared memory flag is set, used by memory interceptor, memory tools,
and KG search. UI warning updated to note data is not migrated on toggle.
This commit is contained in:
viettranx committed 2026-03-12 12:09:38 +07:00
1 parent 25b24ebd50
commit bece4525ba
8 files changed
+36 -10

No files matched your search

+5 -1
View File
@@ -104,8 +104,12 @@ func (l *Loop) runLoop(ctx context.Context, req RunRequest) (*RunResult, error)
cachedWs = ws
}
effectiveWorkspace := cachedWs.(string)
if !l.shouldShareWorkspace(req.UserID, req.PeerKind) {
shared := l.shouldShareWorkspace(req.UserID, req.PeerKind)
if !shared {
effectiveWorkspace = filepath.Join(effectiveWorkspace, sanitizePathSegment(req.UserID))
} else {
// Shared workspace → share memory across users too.
ctx = store.WithSharedMemory(ctx)
}
if err := os.MkdirAll(effectiveWorkspace, 0755); err != nil {
slog.Warn("failed to create user workspace directory", "workspace", effectiveWorkspace, "user", req.UserID, "error", err)
+22
View File
@@ -23,6 +23,8 @@ const (
SelfEvolveKey contextKey = "goclaw_self_evolve"
// LocaleKey is the context key for the user's preferred locale (e.g. "en", "vi", "zh").
LocaleKey contextKey = "goclaw_locale"
// SharedMemoryKey indicates memory should be shared (no per-user scoping).
SharedMemoryKey contextKey = "goclaw_shared_memory"
)
// WithUserID returns a new context with the given user ID.
@@ -90,6 +92,26 @@ func SelfEvolveFromContext(ctx context.Context) bool {
return false
}
// WithSharedMemory returns a context flagged for shared memory (skip per-user scoping).
func WithSharedMemory(ctx context.Context) context.Context {
return context.WithValue(ctx, SharedMemoryKey, true)
}
// IsSharedMemory returns true if memory should be shared across users.
func IsSharedMemory(ctx context.Context) bool {
v, _ := ctx.Value(SharedMemoryKey).(bool)
return v
}
// MemoryUserID returns the userID to use for memory operations.
// Returns "" (shared/global) when shared memory is active, otherwise the per-user ID.
func MemoryUserID(ctx context.Context) string {
if IsSharedMemory(ctx) {
return ""
}
return UserIDFromContext(ctx)
}
// WithLocale returns a new context with the given locale.
func WithLocale(ctx context.Context, locale string) context.Context {
return context.WithValue(ctx, LocaleKey, locale)
+1 -1
View File
@@ -65,7 +65,7 @@ func (t *KnowledgeGraphSearchTool) Execute(ctx context.Context, args map[string]
if agentID == uuid.Nil {
return ErrorResult("agent context not available")
}
userID := store.UserIDFromContext(ctx)
userID := store.MemoryUserID(ctx)
query, _ := args["query"].(string)
if query == "" {
+2 -2
View File
@@ -72,7 +72,7 @@ func (t *MemorySearchTool) Execute(ctx context.Context, args map[string]any) *Re
return ErrorResult("memory system not available")
}
userID := store.UserIDFromContext(ctx)
userID := store.MemoryUserID(ctx)
searchOpts := store.MemorySearchOptions{
MaxResults: maxResults,
MinScore: minScore,
@@ -167,7 +167,7 @@ func (t *MemoryGetTool) Execute(ctx context.Context, args map[string]any) *Resul
return ErrorResult("memory system not available")
}
userID := store.UserIDFromContext(ctx)
userID := store.MemoryUserID(ctx)
// Try per-user first, then global
content, err := t.memStore.GetDocument(ctx, agentID.String(), userID, path)
+3 -3
View File
@@ -96,7 +96,7 @@ func (m *MemoryInterceptor) ReadFile(ctx context.Context, path string) (string,
// Normalize absolute path to workspace-relative for DB storage
relPath := normalizeToRelative(path, m.workspace)
userID := store.UserIDFromContext(ctx)
userID := store.MemoryUserID(ctx)
agentStr := agentID.String()
// Try per-user first, then global
@@ -136,7 +136,7 @@ func (m *MemoryInterceptor) WriteFile(ctx context.Context, path, content string)
// Normalize absolute path to workspace-relative for DB storage
relPath := normalizeToRelative(path, m.workspace)
userID := store.UserIDFromContext(ctx)
userID := store.MemoryUserID(ctx)
agentStr := agentID.String()
// Write document to DB
@@ -175,7 +175,7 @@ func (m *MemoryInterceptor) ListFiles(ctx context.Context, path string) (string,
return "", false, nil
}
userID := store.UserIDFromContext(ctx)
userID := store.MemoryUserID(ctx)
docs, err := m.memStore.ListDocuments(ctx, agentID.String(), userID)
if err != nil {
return "", true, err
+1 -1
View File
@@ -286,7 +286,7 @@
"sharedUsers": "Shared Users",
"sharedUsersTip": "Specific user IDs that always share workspace regardless of DM/Group settings. Format: channel:userId (e.g. telegram:386246614)",
"userIdPlaceholder": "Enter user ID (e.g. telegram:386246614)",
"warning": "When sharing is enabled, all shared users can read and write files in the same workspace directory. Memory and context files remain isolated per user."
"warning": "When sharing is enabled, all shared users can read and write files in the same workspace directory. Memory is also shared; context files remain isolated per user. Toggling this setting does not migrate existing data — previous per-user memories will no longer be accessible."
},
"compaction": {
"title": "Compaction",
+1 -1
View File
@@ -286,7 +286,7 @@
"sharedUsers": "User được chia sẻ",
"sharedUsersTip": "Danh sách user ID cụ thể luôn dùng chung workspace. Định dạng: channel:userId (vd: telegram:386246614)",
"userIdPlaceholder": "Nhập user ID (vd: telegram:386246614)",
"warning": "Khi bật chia sẻ, tất cả người dùng được chia sẻ có thể đọc và ghi file trong cùng thư mục workspace. Bộ nhớ và file ngữ cảnh vẫn được cô lập theo từng người dùng."
"warning": "Khi bật chia sẻ, tất cả người dùng được chia sẻ có thể đọc và ghi file trong cùng thư mục workspace. Bộ nhớ cũng được chia sẻ; file ngữ cảnh vẫn được cô lập theo từng người dùng. Thay đổi cài đặt này không di chuyển dữ liệu cũ — bộ nhớ per-user trước đó sẽ không còn truy cập được."
},
"compaction": {
"title": "Nén ngữ cảnh",
+1 -1
View File
@@ -286,7 +286,7 @@
"sharedUsers": "共享用户",
"sharedUsersTip": "始终共享工作区的特定用户ID。格式:channel:userId(例如 telegram:386246614)",
"userIdPlaceholder": "输入用户ID(例如 telegram:386246614)",
"warning": "启用共享后,所有共享用户可以读写同一工作区目录中的文件。记忆和上下文文件仍按用户隔离。"
"warning": "启用共享后,所有共享用户可以读写同一工作区目录中的文件。记忆也会共享;上下文文件仍按用户隔离。切换此设置不会迁移现有数据——之前的用户独立记忆将无法访问。"
},
"compaction": {
"title": "压缩",