first commit
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled

This commit is contained in:
2026-03-17 16:22:57 +03:30
commit 3d5eaf9445
15349 changed files with 2847338 additions and 0 deletions
@@ -0,0 +1,183 @@
import type { BufferWindowMemoryInput } from '@langchain/classic/memory';
import { BufferWindowMemory } from '@langchain/classic/memory';
import {
NodeConnectionTypes,
type INodeType,
type INodeTypeDescription,
type ISupplyDataFunctions,
type SupplyData,
} from 'n8n-workflow';
import { getSessionId } from '@utils/helpers';
import { logWrapper, getConnectionHintNoticeField } from '@n8n/ai-utilities';
import {
sessionIdOption,
sessionKeyProperty,
contextWindowLengthProperty,
expressionSessionKeyProperty,
} from '../descriptions';
class MemoryChatBufferSingleton {
private static instance: MemoryChatBufferSingleton;
private memoryBuffer: Map<
string,
{ buffer: BufferWindowMemory; created: Date; last_accessed: Date }
>;
private constructor() {
this.memoryBuffer = new Map();
}
static getInstance(): MemoryChatBufferSingleton {
if (!MemoryChatBufferSingleton.instance) {
MemoryChatBufferSingleton.instance = new MemoryChatBufferSingleton();
}
return MemoryChatBufferSingleton.instance;
}
async getMemory(
sessionKey: string,
memoryParams: BufferWindowMemoryInput,
): Promise<BufferWindowMemory> {
await this.cleanupStaleBuffers();
let memoryInstance = this.memoryBuffer.get(sessionKey);
if (memoryInstance) {
memoryInstance.last_accessed = new Date();
} else {
const newMemory = new BufferWindowMemory(memoryParams);
memoryInstance = {
buffer: newMemory,
created: new Date(),
last_accessed: new Date(),
};
this.memoryBuffer.set(sessionKey, memoryInstance);
}
return memoryInstance.buffer;
}
private async cleanupStaleBuffers(): Promise<void> {
const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000);
for (const [key, memoryInstance] of this.memoryBuffer.entries()) {
if (memoryInstance.last_accessed < oneHourAgo) {
await this.memoryBuffer.get(key)?.buffer.clear();
this.memoryBuffer.delete(key);
}
}
}
}
export class MemoryBufferWindow implements INodeType {
description: INodeTypeDescription = {
displayName: 'Simple Memory',
name: 'memoryBufferWindow',
icon: 'fa:database',
iconColor: 'black',
group: ['transform'],
version: [1, 1.1, 1.2, 1.3],
description: 'Stores in n8n memory, so no credentials required',
defaults: {
name: 'Simple Memory',
},
codex: {
categories: ['AI'],
subcategories: {
AI: ['Memory'],
Memory: ['For beginners'],
},
resources: {
primaryDocumentation: [
{
url: 'https://docs.n8n.io/integrations/builtin/cluster-nodes/sub-nodes/n8n-nodes-langchain.memorybufferwindow/',
},
],
},
},
builderHint: {
message:
'Reuse with multiple agents in the same workflow by connecting to multiple agent nodes so agents have a shared context.',
},
inputs: [],
outputs: [NodeConnectionTypes.AiMemory],
outputNames: ['Memory'],
properties: [
getConnectionHintNoticeField([NodeConnectionTypes.AiAgent]),
{
displayName:
'This node stores memory locally in the n8n instance. It is not compatible with Queue Mode or Multi-Main setups, as memory will not be shared across workers. For production use with scaling, consider using an external memory store such as Redis, Postgres, or another persistent memory node.',
name: 'scalingNotice',
type: 'notice',
default: '',
},
{
displayName: 'Session Key',
name: 'sessionKey',
type: 'string',
default: 'chat_history',
description: 'The key to use to store the memory in the workflow data',
displayOptions: {
show: {
'@version': [1],
},
},
},
{
displayName: 'Session ID',
name: 'sessionKey',
type: 'string',
default: '={{ $json.sessionId }}',
description: 'The key to use to store the memory',
displayOptions: {
show: {
'@version': [1.1],
},
},
},
{
...sessionIdOption,
displayOptions: {
show: {
'@version': [{ _cnd: { gte: 1.2 } }],
},
},
},
expressionSessionKeyProperty(1.3),
sessionKeyProperty,
contextWindowLengthProperty,
],
};
async supplyData(this: ISupplyDataFunctions, itemIndex: number): Promise<SupplyData> {
const contextWindowLength = this.getNodeParameter('contextWindowLength', itemIndex) as number;
const workflowId = this.getWorkflow().id;
const memoryInstance = MemoryChatBufferSingleton.getInstance();
const nodeVersion = this.getNode().typeVersion;
let sessionId;
if (nodeVersion >= 1.2) {
sessionId = getSessionId(this, itemIndex);
} else {
sessionId = this.getNodeParameter('sessionKey', itemIndex) as string;
}
const memory = await memoryInstance.getMemory(`${workflowId}__${sessionId}`, {
k: contextWindowLength,
inputKey: 'input',
memoryKey: 'chat_history',
outputKey: 'output',
returnMessages: true,
});
return {
response: logWrapper(memory, this),
};
}
}