Where
SemanticSessionManager._generate_set_id (packages/server/src/memmachine_server/semantic_memory/semantic_session_manager.py:430), with _org_set_id (:415) and _hash_tag_list (:39). Observed on main at 8101f14.
The id is built as
mem_{set_type}_org_{org_id}[_project_{project_id}]_{len(metadata)}_{shake256(sorted keys)[:12]}__{'_'.join(sorted(f"{k}_{v}"))}
Every component is joined with _ and nothing is escaped. SafeId permits _ in org_id and project_id, metadata values are str(value) with no validation, and the hash covers only the metadata keys, not the values or the scope. So two distinct (org_id, project_id, metadata) scopes can produce the same string.
Measured collisions
Run against the real generator on main:
COLLIDE org "a_project_b", org-level set, {producer_id: x} vs org "a", project "b", {producer_id: x}
mem_user_set_org_a_project_b_1_7e8d0b20c039__producer_id_x
COLLIDE same org/project, {a: "x_b_y", b: "z"} vs {a: "x", b: "y_b_z"}
mem_other_set_org_o_project_p_2_b226bacd172e__a_x_b_y_b_z
COLLIDE org "victim", project "prod", {team: eng} vs org "victim_project_prod", org-level, {team: eng}
mem_other_set_org_victim_project_prod_1_05ab79662e20__team_eng
distinct org "a_project_b", org-level, {} vs org "a", project "b", {}
(set_type vs project_set prefix differs)
The three default sets (org-level with no tags, org-level user set, project-level with no tags) do not collide with each other across these scopes. The first and third cases need a project-scoped custom set type with at least one tag on one side; the second needs a set type with two or more tags.
Consequence
The set id is the partition key for features, ingestion history, cluster state, and per-set configuration. Two scopes that share an id read each other's features in search, ingestion consolidates their facts together, deleting the set removes both, and register_set_id_set_type (config_store_sqlalchemy.py:357) is ON CONFLICT DO NOTHING on set_id, so whichever scope registers first owns the set-type mapping.
Any deployment that scopes callers by org_id and lets callers choose org or project names is exposed to cross-scope access through a chosen name; a deployment that assigns opaque ids is not.
Related dead code
_get_all_set_ids (:303) lists sets with list_set_id_starts_with(prefix="org_..."), but every set id starts with mem_, so it can never match. It also has no callers.
Fix direction
Derive the id from a canonical encoding of the whole tuple, for example a hash of the JSON of [org_id, project_id, sorted(metadata.items())], or escape the separator in every component. Either changes every existing id, so it needs a migration in the style of alembic_pg/versions/b65f7f4a9d2c_migrate_legacy_set_ids.py. Remove _get_all_set_ids or make it match the real prefix.
🤖 Written by Claude Fable 5.1 via Claude Code; posted from @edwinyyyu's account.
Where
SemanticSessionManager._generate_set_id(packages/server/src/memmachine_server/semantic_memory/semantic_session_manager.py:430), with_org_set_id(:415) and_hash_tag_list(:39). Observed onmainat 8101f14.The id is built as
Every component is joined with
_and nothing is escaped.SafeIdpermits_inorg_idandproject_id, metadata values arestr(value)with no validation, and the hash covers only the metadata keys, not the values or the scope. So two distinct(org_id, project_id, metadata)scopes can produce the same string.Measured collisions
Run against the real generator on
main:The three default sets (org-level with no tags, org-level user set, project-level with no tags) do not collide with each other across these scopes. The first and third cases need a project-scoped custom set type with at least one tag on one side; the second needs a set type with two or more tags.
Consequence
The set id is the partition key for features, ingestion history, cluster state, and per-set configuration. Two scopes that share an id read each other's features in search, ingestion consolidates their facts together, deleting the set removes both, and
register_set_id_set_type(config_store_sqlalchemy.py:357) isON CONFLICT DO NOTHINGonset_id, so whichever scope registers first owns the set-type mapping.Any deployment that scopes callers by
org_idand lets callers choose org or project names is exposed to cross-scope access through a chosen name; a deployment that assigns opaque ids is not.Related dead code
_get_all_set_ids(:303) lists sets withlist_set_id_starts_with(prefix="org_..."), but every set id starts withmem_, so it can never match. It also has no callers.Fix direction
Derive the id from a canonical encoding of the whole tuple, for example a hash of the JSON of
[org_id, project_id, sorted(metadata.items())], or escape the separator in every component. Either changes every existing id, so it needs a migration in the style ofalembic_pg/versions/b65f7f4a9d2c_migrate_legacy_set_ids.py. Remove_get_all_set_idsor make it match the real prefix.🤖 Written by Claude Fable 5.1 via Claude Code; posted from @edwinyyyu's account.