feat(core): introduce MemoryProvider seam and MemoryService orchestrator

Refactors the existing background skill extractor into a layered memory
subsystem that GeminiClient can drive uniformly across interactive and
non-interactive sessions.

- Adds an internal `MemoryProvider` interface (not exported, not surfaced
  via extensions) as a typing seam between the orchestrator and its
  concrete implementation
- Refactors the existing skill-extraction logic into `DefaultMemoryProvider`,
  the sole implementation of the seam
- Introduces `MemoryService` as a thin orchestrator that owns a
  `DefaultMemoryProvider` and wraps every lifecycle call in try/catch so
  a buggy provider can't crash a turn
- Moves MemoryService ownership into `GeminiClient` (private field exposed
  via optional methods) so non-interactive entry points share the same
  lifecycle as the interactive UI
- Keeps builtin memory extraction asynchronous on startup to avoid
  blocking the first turn
- Adds unit tests for MemoryService, DefaultMemoryProvider, and the
  GeminiClient integration

The whole subsystem remains gated behind the existing
`isMemoryManagerEnabled()` experimental flag.
This commit is contained in:
Sandy Tao
2026-04-16 10:19:49 -07:00
parent 55620235c0
commit 366036a197
12 changed files with 2801 additions and 2068 deletions
+1
View File
@@ -529,6 +529,7 @@ export async function runNonInteractive(
// Cleanup stdin cancellation before other cleanup
cleanupStdinCancellation();
await config.getGeminiClient()?.shutdownSessionServices?.();
scheduler?.dispose();
consolePatcher.cleanup();
coreEvents.off(CoreEvent.UserFeedback, handleUserFeedback);
@@ -616,6 +616,7 @@ export async function runNonInteractive({
cleanupStdinCancellation();
abortController.signal.removeEventListener('abort', abortSession);
await config.getGeminiClient()?.shutdownSessionServices?.();
scheduler?.dispose();
consolePatcher.cleanup();
coreEvents.off(CoreEvent.UserFeedback, handleUserFeedback);
+1 -8
View File
@@ -92,7 +92,6 @@ import {
ApiKeyUpdatedEvent,
LegacyAgentProtocol,
type InjectionSource,
startMemoryService,
} from '@google/gemini-cli-core';
import { validateAuthMethod } from '../config/auth.js';
import process from 'node:process';
@@ -482,13 +481,6 @@ export const AppContainer = (props: AppContainerProps) => {
setConfigInitialized(true);
startupProfiler.flush(config);
// Fire-and-forget memory service (skill extraction from past sessions)
if (config.isMemoryManagerEnabled()) {
startMemoryService(config).catch((e) => {
debugLogger.error('Failed to start memory service:', e);
});
}
const sessionStartSource = resumedSessionData
? SessionStartSource.Resume
: SessionStartSource.Startup;
@@ -540,6 +532,7 @@ export const AppContainer = (props: AppContainerProps) => {
// Fire SessionEnd hook on cleanup (only if hooks are enabled)
await config?.getHookSystem()?.fireSessionEndEvent(SessionEndReason.Exit);
await config?.getGeminiClient()?.shutdownSessionServices?.();
};
registerCleanup(cleanupFn);