Skip to content

feat: add user_mapper.py for cross-channel memory unification#19163

Open
Cyrene963 wants to merge 1 commit into
NousResearch:mainfrom
Cyrene963:feat/cross-channel-memory-mapper
Open

feat: add user_mapper.py for cross-channel memory unification#19163
Cyrene963 wants to merge 1 commit into
NousResearch:mainfrom
Cyrene963:feat/cross-channel-memory-mapper

Conversation

@Cyrene963

Copy link
Copy Markdown

Summary

Addresses community feedback on multi-user isolation design: same user on different channels (CLI, Telegram, Discord) gets separate memory banks.

Solution

user_mapper.py — a filesystem utility that unifies a user's memories across channels via symlinks:

memories/{chat_id}/ → memories/user_{username}/

Commands

# Map a channel to a user
python3 user_mapper.py map --chat-id 7359770766 --user nitrogen

# Migrate existing data + map
python3 user_mapper.py migrate --chat-id 7359770766 --user nitrogen

# List all mappings
python3 user_mapper.py list

# Resolve a chat_id
python3 user_mapper.py resolve --chat-id 7359770766

# Remove mapping
python3 user_mapper.py unmap --chat-id 7359770766

Relationship to PR #17989

PR Problem Solved Layer
#17989 Different users can't see each other's data Code (MemoryStore, session_search)
This PR Same user shares memory across channels Filesystem (symlinks)

Complementary, not competing. #17989 is the foundation; this builds on it.

How It Works

  1. Creates memories/user_{username}/ as the real data directory
  2. Symlinks memories/{chat_id}/user_{username}/
  3. Symlinks global MEMORY.md/USER.mduser_{username}/ (for CLI)
  4. Hermes follows symlinks transparently — zero code changes

Limitations

  • Global MEMORY.md symlink means all non-Telegram sessions (CLI, cron) share the same user's memory. Fine for single-user setups; multi-user CLI needs code-level user_id support.
  • This is a companion tool, not a replacement for proper cross-channel identity in Hermes core.

Test Results

All 5 tests pass locally:

  • ✅ CLI reads correct memory via global symlink
  • ✅ Telegram reads correct memory via chat_id symlink
  • ✅ Different user remains isolated
  • ✅ Write consistency across all paths
  • ✅ user_mapper CLI commands work

@alt-glitch alt-glitch added type/feature New feature or request P3 Low — cosmetic, nice to have comp/agent Core agent loop, run_agent.py, prompt builder comp/cli CLI entry point, hermes_cli/, setup wizard comp/gateway Gateway runner, session dispatch, delivery comp/plugins Plugin system and bundled plugins tool/memory Memory tool and memory providers tool/skills Skills system (list, view, manage) labels May 3, 2026
@Cyrene963 Cyrene963 force-pushed the feat/cross-channel-memory-mapper branch from 1231b79 to 500d2b0 Compare May 3, 2026 09:56
@alt-glitch

Copy link
Copy Markdown
Collaborator

Related to #9308 — both address cross-channel identity/memory unification. #9308 approaches via Honcho owner identity at gateway layer; this PR uses filesystem symlinks as a companion tool.

1 similar comment
@alt-glitch

Copy link
Copy Markdown
Collaborator

Related to #9308 — both address cross-channel identity/memory unification. #9308 approaches via Honcho owner identity at gateway layer; this PR uses filesystem symlinks as a companion tool.

@Cyrene963

Copy link
Copy Markdown
Author

Thanks for pointing out #9308! You're right that both address cross-channel identity, but they solve it at different scopes:

#9308 (Honcho gateway layer):

  • Auto-detects the bot owner via DM + home channel match
  • Works at the Honcho memory plugin level
  • Only covers the owner — other users still get fragmented memories

#19163 (filesystem layer):

The gap in #9308: if a non-owner user (e.g., a classmate sharing the bot) uses both Telegram and CLI, their memories are still split. #9308's _is_owner_source check intentionally skips non-owner users.

Proposed approach:

I'll update #19163 to also support automatic owner detection (symlink the global MEMORY.md/USER.md to the owner's user directory on first setup), reducing the manual config burden.

@Cyrene963 Cyrene963 force-pushed the feat/cross-channel-memory-mapper branch from 500d2b0 to 9ea7ce0 Compare May 3, 2026 10:18
@ether-btc

Copy link
Copy Markdown
Contributor

The user_mapper.py approach is architecturally sound — unified identity mapping across channels prevents the fragmentation problem where the same user appears as different IDs on Telegram vs Discord.

Questions before merge:

  1. Location: scripts/user_mapper.py is an odd home for a runtime component (and this is a net-new addition, not an edit). If this runs at agent startup or on each message, it belongs in agent/ or tools/. scripts/ typically implies standalone utilities. What's the invocation path?

  2. Persistence: Does the mapper state survive session restarts? If channels are added incrementally, the mapper needs to be persisted (DB, file). If it's recomputed on each startup from some canonical source, that should be documented.

  3. Conflict resolution: When two channels map to the same user_id but have conflicting display names or metadata, which wins? There should be an explicit merge strategy.

The core logic looks solid. The file is well-structured with clear docstrings. Resolving the location and persistence questions would unblock merge.

@Cyrene963

Copy link
Copy Markdown
Author

Thanks for the thorough review, @ether-btc! Good questions. Here's the breakdown:

1. Location: scripts/ vs agent/ vs tools/

user_mapper.py is a standalone CLI utility, not a runtime component. It's invoked manually during setup:

python3 user_mapper.py auto-setup      # one-time owner detection
python3 user_mapper.py map --chat-id X --user Y  # manual mapping

It does not run at agent startup or on each message. Once the symlinks are created, Hermes reads memories/{chat_id}/ and follows the symlink transparently — zero runtime code changes. This is why scripts/ is the correct home (same pattern as scripts/release.py, scripts/run_tests.sh).

That said, if the maintainers prefer tools/ for discoverability, I'm happy to move it.

2. Persistence

The mapper state persists across restarts via two mechanisms:

  • Mapping file: ~/.hermes/user_mapping.json — JSON dict of {chat_id: username}. Loaded on each CLI invocation, saved on map/unmap commands.
  • Symlinks: Created on the filesystem (~/.hermes/memories/{chat_id}/ → user_{username}/). These survive restarts, reboots, and hermes update. They're just regular filesystem symlinks.

The mapping is not recomputed on startup — it's explicitly managed via CLI commands. Once set, it persists until manually unmapped.

3. Conflict Resolution

With the symlink approach, there are no metadata conflicts to resolve — the design sidesteps the problem entirely:

  • The canonical key is username (not display name, not metadata)
  • All channels mapped to the same user point to the same directory (user_{username}/)
  • Data naturally merges because it's literally the same filesystem directory
  • Display names and per-channel metadata are not stored in the mapping file — only the {chat_id → username} mapping exists

If two channels map to the same user but have conflicting data (e.g., different USER.md content), the second map command doesn't overwrite — it symlinks to the existing directory. The first channel's data wins (it's already there). The migrate command handles moving existing data into the user directory with explicit confirmation.

Summary: The mapper is intentionally minimal — a CLI tool + JSON file + symlinks. No runtime code, no database, no conflict resolution logic needed because the filesystem handles it.

@Cyrene963 Cyrene963 force-pushed the feat/cross-channel-memory-mapper branch from 9ea7ce0 to 4cf1e75 Compare May 7, 2026 13:44
…cation

Adds  command that:
1. Reads config.yaml to find Telegram home channel chat_id
2. Falls back to largest memory directory detection
3. Automatically migrates data and creates symlinks
4. Sets up global MEMORY.md/USER.md for CLI access

Addresses feedback on NousResearch#9308 overlap:
- NousResearch#9308 auto-detects owner at gateway layer (Honcho only)
- This provides auto-setup for owner + manual mapping for any user
- Works with all memory providers, not just Honcho

Commands: auto-setup, map, unmap, list, resolve, migrate
@Cyrene963 Cyrene963 force-pushed the feat/cross-channel-memory-mapper branch from 4cf1e75 to eba9d52 Compare May 7, 2026 14:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/agent Core agent loop, run_agent.py, prompt builder comp/cli CLI entry point, hermes_cli/, setup wizard comp/gateway Gateway runner, session dispatch, delivery comp/plugins Plugin system and bundled plugins P3 Low — cosmetic, nice to have tool/memory Memory tool and memory providers tool/skills Skills system (list, view, manage) type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants