Make memory runtime-native for companion chat

Replace the old project-scoped file memory plumbing with runtime-native conversational memory and remove obsolete pi-memory-md shipping/wiring.

Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
Harivansh Rathi 2026-03-08 15:24:52 -07:00
parent 9765576c0a
commit 5c389efcf9
10 changed files with 1859 additions and 1536 deletions

View file

@ -61,6 +61,15 @@ import {
type ToolHtmlRenderer,
} from "./export-html/index.js";
import { createToolHtmlRenderer } from "./export-html/tool-renderer.js";
import {
RuntimeMemoryManager,
type RuntimeMemoryForgetInput,
type RuntimeMemoryRebuildResult,
type RuntimeMemoryRememberInput,
type RuntimeMemorySearchResult,
type RuntimeMemoryStatus,
type RuntimeMemoryRecord,
} from "./memory/runtime-memory.js";
import {
type ContextUsage,
type ExtensionCommandContextActions,
@ -337,6 +346,8 @@ export class AgentSession {
// Base system prompt (without extension appends) - used to apply fresh appends each turn
private _baseSystemPrompt = "";
private _memoryManager: RuntimeMemoryManager;
private _memoryWriteQueue: Promise<void> = Promise.resolve();
constructor(config: AgentSessionConfig) {
this.agent = config.agent;
@ -350,6 +361,10 @@ export class AgentSession {
this._extensionRunnerRef = config.extensionRunnerRef;
this._initialActiveToolNames = config.initialActiveToolNames;
this._baseToolsOverride = config.baseToolsOverride;
this._memoryManager = new RuntimeMemoryManager({
sessionManager: this.sessionManager,
settingsManager: this.settingsManager,
});
// Always subscribe to agent events for internal handling
// (session persistence, extensions, auto-compaction, retry logic)
@ -499,6 +514,16 @@ export class AgentSession {
this._resolveRetry();
}
}
if (event.message.role === "user" || event.message.role === "assistant") {
try {
this._memoryManager.recordMessage(event.message);
} catch (error) {
const message =
error instanceof Error ? error.message : String(error);
console.error(`[memory] episode write failed: ${message}`);
}
}
}
// Check auto-retry and auto-compaction after agent completes
@ -513,6 +538,10 @@ export class AgentSession {
}
await this._checkCompaction(msg);
if (msg.stopReason !== "error") {
this._enqueueMemoryPromotion(event.messages);
}
}
}
@ -667,6 +696,9 @@ export class AgentSession {
dispose(): void {
this._disconnectFromAgent();
this._eventListeners = [];
void this._memoryWriteQueue.finally(() => {
this._memoryManager.dispose();
});
}
// =========================================================================
@ -804,6 +836,82 @@ export class AgentSession {
return this._resourceLoader.getPrompts().prompts;
}
async transformRuntimeContext(
messages: AgentMessage[],
signal?: AbortSignal,
): Promise<AgentMessage[]> {
await this._awaitMemoryWrites();
return this._memoryManager.injectContext(messages, { signal });
}
async getMemoryStatus(): Promise<RuntimeMemoryStatus> {
await this._awaitMemoryWrites();
return this._memoryManager.getStatus();
}
async getCoreMemories(): Promise<RuntimeMemoryRecord[]> {
await this._awaitMemoryWrites();
return this._memoryManager.listCoreMemories();
}
async searchMemory(
query: string,
limit?: number,
): Promise<RuntimeMemorySearchResult> {
await this._awaitMemoryWrites();
return this._memoryManager.search(query, limit);
}
async rememberMemory(
input: RuntimeMemoryRememberInput,
): Promise<RuntimeMemoryRecord | null> {
await this._awaitMemoryWrites();
return this._memoryManager.remember(input);
}
async forgetMemory(
input: RuntimeMemoryForgetInput,
): Promise<{ ok: true; forgotten: boolean }> {
await this._awaitMemoryWrites();
return this._memoryManager.forget(input);
}
async rebuildMemory(): Promise<RuntimeMemoryRebuildResult> {
await this._awaitMemoryWrites();
return this._memoryManager.rebuild();
}
private async _awaitMemoryWrites(): Promise<void> {
try {
await this._memoryWriteQueue;
} catch {
// Memory writes are best-effort; failures should not block chat.
}
}
private _enqueueMemoryPromotion(messages: AgentMessage[]): void {
this._memoryWriteQueue = this._memoryWriteQueue
.catch(() => undefined)
.then(async () => {
if (!this.model) {
return;
}
const apiKey = await this._modelRegistry.getApiKey(this.model);
if (!apiKey) {
return;
}
await this._memoryManager.promoteTurn({
model: this.model,
apiKey,
messages,
});
})
.catch((error: unknown) => {
const message = error instanceof Error ? error.message : String(error);
console.error(`[memory] promotion failed: ${message}`);
});
}
private _normalizePromptSnippet(
text: string | undefined,
): string | undefined {