Bug Description
When an MCP write is routed to a cloud project by project_id or by workspace-qualified project, the tool output may return a project-qualified permalink such as:
getting-started/hermes-tests/hermes-bm-smoke-20260524-t142716-z-personal-getting-started
instead of a workspace-qualified canonical permalink such as:
personal/getting-started/hermes-tests/hermes-bm-smoke-20260524-t142716-z-personal-getting-started
In a mixed local+cloud / multi-workspace setup, that returned permalink is not reliably self-routing. Follow-up reads without explicit project / project_id can be routed through the Hermes/Basic Memory configured default project and fail, even though the note was written successfully.
This is especially confusing for agents because the natural workflow is:
write_note(project_id=...)
- parse returned
permalink
- use returned permalink for
read_note / edit_note / delete_note
That workflow is only safe if the returned identifier is canonical enough to self-route.
Environment
Observed through Hermes bm_* MCP tools after the Basic Memory / hermes-basic-memory routing updates.
- Basic Memory runtime:
Basic Memory version: 0.21.3
- Hermes plugin:
basic-memory / hermes-basic-memory 0.3.2
- Hermes provider default project:
hermes-memory
- Workspaces discovered:
personal / Personal
basic-memory-7020de4e925843c68c9056c60d101d9e / Basic Memory org
Steps to Reproduce
Use a setup with two cloud workspaces that both include a project with the same name, e.g. getting-started in both Personal and org workspaces.
- Discover projects:
Relevant entries observed:
{
"name": "getting-started",
"external_id": "9e1f395d-16c1-4398-988a-df34d46d8bba",
"workspace_slug": "personal",
"qualified_name": "personal/getting-started"
}
{
"name": "getting-started",
"external_id": "9d432f92-3a9b-4cc8-aecc-698951490ef8",
"workspace_slug": "basic-memory-7020de4e925843c68c9056c60d101d9e",
"qualified_name": "basic-memory-7020de4e925843c68c9056c60d101d9e/getting-started"
}
- Write to Personal
getting-started by project_id:
bm_write(
project_id="9e1f395d-16c1-4398-988a-df34d46d8bba",
folder="hermes-tests",
title="Hermes BM Smoke 20260524T142716Z personal getting-started",
content="..."
)
- Observe returned output:
# Created note
project: getting-started
file_path: hermes-tests/Hermes BM Smoke 20260524T142716Z personal getting-started.md
permalink: getting-started/hermes-tests/hermes-bm-smoke-20260524-t142716-z-personal-getting-started
- Try to read using the returned permalink without explicit routing:
bm_read(identifier="getting-started/hermes-tests/hermes-bm-smoke-20260524-t142716-z-personal-getting-started")
Observed result from Hermes with default project hermes-memory:
# Note Not Found in hermes-memory: "getting-started/hermes-tests/hermes-bm-smoke-20260524-t142716-z-personal-getting-started"
- Reading with a workspace-qualified identifier works:
bm_read(identifier="memory://personal/getting-started/hermes-tests/hermes-bm-smoke-20260524-t142716-z-personal-getting-started")
The same pattern was observed for the org getting-started project and other cloud-only projects where the returned permalink did not include the workspace slug.
Expected Behavior
For routed cloud writes, Basic Memory should return a canonical identifier that is safe for follow-up calls. Preferably:
permalink: personal/getting-started/hermes-tests/...
or, for the org workspace:
permalink: basic-memory-7020de4e925843c68c9056c60d101d9e/getting-started/hermes-tests/...
At minimum, MCP write/read/search results should include an explicit qualified_name / workspace_slug / canonical memory URL in structured output so clients and agents can safely route follow-up operations.
Actual Behavior
The write succeeds, but the returned permalink may be only project-qualified:
getting-started/hermes-tests/...
That identifier is ambiguous across workspaces and is not safe to use as-is from a Hermes session whose default project is different.
Why this matters
This breaks the agent/client invariant that “the permalink returned by a successful write is the identifier to use for follow-up read/edit/delete.” Agents either have to carry project_id manually forever or reconstruct workspace-qualified identifiers themselves from bm_projects() output.
Suggested Fix / Regression Tests
Suggested behavior:
- If a call was routed by
project_id, return a workspace-qualified canonical permalink when the project belongs to a cloud workspace.
- If a call was routed by workspace-qualified
project, return a workspace-qualified canonical permalink.
- Preserve existing local-only behavior where no workspace exists.
- Ensure duplicate project names across workspaces return distinct canonical permalinks.
Suggested tests:
write_note(project_id=<personal getting-started uuid>) returns personal/getting-started/... or a canonical memory://personal/getting-started/... URL.
write_note(project_id=<org getting-started uuid>) returns basic-memory-.../getting-started/....
- The returned identifier can be passed directly to
read_note without specifying project / project_id.
- Existing workspace-qualified
memory://... read/context routing continues to work.
Bug Description
When an MCP write is routed to a cloud project by
project_idor by workspace-qualifiedproject, the tool output may return a project-qualified permalink such as:instead of a workspace-qualified canonical permalink such as:
In a mixed local+cloud / multi-workspace setup, that returned permalink is not reliably self-routing. Follow-up reads without explicit
project/project_idcan be routed through the Hermes/Basic Memory configured default project and fail, even though the note was written successfully.This is especially confusing for agents because the natural workflow is:
write_note(project_id=...)permalinkread_note/edit_note/delete_noteThat workflow is only safe if the returned identifier is canonical enough to self-route.
Environment
Observed through Hermes
bm_*MCP tools after the Basic Memory / hermes-basic-memory routing updates.Basic Memory version: 0.21.3basic-memory/hermes-basic-memory0.3.2hermes-memorypersonal/ Personalbasic-memory-7020de4e925843c68c9056c60d101d9e/ Basic Memory orgSteps to Reproduce
Use a setup with two cloud workspaces that both include a project with the same name, e.g.
getting-startedin both Personal and org workspaces.bm_projects()Relevant entries observed:
{ "name": "getting-started", "external_id": "9e1f395d-16c1-4398-988a-df34d46d8bba", "workspace_slug": "personal", "qualified_name": "personal/getting-started" } { "name": "getting-started", "external_id": "9d432f92-3a9b-4cc8-aecc-698951490ef8", "workspace_slug": "basic-memory-7020de4e925843c68c9056c60d101d9e", "qualified_name": "basic-memory-7020de4e925843c68c9056c60d101d9e/getting-started" }getting-startedbyproject_id:Observed result from Hermes with default project
hermes-memory:The same pattern was observed for the org
getting-startedproject and other cloud-only projects where the returned permalink did not include the workspace slug.Expected Behavior
For routed cloud writes, Basic Memory should return a canonical identifier that is safe for follow-up calls. Preferably:
or, for the org workspace:
At minimum, MCP write/read/search results should include an explicit
qualified_name/workspace_slug/ canonical memory URL in structured output so clients and agents can safely route follow-up operations.Actual Behavior
The write succeeds, but the returned permalink may be only project-qualified:
That identifier is ambiguous across workspaces and is not safe to use as-is from a Hermes session whose default project is different.
Why this matters
This breaks the agent/client invariant that “the permalink returned by a successful write is the identifier to use for follow-up read/edit/delete.” Agents either have to carry
project_idmanually forever or reconstruct workspace-qualified identifiers themselves frombm_projects()output.Suggested Fix / Regression Tests
Suggested behavior:
project_id, return a workspace-qualified canonical permalink when the project belongs to a cloud workspace.project, return a workspace-qualified canonical permalink.Suggested tests:
write_note(project_id=<personal getting-started uuid>)returnspersonal/getting-started/...or a canonicalmemory://personal/getting-started/...URL.write_note(project_id=<org getting-started uuid>)returnsbasic-memory-.../getting-started/....read_notewithout specifyingproject/project_id.memory://...read/context routing continues to work.