🧠 fix: Persist memory capability on saved agents; honor registration flag

- Add Tools.memory to the v1 systemTools allowlist so filterAuthorizedTools
  no longer silently drops the memory marker when an agent with the Memory
  capability is created/updated/duplicated through the builder (previously
  the capability only worked for ephemeral chats, not persisted agents).
- agentHasInlineMemoryTools now honors an explicit memoryToolsRegistered
  boolean before falling back to the raw `memory` marker, so an initialized
  config whose registration was denied (memoryAvailable false) is not given
  keyed memory context just because the marker survives in tools.
This commit is contained in:
Danny Avila 2026-06-22 14:17:54 -04:00
parent 4754c981aa
commit b5362e9b28
4 changed files with 34 additions and 4 deletions

View file

@ -183,12 +183,12 @@ describe('MCP Tool Authorization', () => {
test('should keep system tools without querying MCP registry', async () => {
const result = await filterAuthorizedTools({
tools: ['execute_code', 'file_search', 'web_search'],
tools: ['execute_code', 'file_search', 'web_search', 'memory'],
userId,
availableTools: {},
});
expect(result).toEqual(['execute_code', 'file_search', 'web_search']);
expect(result).toEqual(['execute_code', 'file_search', 'web_search', 'memory']);
expect(mockGetAllServerConfigs).not.toHaveBeenCalled();
});

View file

@ -56,6 +56,7 @@ const systemTools = {
[Tools.execute_code]: true,
[Tools.file_search]: true,
[Tools.web_search]: true,
[Tools.memory]: true,
};
const MAX_SEARCH_LEN = 100;

View file

@ -8,6 +8,7 @@ import {
createDeleteMemoryTool,
getRequestMemories,
invalidateRequestMemories,
agentHasInlineMemoryTools,
} from './memory';
jest.mock('~/stream/GenerationJobManager');
@ -663,6 +664,30 @@ describe('createMemoryTool tokenLimit enforcement', () => {
});
});
describe('agentHasInlineMemoryTools', () => {
it('returns false for a nullish agent', () => {
expect(agentHasInlineMemoryTools(null)).toBe(false);
expect(agentHasInlineMemoryTools(undefined)).toBe(false);
});
it('honors an explicit memoryToolsRegistered flag over the raw marker', () => {
/** Initialized config whose registration was denied (memoryAvailable false)
* but whose raw `memory` marker survived in tools must not be treated as
* memory-enabled. */
expect(agentHasInlineMemoryTools({ memoryToolsRegistered: false, tools: ['memory'] })).toBe(
false,
);
expect(agentHasInlineMemoryTools({ memoryToolsRegistered: true, tools: [] })).toBe(true);
});
it('falls back to the raw memory marker when no flag is present', () => {
expect(agentHasInlineMemoryTools({ tools: ['memory'] })).toBe(true);
expect(agentHasInlineMemoryTools({ tools: [{ name: 'memory' }] })).toBe(true);
expect(agentHasInlineMemoryTools({ tools: ['execute_code'] })).toBe(false);
expect(agentHasInlineMemoryTools({ tools: [] })).toBe(false);
});
});
describe('getRequestMemories caching', () => {
it('memoizes per request, then re-fetches after invalidation', async () => {
const getFormattedMemories = jest

View file

@ -447,8 +447,12 @@ export function agentHasInlineMemoryTools(agent: InlineMemoryAgent): boolean {
if (!agent) {
return false;
}
if (agent.memoryToolsRegistered === true) {
return true;
/** An initialized config carries an explicit boolean: honor it so an agent
* whose registration was denied (`false`) is not treated as memory-enabled
* just because the raw `memory` marker survives in `tools`. Fall back to the
* marker only for the raw agent, where the flag is absent. */
if (typeof agent.memoryToolsRegistered === 'boolean') {
return agent.memoryToolsRegistered;
}
return (agent.tools ?? []).some(
(entry) =>