Skip to content

Memory/QMD: reuse default model cache and skip ENOENT warnings#12114

Merged
tyler6204 merged 5 commits intomainfrom
fix/qmd-model-cache
Feb 9, 2026
Merged

Memory/QMD: reuse default model cache and skip ENOENT warnings#12114
tyler6204 merged 5 commits intomainfrom
fix/qmd-model-cache

Conversation

@tyler6204
Copy link
Member

@tyler6204 tyler6204 commented Feb 8, 2026

Summary

  • symlink the default QMD models directory into each agent's custom XDG_CACHE_HOME so preinstalled models are reused instead of re-downloaded
  • keep existing target model directories untouched (no overwrite)
  • treat missing default model cache (ENOENT) as an expected first-run no-op instead of logging a warning
  • add/extend tests for symlink creation, existing-target preservation, missing-default no-op behavior, and no-warning assertion for the missing-default case

Testing

  • pnpm tsgo
  • pnpm exec vitest run src/memory/qmd-manager.test.ts

Greptile Overview

Greptile Summary

This PR adds a symlinkSharedModels() step to QmdMemoryManager.initialize() so per-agent XDG_CACHE_HOME directories can reuse a shared/default QMD models cache (avoiding repeated model downloads). It also extends qmd-manager tests to cover symlink creation, preserving an existing target directory, and treating missing default models (ENOENT) as a silent no-op.

Key area to double-check is the first-run behavior of symlink creation: the implementation currently relies on the target directory structure existing before fs.symlink() is called, and if it doesn’t, the code will warn and fall back to re-download behavior.

Confidence Score: 3/5

  • This PR is close, but symlink creation likely fails on a fresh state dir due to missing parent directories.
  • The new model-cache reuse is a good direction, but symlinkSharedModels() appears to attempt to create the symlink before ensuring the target parent path exists, which would reliably throw ENOENT on first run and negate the intended behavior (falling back to downloads). Tests also don’t fully guard against silently falling back in some scenarios.
  • src/memory/qmd-manager.ts (symlinkSharedModels)

@openclaw-barnacle openclaw-barnacle bot added the maintainer Maintainer-authored PR label Feb 8, 2026
Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

Comment on lines +521 to +527
try {
await fs.symlink(defaultModelsDir, targetModelsDir, "dir");
} catch (symlinkErr: unknown) {
const code = (symlinkErr as NodeJS.ErrnoException).code;
if (process.platform === "win32" && (code === "EPERM" || code === "ENOTSUP")) {
await fs.symlink(defaultModelsDir, targetModelsDir, "junction");
} else {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Symlink parent dir missing

symlinkSharedModels() tries to create targetModelsDir directly, but .../xdg-cache/qmd/models’s parent directory (.../xdg-cache/qmd) is not created anywhere. Since initialize() only does mkdir(this.xdgCacheHome) and mkdir(path.dirname(this.indexPath)) (which also resolves to .../xdg-cache/qmd only if index.sqlite is created first), fs.symlink() will throw ENOENT on a fresh state dir and you’ll just warn + fall back to re-downloading. Create path.dirname(targetModelsDir) before calling symlink so the intended sharing works on first run.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/memory/qmd-manager.ts
Line: 521:527

Comment:
**Symlink parent dir missing**

`symlinkSharedModels()` tries to create `targetModelsDir` directly, but `.../xdg-cache/qmd/models`’s parent directory (`.../xdg-cache/qmd`) is not created anywhere. Since `initialize()` only does `mkdir(this.xdgCacheHome)` and `mkdir(path.dirname(this.indexPath))` (which also resolves to `.../xdg-cache/qmd` only if `index.sqlite` is created first), `fs.symlink()` will throw `ENOENT` on a fresh state dir and you’ll just warn + fall back to re-downloading. Create `path.dirname(targetModelsDir)` before calling `symlink` so the intended sharing works on first run.

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +652 to +656
it("does not overwrite existing models directory", async () => {
// Pre-create the custom models dir with different content.
await fs.mkdir(customModelsDir, { recursive: true });
await fs.writeFile(path.join(customModelsDir, "custom-model.bin"), "custom");

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test can be flaky

In "does not overwrite existing models directory", you pre-create customModelsDir before QmdMemoryManager.create(), but initialize() still runs symlinkSharedModels() and may log a warn if the default models dir exists yet the symlink creation fails for unrelated reasons (e.g., permissions on platforms where directory symlinks aren’t available). The test doesn’t assert on logWarnMock, so it can intermittently pass while the feature is effectively broken. Consider asserting no warn occurred in this test as well, or explicitly simulating the platform behavior you expect.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/memory/qmd-manager.test.ts
Line: 652:656

Comment:
**Test can be flaky**

In "does not overwrite existing models directory", you pre-create `customModelsDir` before `QmdMemoryManager.create()`, but `initialize()` still runs `symlinkSharedModels()` and may log a warn if the default models dir exists yet the symlink creation fails for unrelated reasons (e.g., permissions on platforms where directory symlinks aren’t available). The test doesn’t assert on `logWarnMock`, so it can intermittently pass while the feature is effectively broken. Consider asserting no warn occurred in this test as well, or explicitly simulating the platform behavior you expect.

How can I resolve this? If you propose a fix, please make it concise.

QmdMemoryManager overrides XDG_CACHE_HOME to isolate the qmd index
per-agent, but this also moves where qmd looks for its ML models
(~2.1GB). Since models are installed at the default location
(~/.cache/qmd/models/), every qmd invocation would attempt to
re-download them from HuggingFace and time out.

Fix: on initialization, symlink ~/.cache/qmd/models/ into the custom
XDG_CACHE_HOME path so the index stays isolated per-agent while the
shared models are reused. The symlink is only created when the default
models directory exists and the target path does not already exist.

Includes tests for the three key scenarios: symlink creation, existing
directory preservation, and graceful skip when no default models exist.
@tyler6204 tyler6204 force-pushed the fix/qmd-model-cache branch from 23dad73 to efa35a6 Compare February 9, 2026 07:41
@tyler6204 tyler6204 merged commit e4651d6 into main Feb 9, 2026
21 of 24 checks passed
@tyler6204 tyler6204 deleted the fix/qmd-model-cache branch February 9, 2026 07:43
yeboster pushed a commit to yeboster/openclaw that referenced this pull request Feb 9, 2026
…law#12114)

* Memory/QMD: symlink default model cache into custom XDG_CACHE_HOME

QmdMemoryManager overrides XDG_CACHE_HOME to isolate the qmd index
per-agent, but this also moves where qmd looks for its ML models
(~2.1GB). Since models are installed at the default location
(~/.cache/qmd/models/), every qmd invocation would attempt to
re-download them from HuggingFace and time out.

Fix: on initialization, symlink ~/.cache/qmd/models/ into the custom
XDG_CACHE_HOME path so the index stays isolated per-agent while the
shared models are reused. The symlink is only created when the default
models directory exists and the target path does not already exist.

Includes tests for the three key scenarios: symlink creation, existing
directory preservation, and graceful skip when no default models exist.

* Memory/QMD: skip model symlink warning on ENOENT

* test: stabilize warning-filter visibility assertion (openclaw#12114) (thanks @tyler6204)

* fix: add changelog entry for QMD cache reuse (openclaw#12114) (thanks @tyler6204)

* fix: handle plain context-overflow strings in compaction detection (openclaw#12114) (thanks @tyler6204)
NikolasP98 pushed a commit to NikolasP98/openclaw that referenced this pull request Feb 9, 2026
…law#12114)

* Memory/QMD: symlink default model cache into custom XDG_CACHE_HOME

QmdMemoryManager overrides XDG_CACHE_HOME to isolate the qmd index
per-agent, but this also moves where qmd looks for its ML models
(~2.1GB). Since models are installed at the default location
(~/.cache/qmd/models/), every qmd invocation would attempt to
re-download them from HuggingFace and time out.

Fix: on initialization, symlink ~/.cache/qmd/models/ into the custom
XDG_CACHE_HOME path so the index stays isolated per-agent while the
shared models are reused. The symlink is only created when the default
models directory exists and the target path does not already exist.

Includes tests for the three key scenarios: symlink creation, existing
directory preservation, and graceful skip when no default models exist.

* Memory/QMD: skip model symlink warning on ENOENT

* test: stabilize warning-filter visibility assertion (openclaw#12114) (thanks @tyler6204)

* fix: add changelog entry for QMD cache reuse (openclaw#12114) (thanks @tyler6204)

* fix: handle plain context-overflow strings in compaction detection (openclaw#12114) (thanks @tyler6204)
NikolasP98 added a commit to NikolasP98/openclaw that referenced this pull request Feb 9, 2026
Integrated upstream improvements:
- CRITICAL: Fix bundled hooks broken since 2026.2.2 (openclaw#9295)
- Grok web search provider (xAI) with inline citations
- Telegram video note support with tests and docs
- QMD model cache sharing optimization (openclaw#12114)
- Context overflow false positive fix (openclaw#2078)
- Model failover 400 status handling (openclaw#1879)
- Dynamic config loading per-message (openclaw#11372)
- Gateway post-compaction amnesia fix (openclaw#12283)
- Skills watcher: ignore Python venvs and caches
- Telegram send recovery from stale thread IDs
- Cron job parameter recovery (openclaw#12124)
- Auto-reply weekday timestamps (openclaw#12438)
- Utility consolidation refactoring (PNG, JSON, errors)
- Cross-platform test normalization (openclaw#12212)
- macOS Nix defaults support (openclaw#12205)

Preserved DEV enhancements:
- Docker multi-stage build with enhanced tooling (gh, gog, obsidian-cli, uv, nano-pdf, mcporter, qmd)
- Comprehensive .env.example documentation (371 lines)
- Multi-environment docker-compose support (DEV/PRD)
- GOG/Tailscale integration
- Fork-sync and openclaw-docs skills
- UI config editor (Svelte)
- Fork workflow documentation

Merge strategy: Cherry-picked 22 upstream commits, preserved DEV Docker architecture.
Docker files unchanged: Dockerfile, docker-compose.yml, docker-setup.sh, .env.example

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
Ethermious pushed a commit to Ethermious/openclaw that referenced this pull request Feb 9, 2026
…law#12114)

* Memory/QMD: symlink default model cache into custom XDG_CACHE_HOME

QmdMemoryManager overrides XDG_CACHE_HOME to isolate the qmd index
per-agent, but this also moves where qmd looks for its ML models
(~2.1GB). Since models are installed at the default location
(~/.cache/qmd/models/), every qmd invocation would attempt to
re-download them from HuggingFace and time out.

Fix: on initialization, symlink ~/.cache/qmd/models/ into the custom
XDG_CACHE_HOME path so the index stays isolated per-agent while the
shared models are reused. The symlink is only created when the default
models directory exists and the target path does not already exist.

Includes tests for the three key scenarios: symlink creation, existing
directory preservation, and graceful skip when no default models exist.

* Memory/QMD: skip model symlink warning on ENOENT

* test: stabilize warning-filter visibility assertion (openclaw#12114) (thanks @tyler6204)

* fix: add changelog entry for QMD cache reuse (openclaw#12114) (thanks @tyler6204)

* fix: handle plain context-overflow strings in compaction detection (openclaw#12114) (thanks @tyler6204)
lucasmpramos pushed a commit to butley/openclaw that referenced this pull request Feb 10, 2026
…law#12114)

* Memory/QMD: symlink default model cache into custom XDG_CACHE_HOME

QmdMemoryManager overrides XDG_CACHE_HOME to isolate the qmd index
per-agent, but this also moves where qmd looks for its ML models
(~2.1GB). Since models are installed at the default location
(~/.cache/qmd/models/), every qmd invocation would attempt to
re-download them from HuggingFace and time out.

Fix: on initialization, symlink ~/.cache/qmd/models/ into the custom
XDG_CACHE_HOME path so the index stays isolated per-agent while the
shared models are reused. The symlink is only created when the default
models directory exists and the target path does not already exist.

Includes tests for the three key scenarios: symlink creation, existing
directory preservation, and graceful skip when no default models exist.

* Memory/QMD: skip model symlink warning on ENOENT

* test: stabilize warning-filter visibility assertion (openclaw#12114) (thanks @tyler6204)

* fix: add changelog entry for QMD cache reuse (openclaw#12114) (thanks @tyler6204)

* fix: handle plain context-overflow strings in compaction detection (openclaw#12114) (thanks @tyler6204)
slathrop referenced this pull request in slathrop/openclaw-js Feb 11, 2026
- Symlink default model cache into custom XDG_CACHE_HOME per agent
- Skip ENOENT silently when default models directory is missing
- Clean up compaction test imports
- Add changelog entry for QMD cache reuse
yeboster pushed a commit to yeboster/openclaw that referenced this pull request Feb 13, 2026
…law#12114)

* Memory/QMD: symlink default model cache into custom XDG_CACHE_HOME

QmdMemoryManager overrides XDG_CACHE_HOME to isolate the qmd index
per-agent, but this also moves where qmd looks for its ML models
(~2.1GB). Since models are installed at the default location
(~/.cache/qmd/models/), every qmd invocation would attempt to
re-download them from HuggingFace and time out.

Fix: on initialization, symlink ~/.cache/qmd/models/ into the custom
XDG_CACHE_HOME path so the index stays isolated per-agent while the
shared models are reused. The symlink is only created when the default
models directory exists and the target path does not already exist.

Includes tests for the three key scenarios: symlink creation, existing
directory preservation, and graceful skip when no default models exist.

* Memory/QMD: skip model symlink warning on ENOENT

* test: stabilize warning-filter visibility assertion (openclaw#12114) (thanks @tyler6204)

* fix: add changelog entry for QMD cache reuse (openclaw#12114) (thanks @tyler6204)

* fix: handle plain context-overflow strings in compaction detection (openclaw#12114) (thanks @tyler6204)
skyhawk14 pushed a commit to skyhawk14/openclaw that referenced this pull request Feb 13, 2026
…law#12114)

* Memory/QMD: symlink default model cache into custom XDG_CACHE_HOME

QmdMemoryManager overrides XDG_CACHE_HOME to isolate the qmd index
per-agent, but this also moves where qmd looks for its ML models
(~2.1GB). Since models are installed at the default location
(~/.cache/qmd/models/), every qmd invocation would attempt to
re-download them from HuggingFace and time out.

Fix: on initialization, symlink ~/.cache/qmd/models/ into the custom
XDG_CACHE_HOME path so the index stays isolated per-agent while the
shared models are reused. The symlink is only created when the default
models directory exists and the target path does not already exist.

Includes tests for the three key scenarios: symlink creation, existing
directory preservation, and graceful skip when no default models exist.

* Memory/QMD: skip model symlink warning on ENOENT

* test: stabilize warning-filter visibility assertion (openclaw#12114) (thanks @tyler6204)

* fix: add changelog entry for QMD cache reuse (openclaw#12114) (thanks @tyler6204)

* fix: handle plain context-overflow strings in compaction detection (openclaw#12114) (thanks @tyler6204)
mbelinky pushed a commit that referenced this pull request Feb 14, 2026
* Memory/QMD: symlink default model cache into custom XDG_CACHE_HOME

QmdMemoryManager overrides XDG_CACHE_HOME to isolate the qmd index
per-agent, but this also moves where qmd looks for its ML models
(~2.1GB). Since models are installed at the default location
(~/.cache/qmd/models/), every qmd invocation would attempt to
re-download them from HuggingFace and time out.

Fix: on initialization, symlink ~/.cache/qmd/models/ into the custom
XDG_CACHE_HOME path so the index stays isolated per-agent while the
shared models are reused. The symlink is only created when the default
models directory exists and the target path does not already exist.

Includes tests for the three key scenarios: symlink creation, existing
directory preservation, and graceful skip when no default models exist.

* Memory/QMD: skip model symlink warning on ENOENT

* test: stabilize warning-filter visibility assertion (#12114) (thanks @tyler6204)

* fix: add changelog entry for QMD cache reuse (#12114) (thanks @tyler6204)

* fix: handle plain context-overflow strings in compaction detection (#12114) (thanks @tyler6204)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling maintainer Maintainer-authored PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

Comments