* fix(consolidation): default ON when an LLM provider is configured Users with a provider configured (Anthropic / OpenAI / OpenAI-compatible local endpoint via OPENAI_BASE_URL / OpenRouter / Gemini / Minimax / agent-sdk) were silently getting zero graph nodes, lessons, and crystals because CONSOLIDATION_ENABLED defaulted to false. The auto-consolidate pipeline only fires when the flag is true, so 100+ session corpora sat unprocessed unless the user knew to flip the flag manually. The fix: - `isConsolidationEnabled()` now returns true by default whenever a non-noop LLM provider is detectable from env (API key set, OPENAI_BASE_URL set, or AGENTMEMORY_PROVIDER=agent-sdk). - Explicit `CONSOLIDATION_ENABLED=false` or `AGENTMEMORY_PROVIDER=noop` still opt out — no behavior change for BM25-only / noop users. - Skip-reason text on the pipeline function updated to point at the env vars users should set. Closes the silent-no-graph case reported in discussion #612 (franklyfresh: 100 sessions, OpenAI-compatible LLM via Open WebUI / vLLM, summarization working, graph empty). 8 unit tests cover: no-provider default-off, every supported provider default-on, explicit `=false` override, explicit `=true` override, and AGENTMEMORY_PROVIDER=noop wins over API-key presence. * fix(consolidation): honor OPENAI_API_KEY_FOR_LLM + expand skip-reason Review fixes on #696: 1. hasLLMProviderConfigured() now scopes OPENAI_API_KEY to LLM use the same way detectProvider() does: the key only counts when OPENAI_API_KEY_FOR_LLM is not "false" (case-insensitive). Without this, a user who explicitly scoped their OpenAI key to embeddings only would still get consolidation enabled by default. Other keys (ANTHROPIC, OPENROUTER, GEMINI, GOOGLE, MINIMAX, OPENAI_BASE_URL) and AGENTMEMORY_PROVIDER=agent-sdk unchanged. 2. consolidation-pipeline skip-reason text now lists every env path that flips the default: ANTHROPIC_API_KEY / OPENAI_API_KEY / OPENROUTER_API_KEY / GEMINI_API_KEY / GOOGLE_API_KEY / MINIMAX_API_KEY / OPENAI_BASE_URL / AGENTMEMORY_PROVIDER=agent-sdk. Aligns with the actual detection logic so users see all valid ways to enable when they hit the skipped result. 3. Regression test in test/consolidation-default.test.ts asserts OPENAI_API_KEY=sk-... + OPENAI_API_KEY_FOR_LLM=false → isConsolidationEnabled() === false. Pairs with the existing OPENAI_API_KEY-on-by-default case to lock the contract.
This commit is contained in:
@@ -1275,11 +1275,11 @@ AGENTMEMORY_ALLOW_AGENT_SDK=true
|
||||
AGENTMEMORY_AUTO_COMPRESS=true
|
||||
```
|
||||
|
||||
Turn on graph or consolidation features in the same file if you want them:
|
||||
Consolidation (graph nodes, lessons, crystals) is on by default whenever an LLM provider is configured. Explicitly opt out with `CONSOLIDATION_ENABLED=false` if you want LLM-free operation. Graph extraction is a separate flag:
|
||||
|
||||
```env
|
||||
GRAPH_EXTRACTION_ENABLED=true
|
||||
CONSOLIDATION_ENABLED=true
|
||||
# CONSOLIDATION_ENABLED=false # opt out of auto-consolidation
|
||||
```
|
||||
|
||||
### Environment Variables
|
||||
@@ -1389,7 +1389,7 @@ Create `~/.agentmemory/.env`:
|
||||
# Observations are still captured via
|
||||
# PostToolUse regardless of this flag.
|
||||
# GRAPH_EXTRACTION_ENABLED=false
|
||||
# CONSOLIDATION_ENABLED=true
|
||||
# CONSOLIDATION_ENABLED=false # on by default when an LLM provider is configured
|
||||
# LESSON_DECAY_ENABLED=true
|
||||
# OBSIDIAN_AUTO_EXPORT=false
|
||||
# AGENTMEMORY_EXPORT_ROOT=~/.agentmemory
|
||||
|
||||
+23
-1
@@ -323,7 +323,29 @@ export function getGraphBatchSize(): number {
|
||||
}
|
||||
|
||||
export function isConsolidationEnabled(): boolean {
|
||||
return getMergedEnv()["CONSOLIDATION_ENABLED"] === "true";
|
||||
const env = getMergedEnv();
|
||||
const explicit = env["CONSOLIDATION_ENABLED"];
|
||||
if (explicit === "false" || explicit === "0") return false;
|
||||
if (explicit === "true" || explicit === "1") return true;
|
||||
return hasLLMProviderConfigured(env);
|
||||
}
|
||||
|
||||
function hasLLMProviderConfigured(env: Record<string, string | undefined>): boolean {
|
||||
const provider = (env["AGENTMEMORY_PROVIDER"] || "").toLowerCase();
|
||||
if (provider === "noop") return false;
|
||||
const openaiKeyForLlm =
|
||||
env["OPENAI_API_KEY"] &&
|
||||
(env["OPENAI_API_KEY_FOR_LLM"] || "").toLowerCase() !== "false";
|
||||
return Boolean(
|
||||
env["ANTHROPIC_API_KEY"] ||
|
||||
openaiKeyForLlm ||
|
||||
env["OPENROUTER_API_KEY"] ||
|
||||
env["GEMINI_API_KEY"] ||
|
||||
env["GOOGLE_API_KEY"] ||
|
||||
env["MINIMAX_API_KEY"] ||
|
||||
env["OPENAI_BASE_URL"] ||
|
||||
provider === "agent-sdk",
|
||||
);
|
||||
}
|
||||
|
||||
// Per-observation LLM compression is OFF by default as of 0.8.8 (see #138).
|
||||
|
||||
@@ -50,7 +50,7 @@ export function registerConsolidationPipelineFunction(
|
||||
sdk.registerFunction("mem::consolidate-pipeline",
|
||||
async (data?: { tier?: string; force?: boolean; project?: string }) => {
|
||||
if (!data?.force && !isConsolidationEnabled()) {
|
||||
return { success: false, skipped: true, reason: "CONSOLIDATION_ENABLED is not set to true" };
|
||||
return { success: false, skipped: true, reason: "Consolidation disabled: set CONSOLIDATION_ENABLED=true or configure an LLM provider (ANTHROPIC_API_KEY / OPENAI_API_KEY / OPENROUTER_API_KEY / GEMINI_API_KEY / GOOGLE_API_KEY / MINIMAX_API_KEY / OPENAI_BASE_URL / AGENTMEMORY_PROVIDER=agent-sdk)" };
|
||||
}
|
||||
const tier = data?.tier || "all";
|
||||
const decayDays = getConsolidationDecayDays();
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
||||
import { mkdtempSync, writeFileSync, mkdirSync, rmSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
|
||||
const ENV_KEYS = [
|
||||
"CONSOLIDATION_ENABLED",
|
||||
"AGENTMEMORY_PROVIDER",
|
||||
"ANTHROPIC_API_KEY",
|
||||
"OPENAI_API_KEY",
|
||||
"OPENAI_API_KEY_FOR_LLM",
|
||||
"OPENROUTER_API_KEY",
|
||||
"GEMINI_API_KEY",
|
||||
"GOOGLE_API_KEY",
|
||||
"MINIMAX_API_KEY",
|
||||
"OPENAI_BASE_URL",
|
||||
];
|
||||
|
||||
const ORIGINAL_HOME = process.env["HOME"];
|
||||
const ORIGINAL_USERPROFILE = process.env["USERPROFILE"];
|
||||
const ORIGINAL: Record<string, string | undefined> = {};
|
||||
|
||||
let sandboxHome: string;
|
||||
|
||||
async function freshConfig() {
|
||||
vi.resetModules();
|
||||
return await import("../src/config.js");
|
||||
}
|
||||
|
||||
function writeEnv(contents: string) {
|
||||
const dir = join(sandboxHome, ".agentmemory");
|
||||
mkdirSync(dir, { recursive: true });
|
||||
writeFileSync(join(dir, ".env"), contents);
|
||||
}
|
||||
|
||||
describe("isConsolidationEnabled default behavior", () => {
|
||||
beforeEach(() => {
|
||||
sandboxHome = mkdtempSync(join(tmpdir(), "agentmemory-consolidation-"));
|
||||
process.env["HOME"] = sandboxHome;
|
||||
process.env["USERPROFILE"] = sandboxHome;
|
||||
for (const k of ENV_KEYS) {
|
||||
ORIGINAL[k] = process.env[k];
|
||||
delete process.env[k];
|
||||
}
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
if (ORIGINAL_HOME === undefined) delete process.env["HOME"];
|
||||
else process.env["HOME"] = ORIGINAL_HOME;
|
||||
if (ORIGINAL_USERPROFILE === undefined) delete process.env["USERPROFILE"];
|
||||
else process.env["USERPROFILE"] = ORIGINAL_USERPROFILE;
|
||||
for (const k of ENV_KEYS) {
|
||||
if (ORIGINAL[k] === undefined) delete process.env[k];
|
||||
else process.env[k] = ORIGINAL[k];
|
||||
}
|
||||
rmSync(sandboxHome, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it("returns false when no LLM provider configured (default BM25-only mode)", async () => {
|
||||
writeEnv("");
|
||||
const cfg = await freshConfig();
|
||||
expect(cfg.isConsolidationEnabled()).toBe(false);
|
||||
});
|
||||
|
||||
it("returns true by default when ANTHROPIC_API_KEY is set", async () => {
|
||||
writeEnv("ANTHROPIC_API_KEY=sk-test-123");
|
||||
const cfg = await freshConfig();
|
||||
expect(cfg.isConsolidationEnabled()).toBe(true);
|
||||
});
|
||||
|
||||
it("returns true by default when OPENAI_API_KEY is set", async () => {
|
||||
writeEnv("OPENAI_API_KEY=sk-test-123");
|
||||
const cfg = await freshConfig();
|
||||
expect(cfg.isConsolidationEnabled()).toBe(true);
|
||||
});
|
||||
|
||||
it("returns true by default when OPENAI_BASE_URL is set (local OpenAI-compatible)", async () => {
|
||||
writeEnv("OPENAI_BASE_URL=http://localhost:1234/v1");
|
||||
const cfg = await freshConfig();
|
||||
expect(cfg.isConsolidationEnabled()).toBe(true);
|
||||
});
|
||||
|
||||
it("returns true by default when AGENTMEMORY_PROVIDER=agent-sdk", async () => {
|
||||
writeEnv("AGENTMEMORY_PROVIDER=agent-sdk");
|
||||
const cfg = await freshConfig();
|
||||
expect(cfg.isConsolidationEnabled()).toBe(true);
|
||||
});
|
||||
|
||||
it("explicit CONSOLIDATION_ENABLED=false overrides provider-based default", async () => {
|
||||
writeEnv("ANTHROPIC_API_KEY=sk-test-123\nCONSOLIDATION_ENABLED=false");
|
||||
const cfg = await freshConfig();
|
||||
expect(cfg.isConsolidationEnabled()).toBe(false);
|
||||
});
|
||||
|
||||
it("explicit CONSOLIDATION_ENABLED=true overrides absence of provider", async () => {
|
||||
writeEnv("CONSOLIDATION_ENABLED=true");
|
||||
const cfg = await freshConfig();
|
||||
expect(cfg.isConsolidationEnabled()).toBe(true);
|
||||
});
|
||||
|
||||
it("AGENTMEMORY_PROVIDER=noop returns false even with API key set", async () => {
|
||||
writeEnv("AGENTMEMORY_PROVIDER=noop\nANTHROPIC_API_KEY=sk-test-123");
|
||||
const cfg = await freshConfig();
|
||||
expect(cfg.isConsolidationEnabled()).toBe(false);
|
||||
});
|
||||
|
||||
it("OPENAI_API_KEY_FOR_LLM=false scopes the key to embeddings only", async () => {
|
||||
writeEnv("OPENAI_API_KEY=sk-test-123\nOPENAI_API_KEY_FOR_LLM=false");
|
||||
const cfg = await freshConfig();
|
||||
expect(cfg.isConsolidationEnabled()).toBe(false);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user