From 8573650253888a252500856e240385ff8a2553c8 Mon Sep 17 00:00:00 2001 From: Sandy Tao Date: Fri, 17 Apr 2026 17:08:02 -0700 Subject: [PATCH] feat(config): split memoryManager flag into autoMemory (#25601) --- docs/cli/auto-memory.md | 143 ++++++++++++++++++ docs/cli/settings.md | 1 + docs/cli/tutorials/memory-management.md | 2 + docs/reference/configuration.md | 6 + docs/sidebar.json | 5 + evals/skill_extraction.eval.ts | 4 +- packages/cli/src/acp/commands/memory.ts | 4 +- packages/cli/src/config/config.ts | 1 + packages/cli/src/config/settingsSchema.ts | 10 ++ packages/cli/src/test-utils/mockConfig.ts | 1 + packages/cli/src/ui/AppContainer.tsx | 4 +- .../cli/src/ui/commands/memoryCommand.test.ts | 8 +- packages/cli/src/ui/commands/memoryCommand.ts | 4 +- packages/core/src/config/config.test.ts | 44 ++++++ packages/core/src/config/config.ts | 7 + schemas/settings.schema.json | 7 + 16 files changed, 239 insertions(+), 12 deletions(-) create mode 100644 docs/cli/auto-memory.md diff --git a/docs/cli/auto-memory.md b/docs/cli/auto-memory.md new file mode 100644 index 0000000000..8b3f5379da --- /dev/null +++ b/docs/cli/auto-memory.md @@ -0,0 +1,143 @@ +# Auto Memory + +Auto Memory is an experimental feature that mines your past Gemini CLI sessions +in the background and turns recurring workflows into reusable +[Agent Skills](./skills.md). You review, accept, or discard each extracted skill +before it becomes available to future sessions. + + +> [!NOTE] +> This is an experimental feature currently under active development. + +## Overview + +Every session you run with Gemini CLI is recorded locally as a transcript. Auto +Memory scans those transcripts for procedural patterns that recur across +sessions, then drafts each pattern as a `SKILL.md` file in a project-local +inbox. You inspect the draft, decide whether it captures real expertise, and +promote it to your global or workspace skills directory if you want it. + +You'll use Auto Memory when you want to: + +- **Capture team workflows** that you find yourself walking the agent through + more than once. +- **Codify hard-won fixes** for project-specific landmines so future sessions + avoid them. +- **Bootstrap a skills library** without writing every `SKILL.md` by hand. + +Auto Memory complements—but does not replace—the +[`save_memory` tool](../tools/memory.md), which captures single facts into +`GEMINI.md`. Auto Memory captures multi-step procedures into skills. + +## Prerequisites + +- Gemini CLI installed and authenticated. +- At least 10 user messages across recent, idle sessions in the project. Auto + Memory ignores active or trivial sessions. + +## How to enable Auto Memory + +Auto Memory is off by default. Enable it in your settings file: + +1. Open your global settings file at `~/.gemini/settings.json`. If you only + want Auto Memory in one project, edit `.gemini/settings.json` in that + project instead. + +2. Add the experimental flag: + + ```json + { + "experimental": { + "autoMemory": true + } + } + ``` + +3. Restart Gemini CLI. The flag requires a restart because the extraction + service starts during session boot. + +## How Auto Memory works + +Auto Memory runs as a background task on session startup. It does not block the +UI, consume your interactive turns, or surface tool prompts. + +1. **Eligibility scan.** The service indexes recent sessions from + `~/.gemini/tmp//chats/`. Sessions are eligible only if they have + been idle for at least three hours and contain at least 10 user messages. +2. **Lock acquisition.** A lock file in the project's memory directory + coordinates across multiple CLI instances so extraction runs at most once at + a time. +3. **Sub-agent extraction.** A specialized sub-agent (named `confucius`) + reviews the session index, reads any sessions that look like they contain + repeated procedural workflows, and drafts new `SKILL.md` files. Its + instructions tell it to default to creating zero skills unless the evidence + is strong, so most runs produce no inbox items. +4. **Patch validation.** If the sub-agent proposes edits to skills outside the + inbox (for example, an existing global skill), it writes a unified diff + `.patch` file. Auto Memory dry-runs each patch and discards any that do not + apply cleanly. +5. **Notification.** When a run produces new skills or patches, Gemini CLI + surfaces an inline message telling you how many items are waiting. + +## How to review extracted skills + +Use the `/memory inbox` slash command to open the inbox dialog at any time: + +**Command:** `/memory inbox` + +The dialog lists each draft skill with its name, description, and source +sessions. From there you can: + +- **Read** the full `SKILL.md` body before deciding. +- **Promote** a skill to your user (`~/.gemini/skills/`) or workspace + (`.gemini/skills/`) directory. +- **Discard** a skill you do not want. +- **Apply** or reject a `.patch` proposal against an existing skill. + +Promoted skills become discoverable in the next session and follow the standard +[skill discovery precedence](./skills.md#skill-discovery-tiers). + +## How to disable Auto Memory + +To turn off background extraction, set the flag back to `false` in your settings +file and restart Gemini CLI: + +```json +{ + "experimental": { + "autoMemory": false + } +} +``` + +Disabling the flag stops the background service immediately on the next session +start. Existing inbox items remain on disk; you can either drain them with +`/memory inbox` first or remove the project memory directory manually. + +## Data and privacy + +- Auto Memory only reads session files that already exist locally on your + machine. Nothing is uploaded to Gemini outside the normal API calls the + extraction sub-agent makes during its run. +- The sub-agent is instructed to redact secrets, tokens, and credentials it + encounters and to never copy large tool outputs verbatim. +- Drafted skills live in your project's memory directory until you promote or + discard them. They are not automatically loaded into any session. + +## Limitations + +- The sub-agent runs on a preview Gemini Flash model. Extraction quality depends + on the model's ability to recognize durable patterns versus one-off incidents. +- Auto Memory does not extract skills from the current session. It only + considers sessions that have been idle for three hours or more. +- Inbox items are stored per project. Skills extracted in one workspace are not + visible from another until you promote them to the user-scope skills + directory. + +## Next steps + +- Learn how skills are discovered and activated in [Agent Skills](./skills.md). +- Explore the [memory management tutorial](./tutorials/memory-management.md) for + the complementary `save_memory` and `GEMINI.md` workflows. +- Review the experimental settings catalog in + [Settings](./settings.md#experimental). diff --git a/docs/cli/settings.md b/docs/cli/settings.md index b30a08e0d5..7f34365bb0 100644 --- a/docs/cli/settings.md +++ b/docs/cli/settings.md @@ -169,6 +169,7 @@ they appear in the UI. | Model Steering | `experimental.modelSteering` | Enable model steering (user hints) to guide the model during tool execution. | `false` | | Direct Web Fetch | `experimental.directWebFetch` | Enable web fetch behavior that bypasses LLM summarization. | `false` | | Memory Manager Agent | `experimental.memoryManager` | Replace the built-in save_memory tool with a memory manager subagent that supports adding, removing, de-duplicating, and organizing memories. | `false` | +| Auto Memory | `experimental.autoMemory` | Automatically extract reusable skills from past sessions in the background. Review results with /memory inbox. | `false` | | Use the generalist profile to manage agent contexts. | `experimental.generalistProfile` | Suitable for general coding and software development tasks. | `false` | | Enable Context Management | `experimental.contextManagement` | Enable logic for context management. | `false` | diff --git a/docs/cli/tutorials/memory-management.md b/docs/cli/tutorials/memory-management.md index c2406e1d3c..aa0423157f 100644 --- a/docs/cli/tutorials/memory-management.md +++ b/docs/cli/tutorials/memory-management.md @@ -124,3 +124,5 @@ immediately. Force a reload with: - Explore the [Command reference](../../reference/commands.md) for more `/memory` options. - Read the technical spec for [Project context](../../cli/gemini-md.md). +- Try the experimental [Auto Memory](../auto-memory.md) feature to extract + reusable skills from your past sessions automatically. diff --git a/docs/reference/configuration.md b/docs/reference/configuration.md index 0b7b23c4e9..a5a6aa1eb2 100644 --- a/docs/reference/configuration.md +++ b/docs/reference/configuration.md @@ -1729,6 +1729,12 @@ their corresponding top-level category object in your `settings.json` file. - **Default:** `false` - **Requires restart:** Yes +- **`experimental.autoMemory`** (boolean): + - **Description:** Automatically extract reusable skills from past sessions in + the background. Review results with /memory inbox. + - **Default:** `false` + - **Requires restart:** Yes + - **`experimental.generalistProfile`** (boolean): - **Description:** Suitable for general coding and software development tasks. - **Default:** `false` diff --git a/docs/sidebar.json b/docs/sidebar.json index 0d94b1ac60..8ea6f56a09 100644 --- a/docs/sidebar.json +++ b/docs/sidebar.json @@ -96,6 +96,11 @@ ] }, { "label": "Agent Skills", "slug": "docs/cli/skills" }, + { + "label": "Auto Memory", + "badge": "🔬", + "slug": "docs/cli/auto-memory" + }, { "label": "Checkpointing", "slug": "docs/cli/checkpointing" }, { "label": "Headless mode", "slug": "docs/cli/headless" }, { diff --git a/evals/skill_extraction.eval.ts b/evals/skill_extraction.eval.ts index 28ca557f01..2c80146523 100644 --- a/evals/skill_extraction.eval.ts +++ b/evals/skill_extraction.eval.ts @@ -149,12 +149,12 @@ async function readSkillBodies(skillsDir: string): Promise { /** * Shared configOverrides for all skill extraction component evals. - * - experimentalMemoryManager: enables the memory extraction pipeline. + * - experimentalAutoMemory: enables the Auto Memory skill extraction pipeline. * - approvalMode: YOLO auto-approves tool calls (write_file, read_file) so the * background agent can execute without interactive confirmation. */ const EXTRACTION_CONFIG_OVERRIDES = { - experimentalMemoryManager: true, + experimentalAutoMemory: true, approvalMode: ApprovalMode.YOLO, }; diff --git a/packages/cli/src/acp/commands/memory.ts b/packages/cli/src/acp/commands/memory.ts index 73c1c98113..8e990e12a7 100644 --- a/packages/cli/src/acp/commands/memory.ts +++ b/packages/cli/src/acp/commands/memory.ts @@ -135,10 +135,10 @@ export class InboxMemoryCommand implements Command { context: CommandContext, _: string[], ): Promise { - if (!context.agentContext.config.isMemoryManagerEnabled()) { + if (!context.agentContext.config.isAutoMemoryEnabled()) { return { name: this.name, - data: 'The memory inbox requires the experimental memory manager. Enable it with: experimental.memoryManager = true in settings.', + data: 'The memory inbox requires Auto Memory. Enable it with: experimental.autoMemory = true in settings.', }; } diff --git a/packages/cli/src/config/config.ts b/packages/cli/src/config/config.ts index 37277b45eb..d3b807f991 100755 --- a/packages/cli/src/config/config.ts +++ b/packages/cli/src/config/config.ts @@ -990,6 +990,7 @@ export async function loadCliConfig( disabledSkills: settings.skills?.disabled, experimentalJitContext: settings.experimental?.jitContext, experimentalMemoryManager: settings.experimental?.memoryManager, + experimentalAutoMemory: settings.experimental?.autoMemory, contextManagement, modelSteering: settings.experimental?.modelSteering, topicUpdateNarration: diff --git a/packages/cli/src/config/settingsSchema.ts b/packages/cli/src/config/settingsSchema.ts index ba4deb78aa..93ac53ada3 100644 --- a/packages/cli/src/config/settingsSchema.ts +++ b/packages/cli/src/config/settingsSchema.ts @@ -2213,6 +2213,16 @@ const SETTINGS_SCHEMA = { 'Replace the built-in save_memory tool with a memory manager subagent that supports adding, removing, de-duplicating, and organizing memories.', showInDialog: true, }, + autoMemory: { + type: 'boolean', + label: 'Auto Memory', + category: 'Experimental', + requiresRestart: true, + default: false, + description: + 'Automatically extract reusable skills from past sessions in the background. Review results with /memory inbox.', + showInDialog: true, + }, generalistProfile: { type: 'boolean', label: 'Use the generalist profile to manage agent contexts.', diff --git a/packages/cli/src/test-utils/mockConfig.ts b/packages/cli/src/test-utils/mockConfig.ts index 516be675c0..113dc73156 100644 --- a/packages/cli/src/test-utils/mockConfig.ts +++ b/packages/cli/src/test-utils/mockConfig.ts @@ -39,6 +39,7 @@ export const createMockConfig = (overrides: Partial = {}): Config => fireSessionStartEvent: vi.fn().mockResolvedValue(undefined), })), isMemoryManagerEnabled: vi.fn(() => false), + isAutoMemoryEnabled: vi.fn(() => false), getListExtensions: vi.fn(() => false), getExtensions: vi.fn(() => []), getListSessions: vi.fn(() => false), diff --git a/packages/cli/src/ui/AppContainer.tsx b/packages/cli/src/ui/AppContainer.tsx index 4d7675bea3..f9906f6fb5 100644 --- a/packages/cli/src/ui/AppContainer.tsx +++ b/packages/cli/src/ui/AppContainer.tsx @@ -486,8 +486,8 @@ export const AppContainer = (props: AppContainerProps) => { setConfigInitialized(true); startupProfiler.flush(config); - // Fire-and-forget memory service (skill extraction from past sessions) - if (config.isMemoryManagerEnabled()) { + // Fire-and-forget Auto Memory service (skill extraction from past sessions) + if (config.isAutoMemoryEnabled()) { startMemoryService(config).catch((e) => { debugLogger.error('Failed to start memory service:', e); }); diff --git a/packages/cli/src/ui/commands/memoryCommand.test.ts b/packages/cli/src/ui/commands/memoryCommand.test.ts index c0fdb62ba2..abc64fda72 100644 --- a/packages/cli/src/ui/commands/memoryCommand.test.ts +++ b/packages/cli/src/ui/commands/memoryCommand.test.ts @@ -473,7 +473,7 @@ describe('memoryCommand', () => { const mockConfig = { reloadSkills: vi.fn(), - isMemoryManagerEnabled: vi.fn().mockReturnValue(true), + isAutoMemoryEnabled: vi.fn().mockReturnValue(true), }; const context = createMockCommandContext({ services: { @@ -491,11 +491,11 @@ describe('memoryCommand', () => { expect(result).toHaveProperty('component'); }); - it('should return info message when memory manager is disabled', () => { + it('should return info message when auto memory is disabled', () => { if (!inboxCommand.action) throw new Error('Command has no action'); const mockConfig = { - isMemoryManagerEnabled: vi.fn().mockReturnValue(false), + isAutoMemoryEnabled: vi.fn().mockReturnValue(false), }; const context = createMockCommandContext({ services: { @@ -509,7 +509,7 @@ describe('memoryCommand', () => { type: 'message', messageType: 'info', content: - 'The memory inbox requires the experimental memory manager. Enable it with: experimental.memoryManager = true in settings.', + 'The memory inbox requires Auto Memory. Enable it with: experimental.autoMemory = true in settings.', }); }); diff --git a/packages/cli/src/ui/commands/memoryCommand.ts b/packages/cli/src/ui/commands/memoryCommand.ts index 1cb4f27958..9d7a19990e 100644 --- a/packages/cli/src/ui/commands/memoryCommand.ts +++ b/packages/cli/src/ui/commands/memoryCommand.ts @@ -145,12 +145,12 @@ export const memoryCommand: SlashCommand = { }; } - if (!config.isMemoryManagerEnabled()) { + if (!config.isAutoMemoryEnabled()) { return { type: 'message', messageType: 'info', content: - 'The memory inbox requires the experimental memory manager. Enable it with: experimental.memoryManager = true in settings.', + 'The memory inbox requires Auto Memory. Enable it with: experimental.autoMemory = true in settings.', }; } diff --git a/packages/core/src/config/config.test.ts b/packages/core/src/config/config.test.ts index 895d9ca963..ec609f294e 100644 --- a/packages/core/src/config/config.test.ts +++ b/packages/core/src/config/config.test.ts @@ -3443,6 +3443,50 @@ describe('Config JIT Initialization', () => { }); }); + describe('isAutoMemoryEnabled', () => { + it('should default to false', () => { + const params: ConfigParameters = { + sessionId: 'test-session', + targetDir: '/tmp/test', + debugMode: false, + model: 'test-model', + cwd: '/tmp/test', + }; + + config = new Config(params); + expect(config.isAutoMemoryEnabled()).toBe(false); + }); + + it('should return true when experimentalAutoMemory is true', () => { + const params: ConfigParameters = { + sessionId: 'test-session', + targetDir: '/tmp/test', + debugMode: false, + model: 'test-model', + cwd: '/tmp/test', + experimentalAutoMemory: true, + }; + + config = new Config(params); + expect(config.isAutoMemoryEnabled()).toBe(true); + }); + + it('should be independent of experimentalMemoryManager', () => { + const params: ConfigParameters = { + sessionId: 'test-session', + targetDir: '/tmp/test', + debugMode: false, + model: 'test-model', + cwd: '/tmp/test', + experimentalMemoryManager: true, + }; + + config = new Config(params); + expect(config.isMemoryManagerEnabled()).toBe(true); + expect(config.isAutoMemoryEnabled()).toBe(false); + }); + }); + describe('reloadSkills', () => { it('should refresh disabledSkills and re-register ActivateSkillTool when skills exist', async () => { const mockOnReload = vi.fn().mockResolvedValue({ diff --git a/packages/core/src/config/config.ts b/packages/core/src/config/config.ts index 06272970cf..01c6fd7bfd 100644 --- a/packages/core/src/config/config.ts +++ b/packages/core/src/config/config.ts @@ -701,6 +701,7 @@ export interface ConfigParameters { experimentalJitContext?: boolean; autoDistillation?: boolean; experimentalMemoryManager?: boolean; + experimentalAutoMemory?: boolean; experimentalContextManagementConfig?: string; experimentalAgentHistoryTruncation?: boolean; experimentalAgentHistoryTruncationThreshold?: number; @@ -942,6 +943,7 @@ export class Config implements McpContext, AgentLoopContext { private readonly adminSkillsEnabled: boolean; private readonly experimentalJitContext: boolean; private readonly experimentalMemoryManager: boolean; + private readonly experimentalAutoMemory: boolean; private readonly experimentalContextManagementConfig?: string; private readonly memoryBoundaryMarkers: readonly string[]; private readonly topicUpdateNarration: boolean; @@ -1154,6 +1156,7 @@ export class Config implements McpContext, AgentLoopContext { this.experimentalJitContext = params.experimentalJitContext ?? false; this.experimentalMemoryManager = params.experimentalMemoryManager ?? false; + this.experimentalAutoMemory = params.experimentalAutoMemory ?? false; this.experimentalContextManagementConfig = params.experimentalContextManagementConfig; this.memoryBoundaryMarkers = params.memoryBoundaryMarkers ?? ['.git']; @@ -2486,6 +2489,10 @@ export class Config implements McpContext, AgentLoopContext { return this.experimentalMemoryManager; } + isAutoMemoryEnabled(): boolean { + return this.experimentalAutoMemory; + } + getExperimentalContextManagementConfig(): string | undefined { return this.experimentalContextManagementConfig; } diff --git a/schemas/settings.schema.json b/schemas/settings.schema.json index 9a063dcc8b..491db887a4 100644 --- a/schemas/settings.schema.json +++ b/schemas/settings.schema.json @@ -2954,6 +2954,13 @@ "default": false, "type": "boolean" }, + "autoMemory": { + "title": "Auto Memory", + "description": "Automatically extract reusable skills from past sessions in the background. Review results with /memory inbox.", + "markdownDescription": "Automatically extract reusable skills from past sessions in the background. Review results with /memory inbox.\n\n- Category: `Experimental`\n- Requires restart: `yes`\n- Default: `false`", + "default": false, + "type": "boolean" + }, "generalistProfile": { "title": "Use the generalist profile to manage agent contexts.", "description": "Suitable for general coding and software development tasks.",