mirror of
https://github.com/google-gemini/gemini-cli
synced 2026-04-21 13:37:17 +00:00
The MemoryManagerAgent was a save_memory subagent that was slow, spent many turns on simple operations, and offered little visibility into how memories were being updated. Reshape experimental.memoryManager to remove the subagent and let the main agent persist memories itself by editing markdown files directly across four explicit tiers — each fact lives in exactly one tier:
- **Project Instructions** (`./GEMINI.md`): team-shared architecture/conventions/workflows, committed to the repo.
- **Subdirectory Instructions** (e.g. `./src/GEMINI.md`): scoped to one part of the project, committed.
- **Private Project Memory** (`~/.gemini/tmp/<hash>/memory/MEMORY.md` + sibling `*.md` notes): personal-to-the-user, project-specific notes that never get committed.
- **Global Personal Memory** (`~/.gemini/GEMINI.md`): cross-project personal preferences that follow the user into every workspace.
Core changes:
- Delete MemoryManagerAgent and its registration. The agent and its test file are removed entirely. The built-in MemoryTool remains suppressed by the flag (unchanged), so save_memory is still gone when the flag is on — the agent now handles memory persistence directly via edit/write_file.
- Rewrite the operational system prompt branch (snippets.ts and snippets.legacy.ts) to teach the main agent the four-tier model. snippets.ts adds explicit per-tier routing rules with concrete cue phrases, a one-tier-per-fact mutual-exclusion rule that explicitly covers all four tiers, and a tightly scoped MEMORY.md role (index for sibling *.md notes only, never a pointer to any GEMINI.md). snippets.legacy.ts (a frozen historical snapshot per packages/core/GEMINI.md) gets the structural three-tier rewrite only — no new prompt-engineering verbiage, no Global Personal tier.
- Surface both the Private Project Memory file (~/.gemini/tmp/<hash>/memory/MEMORY.md) and the Global Personal Memory file (~/.gemini/GEMINI.md) to the prompt only when memoryManagerEnabled is true. The Private bullet only renders when userProjectMemoryPath is provided; the Global bullet + cross-project routing rule only renders when globalMemoryPath is provided. Legacy callers that don't pass either path get a sensible 2-tier prompt.
- Extend Config.isPathAllowed with a surgical allowlist for ~/.gemini/GEMINI.md so the agent can edit the global personal memory file via edit/write_file. Critically, this is **least-privilege**: an exact-path comparison against `Storage.getGlobalGeminiDir() + getCurrentGeminiMdFilename()`, so settings.json, keybindings.json, oauth_creds.json, and the rest of ~/.gemini/ remain unreachable. The workspace context itself is still NOT broadened to include ~/.gemini/.
- Introduce PROJECT_MEMORY_INDEX_FILENAME = 'MEMORY.md' and rename getProjectMemoryFilePath -> getProjectMemoryIndexFilePath. Split memoryTool's append logic into computeGlobalMemoryContent (preserves the legacy '## Gemini Added Memories' header behavior) and computeProjectMemoryContent (plain bullet append, no header) routed by the scope parameter. Extract sanitizeFact for reuse.
- memoryDiscovery.getUserProjectMemoryPaths prefers MEMORY.md, falling back to a legacy GEMINI.md in the same folder so projects that have not been migrated still load their existing private memory.
- Update settingsSchema.ts (rename label to 'Memory Manager', rewrite description to reflect the 4-tier model and the surgical ~/.gemini/GEMINI.md allowlist) and regenerate schemas/settings.schema.json plus the auto-generated settings docs (docs/cli/settings.md, docs/reference/configuration.md).
Tests and evals:
- snippets-memory-manager.test.ts: assert the new memoryManager-mode prompt structure, including the conditional Private Project Memory bullet, the conditional Global Personal Memory bullet + cross-project routing rule, the per-tier routing block, the four-tier mutual-exclusion rule, and the scoped MEMORY.md role.
- config.test.ts: keep the existing negative test that workspace context does NOT broaden to ~/.gemini/, and add two new tests around the surgical isPathAllowed allowlist — a positive case (~/.gemini/GEMINI.md is allowed) and a least-privilege case (settings.json, keybindings.json, oauth_creds.json under ~/.gemini/ remain disallowed).
- memoryTool.test.ts: cover the new project-scope content path (no header, plain bullet append).
- save_memory.eval.ts:
* Update the proactive long-session eval so it now asserts the "I always prefer Vitest ... in all my projects" preference is routed to the global personal memory file (its correct destination under the 4-tier model), and is NOT mirrored into a committed project GEMINI.md or the private project memory folder.
* Add an eval verifying that team-shared project conventions ("our project uses X", "the team always Y") route to the project-root ./GEMINI.md and are NOT mirrored into the private memory folder (the mutual-exclusion guarantee).
* Add an eval verifying that personal-to-user project notes ("on my machine", "do not commit this") route to the private project memory folder, with substantial detail captured in a sibling *.md note and MEMORY.md updated as the index.
* Add an eval verifying that cross-project personal preferences ("across all my projects", "I always prefer X", "in every workspace") route to the global ~/.gemini/GEMINI.md and are NOT mirrored into a committed project GEMINI.md or the private memory folder.
Validation: lint, typecheck, the affected unit tests in @google/gemini-cli-core (snippets-memory-manager.test.ts, memoryTool.test.ts, config.test.ts, promptProvider.test.ts — 267/267 pass), the schema/docs in-sync check tests, and the full save_memory.eval.ts suite (15/15 pass) all pass. 10x reliability loops on all four memoryManager-mode evals are 10/10 against the final prompt (40/40 total).
Security hardening (PR review feedback):
- sanitizeFact (memoryTool.ts) now also collapses `<` and `>` to spaces. Without this, a malicious fact (or a malicious repo's MEMORY.md content surfaced through save_memory) could carry an XML closing tag like `</user_project_memory>` past the existing newline/markdown sanitization, get persisted to disk, and on every future session that loads the memory file the renderUserMemory injection would let the payload break out of the surrounding context block. Symmetric with the existing newline collapse, minimal content loss for typical durable preferences. memoryTool.test.ts adds an explicit XML-tag-breakout test case asserting the payload is neutralised end-to-end (write_file content + success message).
CI fix:
- path-validation.test.ts: the existing test "should allow access to ~/.gemini if it is added to the workspace" used `~/.gemini/GEMINI.md` as its example file to demonstrate the workspace-addition semantic, but that file is now reachable unconditionally via the new surgical isPathAllowed allowlist. Switch the example to `~/.gemini/settings.json` (NOT on the allowlist), preserving the original "denied -> add to workspace -> allowed" flow the test was written to verify, and double-asserting the least-privilege guarantee that the allowlist does not leak access to other files under ~/.gemini/. Rename the test to reflect the more general intent.
|
||
|---|---|---|
| .. | ||
| admin | ||
| assets | ||
| changelogs | ||
| cli | ||
| core | ||
| examples | ||
| extensions | ||
| get-started | ||
| hooks | ||
| ide-integration | ||
| mermaid | ||
| reference | ||
| resources | ||
| tools | ||
| CONTRIBUTING.md | ||
| index.md | ||
| integration-tests.md | ||
| issue-and-pr-automation.md | ||
| local-development.md | ||
| npm.md | ||
| redirects.json | ||
| release-confidence.md | ||
| releases.md | ||
| sidebar.json | ||