Skip to content

feat(memory): implement persistent session logging and background memory consolidation#24892

Open
hackerxj2010 wants to merge 4 commits intogoogle-gemini:mainfrom
hackerxj2010:main
Open

feat(memory): implement persistent session logging and background memory consolidation#24892
hackerxj2010 wants to merge 4 commits intogoogle-gemini:mainfrom
hackerxj2010:main

Conversation

@hackerxj2010
Copy link
Copy Markdown

Summary

Implements a persistent, hierarchical memory system for the Gemini CLI that automatically logs session activity and consolidates insights into project-level GEMINI.md files.

Changes

  • GEMINI.md Auto-Creation: Starter template generator that auto-creates .gemini/GEMINI.md in trusted workspaces.
  • Session Logger: Non-blocking, buffered JSONL logger with daily rotation (30-day retention).
  • Memory Consolidator: LLM-powered background worker that extracts insights from recent session logs into GEMINI.md.
  • Integration: Wired into MemoryContextManager, SessionSummaryUtils, and local-executor agent loop.

Files Changed

  • packages/core/src/templates/geminiMdTemplate.ts (NEW)
  • packages/core/src/services/sessionLogTypes.ts (NEW)
  • packages/core/src/services/sessionLogger.ts (NEW)
  • packages/core/src/services/sessionLogger.test.ts (NEW)
  • packages/core/src/services/memoryConsolidator.ts (NEW)
  • packages/core/src/services/memoryConsolidator.test.ts (NEW)
  • packages/core/src/services/memoryConsolidationWorker.ts (NEW)
  • packages/core/src/services/sessionSummaryUtils.ts (MODIFIED)
  • packages/core/src/context/memoryContextManager.ts (MODIFIED)
  • packages/core/src/agents/local-executor.ts (MODIFIED)
  • packages/core/src/config/config.ts (MODIFIED)

Testing

  • 11 unit tests for SessionLogger (all passing).
  • 14 unit tests for MemoryConsolidator (all passing).
  • All modified files pass eslint with zero errors.

hackerxj2010 and others added 2 commits April 7, 2026 08:17
This commit implements the foundation for a persistent memory system
similar to Claude Code's memory feature:

- Add memory type taxonomy (user, feedback, project, reference)
- Implement MEMORY.md file discovery and loading
- Build session logging system with daily log files
- Create memory consolidation background process
- Integrate memory context into system prompt

Memory files are stored in ~/.gemini/projects/<project-slug>/memory/
and include:
- MEMORY.md: Index file pointing to memory files
- Individual memory files with frontmatter (name, description, type)

The consolidation worker runs automatically after sessions to keep
memories accurate and up-to-date.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@hackerxj2010 hackerxj2010 requested review from a team as code owners April 8, 2026 05:55
@google-cla
Copy link
Copy Markdown

google-cla bot commented Apr 8, 2026

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a persistent, hierarchical memory system for the Gemini CLI. By logging session activity and automatically consolidating insights into project-specific documentation, the system enables the agent to maintain long-term context across sessions. Additionally, a new background daemon service has been added to support proactive project monitoring and automated tasks, significantly enhancing the agent's ability to stay informed about project state and user preferences.

Highlights

  • Persistent Session Logging: Implemented a non-blocking, buffered JSONL logger that tracks session activity with daily rotation and 30-day retention.
  • Memory Consolidation System: Added a background worker that uses an LLM to extract insights from session logs and update project-level GEMINI.md files.
  • GEMINI.md Automation: Introduced a template generator that automatically creates a structured GEMINI.md file in trusted workspaces to store project context.
  • Daemon Service: Implemented a background daemon service capable of watching project files, performing periodic proactive tasks, and sending desktop notifications.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 8, 2026

🛑 Action Required: Evaluation Approval

Steering changes have been detected in this PR. To prevent regressions, a maintainer must approve the evaluation run before this PR can be merged.

Maintainers:

  1. Go to the Workflow Run Summary.
  2. Click the yellow 'Review deployments' button.
  3. Select the 'eval-gate' environment and click 'Approve'.

Once approved, the evaluation results will be posted here automatically.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a persistent memory system and a background daemon service for proactive project management. The memory system features daily session logging and an LLM-driven consolidation process that merges insights into GEMINI.md files using background worker threads. The daemon service monitors project files to detect TODOs, failing tests, and outdated dependencies, providing desktop notifications. Review feedback identifies critical command injection vulnerabilities in the notification service across macOS, Linux, and Windows. Additionally, improvements are suggested for daemon status tracking and the robust resolution of worker script paths and model versions.

Note: Security Review did not run due to the size of the PR.

Comment on lines +141 to +168
private async notifyMacOS(options: NotificationOptions): Promise<boolean> {
const { title, body, sound = true } = options;

// Escape quotes in the title and body
const escapedTitle = title.replace(/"/g, '\\"');
const escapedBody = body.replace(/"/g, '\\"');

// Use osascript for macOS notifications
const script = sound
? `display notification "${escapedBody}" with title "${escapedTitle}" sound name "default"`
: `display notification "${escapedBody}" with title "${escapedTitle}"`;

try {
const { exec } = await import('node:child_process');
return await new Promise((resolve) => {
exec(`osascript -e '${script}'`, (error) => {
if (error) {
debugLogger.error('[Notifier] macOS notification failed:', error);
resolve(false);
} else {
resolve(true);
}
});
});
} catch {
return false;
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-critical critical

The notifyMacOS method is vulnerable to command injection. The title and body strings are interpolated into a shell command string after only escaping double quotes. Since the entire script is wrapped in single quotes for the exec call, an attacker can break out of the single-quoted string by providing a title containing a single quote (e.g., '; touch /tmp/pwned; ').

Additionally, using child_process.exec is generally discouraged for executing commands with user-provided input as it spawns a shell. It is much safer to use child_process.execFile which accepts arguments as an array and does not invoke a shell.

Comment on lines +181 to +191
return await new Promise((resolve) => {
exec(`notify-send ${args.map(a => `"${a}"`).join(' ')}`, (error) => {
if (error) {
debugLogger.error('[Notifier] Linux notification failed:', error);
resolve(false);
} else {
resolve(true);
}
});
});
} catch {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-critical critical

The notifyLinux method is vulnerable to command injection because it joins arguments into a single string for child_process.exec. Shell metacharacters in the title or body (e.g., backticks or $(...)) will be evaluated by the system shell.

Refactor this to use execFile for safe argument passing.

      const { execFile } = await import('node:child_process');
      return await new Promise((resolve) => {
        execFile('notify-send', args, (error) => {
          if (error) {
            debugLogger.error('[Notifier] Linux notification failed:', error);
            resolve(false);
          } else {
            resolve(true);
          }
        });
      });

Comment on lines +196 to +240
private async notifyWindows(options: NotificationOptions): Promise<boolean> {
const { title, body } = options;

// Escape for PowerShell
const escapedTitle = title.replace(/'/g, "''");
const escapedBody = body.replace(/'/g, "''");

// Use PowerShell with Windows native toast notification
const psScript = `
[Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] | Out-Null
[Windows.Data.Xml.Dom.XmlDocument, Windows.Data.Xml.Dom.XmlDocument, ContentType = WindowsRuntime] | Out-Null

$template = @"
<toast>
<visual>
<binding template="ToastText02">
<text id="1">${escapedTitle}</text>
<text id="2">${escapedBody}</text>
</binding>
</visual>
</toast>
"@

$xml = New-Object Windows.Data.Xml.Dom.XmlDocument
$xml.LoadXml($template)
$toast = [Windows.UI.Notifications.ToastNotification]::new($xml)
[Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier("Gemini CLI").Show($toast)
`;

try {
const { exec } = await import('node:child_process');
return await new Promise((resolve) => {
exec(`powershell -Command "${psScript.replace(/\n/g, ' ')}"`, (error) => {
if (error) {
// Fallback to a simpler method
this.notifyWindowsFallback(title, body).then(resolve);
} else {
resolve(true);
}
});
});
} catch {
return this.notifyWindowsFallback(title, body);
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-critical critical

The notifyWindows method is vulnerable to command injection. The psScript is interpolated into a powershell -Command "..." string. If the title or body contains a double quote, it can break out of the PowerShell command string and execute arbitrary shell commands.

Use execFile to invoke powershell.exe and pass the script via the -EncodedCommand flag or as a direct argument without wrapping it in a shell-interpreted string.

Comment on lines +110 to +131
export async function getDaemonStatus(config: Config): Promise<DaemonStatus> {
// If we have a running instance, return its status
if (daemonInstance) {
return daemonInstance.getStatus();
}

// Otherwise, check if there's a daemon PID file
const tempInstance = new DaemonService({
projectRoot: config.getProjectRoot(),
configDir: config.storage.getProjectTempDir(),
});

// The status will reflect whether a daemon is running
return {
state: 'stopped',
pid: null,
uptime: null,
lastTick: null,
actionCount: 0,
watchCount: 0,
};
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The getDaemonStatus function incorrectly reports the daemon as 'stopped' if the local daemonInstance singleton is null. Instead of relying on the existence of the instance object, use an explicit state variable to track the daemon's lifecycle. When checking if the background process is alive, ensure the implementation follows repository guidelines for Windows by avoiding high-overhead checks and potentially assuming the process is alive to prevent performance degradation.

References
  1. When managing the state of asynchronous operations, rely on an explicit state variable (e.g., a state enum) rather than checking for the existence of a promise object.
  2. When checking if a process is alive on Windows, avoid solutions that introduce significant performance overhead, even if it means making assumptions (e.g., always returning true) to prevent performance degradation.

logDir: config.storage.getSessionLogDir(),
geminiMdPath: path.join(config.getProjectRoot(), 'GEMINI.md'),
apiKey,
model: 'gemini-2.5-flash',
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The model name gemini-2.5-flash appears to be a typo or refers to a non-existent version (the current latest is gemini-2.0-flash). Hardcoding this specific version string will cause the consolidation worker to fail if the model is not available in the Gemini API. It is better to use config.getActiveModel() or a constant like DEFAULT_GEMINI_FLASH_MODEL from the core configuration.

Suggested change
model: 'gemini-2.5-flash',
model: config.getActiveModel(),

try {
// Use import.meta.url for ESM compatibility
const { fileURLToPath } = await import('node:url');
const currentDir = path.dirname(fileURLToPath(import.meta.url));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Hardcoding the .js extension for the worker script is fragile. If the CLI is running in a development environment, the compiled .js file may not exist. Use a robust path resolution function like resolveToRealPath to handle both source and compiled files. Also, be aware that relative paths from a dist directory might require navigating up three levels (../../../) due to the project's build structure.

References
  1. Ensure consistent path resolution by using a single, robust function (e.g., resolveToRealPath) for all related path validations.
  2. Relative paths from a dist directory might require navigating up three levels (../../../) due to the project's build structure.

@gemini-cli gemini-cli bot added the status/need-issue Pull requests that need to have an associated issue. label Apr 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

status/need-issue Pull requests that need to have an associated issue.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant