Skip to content

fix: import Hermes USER.md native memories - #2378

Open
keeponlight wants to merge 1 commit into
MemTensor:mainfrom
keeponlight:fix/hermes-native-user-import
Open

keeponlight wants to merge 1 commit into
MemTensor:mainfrom
keeponlight:fix/hermes-native-user-import

Conversation

@keeponlight

Copy link
Copy Markdown

Supersedes #2360 (closed automatically due to base branch dev-v2.0.34 deletion).

Description

Hermes native scans and paged imports only read MEMORY.md, silently omitting the user profiles stored in the sibling USER.md. Both endpoints now include the optional profile file and report the combined entry count and byte size.

Imported traces retain their source filename in the existing tags field and use that file's modification time. The cache fingerprints each file separately, so same-size edits to the older file and creation/removal of USER.md invalidate cached pages. Existing MEMORY.md IDs are preserved; profile IDs are distinct and remain stable when MEMORY.md grows. Missing USER.md remains valid, while other read errors are reported.

No new dependencies or public request/response schema changes.

Related Issue (Required): Fixes #2306

Reviewer: @syzsunshine219

This PR targets main (rebased following the merge and deletion of dev-v2.0.34).

Type of change

  • Bug fix (non-breaking change which fixes an issue)

How Has This Been Tested?

Tested on Windows with Node.js 24.13.1 and Vitest 2.1.9.

  • Unit Test — nine new regression cases cover both files, paging, source tags/timestamps, optional empty/missing profiles, stable IDs, cache invalidation, and read errors. The regression suite failed before the fix.
  • Test Script Or Test Steps — run from apps/memos-local-plugin:
npm test -- tests/unit/server/hermes-native-import.test.ts tests/unit/server/import-export-path.test.ts
npm run lint
npm run build

Results:

Test Files  2 passed (2)
     Tests  12 passed (12)

TypeScript lint and build passed. Repository-root make format passed (All checks passed!; 629 files left unchanged). The normal pre-commit hook passed for the committed files.

The broader npm test -- tests/unit/server run reports 105 passed / 10 failed. Re-running with the original importer from base commit 0f747744 reports 96 passed / the same 10 failed: six assertions assume POSIX path separators, two Windows lifecycle tests lack a response mock, and two SSE shutdown tests fail their unsubscribe assertions. These existing failures are outside this fix. No FastAPI pipeline/API contract changes are involved.

Checklist

  • I have performed a self-review of my own code.
  • I have commented my code in hard-to-understand areas.
  • I have added tests that prove my fix is effective.
  • Related MemOS-Docs issue/PR considered — not applicable to this bug fix; existing import endpoints and public schemas are unchanged.
  • I have linked the issue to this PR.
  • I have mentioned the person who will review this PR.

Reviewer Checklist

@Memtensor-AI Memtensor-AI added area:plugin OpenClaw & Hermes status:in-progress Someone or AI is working on it | 人工或 AI 正在处理 labels Sep 16, 2026
@Memtensor-AI

Memtensor-AI commented Sep 16, 2026 •

Copy link
Copy Markdown
Collaborator

🤖 Open Code Review

Target: PR #2378
Task: 4a3d9993ff0de58e
Base: main
Head: fix/hermes-native-user-import

🔍 OpenCodeReview found 3 issue(s) in this PR.


1. apps/memos-local-plugin/server/routes/import-export.ts (L410-L418)

The stat calls for MEMORY.md and USER.md are sequential, even though both results are needed together to compute the fingerprint. Since the two stat calls have no dependency on each other (USER.md path is derivable immediately), they could be parallelised. More importantly, if MEMORY.md stat fails (non-existent or permissions error) the function throws before USER.md is even attempted, which is the correct fast-fail behaviour — but if USER.md stat succeeds while MEMORY.md later fails to readFile, the error propagates fine because Promise.all is used for reads. No runtime bug here, but consider using Promise.all for the two stat calls to reduce latency on every cache miss:

const [memInfo, userInfo] = await Promise.allSettled([
  stat(path),
  stat(userPath),
]);
if (memInfo.status === 'rejected') throw memInfo.reason;

This is a minor performance observation — the current sequential approach is functionally correct.


2. apps/memos-local-plugin/server/routes/import-export.ts (L432)

Accessing a typed array by index without a non-null assertion. TypeScript strict mode (enabled in tsconfig.json) does not enable noUncheckedIndexedAccess, so contents[fileIndex] types as string (not string | undefined), even though a runtime undefined is impossible here because contents is produced by Promise.all(files.map(...)) with the same length as files. This is safe today, but if this access pattern is refactored (e.g., the index source changes), the lack of a null-check could silently pass undefined into splitHermesNativeMemories.

Suggestion: destructure directly or use a non-null assertion to make the intent explicit:

const raw = contents[fileIndex]!;
💡 Suggested Change

Before:

    const raw = contents[fileIndex];

After:

    const raw = contents[fileIndex]!;

3. apps/memos-local-plugin/server/routes/import-export.ts (L482)

The trace ID scheme now branches on the literal "MEMORY.md", while the same literal is independently re-declared in readHermesNativeMemories (file: "MEMORY.md" / "USER.md") and in resolveHermesNativeMemoryPath. These three occurrences are coupled implicitly — if any one is changed or a new source file is added (e.g. a differently-cased name), the memory.file === "MEMORY.md" check silently falls into the prefixed branch and every existing tr_hm_/ep_hm_ ID changes, producing duplicate rows on upgrade rather than a compile error.

Suggestion: lift the filenames into shared constants and type the field as a union so the coupling is checked by the compiler:

const HERMES_MEMORY_FILE = "MEMORY.md";
const HERMES_USER_FILE = "USER.md";
type HermesNativeFile = typeof HERMES_MEMORY_FILE | typeof HERMES_USER_FILE;

then build identity from the constant instead of the inline string.

Generated by cloud-assistant via Open Code Review.

@keeponlight

Copy link
Copy Markdown
Author

Hi @hijzy @syzsunshine219,

Friendly ping for review!

This PR is a clean resubmission of #2360 (which was automatically closed when dev-v2.0.34 was merged into main and deleted).

Could you please take a look when you have a moment? Thank you!

@Memtensor-AI

Copy link
Copy Markdown
Collaborator

✅ Automated Test Results: PASSED

All tests passed (85/85 executed). memos_local_plugin/unit: 85/85. Duration: 4s

Branch: fix/hermes-native-user-import

@keeponlight

keeponlight commented Oct 14, 2026 •

Copy link
Copy Markdown
Author

Rebased onto the latest main (a204c03e) and addressed all 4 Open Code Review findings from #2378:

Verification on a204c03e:

  • npx vitest run tests/unit/server/hermes-native-import.test.ts: 9/9 passed
  • Full plugin test suite (npm test): 187 test files, 1,606 passed
  • npm run lint & npm run build: passed (0 errors)

cc @Hun-ger @syzsunshine219 @whipser030 — ready for review whenever you have a moment!

@Memtensor-AI

Copy link
Copy Markdown
Collaborator

⚠️ Automated Test Results: ENV ISSUE

The test environment encountered an issue that requires manual attention.

Details: Environment preparation failed before any gating tests executed. Failed scopes: memos_local_plugin
Branch: fix/hermes-native-user-import

@keeponlight
keeponlight force-pushed the fix/hermes-native-user-import branch from cd3237c to a204c03 Compare October 14, 2026 10:29
@Memtensor-AI

Copy link
Copy Markdown
Collaborator

⚠️ Automated Test Results: ENV ISSUE

The test environment encountered an issue that requires manual attention.

Details: Environment preparation failed before any gating tests executed. Failed scopes: memos_local_plugin
Branch: fix/hermes-native-user-import

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:plugin OpenClaw & Hermes status:in-progress Someone or AI is working on it | 人工或 AI 正在处理

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Native memory import ignores USER.md (Hermes adapter reads only MEMORY.md)

4 participants