Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1229,11 +1229,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
Expand Down Expand Up @@ -1343,7 +1343,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
Expand Down
24 changes: 23 additions & 1 deletion src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

// Per-observation LLM compression is OFF by default as of 0.8.8 (see #138).
Expand Down
2 changes: 1 addition & 1 deletion src/functions/consolidation-pipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
112 changes: 112 additions & 0 deletions test/consolidation-default.test.ts
Original file line number Diff line number Diff line change
@@ -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);
});
});