Skip to content

Commit 098f4ee

Browse files
committed
fix(memory-qmd): restore qmd compatibility defaults
1 parent ca462fb commit 098f4ee

7 files changed

Lines changed: 51 additions & 10 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ Docs: https://docs.openclaw.ai
125125
- Discord: keep REST, webhook, and monitor traffic on the configured proxy, preserve component-only media sends, honor `@everyone` and `@here` mention gates, keep ACK reactions on the active account, and split voice connect/playback timeouts so auto-join is more reliable. (#57465, #60361, #60345) Thanks @geekhuashan.
126126
- WhatsApp: restore `channels.whatsapp.blockStreaming` and reset watchdog timeouts after reconnect so quiet chats stop falling into reconnect loops. (#60007, #60069) Thanks @MonkeyLeeT and @mcaxtr.
127127
- Memory: keep `memory-core` builtin embedding registration on the already-registered path so selecting `memory-core` no longer recurses through plugin discovery and crashes during startup. (#61402) Thanks @ngutman.
128+
- Memory/QMD: prefer modern `qmd collection add --glob`, accept newer single-line JSON hit metadata while keeping legacy line fields, and refresh QMD docs/model-override guidance without breaking older QMD releases. Thanks @vincentkoc.
128129
- MS Teams: download inline DM images via Graph API and preserve channel reply threading in proactive fallback. (#52212, #55198) Thanks @Ted-developer and @hyojin.
129130
- MS Teams: replace the deprecated Teams SDK HttpPlugin stub with `httpServerAdapter` so recurring gateway deprecation warnings stop firing and the Express 5 compatibility workaround stays on the supported SDK path. (#60939) Thanks @coolramukaka-sys.
130131
- Matrix/exec approvals: anchor seeded approval reactions to the primary Matrix prompt event, resolve them from event metadata instead of prompt text, and clean up chunked approval prompts correctly. (#60931) Thanks @gumadeiras.

docs/concepts/memory-qmd.md

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ binary, and can index content beyond your workspace memory files.
2525

2626
### Prerequisites
2727

28-
- Install QMD: `bun install -g @tobilu/qmd`
28+
- Install QMD: `npm install -g @tobilu/qmd` or `bun install -g @tobilu/qmd`
2929
- SQLite build that allows extensions (`brew install sqlite` on macOS).
3030
- QMD must be on the gateway's `PATH`.
3131
- macOS and Linux work out of the box. Windows is best supported via WSL2.
@@ -43,6 +43,8 @@ binary, and can index content beyond your workspace memory files.
4343
OpenClaw creates a self-contained QMD home under
4444
`~/.openclaw/agents/<agentId>/qmd/` and manages the sidecar lifecycle
4545
automatically -- collections, updates, and embedding runs are handled for you.
46+
It prefers current QMD collection and MCP query shapes, but still falls back to
47+
legacy `--mask` collection flags and older MCP tool names when needed.
4648

4749
## How the sidecar works
4850

@@ -59,6 +61,20 @@ The first search may be slow -- QMD auto-downloads GGUF models (~2 GB) for
5961
reranking and query expansion on the first `qmd query` run.
6062
</Info>
6163

64+
## Model overrides
65+
66+
QMD model environment variables pass through unchanged from the gateway
67+
process, so you can tune QMD globally without adding new OpenClaw config:
68+
69+
```bash
70+
export QMD_EMBED_MODEL="hf:Qwen/Qwen3-Embedding-0.6B-GGUF/Qwen3-Embedding-0.6B-Q8_0.gguf"
71+
export QMD_RERANK_MODEL="/absolute/path/to/reranker.gguf"
72+
export QMD_GENERATE_MODEL="/absolute/path/to/generator.gguf"
73+
```
74+
75+
After changing the embedding model, rerun embeddings so the index matches the
76+
new vector space.
77+
6278
## Indexing extra paths
6379

6480
Point QMD at additional directories to make them searchable:

docs/reference/memory-config.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,15 @@ Set `memory.backend = "qmd"` to enable. All QMD settings live under
377377
| `sessions.retentionDays` | `number` | -- | Transcript retention |
378378
| `sessions.exportDir` | `string` | -- | Export directory |
379379

380+
OpenClaw prefers the current QMD collection and MCP query shapes, but keeps
381+
older QMD releases working by falling back to legacy `--mask` collection flags
382+
and older MCP tool names when needed.
383+
384+
QMD model overrides stay on the QMD side, not OpenClaw config. If you need to
385+
override QMD's models globally, set environment variables such as
386+
`QMD_EMBED_MODEL`, `QMD_RERANK_MODEL`, and `QMD_GENERATE_MODEL` in the gateway
387+
runtime environment.
388+
380389
### Update schedule
381390

382391
| Key | Type | Default | Description |

extensions/memory-core/src/memory/qmd-manager.test.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -895,7 +895,7 @@ describe("QmdMemoryManager", () => {
895895
);
896896
});
897897

898-
it("prefers --mask for collection add and falls back to --glob when --mask is rejected", async () => {
898+
it("prefers --glob for collection add and falls back to --mask when --glob is rejected", async () => {
899899
cfg = {
900900
...cfg,
901901
memory: {
@@ -917,10 +917,10 @@ describe("QmdMemoryManager", () => {
917917
}
918918
if (args[0] === "collection" && args[1] === "add") {
919919
const child = createMockChild({ autoClose: false });
920-
const flag = args.includes("--mask") ? "--mask" : args.includes("--glob") ? "--glob" : "";
920+
const flag = args.includes("--glob") ? "--glob" : args.includes("--mask") ? "--mask" : "";
921921
addFlagCalls.push(flag);
922-
if (flag === "--mask") {
923-
emitAndClose(child, "stderr", "unknown flag: --mask", 1);
922+
if (flag === "--glob") {
923+
emitAndClose(child, "stderr", "unknown flag: --glob", 1);
924924
return child;
925925
}
926926
queueMicrotask(() => child.closeWith(0));
@@ -932,7 +932,7 @@ describe("QmdMemoryManager", () => {
932932
const { manager } = await createManager({ mode: "full" });
933933
await manager.close();
934934

935-
expect(addFlagCalls).toEqual(["--mask", "--glob", "--glob", "--glob"]);
935+
expect(addFlagCalls).toEqual(["--glob", "--mask", "--mask", "--mask"]);
936936
expect(logWarnMock).toHaveBeenCalledWith(
937937
expect.stringContaining("retrying with legacy compatibility flag"),
938938
);

extensions/memory-core/src/memory/qmd-manager.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,7 @@ export class QmdMemoryManager implements MemorySearchManager {
274274
private attemptedNullByteCollectionRepair = false;
275275
private attemptedDuplicateDocumentRepair = false;
276276
private readonly sessionWarm = new Set<string>();
277-
private collectionPatternFlag: QmdCollectionPatternFlag | null = "--mask";
277+
private collectionPatternFlag: QmdCollectionPatternFlag | null = "--glob";
278278

279279
private constructor(params: {
280280
cfg: OpenClawConfig;
@@ -653,7 +653,7 @@ export class QmdMemoryManager implements MemorySearchManager {
653653

654654
private async addCollection(pathArg: string, name: string, pattern: string): Promise<void> {
655655
const candidateFlags: QmdCollectionPatternFlag[] =
656-
this.collectionPatternFlag === "--mask" ? ["--mask", "--glob"] : ["--glob", "--mask"];
656+
this.collectionPatternFlag === "--glob" ? ["--glob", "--mask"] : ["--mask", "--glob"];
657657
let lastError: unknown;
658658
for (const flag of candidateFlags) {
659659
try {

src/memory-host-sdk/host/qmd-query-parser.test.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,18 @@ complete`,
4040
]);
4141
});
4242

43+
it("maps single-line qmd line metadata onto both line bounds", () => {
44+
const results = parseQmdQueryJson('[{"docid":"abc","score":0.5,"line":9}]', "");
45+
expect(results).toEqual([
46+
{
47+
docid: "abc",
48+
score: 0.5,
49+
startLine: 9,
50+
endLine: 9,
51+
},
52+
]);
53+
});
54+
4355
it("treats plain-text no-results from stderr as an empty result set", () => {
4456
const results = parseQmdQueryJson("", "No results found\n");
4557
expect(results).toEqual([]);

src/memory-host-sdk/host/qmd-query-parser.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,15 +89,18 @@ function parseQmdQueryResultArray(raw: string): QmdQueryResult[] | null {
8989
const file = typeof record.file === "string" ? record.file : undefined;
9090
const snippet = typeof record.snippet === "string" ? record.snippet : undefined;
9191
const body = typeof record.body === "string" ? record.body : undefined;
92+
const line = parseQmdLineNumber(record.line);
93+
const startLine = parseQmdLineNumber(record.start_line ?? record.startLine) ?? line;
94+
const endLine = parseQmdLineNumber(record.end_line ?? record.endLine) ?? line;
9295
return {
9396
docid,
9497
score,
9598
collection,
9699
file,
97100
snippet,
98101
body,
99-
startLine: parseQmdLineNumber(record.start_line ?? record.startLine),
100-
endLine: parseQmdLineNumber(record.end_line ?? record.endLine),
102+
startLine,
103+
endLine,
101104
} as QmdQueryResult;
102105
});
103106
} catch {

0 commit comments

Comments
 (0)