@@ -422,6 +422,30 @@ def _split_workspace_memory_url_segments(identifier: str) -> tuple[str, str, str
422422 return workspace_slug , project_identifier , remainder
423423
424424
425+ def _canonical_memory_path_for_workspace (
426+ * ,
427+ workspace_slug : str ,
428+ workspace_type : str ,
429+ project_permalink : str ,
430+ remainder : str ,
431+ include_project : bool ,
432+ ) -> str :
433+ """Return the stored canonical path for a workspace-qualified memory URL."""
434+ normalized_remainder = remainder .strip ("/" )
435+ if workspace_type == "organization" :
436+ prefix = f"{ generate_permalink (workspace_slug )} /{ project_permalink } "
437+ elif workspace_type == "personal" :
438+ prefix = project_permalink if include_project else ""
439+ else :
440+ raise ValueError (f"Unsupported workspace_type for memory URL routing: { workspace_type } " )
441+
442+ if not prefix :
443+ return normalized_remainder
444+ if not normalized_remainder :
445+ return prefix
446+ return f"{ prefix } /{ normalized_remainder } "
447+
448+
425449def _cloud_workspace_discovery_available (config : BasicMemoryConfig ) -> bool :
426450 """Return True when workspace discovery can be used without forcing local routing."""
427451 from basic_memory .mcp .async_client import (
@@ -496,7 +520,13 @@ async def resolve_workspace_qualified_memory_url(
496520 )
497521
498522 entry = matches [0 ]
499- canonical_path = f"{ entry .workspace .slug } /{ entry .project .permalink } /{ remainder } "
523+ canonical_path = _canonical_memory_path_for_workspace (
524+ workspace_slug = entry .workspace .slug ,
525+ workspace_type = entry .workspace .workspace_type ,
526+ project_permalink = entry .project .permalink ,
527+ remainder = remainder ,
528+ include_project = ConfigManager ().config .permalinks_include_project ,
529+ )
500530 return WorkspaceMemoryUrlResolution (entry = entry , canonical_path = canonical_path )
501531
502532
@@ -1005,7 +1035,19 @@ async def resolve_project_and_path(
10051035 if normalized_path == qualified_prefix or normalized_path .startswith (
10061036 f"{ qualified_prefix } /"
10071037 ):
1008- return cached_project , normalized_path , True
1038+ remainder = (
1039+ ""
1040+ if normalized_path == qualified_prefix
1041+ else normalized_path .removeprefix (f"{ qualified_prefix } /" )
1042+ )
1043+ resolved_path = _canonical_memory_path_for_workspace (
1044+ workspace_slug = cached_workspace .slug ,
1045+ workspace_type = cached_workspace .workspace_type ,
1046+ project_permalink = cached_project .permalink ,
1047+ remainder = remainder ,
1048+ include_project = bool (include_project ),
1049+ )
1050+ return cached_project , resolved_path , True
10091051
10101052 workspace_context = current_workspace_permalink_context ()
10111053 if workspace_context and project :
@@ -1016,7 +1058,19 @@ async def resolve_project_and_path(
10161058 f"{ qualified_prefix } /"
10171059 ):
10181060 active_project = await get_active_project (client , project , context , headers )
1019- return active_project , normalized_path , True
1061+ remainder = (
1062+ ""
1063+ if normalized_path == qualified_prefix
1064+ else normalized_path .removeprefix (f"{ qualified_prefix } /" )
1065+ )
1066+ resolved_path = _canonical_memory_path_for_workspace (
1067+ workspace_slug = workspace_context .workspace_slug ,
1068+ workspace_type = workspace_context .workspace_type ,
1069+ project_permalink = project_permalink ,
1070+ remainder = remainder ,
1071+ include_project = bool (include_project ),
1072+ )
1073+ return active_project , resolved_path , True
10201074
10211075 project_prefix , remainder = _split_project_prefix (normalized_path )
10221076 include_project = config .permalinks_include_project
0 commit comments