Skip to content

Implementing user-scoped memory#309942

Open
sneefyyy wants to merge 94 commits intomicrosoft:mainfrom
sneefyyy:jack/user-scoped-memories
Open

Implementing user-scoped memory#309942
sneefyyy wants to merge 94 commits intomicrosoft:mainfrom
sneefyyy:jack/user-scoped-memories

Conversation

@sneefyyy
Copy link
Copy Markdown

@sneefyyy sneefyyy commented Apr 14, 2026

User-Scoped Memories + Unified /prompt Endpoint Migration

What this PR does

This PR extends Copilot Memory (CAPI) support in four areas:

  1. User-scoped memory — adds a scope parameter to the store_memory tool so facts can be stored per-user rather than only per-repo
  2. Unified /prompt endpoint — migrates away from a bespoke memory fetch API to the shared /prompt endpoint, which returns context, store instructions, and tool schema in one call
  3. Turn-0 tool availability fixstore_memory was always missing from the tool list on the first turn of every conversation; this is now fixed
  4. Local memory tool coexistence — when MemoryToolEnabled and CopilotMemoryEnabled are both on, instructions and context for both storage systems are shown together rather than one replacing the other

Changes in detail

1. User-scoped memory

store_memory now accepts an optional scope field ('repo' | 'user', defaulting to 'repo'). Routing:

  • scope: 'repo'AgentMemoryService.storeRepoMemory()storeMemory({ scope: 'repository' })
  • scope: 'user'AgentMemoryService.storeUserMemory()storeMemory({ scope: 'user' })

The legacy memory tool also mirrors user create operations to CAPI via _syncUserMemoryCAPI() when the config flag is on. This is intentional one-way sync: the local memory tool remains the source of truth for in-session CRUD (view/str_replace/delete), and CAPI provides ambient context injection at conversation start via the /prompt response. The two systems are complementary.

2. Unified /prompt endpoint

Before: MemoryContextPrompt fetched repo memories via a separate getRepoMemories() call and formatted them locally. MemoryInstructionsPrompt used a large hardcoded <repoMemoryInstructions> block.

After: Both components read from AgentMemoryService.getMemoryPrompt(), which calls fetchMemoryPrompts() from @github/copilot-agentic-tools/memory. The response provides:

  • memoriesContext.prompt — server-formatted memory context, rendered in <memory_context> (replaces <repository_memories> and the local formatMemories() method)
  • storeInstructions.prompt — server-authored store guidance, rendered in <storeMemoryInstructions> (replaces the hardcoded <repoMemoryInstructions> block)
  • storeToolDefinition — server-provided schema version, used by StoreMemoryTool.alternativeDefinition() to resolve the correct Zod schema via resolveStoreMemorySchema(definitionVersion) and convert it to JSON Schema at runtime

This means Copilot Memory's instructions and context evolve server-side without requiring a client ship.

3. Turn-0 tool availability fix

Before: getAgentTools() called getCachedMemoryPrompt() synchronously. The cache was never populated at that point (it was primed later in buildPrompt()), so store_memory was excluded from the tool list on every first turn.

After: getAgentTools() awaits getMemoryPrompt() on a cache miss when the config is enabled, priming the cache itself. AgentMemoryCachePrimer.primeCache() (called from buildPrompt() on turn 0) then hits the already-warm cache and is a no-op — no duplicate network call.

// agentIntent.ts — getAgentTools()
const sessionId = getSessionIdFromToken(request.toolInvocationToken);
let cachedMemoryPrompt = agentMemoryService.getCachedMemoryPrompt(sessionId);
if (!cachedMemoryPrompt && configurationService.getExperimentBasedConfig(ConfigKey.CopilotMemoryEnabled, experimentationService)) {
    cachedMemoryPrompt = await agentMemoryService.getMemoryPrompt(undefined, sessionId);
}
allowTools[ToolName.StoreMemory] = !!cachedMemoryPrompt;

4. Local memory tool coexistence (memoryContextPrompt.tsx)

When both MemoryToolEnabled and CopilotMemoryEnabled are on, the model sees local repo files listed in <repoMemory> (from the local memory tool's storage). Previously, the instructions section only showed the CAPI description ("only create supported") with no guidance for those local files.

The scope description and guidelines now handle three cases explicitly:

Flags Scope description Guidelines
Local only Local repo storage, full CRUD JSON format, create/view usage
CAPI only CAPI-only, create supported Server storeMemoryInstructions
Both CAPI for new facts; use memory tool to view/edit existing local files Local tool guidelines (view/str_replace/delete/rename) alongside server storeMemoryInstructions

Caching architecture

The /prompt response is cached per session in AgentMemoryService._conversationMemoryCache: Map<sessionId, MemoryPromptResponse>. An _inflightPrompts map prevents duplicate network calls if the same session triggers concurrent fetches. Cache is cleared when the session is disposed via chatSessionService.onDidDisposeChatSession.

Turn-by-turn flow:

Turn 0:
  getAgentTools()                              [awaited before buildPrompt]
    → getCachedMemoryPrompt(sessionId)         → miss
    → await getMemoryPrompt(sessionId)         → network fetch, cache populated
    → allowTools[StoreMemory] = true
  buildPrompt()
    → agentMemoryCachePrimer.primeCache(sessionId)  → cache hit, no-op
    → render()
      → MemoryContextPrompt                    → getCachedMemoryPrompt() → hit
      → MemoryInstructionsPrompt               → getCachedMemoryPrompt() → hit

Turn N (all hits):
  getAgentTools() → getCachedMemoryPrompt() → hit
  buildPrompt()   → render()               → hit

Session end:
  onDidDisposeChatSession(sessionId) → clearCache(sessionId)

AgentMemoryCachePrimer is a cache-warming service, not a VS Code tool registrar. It has no tool registration state to clean up when the config is disabled. Tool availability is re-evaluated each turn in getAgentTools() by reading the config flag at call time.


Other changes

  • getSessionIdFromToken helper — replaced two duplicate (x as any)?.sessionResource extractions in agentIntent.ts with a single typed helper, reducing the as any surface to one place
  • History gate fixpromptContext.history?.length === 0 would silently skip cache priming when history is undefined; corrected to !promptContext.history || promptContext.history.length === 0
  • Cache stampede guard_inflightPrompts map in AgentMemoryService deduplicates concurrent calls for the same session; previously two simultaneous callers would each make a full network round trip
  • Rename: AgentMemoryToolRegistrarAgentMemoryCachePrimer — the old name implied VS Code tool registration; the class only primes the prompt cache
  • Safe error handlingerror.message in two memoryTool.tsx catch blocks narrowed to error instanceof Error ? error.message : String(error)

What is NOT in this PR

  • vote_memory / vote_memory tool: Not implemented. The IAgentMemoryService interface has no voteOnMemory method and no vote_memory tool is registered anywhere.
  • CAPI read/list/delete: The memory tool's view, str_replace, delete, and rename operations for user-scoped paths remain local-only. CAPI memories surface as ambient context at conversation start via memoriesContext.prompt; they are not a CRUD-accessible store in this PR.
  • definitionVersion runtime assertion: If the shared package and server ever drift on schema version, the client will silently use the wrong schema. A runtime check is a recommended follow-up.

Tests

File What it covers
agentMemoryService.spec.ts storeRepoMemory, storeUserMemory, getMemoryPrompt (cache hit / miss / clear / concurrent), getBaseUrl, config guard, error handling
storeMemoryTool.spec.tsx Scope routing (repo / user / default), payload passthrough, failure handling, alternativeDefinition (empty cache, missing storeToolDefinition, schema override)
memoryTool.spec.tsx Repo CAPI path (_dispatchRepoCAPI), user CAPI sync (_syncUserMemoryCAPI) for JSON and plain-text formats, session-scope exclusion, telemetry

Tested locally, and was able to store memories using store_memory
Screenshot 2026-04-21 at 2 38 51 PM

Then after asking it to remember a few things about me ->

image image

Copilot AI review requested due to automatic review settings April 14, 2026 19:02
@sneefyyy sneefyyy changed the title porting changes from old vscode-copilot-chat repo Implementing user-scoped memory Apr 14, 2026
@sneefyyy
Copy link
Copy Markdown
Author

@microsoft-github-policy-service agree company="Microsoft"

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Ports the user-scoped Copilot Memory feature into vscode/extensions/copilot by switching memory operations to @github/copilot-agentic-tools/memory and dynamically registering memory tools from the /prompt endpoint.

Changes:

  • Rewrites AgentMemoryService to use the shared memory client library and adds user-memory/voting/prompt APIs.
  • Adds dynamic tool registration (AgentMemoryToolRegistrar) plus new LLM tools (store_memory, optional vote_memory).
  • Updates prompt rendering and memory tool routing to support unified /prompt output and user-scoped CAPI creates, with test mock adjustments.
Show a summary per file
File Description
extensions/copilot/src/extension/tools/common/agentMemoryService.ts Replaces hand-rolled CAPI calls with @github/copilot-agentic-tools/memory and expands the service API.
extensions/copilot/src/extension/tools/node/agentMemoryToolRegistrar.ts Fetches /prompt and registers model-specific memory tools based on server definitions.
extensions/copilot/src/extension/tools/node/storeMemoryTool.tsx Implements store_memory tool and builds JSON schema based on server definition version.
extensions/copilot/src/extension/tools/node/voteMemoryTool.tsx Implements optional vote_memory tool and its tool definition schema.
extensions/copilot/src/extension/tools/node/memoryContextPrompt.tsx Renders unified <memory_context> from /prompt, registers tools, and falls back to legacy repo memories.
extensions/copilot/src/extension/tools/node/memoryTool.tsx Routes user-scope creates to CAPI when enabled; normalizes citations at parse time.
extensions/copilot/src/extension/tools/node/test/memoryTool.spec.tsx Updates mocks to satisfy the expanded IAgentMemoryService interface.
extensions/copilot/src/extension/extension/vscode-node/services.ts Registers the new IAgentMemoryToolRegistrar service.
extensions/copilot/package.json Adds local workspace dependency on @github/copilot-agentic-tools.

Copilot's findings

  • Files reviewed: 9/9 changed files
  • Comments generated: 6

Comment on lines +83 to +90
if (enableCopilotMemory) {
const repoNwo = await this.getRepoNwo();
// Fetch prompt and register tools in parallel — both depend on the same /prompt call
// but registerMemoryTools() makes its own call so they can run concurrently.
const [promptResponse] = await Promise.all([
this.agentMemoryService.getMemoryPrompt(repoNwo),
this.agentMemoryToolRegistrar.registerMemoryTools(),
]);
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

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

getMemoryPrompt(repoNwo) and registerMemoryTools() both fetch /prompt, so this does two network calls on every render when Copilot Memory is enabled. Consider refactoring so the /prompt response is fetched once and reused for both prompt rendering and tool registration (e.g., change registerMemoryTools to accept a MemoryPromptResponse, or add a fetchAndRegisterMemoryTools(repoNwo) that returns the fetched response).

Copilot uses AI. Check for mistakes.
}
} catch (error) {
this.logService.error('[MemoryTool] Error creating user memory:', error);
return { text: `Error: Cannot create user memory: ${error.message}`, outcome: 'error' };
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

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

In TS configs that enable useUnknownInCatchVariables, error is unknown and accessing error.message is a type error (and may also be unsafe at runtime). Align with the rest of the file by narrowing (error instanceof Error ? error.message : String(error)) or by interpolating String(error).

Suggested change
return { text: `Error: Cannot create user memory: ${error.message}`, outcome: 'error' };
const errorMessage = error instanceof Error ? error.message : String(error);
return { text: `Error: Cannot create user memory: ${errorMessage}`, outcome: 'error' };

Copilot uses AI. Check for mistakes.
const { fact, direction, reason } = options.input;

try {
// Vote on both scopes; the service will route to the right endpoint
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

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

The comment says this votes on both scopes, but the implementation always uses 'repository'. Either update the comment to match the behavior, or implement the intended behavior (e.g., route scope based on server definition / memory origin, or attempt repository and then user if appropriate).

Suggested change
// Vote on both scopes; the service will route to the right endpoint
// Vote on the repository-scoped memory entry.

Copilot uses AI. Check for mistakes.
description: "Vote direction: 'upvote' for useful verified memories, 'downvote' for incorrect or outdated memories.",
},
reason: {
type: 'string',
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

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

Other schema description strings in this file are guarded with // eslint-disable-next-line local/no-unexternalized-strings, but this one is not. If the repo enforces local/no-unexternalized-strings for JSON schema descriptions, this will fail lint; apply the same disable (or externalize the string) consistently.

Suggested change
type: 'string',
type: 'string',
// eslint-disable-next-line local/no-unexternalized-strings

Copilot uses AI. Check for mistakes.
private makeLogger() {
return {
info: (msg: string) => this.logService.info(`[AgentMemoryService] ${msg}`),
error: (msg: string) => this.logService.warn(`[AgentMemoryService] ${msg}`),
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

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

The error logger maps to logService.warn, which can downgrade severity and make operational triage harder (especially if the underlying library uses logger.error for failures). Map error to this.logService.error(...) (or rename the field if you intentionally want warn-level behavior).

Suggested change
error: (msg: string) => this.logService.warn(`[AgentMemoryService] ${msg}`),
error: (msg: string) => this.logService.error(`[AgentMemoryService] ${msg}`),

Copilot uses AI. Check for mistakes.
Comment on lines +66 to +77
async registerMemoryTools(): Promise<void> {
const enabled = this.configurationService.getExperimentBasedConfig(ConfigKey.CopilotMemoryEnabled, this.experimentationService);
if (!enabled) {
return;
}

const repoNwo = await this.getRepoNwo();
const promptResponse = await this.agentMemoryService.getMemoryPrompt(repoNwo);
if (!promptResponse) {
this.logService.warn('[AgentMemoryToolRegistrar] Could not fetch memory prompt — skipping tool registration');
return;
}
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

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

This introduces new behavior (dynamic tool registration driven by /prompt, including optional vote tool). Consider adding unit tests to cover: (1) registering store_memory using storeToolDefinition ?? toolDefinition, (2) conditional registration of vote_memory when voteToolDefinition is present, and (3) disposal/re-registration behavior via _registrations.clear().

Copilot uses AI. Check for mistakes.
@sneefyyy sneefyyy requested a review from Copilot April 14, 2026 19:28
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Ports user-scoped “Copilot Memory” support into vscode/extensions/copilot by switching to @github/copilot-agentic-tools/memory and adding dynamic tool registration driven by the /prompt endpoint.

Changes:

  • Rewrite AgentMemoryService to use shared memory client APIs (fetchRecentMemories, storeMemory, voteMemory, fetchMemoryPrompts) and add user-scoped methods.
  • Add dynamic model-specific tool registration (store_memory, optional vote_memory) and implement the corresponding tool handlers.
  • Update prompt rendering to prefer unified /prompt content and update tests/mocks for the expanded memory service interface.
Show a summary per file
File Description
extensions/copilot/src/extension/tools/node/voteMemoryTool.tsx Adds vote_memory tool implementation and a tool definition builder.
extensions/copilot/src/extension/tools/node/storeMemoryTool.tsx Adds store_memory tool implementation and schema building via definition version.
extensions/copilot/src/extension/tools/node/agentMemoryToolRegistrar.ts Registers memory tools dynamically based on /prompt response; manages registrations.
extensions/copilot/src/extension/tools/node/memoryContextPrompt.tsx Fetches unified memory prompt content and registers memory tools during prompt render.
extensions/copilot/src/extension/tools/node/memoryTool.tsx Routes user-scope creates through CAPI when enabled; normalizes citations parsing.
extensions/copilot/src/extension/tools/common/agentMemoryService.ts Migrates CAPI calls to the shared memory client library; adds user store/vote/prompt APIs.
extensions/copilot/src/extension/tools/node/test/memoryTool.spec.tsx Updates mocks to satisfy expanded memory service interface/types.
extensions/copilot/src/extension/extension/vscode-node/services.ts Registers the new IAgentMemoryToolRegistrar service.
extensions/copilot/package.json Adds local workspace dependency on @github/copilot-agentic-tools.

Copilot's findings

  • Files reviewed: 9/9 changed files
  • Comments generated: 10

Comment on lines +84 to +90
const repoNwo = await this.getRepoNwo();
// Fetch prompt and register tools in parallel — both depend on the same /prompt call
// but registerMemoryTools() makes its own call so they can run concurrently.
const [promptResponse] = await Promise.all([
this.agentMemoryService.getMemoryPrompt(repoNwo),
this.agentMemoryToolRegistrar.registerMemoryTools(),
]);
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

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

This reliably triggers two /prompt fetches per render when Copilot Memory is enabled: one in getMemoryPrompt() here and a second inside registerMemoryTools(). Consider changing the registrar API to accept the already-fetched promptResponse (or return tool defs from getMemoryPrompt) so the response is fetched once and reused.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

fix

Comment on lines +295 to +303
// Route user-scope creates through CAPI when enabled
const capiEnabled = await this.agentMemoryService.checkMemoryEnabled();
if (capiEnabled && params.command === 'create') {
const result = await this._userCreate(params as ICreateParams);
this._sendLocalTelemetry(params.command, 'user', result.outcome, requestId, model);
return result.text;
}

// Fall back to local file-based user memory
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

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

When CAPI memory is enabled, only create is routed to user-scoped cloud storage, while read/list/delete still operate on local file storage. This makes behavior inconsistent (e.g., a created cloud memory won’t be visible to subsequent local list/read). Either route the other user-scope commands through the same CAPI-backed storage when enabled, or explicitly block/redirect those commands with a clear message to avoid silently diverging stores.

Suggested change
// Route user-scope creates through CAPI when enabled
const capiEnabled = await this.agentMemoryService.checkMemoryEnabled();
if (capiEnabled && params.command === 'create') {
const result = await this._userCreate(params as ICreateParams);
this._sendLocalTelemetry(params.command, 'user', result.outcome, requestId, model);
return result.text;
}
// Fall back to local file-based user memory
// Route user-scope memory through CAPI when enabled
const capiEnabled = await this.agentMemoryService.checkMemoryEnabled();
if (capiEnabled) {
if (params.command === 'create') {
const result = await this._userCreate(params as ICreateParams);
this._sendLocalTelemetry(params.command, 'user', result.outcome, requestId, model);
return result.text;
}
const result: MemoryToolResult = {
text: `Error: The '${params.command}' operation is not supported for user memories while CAPI memory is enabled. Only 'create' is allowed to avoid mixing cloud-backed and local user memory stores.`,
outcome: 'error'
};
this._sendLocalTelemetry(params.command, 'user', result.outcome, requestId, model);
return result.text;
}
// Fall back to local file-based user memory when CAPI is disabled

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

look into why the local one even exists

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

and make sure it already existed before


const success = await this.agentMemoryService.storeUserMemory(entry);
if (success) {
return { text: `File created successfully at: ${params.path}`, outcome: 'success' };
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

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

In the CAPI-enabled path, no file is created at params.path; the memory is stored remotely. Returning “File created successfully at …” is misleading. Adjust the success message to reflect the actual outcome (e.g., that a user memory was stored), or ensure a file is actually created if the tool contract requires it.

Suggested change
return { text: `File created successfully at: ${params.path}`, outcome: 'success' };
return { text: 'User memory stored successfully.', outcome: 'success' };

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

fix, and look into it

}

private getBaseUrl(): string {
return this.capiClientService.capiPingURL.replace(/\/_ping$/, '');
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

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

checkMemoryEnabled() now uses raw fetch and manually derives the base URL from capiPingURL. This bypasses ICAPIClientService behaviors (proxy handling, agent configuration, shared headers/telemetry, retries/timeouts), and replace(/\\/_ping$/, '') assumes a specific ping URL shape. Prefer using the same shared client/library approach for enablement checks (or route through capiClientService) to keep networking behavior consistent and robust.

Suggested change
return this.capiClientService.capiPingURL.replace(/\/_ping$/, '');
const pingUrl = new URL(this.capiClientService.capiPingURL);
const baseUrl = new URL('.', pingUrl);
return baseUrl.href.endsWith('/') ? baseUrl.href.slice(0, -1) : baseUrl.href;

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

implement this change, but also change the package

Comment on lines 210 to 220
const [owner, repo] = repoNwo.split('/');
const baseUrl = this.getBaseUrl();
const url = `${baseUrl}/agents/swe/internal/memory/v0/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/enabled`;

const response = await fetch(url, {
method: 'GET',
headers: {
'Authorization': `Bearer ${session.accessToken}`
}
}, {
type: RequestType.CopilotAgentMemory,
repo: repoNwo,
action: 'enabled'
'Authorization': `Bearer ${token}`,
'Copilot-Integration-Id': INTEGRATION_ID,
},
});
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

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

checkMemoryEnabled() now uses raw fetch and manually derives the base URL from capiPingURL. This bypasses ICAPIClientService behaviors (proxy handling, agent configuration, shared headers/telemetry, retries/timeouts), and replace(/\\/_ping$/, '') assumes a specific ping URL shape. Prefer using the same shared client/library approach for enablement checks (or route through capiClientService) to keep networking behavior consistent and robust.

Copilot uses AI. Check for mistakes.
Comment on lines +48 to +81
export function buildVoteMemoryToolDefinition(
name: string,
description: string,
): vscode.LanguageModelToolDefinition {
return {
name,
displayName: 'Vote on Memory',
description,
tags: [],
source: undefined,
inputSchema: {
type: 'object',
properties: {
fact: {
type: 'string',
// eslint-disable-next-line local/no-unexternalized-strings
description: "The exact text of the 'fact' field from the original memory. Must match the string exactly as it appears in the prompt.",
},
direction: {
type: 'string',
enum: ['upvote', 'downvote'],
// eslint-disable-next-line local/no-unexternalized-strings
description: "Vote direction: 'upvote' for useful verified memories, 'downvote' for incorrect or outdated memories.",
},
reason: {
type: 'string',
// eslint-disable-next-line local/no-unexternalized-strings
description: 'A clear and detailed explanation of the reason for your vote. Must be at least 2-3 sentences long.',
},
},
required: ['fact', 'direction', 'reason'],
},
};
}
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

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

PR description says tool definition/schema is returned by the server /prompt endpoint, but this builder hardcodes the vote tool input schema locally (only name/description are server-derived). If the server schema evolves, clients may register an incompatible schema. Consider consuming and passing through the server-provided input schema for vote_memory rather than hardcoding it.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

perhaps outdated, check to see if it uses agentic tools

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

The server's /prompt response (ToolDefinition) only provides name, description, and definitionVersion. It doesn't include an input schema. The input schema here mirrors voteMemoryInputSchema from @github/copilot-agentic-tools/memory, which is the source of truth. The duplication is a known trade-off since VS Code's LanguageModelToolDefinition.inputSchema requires a plain JSON Schema object and we don't have zod-to-json-schema available to derive it automatically.

Comment on lines +69 to +85
export function buildStoreMemoryToolDefinition(
name: string,
description: string,
definitionVersion: string,
): vscode.LanguageModelToolDefinition {
const schema = resolveStoreMemorySchema(definitionVersion);
const jsonSchema = zodSchemaToJsonSchema(schema);

return {
name,
displayName: 'Store Memory',
description,
tags: [],
source: undefined,
inputSchema: jsonSchema,
};
}
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

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

Similar to the vote tool: the PR description indicates the server returns the tool schema, but here the schema is derived locally via definitionVersion + a minimal Zod→JSON-schema adapter. If the server starts returning richer/changed schemas, this may drift. If /prompt already provides a JSON schema, prefer using it directly (and reduce the amount of schema logic maintained client-side).

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

why are we converting zod schemas to json?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Same thing as above, needs to be in JSON

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Server doesn't provide a JSON schema, so this isn't actionable here right no

Comment on lines +44 to +64
private async getRepoNwo(): Promise<string | undefined> {
try {
const workspaceFolders = this.workspaceService.getWorkspaceFolders();
if (!workspaceFolders || workspaceFolders.length === 0) {
return undefined;
}
const repo = await this.gitService.getRepository(workspaceFolders[0]);
if (!repo) {
return undefined;
}
for (const remoteUrl of getOrderedRemoteUrlsFromContext(repo)) {
const repoId = getGithubRepoIdFromFetchUrl(remoteUrl);
if (repoId) {
return toGithubNwo(repoId);
}
}
return undefined;
} catch {
return undefined;
}
}
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

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

This getRepoNwo() implementation is duplicated in multiple places in the diff (AgentMemoryService, MemoryContextPrompt, AgentMemoryToolRegistrar). Duplicated repo detection logic is prone to divergence; consider extracting a shared helper (or exposing a single service method) so URL parsing and folder selection behavior stays consistent.

Copilot uses AI. Check for mistakes.
}
} catch (error) {
this.logService.error('[VoteMemoryTool] Error voting on memory:', error);
return new LanguageModelToolResult([new LanguageModelTextPart(`Error voting on memory: ${error}`)]);
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

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

Interpolating ${error} often yields [object Object] and can leak overly verbose details. Prefer formatting as error instanceof Error ? error.message : String(error) (and optionally include a stable, actionable message for the model).

Suggested change
return new LanguageModelToolResult([new LanguageModelTextPart(`Error voting on memory: ${error}`)]);
const errorMessage = error instanceof Error ? error.message : String(error);
return new LanguageModelToolResult([new LanguageModelTextPart(`Error voting on memory: ${errorMessage}`)]);

Copilot uses AI. Check for mistakes.
@sneefyyy
Copy link
Copy Markdown
Author

look at fallbacks and make sure that no new fallbacks were created, sometimes it is just unreachable

@aiday-mar aiday-mar requested a review from bhavyaus April 16, 2026 10:27
@aiday-mar aiday-mar removed their assignment Apr 16, 2026
* @deprecated Use StoreMemoryRequest from @github/copilot-agentic-tools/memory directly.
*/
export interface RepoMemoryEntry {
export interface UserMemoryEntry {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Once Audree's PR is merged and used here, we should check on interface definations for combined user and repo entry - making the swap like this does not look right.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Agreed, this doesn't look right, especially since there shouldn't be a difference between UserMemoryEntry and RepoMemoryEntry (they contain the same fields).

}

/**
* Type guard to validate if an object is a valid RepoMemoryEntry.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

please undo the formatting and use the repo defaults

Comment thread extensions/copilot/package.json Outdated
"@anthropic-ai/sdk": "^0.82.0",
"@github/blackbird-external-ingest-utils": "^0.3.0",
"@github/copilot": "^1.0.24",
"@github/copilot-agentic-tools": "file:../../../copilot-agentic-tools",
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

this is incorrect

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

stand in, until Audree's PR is merged, I can remove it

* Implements the vote_memory tool using @github/copilot-agentic-tools/memory.
* Only registered when voteToolDefinition is present in the /prompt response.
*/
export class VoteMemoryTool implements ICopilotModelSpecificTool<VoteMemoryInput> {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

what is this tool helping with? we should not introduce this if copilot memory is not enabled.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

was a fragment of me porting over from an old repo and didn't specify to copilot correctly, removing it

@sneefyyy sneefyyy marked this pull request as draft April 16, 2026 15:37
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Ports the user-scoped memory integration into extensions/copilot by replacing the previous CAPI request approach with the shared @github/copilot-agentic-tools/memory client, and wiring prompt/tooling changes to support the new unified /prompt flow.

Changes:

  • Reworked AgentMemoryService to use @github/copilot-agentic-tools/memory for enablement checks, fetching recent memories, storing memories, and fetching the unified prompt response.
  • Added a new AgentMemoryToolRegistrar and StoreMemoryTool to dynamically register the store_memory model-specific tool based on the /prompt response.
  • Updated MemoryContextPrompt and MemoryTool to consume the unified prompt (with fallback) and normalize citations handling.
Show a summary per file
File Description
extensions/copilot/src/extension/tools/node/test/memoryTool.spec.tsx Updates mock IAgentMemoryService implementation for the expanded memory API surface.
extensions/copilot/src/extension/tools/node/storeMemoryTool.tsx Introduces the store_memory tool implementation and a minimal schema builder.
extensions/copilot/src/extension/tools/node/memoryTool.tsx Adjusts repository-memory create path parsing/citations normalization and success messaging.
extensions/copilot/src/extension/tools/node/memoryContextPrompt.tsx Uses unified /prompt response (<memory_context>) with fallback to legacy repo memory rendering.
extensions/copilot/src/extension/tools/node/agentMemoryToolRegistrar.ts Adds a service that registers model-specific memory tools from /prompt response definitions.
extensions/copilot/src/extension/tools/common/agentMemoryService.ts Replaces request-type based CAPI calls with @github/copilot-agentic-tools/memory APIs and adds /prompt support.
extensions/copilot/src/extension/extension/vscode-node/services.ts Registers IAgentMemoryToolRegistrar in the extension service container.
extensions/copilot/package.json Adds the new @github/copilot-agentic-tools dependency.

Copilot's findings

Comments suppressed due to low confidence (1)

extensions/copilot/src/extension/tools/node/memoryTool.tsx:658

  • _localCreate() writes a file to the filesystem, but the success message was changed to "User memory stored successfully." This is inaccurate for session/user local memory operations and will fail the unit tests that assert "File created successfully at: ...". Please restore a file-create success message for local storage paths (only the CAPI-backed repo path should avoid file wording).
			const content = new TextEncoder().encode(params.file_text);
			await this.fileSystemService.writeFile(uri, content);
			if (scope === 'session') {
				this.memoryCleanupService.markAccessed(uri);
			}
			this.logService.debug(`[MemoryTool] Created memory file: ${params.path}`);
			return { text: 'User memory stored successfully.', outcome: 'success'};
		} catch (error) {
  • Files reviewed: 8/8 changed files
  • Comments generated: 6

Comment on lines +17 to +24
/**
* Register store_memory (and optionally vote_memory) as model-specific tools using an
* already-fetched prompt response. When called without a response, fetches the /prompt
* endpoint itself. Safe to call multiple times — re-registers on each call to pick up
* updated tool definitions from the server.
*/
registerMemoryTools(promptResponse?: MemoryPromptResponse): Promise<void>;
}
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

The JSDoc says this registrar will register store_memory "(and optionally vote_memory)", and the PR description mentions vote_memory, but this implementation only registers store_memory. Either implement vote_memory registration based on the /prompt response (and add the tool), or remove/update the comment/PR description so behavior matches what's shipped.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Same thing--let's just update the PR description

Comment thread extensions/copilot/package.json Outdated
"@anthropic-ai/sdk": "^0.82.0",
"@github/blackbird-external-ingest-utils": "^0.3.0",
"@github/copilot": "^1.0.24",
"@github/copilot-agentic-tools": "file:../../../copilot-agentic-tools",
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

Adding "@github/copilot-agentic-tools" as a file:../../../copilot-agentic-tools dependency points outside this repository, which will break clean installs/CI checkouts (the target directory isn't present here). It also leaves extensions/copilot/package-lock.json out of sync (no entry for this package). Please switch to a published/workspace-managed dependency that exists in CI (or vendor it into the repo) and update the lockfile accordingly.

Suggested change
"@github/copilot-agentic-tools": "file:../../../copilot-agentic-tools",
"@github/copilot-agentic-tools": "^1.0.0",

Copilot uses AI. Check for mistakes.
const success = await this.agentMemoryService.storeRepoMemory(entry);
if (success) {
return { text: `File created successfully at: ${params.path}`, outcome: 'success' };
return { text: 'User memory stored successfully.', outcome: 'success'};
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

In the /memories/repo/ CAPI path, this command stores a repository-scoped memory via storeRepoMemory(), but the success text says "User memory stored successfully." This is misleading (and breaks the existing tests that look for the repo create message). Please return a repo-scoped success message (and/or include the path/subject), distinct from user memory wording.

This issue also appears on line 651 of the same file.

Suggested change
return { text: 'User memory stored successfully.', outcome: 'success'};
return { text: l10n.t("Repository memory stored successfully for \"{0}\".", entry.subject), outcome: 'success' };

Copilot uses AI. Check for mistakes.
Comment on lines 223 to 229
private formatMemories(memories: MemoryResponse[]): string {
return memories.map(m => {
const lines = [`**${m.subject}**`, `- Fact: ${m.fact}`];

// Format citations (handle both string and string[] formats)
if (m.citations) {
const citationsArray = normalizeCitations(m.citations) ?? [];
if (citationsArray.length > 0) {
lines.push(`- Citations: ${citationsArray.join(', ')}`);
}
if (m.citations.length > 0) {
lines.push(`- Citations: ${m.citations.join(', ')}`);
}
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

MemoryResponse.citations is treated as always-present (m.citations.length), but the service/type guard still allows citations to be undefined/legacy. This can throw at runtime when formatting telemetry/context. Please guard with optional chaining or default to an empty array before reading .length/join().

See below for a potential fix:

			const citations = m.citations ?? [];

			if (citations.length > 0) {
				lines.push(`- Citations: ${citations.join(', ')}`);

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

believe this should be fixed, double check

Comment on lines 56 to 88
@@ -61,45 +88,47 @@ export function isRepoMemoryEntry(obj: unknown): obj is RepoMemoryEntry {
return true;
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

isRepoMemoryEntry() is declared as a type guard for MemoryResponse, but it currently accepts legacy shapes like citations: string and category. That makes the guard unsound: callers can treat the result as MemoryResponse and do m.citations.length (as in MemoryContextPrompt), leading to runtime errors. Either (1) tighten the guard to only accept the actual MemoryResponse shape (e.g., citations as string[]/default []), or (2) change the guard return type to a legacy/union type and normalize before returning MemoryResponse.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I am not sure if this needs to be changed

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Before this PR, VSCode was calling the CAPI API directly and getting back raw JSON. This function was a runtime check that said, "does this blob of JSON actually look like a memory object?" Now that we're using the package, it makes the API call and returns a typed MemoryResponse, and that validation is happening within the package. So we shouldn't need this

Comment on lines +59 to +60
this.logService.error('[StoreMemoryTool] Error storing memory:', error);
return new LanguageModelToolResult([new LanguageModelTextPart(`Error storing memory: ${error}`)]);
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

ILogService.error expects the Error (or error string) as the first argument and an optional string context message as the second. Here it's called with a string first and the caught error second, which is both type-incorrect and loses structured error details. Also, returning the raw ${error} to the model/user can leak internal details. Please log as error-first with a stable context message, and return a generic failure message (optionally include a correlation id).

Suggested change
this.logService.error('[StoreMemoryTool] Error storing memory:', error);
return new LanguageModelToolResult([new LanguageModelTextPart(`Error storing memory: ${error}`)]);
const correlationId = `store-memory-${Date.now()}`;
const errorForLog = error instanceof Error ? error : String(error);
this.logService.error(errorForLog, `[StoreMemoryTool] Error storing memory. CorrelationId: ${correlationId}`);
return new LanguageModelToolResult([new LanguageModelTextPart(`Failed to store memory. Please try again later. Reference ID: ${correlationId}`)]);

Copilot uses AI. Check for mistakes.
confirmation = this.forward(this._wrapped.confirmation.bind(this._wrapped));
warning = this.forward(this._wrapped.warning.bind(this._wrapped));
info = this.forward(this._wrapped.info.bind(this._wrapped));
info = this.forward(this._wrapped.info?.bind(this._wrapped) || (() => { }));
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Is it related to our changes? I'd say if it's not directly related to or necessary for shipping the user memory capability, I'd remove it from the PR. My opinion is that, especially when working in code that we're not the owners of, we should have the lightest touch possible


const cachedMemoryPrompt = agentMemoryService.getCachedMemoryPrompt();
if (!cachedMemoryPrompt && configurationService.getExperimentBasedConfig(ConfigKey.CopilotMemoryEnabled, experimentationService)) {
logService.debug('[getAgentTools] CopilotMemory is enabled but cache is not primed yet — StoreMemory tool will be disabled this turn');
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

So what's happening here is a timing issue: getAgentTools() runs before buildPrompt(), but the cache doesn't get primed until buildPrompt() calls registerMemoryTools() on turn 0 (when history.length === 0). That means on the
very first turn of every conversation, getCachedMemoryPrompt() will always return undefined here, and store_memory will be excluded from the tool list.

As a general pattern, logging an expected failure instead of fixing it is an anti-pattern. If we know the cache won't be ready here, the right move is to fix the ordering, not log around it.

A couple of ways to fix this:

  • Prime the cache earlier (e.g., at session creation) so it's ready by the time getAgentTools() runs
  • Allow the tool unconditionally when the config flag is on and have it fetch on-demand if the cache misses
  • Await priming here if the cache isn't populated yet

allowTools[ToolName.MultiReplaceString] = true;
}

const cachedMemoryPrompt = agentMemoryService.getCachedMemoryPrompt();
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

See comment below, that this might not be the right approach. But, more generally, getCachedMemoryPrompt() is being called here without a sessionId, which means it falls back to returning the first entry in the Map — that's non-deterministic when multiple sessions are active and could return the prompt/schema from a completely different conversation. Since request is available in this function, you should be able to extract and pass a sessionId here (and everywhere you're calling this function).

import { describe, expect, it } from 'vitest';
import { isRepoMemoryEntry, normalizeCitations } from '../agentMemoryService';

describe('AgentMemoryService', () => {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The old test file covered isRepoMemoryEntry and normalizeCitations, and since those helpers are gone, deleting their tests makes sense. But the new service has significant new behavior that needs coverage:

  • Cache lifecycle: getMemoryPrompt() cache hit/miss, session-scoped keying, getCachedMemoryPrompt() with and without sessionId
  • Cache cleanup: clearCache(sessionId) via onDidDisposeChatSession
  • storeUserMemory()/ storeRepoMemory(): success, failure, and missing auth token paths

baseUrl: this.getBaseUrl(),
agent: 'vscode',
logger: this.makeLogger(),
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Can we add the base_model if available here and for storing repo memories? See https://github.com/github/copilot-agentic-tools/blob/main/src/memory/api.ts#L241)

Note: Only `create` is supported for `/memories/repo/` paths.<br />
If the user asks how to view or manage their repo memories refer them to https://docs.github.com/en/copilot/how-tos/use-copilot-agents/copilot-memory.<br />
{storeInstructionsPrompt && (
<Tag name='storeMemoryInstructions'>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

You're replacing the entire hardcoded repo memory instructions block with the server-provided storeInstructionsPrompt. The issue is that the server has no awareness of the local memory tool, so when enableCopilotMemory is true, all guidance about local repo memory files (/memories/repo/) disappears from the prompt.

The MemoryContextPrompt above still shows local repo files in context (the L85 fix is good!), but without any instructions here, the model sees those files with no guidance on what they are or how to interact with them.

Could we keep the local repo memory instructions alongside the server-provided ones when both flags are on? Something like rendering the local instructions when enableMemoryTool is true, and the server instructions when enableCopilotMemory is true, since they're not mutually exclusive.


constructor(
@ILogService private readonly logService: ILogService
@ILogService protected readonly logService: ILogService
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

revert this?

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Copilot's findings

Files not reviewed (1)
  • extensions/copilot/package-lock.json: Language not supported
Comments suppressed due to low confidence (2)

extensions/copilot/src/extension/tools/node/memoryContextPrompt.tsx:90

  • This text says local repo memory files can be read via the memory tool, but when Copilot Memory is enabled memoryTool currently rejects view for /memories/repo/* (only create is supported). Either adjust the messaging to avoid suggesting an unsupported operation, or change the routing so local repo files remain viewable/editable when both systems are enabled.
					<Tag name='repoMemory'>
						{localRepoMemoryFiles && localRepoMemoryFiles.length > 0
							? <>The following files exist in your repository memory (/memories/repo/). These are scoped to the current workspace. Use the {ToolName.Memory} tool to read them if needed.<br /><br />{localRepoMemoryFiles.join('\n')}</>
							: <>Repository memory (/memories/repo/) is empty. No workspace-scoped notes have been created yet.</>
						}

extensions/copilot/src/extension/tools/node/memoryContextPrompt.tsx:285

  • These instructions say the memory tool can view/str_replace/delete/rename local /memories/repo/* files while Copilot Memory is enabled, but MemoryTool currently rejects non-create commands for repo paths when Copilot Memory is on. Either update the routing to keep local repo files manageable in this mode, or adjust the instructions to match current behavior.
					{enableCopilotMemory && <>Guidelines for local repository memory (`/memories/repo/`):<br />
					- Use the `{ToolName.Memory}` tool to `view`, `str_replace`, `delete`, or `rename` existing local repo memory files.<br />
					- New repository facts should be stored via Copilot Memory (see instructions below) rather than creating new local files.<br />
					</>}
  • Files reviewed: 18/19 changed files
  • Comments generated: 6

Comment on lines +47 to +50
const zodSchema = resolveStoreMemorySchema(toolDef.definitionVersion);
const inputSchema = zodToJsonSchema(zodSchema, { target: 'openApi3' }) as { [key: string]: unknown };
return { ...tool, description: toolDef.description, inputSchema };
}
Copy link

Copilot AI Apr 28, 2026

Choose a reason for hiding this comment

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

alternativeDefinition() converts a Zod schema to JSON schema on every call. Since the schema is determined solely by definitionVersion, consider memoizing the generated inputSchema per version (or caching the final tool definition) to avoid repeated work during tool list construction.

Copilot uses AI. Check for mistakes.
Comment on lines 274 to 278
// Route /memories/repo/* to CAPI if enabled, otherwise local storage
if (isRepoPath(path)) {
const capiEnabled = await this.agentMemoryService.checkMemoryEnabled();
const capiEnabled = this.configurationService.getExperimentBasedConfig(ConfigKey.CopilotMemoryEnabled, this.experimentationService);
if (capiEnabled) {
const result = await this._dispatchRepoCAPI(params, path);
Copy link

Copilot AI Apr 28, 2026

Choose a reason for hiding this comment

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

When Copilot Memory is enabled, repo paths are routed into _dispatchRepoCAPI, which only supports create. This prevents viewing/editing existing local /memories/repo/* files in the combined-flags scenario, even though the prompt UI now lists those files and suggests using the memory tool to read/edit them. Consider routing only create through CAPI and handling the other commands via the local repo store (or stop listing local repo files / adjust instructions).

See below for a potential fix:

		// Route repo creates to CAPI if enabled; other repo operations continue to use local storage
		if (isRepoPath(path)) {
			const capiEnabled = this.configurationService.getExperimentBasedConfig(ConfigKey.CopilotMemoryEnabled, this.experimentationService);
			if (capiEnabled && params.command === 'create') {

Copilot uses AI. Check for mistakes.
const sessionMemoryFiles = enableMemoryTool ? await this.getSessionMemoryFiles(this.props.sessionResource) : undefined;
const localRepoMemoryFiles = enableMemoryTool ? await this.getLocalRepoMemoryFiles() : undefined;

// When CAPI memory is enabled, read from the cache primed by AgentMemoryToolRegistrar
Copy link

Copilot AI Apr 28, 2026

Choose a reason for hiding this comment

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

Comment references AgentMemoryToolRegistrar, but the cache priming service was renamed to AgentMemoryCachePrimer. Update the comment to match the current class/service name to avoid confusion when debugging cache behavior.

This issue also appears in the following locations of the same file:

  • line 86
  • line 282
Suggested change
// When CAPI memory is enabled, read from the cache primed by AgentMemoryToolRegistrar
// When CAPI memory is enabled, read from the cache primed by AgentMemoryCachePrimer

Copilot uses AI. Check for mistakes.
}
// Call with sessionId but let service auto-determine repoNwo to match original working behavior
const response = await this.agentMemoryService.getMemoryPrompt(undefined, sessionId);
this.logService.info(`[AgentMemoryToolRegistrar] primed memory prompt cache for session ${sessionId || 'default'}, definitionVersion=${response?.storeToolDefinition?.definitionVersion ?? 'none'}`);
Copy link

Copilot AI Apr 28, 2026

Choose a reason for hiding this comment

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

Log prefix still says AgentMemoryToolRegistrar even though this service/class is AgentMemoryCachePrimer. Align the log tag with the new name so log filtering/searching continues to work as expected.

Suggested change
this.logService.info(`[AgentMemoryToolRegistrar] primed memory prompt cache for session ${sessionId || 'default'}, definitionVersion=${response?.storeToolDefinition?.definitionVersion ?? 'none'}`);
this.logService.info(`[AgentMemoryCachePrimer] primed memory prompt cache for session ${sessionId || 'default'}, definitionVersion=${response?.storeToolDefinition?.definitionVersion ?? 'none'}`);

Copilot uses AI. Check for mistakes.
Comment on lines +254 to 256
const result = tools
.filter(tool => {
// 0. If the tool was a model specific tool with an override, it'll be mixed in in the 'map' later.
Copy link

Copilot AI Apr 28, 2026

Choose a reason for hiding this comment

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

This intermediate result variable is immediately returned and doesn’t add value vs returning the filter/map chain directly. Consider reverting to a direct return to keep the change minimal (and avoid leaving trailing whitespace in the hunk).

Copilot uses AI. Check for mistakes.
Comment on lines 108 to 111
const omitBaseAgentInstructions = this.configurationService.getConfig(ConfigKey.Advanced.OmitBaseAgentInstructions);
// TODO:@bhavyau find a better way to extract session resource
const sessionResourceStr = (this.props.promptContext.tools?.toolInvocationToken as any)?.sessionResource as string | undefined;
const baseAgentInstructions = <>
Copy link

Copilot AI Apr 28, 2026

Choose a reason for hiding this comment

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

sessionResourceStr is extracted from toolInvocationToken via as any, even though a typed promptContext.request?.sessionResource is already available a few lines later. Consider using this.props.promptContext.request?.sessionResource?.toString() (or passing sessionId/sessionResource through props explicitly) to avoid relying on an untyped token shape.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants