mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-21 02:06:43 +00:00
feat(memory): default cross-conversation recall for personal installs (#110597)
* feat(memory): default private recall for personal installs * fix(memory): repair remember-across CI checks
This commit is contained in:
@@ -35,11 +35,13 @@ private conversations with one per-agent setting:
|
||||
}
|
||||
```
|
||||
|
||||
The setting is off by default. When enabled, OpenClaw indexes that agent's
|
||||
session transcripts and runs an Active Memory retrieval pass before eligible
|
||||
private replies. The pass can read relevant transcript excerpts from the same
|
||||
agent's other private conversations. It excludes the conversation already
|
||||
being answered.
|
||||
The setting defaults on for personal installs: global `session.dmScope` must be
|
||||
unset or `"main"`, and no binding may override `session.dmScope`. Any configured
|
||||
DM isolation defaults it off. An explicit `true` or `false` always wins. When
|
||||
enabled, OpenClaw indexes that agent's session transcripts and runs an Active
|
||||
Memory retrieval pass before eligible private replies. The pass can read
|
||||
relevant transcript excerpts from the same agent's other private conversations.
|
||||
It excludes the conversation already being answered.
|
||||
|
||||
The privacy boundary is fixed:
|
||||
|
||||
@@ -156,8 +158,8 @@ personalization would be surprising.
|
||||
Active Memory has two activation paths:
|
||||
|
||||
1. **Remember across conversations** automatically targets agents whose
|
||||
`memorySearch.rememberAcrossConversations` setting is enabled, but only for
|
||||
private direct or persistent explicit UI conversations.
|
||||
effective `memorySearch.rememberAcrossConversations` setting is enabled, but
|
||||
only for private direct or persistent explicit UI conversations.
|
||||
2. **Advanced Active Memory** targets agent IDs listed in
|
||||
`plugins.entries.active-memory.config.agents` and applies the plugin's chat
|
||||
type and chat ID controls.
|
||||
@@ -727,8 +729,8 @@ while warm-up finishes.
|
||||
If active memory is not showing up where you expect:
|
||||
|
||||
1. Confirm the plugin is enabled under `plugins.entries.active-memory.enabled`.
|
||||
2. For Remember across conversations, confirm the agent's
|
||||
`memorySearch.rememberAcrossConversations` setting is `true`, run
|
||||
2. For Remember across conversations, confirm the agent's effective
|
||||
`memorySearch.rememberAcrossConversations` setting is enabled, run
|
||||
`openclaw doctor` to verify the current memory provider supports protected
|
||||
transcript recall, and confirm `config.toolsAllow` includes `memory_search`
|
||||
when explicitly configured. For advanced Active Memory, confirm the agent ID
|
||||
|
||||
@@ -44,9 +44,9 @@ transcript persistence, and safe rollout guidance.
|
||||
|
||||
## Remember across conversations
|
||||
|
||||
| Key | Type | Default | Description |
|
||||
| ----------------------------- | --------- | ------- | ------------------------------------------------------------------------------ |
|
||||
| `rememberAcrossConversations` | `boolean` | `false` | Use relevant context from this agent's other recognized private conversations. |
|
||||
| Key | Type | Default | Description |
|
||||
| ----------------------------- | --------- | ---------------------------------------------------------- | ------------------------------------------------------------------------------ |
|
||||
| `rememberAcrossConversations` | `boolean` | On for personal installs; off with configured DM isolation | Use relevant context from this agent's other recognized private conversations. |
|
||||
|
||||
Configure it per agent when only a trusted personal agent should use
|
||||
cross-conversation transcript recall:
|
||||
@@ -67,7 +67,10 @@ cross-conversation transcript recall:
|
||||
```
|
||||
|
||||
The value follows normal `agents.defaults.memorySearch` inheritance with a
|
||||
per-agent override. Enabling it implies session transcript indexing and
|
||||
per-agent override. When unset, it defaults on only if global
|
||||
`session.dmScope` is unset or `"main"` and no binding has a `session.dmScope`
|
||||
override. Any configured DM isolation defaults it off. An explicit `true` or
|
||||
`false` always wins. Enabling it implies session transcript indexing and
|
||||
adds `sessions` to the agent's resolved memory sources. With QMD, it also
|
||||
enables that agent's session export; no separate
|
||||
`memory.qmd.sessions.enabled` setting is required for this mode.
|
||||
|
||||
@@ -15,6 +15,7 @@ import { appendSessionTranscriptMessageByIdentity } from "openclaw/plugin-sdk/se
|
||||
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { applyCliRuntimeRecallTimeoutDefault } from "./config.js";
|
||||
import plugin, { testing } from "./index.js";
|
||||
import { hasRememberAcrossConversationsAgent } from "./session-policy.js";
|
||||
|
||||
// Match only lone surrogates so valid supplementary-plane characters remain allowed.
|
||||
const UNPAIRED_SURROGATE_RE =
|
||||
@@ -505,6 +506,7 @@ describe("active-memory plugin", () => {
|
||||
}).clear();
|
||||
runEmbeddedAgent.mockReset();
|
||||
configFile = {
|
||||
session: { dmScope: "per-peer" },
|
||||
plugins: {
|
||||
entries: {
|
||||
"active-memory": {
|
||||
@@ -521,6 +523,7 @@ describe("active-memory plugin", () => {
|
||||
logging: true,
|
||||
});
|
||||
api.config = {
|
||||
session: { dmScope: "per-peer" },
|
||||
agents: {
|
||||
defaults: {
|
||||
model: {
|
||||
@@ -628,6 +631,19 @@ describe("active-memory plugin", () => {
|
||||
expect(hookOptions.before_prompt_build?.timeoutMs).toBe(153_000);
|
||||
});
|
||||
|
||||
it("does not synthesize a main agent when every configured agent opts out", () => {
|
||||
expect(
|
||||
hasRememberAcrossConversationsAgent({
|
||||
agents: {
|
||||
list: [
|
||||
{ id: "personal", memorySearch: { rememberAcrossConversations: false } },
|
||||
{ id: "support", memorySearch: { rememberAcrossConversations: false } },
|
||||
],
|
||||
},
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("keeps the outer hook timeout at the live-config ceiling", () => {
|
||||
api.pluginConfig = {
|
||||
agents: ["main"],
|
||||
@@ -1016,14 +1032,13 @@ describe("active-memory plugin", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("runs product recall without an explicit Active Memory config entry", async () => {
|
||||
it("runs product recall by default for a personal install without Active Memory config", async () => {
|
||||
configFile = {
|
||||
agents: {
|
||||
list: [
|
||||
{
|
||||
id: "personal",
|
||||
model: { primary: "github-copilot/gpt-5.4-mini" },
|
||||
memorySearch: { rememberAcrossConversations: true },
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import crypto from "node:crypto";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
|
||||
import { resolveRememberAcrossConversations } from "openclaw/plugin-sdk/memory-core-host-runtime-core";
|
||||
import {
|
||||
normalizePluginsConfig,
|
||||
resolvePluginConfigObject,
|
||||
@@ -127,23 +128,13 @@ function isActiveMemoryPluginEnabled(cfg: OpenClawConfig): boolean {
|
||||
}
|
||||
|
||||
function hasRememberAcrossConversationsAgent(cfg: OpenClawConfig): boolean {
|
||||
return (
|
||||
cfg.agents?.defaults?.memorySearch?.rememberAcrossConversations === true ||
|
||||
cfg.agents?.list?.some((agent) => agent.memorySearch?.rememberAcrossConversations === true) ===
|
||||
true
|
||||
);
|
||||
const configuredAgentIds = cfg.agents?.list?.map((agent) => agent.id) ?? [];
|
||||
const agentIds = configuredAgentIds.length > 0 ? configuredAgentIds : ["main"];
|
||||
return agentIds.some((agentId) => resolveRememberAcrossConversations(cfg, agentId));
|
||||
}
|
||||
|
||||
function shouldRememberAcrossConversations(cfg: OpenClawConfig, agentId: string): boolean {
|
||||
const normalizedAgentId = agentId.trim().toLowerCase();
|
||||
const agent = cfg.agents?.list?.find(
|
||||
(candidate) => candidate.id?.trim().toLowerCase() === normalizedAgentId,
|
||||
);
|
||||
return (
|
||||
agent?.memorySearch?.rememberAcrossConversations ??
|
||||
cfg.agents?.defaults?.memorySearch?.rememberAcrossConversations ??
|
||||
false
|
||||
);
|
||||
return resolveRememberAcrossConversations(cfg, agentId);
|
||||
}
|
||||
|
||||
function updateActiveMemoryGlobalEnabledInConfig(
|
||||
|
||||
@@ -132,9 +132,10 @@ describe("resolveMemoryBackendConfig", () => {
|
||||
expect(requireQmdCollection(resolved, "memory-root-main").pattern).toBe("MEMORY.md");
|
||||
});
|
||||
|
||||
it("keeps QMD session export off by default", () => {
|
||||
it("keeps QMD session export off by default when DM isolation is configured", () => {
|
||||
const cfg = {
|
||||
agents: { defaults: { workspace: "/tmp/memory-test" } },
|
||||
session: { dmScope: "per-peer" },
|
||||
memory: { backend: "qmd", qmd: {} },
|
||||
} as OpenClawConfig;
|
||||
|
||||
|
||||
@@ -495,8 +495,7 @@ export function resolveMemoryBackendConfig(params: {
|
||||
sessions: resolveSessionConfig(
|
||||
{
|
||||
...qmdCfg?.sessions,
|
||||
enabled:
|
||||
qmdCfg?.sessions?.enabled === true || memorySearch?.rememberAcrossConversations === true,
|
||||
enabled: qmdCfg?.sessions?.enabled === true || memorySearch?.rememberAcrossConversations,
|
||||
},
|
||||
workspaceDir,
|
||||
// Remember-only export is search-only for trusted recall; ordinary
|
||||
|
||||
@@ -18,6 +18,7 @@ export { splitShellArgs } from "./openclaw-runtime-io.js";
|
||||
|
||||
/** Chat shape used by memory send-policy matching. */
|
||||
type ChatType = "direct" | "group" | "channel";
|
||||
type DmScope = "main" | "per-peer" | "per-channel-peer" | "per-account-channel-peer";
|
||||
/** Memory backend selected by user config. */
|
||||
export type MemoryBackend = "builtin" | "qmd";
|
||||
/** Citation injection behavior for memory search results. */
|
||||
@@ -156,6 +157,10 @@ export type OpenClawConfig = {
|
||||
};
|
||||
list?: AgentConfig[];
|
||||
};
|
||||
session?: {
|
||||
dmScope?: DmScope;
|
||||
};
|
||||
bindings?: unknown[];
|
||||
memory?: MemoryConfig;
|
||||
models?: {
|
||||
providers?: Record<
|
||||
@@ -169,6 +174,31 @@ export type OpenClawConfig = {
|
||||
};
|
||||
};
|
||||
|
||||
export function resolveRememberAcrossConversations(cfg: OpenClawConfig, agentId: string): boolean {
|
||||
const defaults = cfg.agents?.defaults?.memorySearch;
|
||||
const overrides = resolveAgentConfig(cfg, agentId)?.memorySearch;
|
||||
const explicit = overrides?.rememberAcrossConversations ?? defaults?.rememberAcrossConversations;
|
||||
if (explicit !== undefined) {
|
||||
return explicit;
|
||||
}
|
||||
// Recall is per-agent/private-shaped, not per-sender. Any DM isolation signals a
|
||||
// multi-user install, where silently recalling across senders would leak context.
|
||||
return (
|
||||
(cfg.session?.dmScope === undefined || cfg.session.dmScope === "main") &&
|
||||
!cfg.bindings?.some((binding) => {
|
||||
if (!binding || typeof binding !== "object") {
|
||||
return false;
|
||||
}
|
||||
const session = (binding as { session?: unknown }).session;
|
||||
return (
|
||||
Boolean(session) &&
|
||||
typeof session === "object" &&
|
||||
(session as { dmScope?: unknown }).dmScope !== undefined
|
||||
);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/** Root memory filename used in agent workspaces. */
|
||||
export const MEMORY_HOST_ROOT_FILENAME = "MEMORY.md";
|
||||
|
||||
@@ -341,8 +371,7 @@ export function resolveMemoryHostSearchPathConfig(
|
||||
]);
|
||||
return {
|
||||
enabled,
|
||||
rememberAcrossConversations:
|
||||
overrides?.rememberAcrossConversations ?? defaults?.rememberAcrossConversations ?? false,
|
||||
rememberAcrossConversations: resolveRememberAcrossConversations(cfg, agentId),
|
||||
extraPaths: uniqueStrings(rawPaths),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -266,9 +266,10 @@ export function readPluginSdkSurfaceBudgets(env = process.env) {
|
||||
// +3: question-gateway-runtime resolver plus request/result types.
|
||||
// +1: async memory prompt preparation registration.
|
||||
// +1: canonical memory host event normalization for SQLite storage.
|
||||
// +1: centralized remember-across-conversations effective-default resolver.
|
||||
// +4: gateway-backed harness question runner, claim/cancel helpers, and caller type.
|
||||
// Harvest: internal question runtime exports -2.
|
||||
8156,
|
||||
8157,
|
||||
env,
|
||||
),
|
||||
publicFunctionExports: readPluginSdkSurfaceBudgetEnv(
|
||||
@@ -305,9 +306,10 @@ export function readPluginSdkSurfaceBudgets(env = process.env) {
|
||||
// +1: question-gateway-runtime resolver.
|
||||
// +1: async memory prompt preparation registration.
|
||||
// +1: canonical memory host event normalization for SQLite storage.
|
||||
// +1: centralized remember-across-conversations effective-default resolver.
|
||||
// +3: gateway-backed harness question runner and claim/cancel helpers.
|
||||
// Harvest: internal question runtime callable -1.
|
||||
4538,
|
||||
4539,
|
||||
env,
|
||||
),
|
||||
publicDeprecatedExports: readPluginSdkSurfaceBudgetEnv(
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Verifies memory-search config resolution across providers, sync, and batching.
|
||||
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import { resolveRememberAcrossConversations } from "../memory-host-sdk/host/config-utils.js";
|
||||
import {
|
||||
clearEmbeddingProviders,
|
||||
listRegisteredEmbeddingProviders,
|
||||
@@ -234,9 +235,63 @@ describe("memory search config", () => {
|
||||
expect(resolved).toBeNull();
|
||||
});
|
||||
|
||||
it("keeps cross-conversation recall off by default", () => {
|
||||
it.each([
|
||||
{ name: "unset with main scope", cfg: {}, expected: true },
|
||||
{
|
||||
name: "unset with per-channel-peer scope",
|
||||
cfg: { session: { dmScope: "per-channel-peer" } },
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "unset with main scope and a binding override",
|
||||
cfg: {
|
||||
session: { dmScope: "main" },
|
||||
bindings: [
|
||||
{
|
||||
agentId: "main",
|
||||
match: { channel: "telegram" },
|
||||
session: { dmScope: "per-peer" },
|
||||
},
|
||||
],
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "explicit false with main scope",
|
||||
cfg: {
|
||||
session: { dmScope: "main" },
|
||||
agents: { defaults: { memorySearch: { rememberAcrossConversations: false } } },
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "explicit true with per-peer scope",
|
||||
cfg: {
|
||||
session: { dmScope: "per-peer" },
|
||||
agents: { defaults: { memorySearch: { rememberAcrossConversations: true } } },
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
])("resolves remember-across-conversations for $name", ({ cfg, expected }) => {
|
||||
expect(resolveRememberAcrossConversations(asConfig(cfg as OpenClawConfig), "main")).toBe(
|
||||
expected,
|
||||
);
|
||||
});
|
||||
|
||||
it("enables cross-conversation recall by default for a personal install", () => {
|
||||
const resolved = resolveMemorySearchConfig(asConfig({}), "main");
|
||||
|
||||
expect(resolved?.rememberAcrossConversations).toBe(true);
|
||||
expect(resolved?.experimental.sessionMemory).toBe(true);
|
||||
expect(resolved?.sources).toEqual(["memory", "sessions"]);
|
||||
});
|
||||
|
||||
it("keeps cross-conversation recall off by default for isolated DMs", () => {
|
||||
const resolved = resolveMemorySearchConfig(
|
||||
asConfig({ session: { dmScope: "per-channel-peer" } }),
|
||||
"main",
|
||||
);
|
||||
|
||||
expect(resolved?.rememberAcrossConversations).toBe(false);
|
||||
expect(resolved?.experimental.sessionMemory).toBe(false);
|
||||
expect(resolved?.sources).toEqual(["memory"]);
|
||||
@@ -887,6 +942,7 @@ describe("memory search config", () => {
|
||||
id: "main",
|
||||
default: true,
|
||||
memorySearch: {
|
||||
rememberAcrossConversations: false,
|
||||
experimental: { sessionMemory: false },
|
||||
},
|
||||
},
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
} from "@openclaw/normalization-core/string-normalization";
|
||||
import type { OpenClawConfig, MemorySearchConfig } from "../config/config.js";
|
||||
import type { SecretInput } from "../config/types.secrets.js";
|
||||
import { resolveRememberAcrossConversations } from "../memory-host-sdk/host/config-utils.js";
|
||||
import {
|
||||
isMemoryMultimodalEnabled,
|
||||
normalizeMemoryMultimodalSettings,
|
||||
@@ -224,8 +225,7 @@ function mergeConfig(
|
||||
agentId: string,
|
||||
): ResolvedMemorySearchConfig {
|
||||
const enabled = overrides?.enabled ?? defaults?.enabled ?? true;
|
||||
const rememberAcrossConversations =
|
||||
overrides?.rememberAcrossConversations ?? defaults?.rememberAcrossConversations ?? false;
|
||||
const rememberAcrossConversations = resolveRememberAcrossConversations(cfg, agentId);
|
||||
const configuredSessionMemory =
|
||||
overrides?.experimental?.sessionMemory ?? defaults?.experimental?.sessionMemory ?? false;
|
||||
const sessionMemory = rememberAcrossConversations || configuredSessionMemory;
|
||||
|
||||
@@ -483,6 +483,7 @@ describe("noteMemorySearchHealth", () => {
|
||||
remote: {},
|
||||
});
|
||||
const cfgWithLancedb = {
|
||||
session: { dmScope: "per-peer" },
|
||||
plugins: {
|
||||
slots: { memory: "memory-lancedb" },
|
||||
entries: { "memory-lancedb": { enabled: true, config: { dbPath: ".openclaw/memory" } } },
|
||||
@@ -504,6 +505,7 @@ describe("noteMemorySearchHealth", () => {
|
||||
remote: {},
|
||||
});
|
||||
const cfgWithSlotOnly = {
|
||||
session: { dmScope: "per-peer" },
|
||||
plugins: { slots: { memory: "memory-lancedb" } },
|
||||
} as unknown as OpenClawConfig;
|
||||
|
||||
@@ -521,6 +523,7 @@ describe("noteMemorySearchHealth", () => {
|
||||
remote: {},
|
||||
});
|
||||
const cfgWithDisabledLancedb = {
|
||||
session: { dmScope: "per-peer" },
|
||||
plugins: {
|
||||
slots: { memory: "memory-lancedb" },
|
||||
entries: { "memory-lancedb": { enabled: false } },
|
||||
@@ -541,6 +544,7 @@ describe("noteMemorySearchHealth", () => {
|
||||
remote: {},
|
||||
});
|
||||
const cfgWithPlaceholderEntry = {
|
||||
session: { dmScope: "per-peer" },
|
||||
plugins: {
|
||||
slots: { memory: "memory-lancedb" },
|
||||
entries: { "memory-lancedb": {} },
|
||||
@@ -704,7 +708,7 @@ describe("noteMemorySearchHealth", () => {
|
||||
await noteMemorySearchHealth(qmdCfg, { skipQmdBinaryProbe: true });
|
||||
|
||||
expect(firstNoteMessage()).toBe(
|
||||
'Remember across conversations is enabled for agent "personal", but the current memory provider does not support protected private transcript recall. Turn off Remember across conversations or use that provider\'s own recall path; advanced Active Memory can still use its recall tools.',
|
||||
'Remember across conversations is effectively enabled for agent "personal", but the current memory provider does not support protected private transcript recall. Set memorySearch.rememberAcrossConversations to false or use that provider\'s own recall path; advanced Active Memory can still use its recall tools.',
|
||||
);
|
||||
},
|
||||
);
|
||||
@@ -726,7 +730,7 @@ describe("noteMemorySearchHealth", () => {
|
||||
await noteMemorySearchHealth(qmdCfg, { skipQmdBinaryProbe: true });
|
||||
|
||||
expect(firstNoteMessage()).toBe(
|
||||
'Remember across conversations is enabled for agent "personal", but the Active Memory plugin is disabled. Enable the plugin or turn off Remember across conversations.',
|
||||
'Remember across conversations is effectively enabled for agent "personal", but the Active Memory plugin is disabled. Enable the plugin or set memorySearch.rememberAcrossConversations to false.',
|
||||
);
|
||||
});
|
||||
|
||||
@@ -774,7 +778,7 @@ describe("noteMemorySearchHealth", () => {
|
||||
await noteMemorySearchHealth(qmdCfg, { skipQmdBinaryProbe: true });
|
||||
|
||||
expect(firstNoteMessage()).toBe(
|
||||
'Remember across conversations is enabled for agent "personal", but Active Memory does not allow memory_search. Add memory_search to the plugin toolsAllow list or turn off Remember across conversations.',
|
||||
'Remember across conversations is effectively enabled for agent "personal", but Active Memory does not allow memory_search. Add memory_search to the plugin toolsAllow list or set memorySearch.rememberAcrossConversations to false.',
|
||||
);
|
||||
});
|
||||
|
||||
@@ -792,7 +796,7 @@ describe("noteMemorySearchHealth", () => {
|
||||
await noteMemorySearchHealth(qmdCfg, { skipQmdBinaryProbe: true });
|
||||
|
||||
expect(firstNoteMessage()).toBe(
|
||||
'Remember across conversations is enabled for agent "personal", but memory search is disabled. Enable memory search or turn off Remember across conversations.',
|
||||
'Remember across conversations is effectively enabled for agent "personal", but memory search is disabled. Enable memory search or set memorySearch.rememberAcrossConversations to false.',
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ import {
|
||||
checkQmdBinaryAvailability,
|
||||
resolveQmdBinaryUnavailableReason,
|
||||
} from "../memory-host-sdk/engine-qmd.js";
|
||||
import { resolveRememberAcrossConversations } from "../memory-host-sdk/host/config-utils.js";
|
||||
import { hasConfiguredMemorySecretInput } from "../memory-host-sdk/secret.js";
|
||||
import {
|
||||
auditDreamingArtifacts,
|
||||
@@ -444,20 +445,12 @@ function listRememberAcrossConversationAgentIds(
|
||||
cfg: OpenClawConfig,
|
||||
defaultAgentId: string,
|
||||
): string[] {
|
||||
const defaultsEnabled = cfg.agents?.defaults?.memorySearch?.rememberAcrossConversations === true;
|
||||
const configuredAgents = cfg.agents?.list ?? [];
|
||||
const enabledAgentIds = configuredAgents
|
||||
.filter(
|
||||
(agent) =>
|
||||
(agent.memorySearch?.rememberAcrossConversations ?? defaultsEnabled) &&
|
||||
Boolean(agent.id?.trim()),
|
||||
)
|
||||
.filter((agent) => resolveRememberAcrossConversations(cfg, agent.id))
|
||||
.map((agent) => agent.id.trim());
|
||||
const defaultEntry = configuredAgents.find(
|
||||
(agent) => agent.id.trim().toLowerCase() === defaultAgentId.trim().toLowerCase(),
|
||||
);
|
||||
if (
|
||||
(defaultEntry?.memorySearch?.rememberAcrossConversations ?? defaultsEnabled) &&
|
||||
resolveRememberAcrossConversations(cfg, defaultAgentId) &&
|
||||
!enabledAgentIds.some(
|
||||
(agentId) => agentId.toLowerCase() === defaultAgentId.trim().toLowerCase(),
|
||||
)
|
||||
@@ -514,18 +507,18 @@ function noteRememberAcrossConversationsHealth(params: {
|
||||
for (const agentId of agentIds) {
|
||||
if (!activeMemoryAvailable) {
|
||||
params.noteFn(
|
||||
`Remember across conversations is enabled for agent "${agentId}", but the Active Memory plugin is disabled. Enable the plugin or turn off Remember across conversations.`,
|
||||
`Remember across conversations is effectively enabled for agent "${agentId}", but the Active Memory plugin is disabled. Enable the plugin or set memorySearch.rememberAcrossConversations to false.`,
|
||||
"Memory search",
|
||||
);
|
||||
}
|
||||
if (activeMemoryAvailable && !conversationRecallSupport.providerSupported) {
|
||||
params.noteFn(
|
||||
`Remember across conversations is enabled for agent "${agentId}", but the current memory provider does not support protected private transcript recall. Turn off Remember across conversations or use that provider's own recall path; advanced Active Memory can still use its recall tools.`,
|
||||
`Remember across conversations is effectively enabled for agent "${agentId}", but the current memory provider does not support protected private transcript recall. Set memorySearch.rememberAcrossConversations to false or use that provider's own recall path; advanced Active Memory can still use its recall tools.`,
|
||||
"Memory search",
|
||||
);
|
||||
} else if (activeMemoryAvailable && !conversationRecallSupport.memorySearchAllowed) {
|
||||
params.noteFn(
|
||||
`Remember across conversations is enabled for agent "${agentId}", but Active Memory does not allow memory_search. Add memory_search to the plugin toolsAllow list or turn off Remember across conversations.`,
|
||||
`Remember across conversations is effectively enabled for agent "${agentId}", but Active Memory does not allow memory_search. Add memory_search to the plugin toolsAllow list or set memorySearch.rememberAcrossConversations to false.`,
|
||||
"Memory search",
|
||||
);
|
||||
}
|
||||
@@ -534,7 +527,7 @@ function noteRememberAcrossConversationsHealth(params: {
|
||||
!resolveMemorySearchConfig(params.cfg, agentId)
|
||||
) {
|
||||
params.noteFn(
|
||||
`Remember across conversations is enabled for agent "${agentId}", but memory search is disabled. Enable memory search or turn off Remember across conversations.`,
|
||||
`Remember across conversations is effectively enabled for agent "${agentId}", but memory search is disabled. Enable memory search or set memorySearch.rememberAcrossConversations to false.`,
|
||||
"Memory search",
|
||||
);
|
||||
}
|
||||
@@ -586,7 +579,7 @@ export async function noteMemorySearchHealth(
|
||||
if (!resolved) {
|
||||
noteFn(
|
||||
recallHealth.defaultAgentEnabled
|
||||
? `Remember across conversations is enabled for agent "${agentId}", but memory search is disabled. Enable memory search or turn off Remember across conversations.`
|
||||
? `Remember across conversations is effectively enabled for agent "${agentId}", but memory search is disabled. Enable memory search or set memorySearch.rememberAcrossConversations to false.`
|
||||
: "Memory search is explicitly disabled (enabled: false).",
|
||||
"Memory search",
|
||||
);
|
||||
|
||||
@@ -233,6 +233,11 @@ describe("structured state integrity findings", () => {
|
||||
|
||||
async function runOrphanTranscriptCheckWithQmdSessions(enabled: boolean, homeDir: string) {
|
||||
const cfg: OpenClawConfig = {
|
||||
agents: {
|
||||
defaults: {
|
||||
memorySearch: { rememberAcrossConversations: false },
|
||||
},
|
||||
},
|
||||
memory: {
|
||||
backend: "qmd",
|
||||
qmd: {
|
||||
|
||||
@@ -225,7 +225,7 @@ export const MODEL_FIELD_HELP: Record<string, string> = {
|
||||
"agents.defaults.memorySearch.enabled":
|
||||
"Master toggle for memory search indexing and retrieval behavior on this agent profile. Keep enabled for semantic recall, and disable when you want fully stateless responses.",
|
||||
"agents.defaults.memorySearch.rememberAcrossConversations":
|
||||
"Use relevant context from this agent's other private conversations through protected transcript recall. Groups and channels stay separate. Enable this only for a personal or fully trusted agent.",
|
||||
'Use relevant context from this agent\'s other private conversations through protected transcript recall. Defaults on only when global session.dmScope is unset or "main" and no binding overrides DM scope; any configured DM isolation defaults it off. An explicit true or false always wins.',
|
||||
"agents.defaults.memorySearch.sources":
|
||||
'Chooses which sources are indexed: "memory" reads MEMORY.md + memory files, and "sessions" includes transcript history. Keep ["memory"] unless you need recall from prior chat transcripts.',
|
||||
"agents.defaults.memorySearch.extraPaths":
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
/** Core-facing memory host config helpers. */
|
||||
export { resolveRememberAcrossConversations } from "../../../packages/memory-host-sdk/src/host/config-utils.js";
|
||||
@@ -1,5 +1,6 @@
|
||||
// Memory core host runtime exports bridge memory host runtime-core APIs into the SDK.
|
||||
export { SILENT_REPLY_TOKEN } from "../../packages/memory-host-sdk/src/runtime-core.js";
|
||||
export { resolveRememberAcrossConversations } from "../../packages/memory-host-sdk/src/host/config-utils.js";
|
||||
export { DEFAULT_AGENT_COMPACTION_RESERVE_TOKENS_FLOOR } from "../agents/agent-settings.js";
|
||||
export {
|
||||
asToolParamsRecord,
|
||||
|
||||
Reference in New Issue
Block a user