Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
42b17a0
fix: default-disable dream plugin
bittergreen Jun 1, 2026
20210b1
docs: add readme for dream
bittergreen Jun 1, 2026
96de57f
docs: add readme for plugins
bittergreen Jun 1, 2026
7e9599f
fix: repair pref bug for empty preference filed (#2007)
Wang-Daoji Jul 1, 2026
dcb5918
Fix #2008: fix: ci test failed (#2010)
Memtensor-AI Jul 1, 2026
5408e33
fix: Tighten preference memory prompt to avoid over-extractions.
bittergreen Jul 1, 2026
7e8af5e
fix: Tighten preference memory prompt to avoid over-extractions. (#2016)
bittergreen Jul 1, 2026
70b2180
Fix #1358: fix(Windows): Path format mismatch causes better-sqlite3 l…
Memtensor-AI Jul 1, 2026
7c5aa64
Fix #1595: FTS5 query failures with Hermes Agent instructional prompt…
Memtensor-AI Jul 1, 2026
1feefe1
Fix #1490: fix: 本地viewer不会解析配置文件的环境变量 (#1984)
Memtensor-AI Jul 1, 2026
e0af149
Fix #1377: fix: Title: memos-local-openclaw-plugin corrupts openclaw.…
Memtensor-AI Jul 1, 2026
33c9abc
Fix #1372: fix(memos-local-openclaw): Viewer API search 接口 limit 和 mi…
Memtensor-AI Jul 1, 2026
3ee48ff
Fix #1362: 添加 excludeSessionKey 选项,避免召回当前会话内容 (#1978)
Memtensor-AI Jul 1, 2026
8124c9b
Fix #1315: [Bug] Topic Judge 100% failure rate - MiniMax API returns …
Memtensor-AI Jul 1, 2026
c9ade0b
Fix #1927: fix: initialize() spawns new headless bridge without closi…
Memtensor-AI Jul 1, 2026
940bccf
chore: remove pending label from issue templates (#1858)
Memtensor-AI Jul 1, 2026
d07f8fc
Fix #1915: fix(memos-local-plugin): bridge status detection misses he…
Memtensor-AI Jul 1, 2026
56644ba
Fix #1913: turn.start injection collapses to empty when top hits are …
Memtensor-AI Jul 1, 2026
6c4a093
Fix #1815: feat: [memos-local-plugin] tool_result_persist hook return…
Memtensor-AI Jul 1, 2026
bafe1ae
Merge dev-v2.0.22 into wq-v2.0.19
Jul 2, 2026
124ba66
Fix #1966: bug: ghost trace in episode.trace_ids_json causes infinite…
Memtensor-AI Jul 2, 2026
15b100a
Fix #1736: [Bug] memos-local-hermes-plugin: bridge fails to start on …
Memtensor-AI Jul 2, 2026
290b2ca
Fix #1514: Auto-recall hardcodes maxResults: 10, ignoring recall.maxR…
Memtensor-AI Jul 2, 2026
4e9bbce
Dev 20260604 v2.0.19 (#2033)
CarltonXiang Jul 2, 2026
76bf18c
Fix #1596: fix: System overview doesn't show the correct configuratio…
Memtensor-AI Jul 2, 2026
dddfbe8
Fix #1910: Bridge process leak: every turn spawns new bridge.cjs, 4+ …
CarltonXiang Jul 2, 2026
609ded4
Fix #1559: fix: registerMemoryCapability API not found in OpenClaw 20…
Memtensor-AI Jul 2, 2026
c99d639
Fix #2028: fix: memory error (#2031)
Memtensor-AI Jul 2, 2026
0824256
Fix #1619: Bug: npm package ships TypeScript source only — no compile…
Memtensor-AI Jul 2, 2026
5646c13
Fix #1929: Bug: /api/v1/embeddings/maintenance causes 100% CPU and ev…
Memtensor-AI Jul 2, 2026
3bfd148
fix(memos-local-plugin): system message ordering + local endpoint api…
redashes1984 Jul 2, 2026
3b43c93
fix(openclaw): restart promptly after update install (#2032)
Memtensor-AI Jul 2, 2026
c092077
feat: Support independent model config for preference memory.
bittergreen Jul 2, 2026
a999775
fix(memos-local): align trace list pagination cap (#2026)
Memtensor-AI Jul 2, 2026
b54554b
docs: update benchmark PR messaging (#2019)
MatthewZhuang Jul 2, 2026
9176e38
Fix #1347: 建议:增加自动召回的短查询过滤参数(min_query_length) (#1976)
Memtensor-AI Jul 2, 2026
3b65dae
Fix #1311: bug(openclaw-plugin): auto-recall contaminates cron runs w…
Memtensor-AI Jul 2, 2026
eca8e46
fix(memos-local-plugin): detect Hermes chat with global flags (#1951)
dashitongzhi Jul 2, 2026
b745665
fix: make MemOS startup recovery non-blocking (#1758)
leavrcn Jul 2, 2026
b0ce713
fix(memos-local-plugin): support /memos/ proxy prefix in viewer SPA (…
Starfie1d1272 Jul 2, 2026
ab7029b
feat: Support independent model config for preference memory. (#2039)
bittergreen Jul 2, 2026
cc427ae
default-disable dream plugin & add READMEs (#1851)
bittergreen Jul 2, 2026
06836ef
Fix #1493: fix: /product/add returns 200 success but stores no memory…
Memtensor-AI Jul 2, 2026
38ee5c2
Fix #1342: [Bug] v2.0.10 依赖冲突: numpy/scipy/chonkie 版本不兼容 (#1971)
Memtensor-AI Jul 2, 2026
ca75756
Fix #1523: [Bug] timed_with_status decorator silently swallows except…
Memtensor-AI Jul 2, 2026
c353fbe
chore: update poetry.lock
Jul 2, 2026
7dd42dd
Fix #1863: [memos-local-openclaw] embedLocal leaks native ONNX memory…
Memtensor-AI Jul 2, 2026
f314f47
feat(memos-local): auto-promote candidate skills without trial (#1943)
a844810597 Jul 2, 2026
53df458
fix(memos-local): L2 candidate stuck + bridge subprocess leak (#1933)
a844810597 Jul 2, 2026
f75fc6d
fix(bridge): derive profileId dynamically from MEMOS_HOME (#1949)
redashes1984 Jul 2, 2026
f1f31e5
Fix #1929: Bug: /api/v1/embeddings/maintenance causes 100% CPU and ev…
Memtensor-AI Jul 2, 2026
4e16c68
Fix #1808: fix: [memos-local-plugin] Dreaming background processing s…
Memtensor-AI Jul 2, 2026
c507b04
Fix #1797: bug(hermes): _bridge_ok cache permanently disables provide…
Memtensor-AI Jul 2, 2026
290fbf1
Fix #1747: bridge.cts 启动竞态: startStdioServer 应在 core.init 之前启动 (#1999)
Memtensor-AI Jul 2, 2026
2e28b2a
Fix #1355: [Bug] /product/add returns 200 but memory not stored in Ne…
Memtensor-AI Jul 2, 2026
18e90e7
fix: ci ruff check (#2045)
bittergreen Jul 2, 2026
11d9258
Fix #1518: (capture) Internal "Review the conversation above" prompts…
CarltonXiang Jul 2, 2026
44bec71
Fix #1733: [Bug] @memtensor/memos-lite-openclaw-plugin v0.2.3 fails t…
Memtensor-AI Jul 2, 2026
e3f4e51
Fix #1395: Scheduler initialized but not processing tasks (uvicorn vs…
Memtensor-AI Jul 2, 2026
ca6ca9c
Fix #1527: [Bug] suggestion_handler sends system-only message — fails…
Memtensor-AI Jul 2, 2026
2270510
Fix #1733: [Bug] @memtensor/memos-lite-openclaw-plugin v0.2.3 fails t…
Memtensor-AI Jul 2, 2026
62af341
Fix #1747: bridge.cts 启动竞态: startStdioServer 应在 core.init 之前启动 (#2046)
Memtensor-AI Jul 2, 2026
c9f270f
Fix #1559: fix: registerMemoryCapability API not found in OpenClaw 20…
Memtensor-AI Jul 2, 2026
1771568
Fix #2028: fix: memory error (#2036)
Memtensor-AI Jul 2, 2026
b50246b
Fix #1543: fix: MEMOS不支持JSON5格式的openclaw.json (#1990)
Memtensor-AI Jul 2, 2026
a612fe9
Fix #1333: [Feature Request] Re-embed existing memories when embeddin…
Memtensor-AI Jul 2, 2026
53546ff
Fix #1532: Agent incorrectly acts on cross-session memory context in …
Memtensor-AI Jul 2, 2026
783c545
chore: change version number to v2.0.22
Jul 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/bug-report.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: "\U0001F41B Bug Report"
description: Report a bug to help us improve MemOS | 报告错误以帮助我们改进 MemOS
title: "fix: "
labels: ["bug", "pending"]
labels: ["bug"]
body:

- type: checkboxes
Expand Down
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/feature-request.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: "\U0001F680 Feature request"
description: Submit a request for a new feature | 申请添加新功能
title: "feat: "
labels: ["enhancement", "pending"]
labels: ["enhancement"]
body:

- type: checkboxes
Expand Down
21 changes: 9 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@
</p>

<p align="center">
<strong>🎯 +43.70% Accuracy vs. OpenAI Memory</strong><br/>
<strong>🏆 Top-tier Long-term Memory + Personalization</strong><br/>
<strong>💰 Saves 35.24% Memory Tokens</strong><br/>
<sub>LoCoMo 75.80 • LongMemEval +40.43% • PrefEval-10 +2568% • PersonaMem +40.75%</sub>
<strong>🏆 Leading Performance Across Agent and User Memory Benchmarks</strong><br/>
<strong>🤖 OpenClaw Task Completion Improves from 36.63% to 50.87%</strong><br/>
<strong>🎯 92.34 on LoCoMo and 93.40 on LongMemEval</strong><br/>
<strong>📊 Unified Evaluation Across 14 Commercial Memory Products</strong>
<!-- <a href="https://memos.openmem.net/">
<img src="https://statics.memtensor.com.cn/memos/github_api_free_banner.gif" alt="MemOS Free API Banner">
</a> -->
Expand Down Expand Up @@ -71,6 +71,9 @@ Your lobsters and Hermes Agents now have **the best** memory system — choose *

### News

- **2026-07-02** · 🏆 **MemOS Advances Agent and User Memory Benchmarks**
With MemOS, **OpenClaw** improves average task completion from **36.63% to 50.87%** across five agent tasks. MemOS also achieves **92.34 on LoCoMo** and **93.40 on LongMemEval**, and leads in **OmniMemEval**, a unified evaluation of 14 commercial memory products across ten datasets.

- **2026-05-09** · 🧠 **memos-local-plugin 2.0**
Official local memory plugin for **Hermes Agent** and **OpenClaw**. One core powers self-evolving memory across L1 traces, L2 policies, L3 world models, and crystallized Skills, with local-first storage and feedback-driven retrieval.

Expand Down Expand Up @@ -273,14 +276,8 @@ MemOS is a **Memory Operating System** for LLMs and AI agents that unifies store

### What are the benchmark results?

| Benchmark | MemOS Result | Improvement |
|-----------|--------------|-------------|
| LoCoMo | 75.80 | - |
| LongMemEval | +40.43% vs baseline | - |
| PrefEval-10 | +2568% | - |
| PersonaMem | +40.75% | - |
| **vs OpenAI Memory** | +43.70% Accuracy | - |
| **Token Savings** | 35.24% | - |
MemOS achieves **92.34 on LoCoMo** and **93.40 on LongMemEval** for User Memory, while improving **OpenClaw** average task completion from **36.63% to 50.87%** across five Agent Memory tasks. For details, see [OmniMemEval](https://github.com/MemTensor/OmniMemEval), our unified evaluation framework for benchmarking 14 commercial memory products across ten datasets.


### How does MemOS compare to other memory solutions?

Expand Down
4 changes: 3 additions & 1 deletion apps/memos-local-openclaw/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -514,12 +514,14 @@ All optional — shown with defaults:
"recall": {
"maxResultsDefault": 6, // Default search results
"maxResultsMax": 20, // Max search results
"autoRecallMaxResults": 6, // Optional: override max results for the auto-recall hook only. Defaults to `maxResultsDefault`. Set lower (e.g. 3 or 5) to reduce auto-injected tokens while keeping `memory_search` results richer.
"minScoreDefault": 0.45, // Default min score threshold
"minScoreFloor": 0.35, // Lowest allowed min score
"rrfK": 60, // RRF fusion constant
"mmrLambda": 0.7, // MMR relevance vs diversity (0-1)
"recencyHalfLifeDays": 14, // Time decay half-life
"vectorSearchMaxChunks": 0 // 0 = search all (default). Set 200000–300000 only if search is slow on huge DBs
"vectorSearchMaxChunks": 0, // 0 = search all (default). Set 200000–300000 only if search is slow on huge DBs
"autoRecallMinQueryLength": 4 // Skip auto-recall when the normalised user prompt is shorter than this many chars (filters "好的"/"可以"/"?"). 0 disables the guard. Has no effect on explicit memory_search tool calls.
},
"dedup": {
"similarityThreshold": 0.75, // Cosine similarity for smart-dedup candidates (Top-5)
Expand Down
185 changes: 143 additions & 42 deletions apps/memos-local-openclaw/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@ import { createRequire } from "node:module";
import { fileURLToPath } from "url";
import { buildContext } from "./src/config";
import type { HostModelsConfig } from "./src/openclaw-api";
import { isPathInside } from "./src/path-utils";
import { ensureSqliteBinding } from "./src/storage/ensure-binding";
import { SqliteStore } from "./src/storage/sqlite";
import { Embedder } from "./src/embedding";
import { IngestWorker } from "./src/ingest/worker";
import { RecallEngine } from "./src/recall/engine";
import { shouldSkipAutoRecallForSession } from "./src/recall/session-policy";
import { captureMessages, stripInboundMetadata } from "./src/capture";
import { DEFAULTS } from "./src/types";
import type { SearchHit } from "./src/types";
Expand All @@ -31,6 +33,8 @@ import { SkillInstaller } from "./src/skill/installer";
import { Summarizer } from "./src/ingest/providers";
import { MEMORY_GUIDE_SKILL_MD } from "./src/skill/bundled-memory-guide";
import { Telemetry } from "./src/telemetry";
import { parseJsonOrJson5 } from "./src/shared/json5";
import { patchOpenclawAllowFile } from "./src/shared/openclaw-config";


/** Remove near-duplicate hits based on summary word overlap (>70%). Keeps first (highest-scored) hit. */
Expand Down Expand Up @@ -94,7 +98,11 @@ const buildMemoryPromptSection = ({ availableTools, citationsMode }: {
return lines;
};

function normalizeAutoRecallQuery(rawPrompt: string): string {
const INSTRUCTIONAL_PROMPT_MAX_LEN = 300;
const SYSTEM_ROLE_PROMPT_RE = /^You are\b/i;
const HERMES_SKILL_REVIEW_RE = /Review the conversation above/i;

export function normalizeAutoRecallQuery(rawPrompt: string): string {
let query = rawPrompt.trim();

const senderTag = "Sender (untrusted metadata):";
Expand Down Expand Up @@ -125,6 +133,17 @@ function normalizeAutoRecallQuery(rawPrompt: string): string {
query = query.replace(INTERNAL_CONTEXT_RE, "").trim();
query = query.replace(CONTINUE_PROMPT_RE, "").trim();

// Drop instructional prompts (system role prompts, Hermes skill-review prompts,
// over-long instruction blobs). These shapes are not user search intents — passing
// them to FTS5 fails the sanitize step and wastes a downstream LLM filter call.
if (
query.length > INSTRUCTIONAL_PROMPT_MAX_LEN
|| SYSTEM_ROLE_PROMPT_RE.test(query)
|| HERMES_SKILL_REVIEW_RE.test(query)
) {
return "";
}

return query;
}

Expand All @@ -150,6 +169,27 @@ const pluginConfigSchema = {
},
};

/**
* Narrow view of the OpenClaw plugin API surface that this plugin uses for memory
* registration. Hoisted to module scope (rather than inlined at the call site) so that
* future maintainers can see exactly which host methods the feature detection covers,
* and so nobody accidentally reaches for other `OpenClawPluginApi` members through the
* narrowed reference. See issue #1559.
*
* - `registerMemoryPromptSection` was introduced in OpenClaw 2026.3.31.
* - `registerMemoryCapability` is the legacy umbrella call that the plugin used to rely
* on; kept here purely so older gateways still work.
*/
interface MemoryRegistrationApi {
registerMemoryPromptSection?: (builder: typeof buildMemoryPromptSection) => void;
registerMemoryCapability?: (capability: { promptBuilder: typeof buildMemoryPromptSection }) => void;
}

interface ExtendedLogger {
warn(msg: string): void;
error?(msg: string): void;
}

const memosLocalPlugin = {
id: "memos-local-openclaw-plugin",
name: "MemOS Local Memory",
Expand All @@ -160,9 +200,29 @@ const memosLocalPlugin = {
configSchema: pluginConfigSchema,

register(api: OpenClawPluginApi) {
api.registerMemoryCapability({
promptBuilder: buildMemoryPromptSection,
});
// OpenClaw 2026.3.31 split the legacy `registerMemoryCapability` facade
// into focused registration methods. Prefer the new prompt-section API,
// then fall back to the legacy capability registration so older hosts
// still load.
const memoryApi = api as OpenClawPluginApi & MemoryRegistrationApi;

if (typeof memoryApi.registerMemoryPromptSection === "function") {
memoryApi.registerMemoryPromptSection(buildMemoryPromptSection);
} else if (typeof memoryApi.registerMemoryCapability === "function") {
memoryApi.registerMemoryCapability({ promptBuilder: buildMemoryPromptSection });
} else {
const message =
"memos-local: host SDK exposes neither registerMemoryPromptSection " +
"nor registerMemoryCapability; memory prompt section will not be " +
"installed. The plugin may be incompatible with this OpenClaw gateway version.";
const logger = api.logger as ExtendedLogger;
if (typeof logger.error === "function") {
logger.error(message);
} else {
logger.warn(message);
}
throw new Error(message);
}

const moduleDir = path.dirname(fileURLToPath(import.meta.url));
const localRequire = createRequire(import.meta.url);
Expand All @@ -182,17 +242,6 @@ const memosLocalPlugin = {

const pluginDir = detectPluginDir(moduleDir);

function normalizeFsPath(p: string): string {
return path.resolve(p).replace(/^\\\\\?\\/, "").toLowerCase();
}

function isPathInside(baseDir: string, targetPath: string): boolean {
const baseNorm = normalizeFsPath(baseDir);
const targetNorm = normalizeFsPath(targetPath);
const rel = path.relative(baseNorm, targetNorm);
return rel === "" || (!rel.startsWith("..") && !path.isAbsolute(rel));
}

function runNpm(args: string[]) {
const { spawnSync } = localRequire("child_process") as typeof import("node:child_process");
return spawnSync(npmCmd, args, {
Expand Down Expand Up @@ -292,8 +341,8 @@ const memosLocalPlugin = {
const configPath = path.join(stateDir, "state", "memos-local", "config.json");
if (Object.keys(pluginCfg).length === 0 && fs.existsSync(configPath)) {
try {
const fileConfig = JSON.parse(fs.readFileSync(configPath, "utf-8"));
pluginCfg = fileConfig;
const fileConfig = parseJsonOrJson5(fs.readFileSync(configPath, "utf-8"));
pluginCfg = fileConfig as Record<string, unknown>;
api.logger.info(`memos-local: loaded config from ${configPath}`);
} catch (e) {
api.logger.warn(`memos-local: failed to load config from ${configPath}: ${e}`);
Expand Down Expand Up @@ -351,23 +400,17 @@ const memosLocalPlugin = {
ctx.log.warn(`memos-local: could not write to managed skills dir: ${e}`);
}

// Ensure plugin tools are enabled in openclaw.json tools.allow
// Ensure plugin tools are enabled in openclaw.json tools.allow.
// openclaw.json is JSON5 (supports // comments / single-quoted strings /
// trailing commas) — the patcher tolerates all of these (see issue #1543).
try {
const openclawJsonPath = path.join(stateDir, "openclaw.json");
if (fs.existsSync(openclawJsonPath)) {
const raw = fs.readFileSync(openclawJsonPath, "utf-8");
const cfg = JSON.parse(raw);
const allow: string[] | undefined = cfg?.tools?.allow;
if (Array.isArray(allow) && allow.length > 0 && !allow.includes("group:plugins") && !allow.includes("*")) {
const lastEntry = JSON.stringify(allow[allow.length - 1]);
const patched = raw.replace(
new RegExp(`(${lastEntry})(\\s*\\])`),
`$1,\n "group:plugins"$2`,
);
if (patched !== raw && patched.includes("group:plugins")) {
fs.writeFileSync(openclawJsonPath, patched, "utf-8");
ctx.log.info("memos-local: added 'group:plugins' to tools.allow in openclaw.json");
}
const result = patchOpenclawAllowFile(openclawJsonPath);
if (result.changed) {
ctx.log.info("memos-local: added 'group:plugins' to tools.allow in openclaw.json");
} else if (result.reason) {
ctx.log.debug(`memos-local: tools.allow not patched (${result.reason})`);
}
}
} catch (e) {
Expand Down Expand Up @@ -1863,14 +1906,28 @@ Groups: ${groupNames.length > 0 ? groupNames.join(", ") : "(none)"}`,

// ─── Auto-recall: inject relevant memories before agent starts ───

// Track current session to detect cross-session memories
let currentSessionKey: string | null = null;

api.on("before_prompt_build", async (event: { prompt?: string; messages?: unknown[] }, hookCtx?: { agentId?: string; sessionKey?: string }) => {
if (!allowPromptInjection) return {};
if (!event.prompt || event.prompt.length < 3) return;

// ─── Cron / excluded-session gate (GitHub #1311) ───────────────
// Skip auto-recall (and skill auto-recall) entirely for OpenClaw
// cron sessions by default. The cron prompt is the spec — recalling
// prior meta-discussion about the cron contaminates the next run.
const recallSessionKey = hookCtx?.sessionKey ?? (event as any)?.sessionKey;
if (shouldSkipAutoRecallForSession(recallSessionKey, ctx.config.autoRecall)) {
ctx.log.info(`auto-recall: skipping (cron/excluded session: "${recallSessionKey}")`);
return;
}

const recallAgentId = hookCtx?.agentId ?? (event as any)?.agentId ?? (event as any)?.profileId ?? "main";
currentAgentId = recallAgentId;
const recallOwnerFilter = [`agent:${recallAgentId}`, "public"];
ctx.log.info(`auto-recall: agentId=${recallAgentId} (from hookCtx)`);
const incomingSessionKey = hookCtx?.sessionKey ?? "default";
ctx.log.info(`auto-recall: agentId=${recallAgentId} sessionKey=${incomingSessionKey} (from hookCtx)`);

const recallT0 = performance.now();
let recallQuery = "";
Expand All @@ -1882,16 +1939,35 @@ Groups: ${groupNames.length > 0 ? groupNames.join(", ") : "(none)"}`,
const query = normalizeAutoRecallQuery(rawPrompt);
recallQuery = query;

if (query.length < 2) {
ctx.log.debug("auto-recall: extracted query too short, skipping");
// Detect if this is a new session
const isNewSession = currentSessionKey !== incomingSessionKey || NEW_SESSION_PROMPT_RE.test(rawPrompt);
if (isNewSession && currentSessionKey !== null) {
ctx.log.info(`auto-recall: new session detected (prev=${currentSessionKey}, curr=${incomingSessionKey})`);
}
currentSessionKey = incomingSessionKey;

const minQueryLength = ctx.config.recall?.autoRecallMinQueryLength ?? DEFAULTS.autoRecallMinQueryLength;
if (query.length < minQueryLength) {
ctx.log.debug(`auto-recall: query too short (len=${query.length} < minQueryLength=${minQueryLength}), skipping`);
return;
}
ctx.log.debug(`auto-recall: query="${query.slice(0, 80)}"`);

// ── Phase 1: Local search ∥ Hub search (parallel) ──
const arLocalP = engine.search({ query, maxResults: 10, minScore: 0.45, ownerFilter: recallOwnerFilter });
// Issue #1514: previously hardcoded maxResults: 10 here, which made
// `recall.maxResultsDefault` and the new `recall.autoRecallMaxResults`
// ineffective for the auto-recall path. Resolution order:
// 1. `recall.autoRecallMaxResults` (explicit auto-recall cap)
// 2. `recall.maxResultsDefault` (shared default with memory_search)
// 3. literal 10 (defensive — `resolveConfig`
// always fills `maxResultsDefault`, so this fires only if a
// caller built a context without going through `resolveConfig`).
const recallCfg = ctx.config.recall ?? {};
const autoRecallMax = recallCfg.autoRecallMaxResults ?? recallCfg.maxResultsDefault ?? 10;

const arLocalP = engine.search({ query, maxResults: autoRecallMax, minScore: 0.45, ownerFilter: recallOwnerFilter });
const arHubP = ctx.config?.sharing?.enabled
? hubSearchMemories(store, ctx, { query, maxResults: 10, scope: "all" })
? hubSearchMemories(store, ctx, { query, maxResults: autoRecallMax, scope: "all" })
.catch((err: any) => { ctx.log.debug(`auto-recall: hub search failed (${err})`); return { hits: [] as any[], meta: {} }; })
: Promise.resolve({ hits: [] as any[], meta: {} });

Expand Down Expand Up @@ -2014,10 +2090,18 @@ Groups: ${groupNames.length > 0 ? groupNames.join(", ") : "(none)"}`,
filteredHits = deduplicateHits(filteredHits);
ctx.log.debug(`auto-recall: merged ${allRawHits.length} → ${beforeDedup} relevant → ${filteredHits.length} after dedup, sufficient=${sufficient}`);

// Check if any memories are from a different session
const hasCrossSessionMemories = filteredHits.some(h =>
h.source.sessionKey && h.source.sessionKey !== incomingSessionKey
);
ctx.log.debug(`auto-recall: isNewSession=${isNewSession}, hasCrossSessionMemories=${hasCrossSessionMemories}`);

const lines = filteredHits.map((h, i) => {
const excerpt = h.original_excerpt;
const isCrossSession = h.source.sessionKey && h.source.sessionKey !== incomingSessionKey;
const sessionTag = isCrossSession ? " [from previous session]" : "";
const oTag = h.origin === "local-shared" ? " [本机共享]" : h.origin === "hub-memory" ? " [团队缓存]" : "";
const parts: string[] = [`${i + 1}. [${h.source.role}]${oTag}`];
const parts: string[] = [`${i + 1}. [${h.source.role}]${sessionTag}${oTag}`];
if (excerpt) parts.push(` ${excerpt}`);
parts.push(` chunkId="${h.ref.chunkId}"`);
if (h.taskId) {
Expand All @@ -2042,15 +2126,32 @@ Groups: ${groupNames.length > 0 ? groupNames.join(", ") : "(none)"}`,
tips.push("- Need more surrounding dialogue → call `memory_timeline(chunkId=\"...\")` to expand context around a hit");
const tipsText = "\n\nAvailable follow-up tools:\n" + tips.join("\n");

// Use different instructions based on whether memories are from current or previous sessions
const contextParts = [
"## User's conversation history (from memory system)",
"",
"IMPORTANT: The following are facts from previous conversations with this user.",
"You MUST treat these as established knowledge and use them directly when answering.",
"Do NOT say you don't know or don't have information if the answer is in these memories.",
"",
lines.join("\n\n"),
];

if (hasCrossSessionMemories || isNewSession) {
contextParts.push(
"IMPORTANT: The following memories are from PREVIOUS SESSIONS.",
"Treat them as BACKGROUND KNOWLEDGE ONLY:",
"- Do NOT act on them unprompted or proactively respond based solely on these memories",
"- WAIT for the user's explicit instruction before taking any action",
"- These memories provide context, but the user must initiate the conversation",
"- If you reference these memories, explicitly note they are from a previous session (e.g., \"根据之前的会话...\" or \"Based on a previous conversation...\")",
"",
);
} else {
contextParts.push(
"IMPORTANT: The following are facts from previous conversations with this user.",
"You MUST treat these as established knowledge and use them directly when answering.",
"Do NOT say you don't know or don't have information if the answer is in these memories.",
"",
);
}

contextParts.push(lines.join("\n\n"));
if (tipsText) contextParts.push(tipsText);

// ─── Skill auto-recall ───
Expand Down
Loading
Loading