diff --git a/packages/components/credentials/DakeraMemoryApi.credential.ts b/packages/components/credentials/DakeraMemoryApi.credential.ts
new file mode 100644
index 00000000000..7e75af1d1fd
--- /dev/null
+++ b/packages/components/credentials/DakeraMemoryApi.credential.ts
@@ -0,0 +1,28 @@
+import { INodeParams, INodeCredential } from '../src/Interface'
+
+class DakeraMemoryApi implements INodeCredential {
+ label: string
+ name: string
+ version: number
+ description: string
+ inputs: INodeParams[]
+
+ constructor() {
+ this.label = 'Dakera Memory API'
+ this.name = 'dakeraMemoryApi'
+ this.version = 1.0
+ this.description =
+ 'Connect to your self-hosted Dakera memory server. ' +
+ 'Start locally: docker run -p 3000:3000 -e DAKERA_API_KEY=your-key dakera/dakera:latest'
+ this.inputs = [
+ {
+ label: 'API Key',
+ name: 'apiKey',
+ type: 'password',
+ description: 'Your Dakera API key (set via DAKERA_API_KEY when starting the server)'
+ }
+ ]
+ }
+}
+
+module.exports = { credClass: DakeraMemoryApi }
diff --git a/packages/components/nodes/memory/DakeraMemory/DakeraMemory.ts b/packages/components/nodes/memory/DakeraMemory/DakeraMemory.ts
new file mode 100644
index 00000000000..71178da88d6
--- /dev/null
+++ b/packages/components/nodes/memory/DakeraMemory/DakeraMemory.ts
@@ -0,0 +1,373 @@
+import { BaseMessage } from '@langchain/core/messages'
+import { InputValues, MemoryVariables, OutputValues } from '@langchain/core/memory'
+import { BaseChatMemory, BaseChatMemoryInput } from '@langchain/community/memory/chat_memory'
+import { ICommonObject, IDatabaseEntity } from '../../../src'
+import { IMessage, INode, INodeData, INodeParams, MemoryMethods, MessageType } from '../../../src/Interface'
+import { getBaseClasses, getCredentialData, getCredentialParam, mapChatMessageToBaseMessage } from '../../../src/utils'
+import { DataSource } from 'typeorm'
+
+// ---------------------------------------------------------------------------
+// Dakera REST client (no external npm package required)
+// ---------------------------------------------------------------------------
+
+interface DakeraSearchResult {
+ id: string
+ content: string
+ score: number
+ metadata?: Record
+}
+
+// Dakera API response shapes
+interface DakeraStoreResponse {
+ memory: { id: string; content: string; agent_id: string; session_id?: string }
+}
+
+interface DakeraSearchResponse {
+ memories: Array<{ memory: { id: string; content: string; metadata?: Record }; score: number }>
+}
+
+class DakeraClient {
+ private baseUrl: string
+ private headers: Record
+
+ constructor(baseUrl: string, apiKey: string) {
+ this.baseUrl = baseUrl.replace(/\/$/, '')
+ this.headers = {
+ 'Content-Type': 'application/json',
+ ...(apiKey ? { Authorization: `Bearer ${apiKey}` } : {}),
+ }
+ }
+
+ async store(content: string, sessionId: string, agentId: string): Promise {
+ const res = await fetch(`${this.baseUrl}/v1/memory/store`, {
+ method: 'POST',
+ headers: this.headers,
+ body: JSON.stringify({
+ content,
+ agent_id: agentId,
+ ...(sessionId ? { session_id: sessionId } : {}),
+ }),
+ })
+ if (!res.ok) {
+ throw new Error(`DakeraClient.store: HTTP ${res.status} ${res.statusText}`)
+ }
+ const data = (await res.json()) as DakeraStoreResponse
+ return data.memory?.id ?? ''
+ }
+
+ async recall(query: string, sessionId: string, agentId: string, topK = 10): Promise {
+ const res = await fetch(`${this.baseUrl}/v1/memory/search`, {
+ method: 'POST',
+ headers: this.headers,
+ body: JSON.stringify({
+ agent_id: agentId,
+ query,
+ top_k: topK,
+ ...(sessionId ? { session_id: sessionId } : {}),
+ }),
+ })
+ if (!res.ok) {
+ throw new Error(`DakeraClient.recall: HTTP ${res.status} ${res.statusText}`)
+ }
+ const data = (await res.json()) as DakeraSearchResponse
+ return (data.memories ?? []).map((item) => ({
+ id: item.memory.id,
+ content: item.memory.content,
+ score: item.score,
+ metadata: item.memory.metadata,
+ }))
+ }
+
+ async forget(sessionId: string, agentId: string): Promise {
+ const res = await fetch(`${this.baseUrl}/v1/memory/forget`, {
+ method: 'POST',
+ headers: this.headers,
+ body: JSON.stringify({
+ agent_id: agentId,
+ ...(sessionId ? { session_id: sessionId } : {}),
+ }),
+ })
+ if (!res.ok) {
+ throw new Error(`DakeraClient.forget: HTTP ${res.status} ${res.statusText}`)
+ }
+ }
+}
+
+// ---------------------------------------------------------------------------
+// Flowise INode — metadata and inputs
+// ---------------------------------------------------------------------------
+
+class DakeraMemory_Memory implements INode {
+ label: string
+ name: string
+ version: number
+ description: string
+ type: string
+ icon: string
+ category: string
+ baseClasses: string[]
+ credential: INodeParams
+ inputs: INodeParams[]
+
+ constructor() {
+ this.label = 'Dakera Memory'
+ this.name = 'dakeraMemory'
+ this.version = 1.0
+ this.type = 'DakeraMemory'
+ this.icon = 'dakera.svg'
+ this.category = 'Memory'
+ this.description =
+ 'Persistent, decay-weighted vector memory backed by a self-hosted Dakera server. ' +
+ 'Memories survive restarts and are semantically searchable across sessions.'
+ this.baseClasses = [this.type, ...getBaseClasses(DakeraMemoryExtended)]
+ this.credential = {
+ label: 'Connect Credential',
+ name: 'credential',
+ type: 'credential',
+ optional: true,
+ description: 'API key for your Dakera server (not required for local dev with no auth)',
+ credentialNames: ['dakeraMemoryApi'],
+ }
+ this.inputs = [
+ {
+ label: 'Base URL',
+ name: 'baseUrl',
+ type: 'string',
+ default: 'http://localhost:3300',
+ description: 'URL of your self-hosted Dakera server',
+ },
+ {
+ label: 'Session ID',
+ name: 'sessionId',
+ type: 'string',
+ description: 'Groups memories by session. Leave empty to use the Flowise Chat ID.',
+ default: '',
+ optional: true,
+ },
+ {
+ label: 'Agent ID',
+ name: 'agentId',
+ type: 'string',
+ description: 'Namespace for memories (e.g. the chatflow name)',
+ default: 'flowise',
+ optional: true,
+ additionalParams: true,
+ },
+ {
+ label: 'Top K Memories',
+ name: 'topK',
+ type: 'number',
+ default: 10,
+ description: 'Number of memories to retrieve per query',
+ optional: true,
+ additionalParams: true,
+ },
+ {
+ label: 'Memory Key',
+ name: 'memoryKey',
+ type: 'string',
+ default: 'history',
+ optional: true,
+ additionalParams: true,
+ },
+ ]
+ }
+
+ async init(nodeData: INodeData, input: string, options: ICommonObject): Promise {
+ const baseUrl = (nodeData.inputs?.baseUrl as string) || 'http://localhost:3300'
+ const agentId = (nodeData.inputs?.agentId as string) || 'flowise'
+ const topK = Number(nodeData.inputs?.topK ?? 10)
+ const memoryKey = (nodeData.inputs?.memoryKey as string) || 'history'
+ const sessionId = (nodeData.inputs?.sessionId as string) || ''
+ const orgId = options.orgId as string
+
+ let apiKey = ''
+ if (nodeData.credential) {
+ const credentialData = await getCredentialData(nodeData.credential, options)
+ apiKey = getCredentialParam('apiKey', credentialData, nodeData) || ''
+ }
+
+ return new DakeraMemoryExtended({
+ baseUrl,
+ apiKey,
+ agentId,
+ topK,
+ memoryKey,
+ sessionId,
+ orgId,
+ input,
+ appDataSource: options.appDataSource as DataSource,
+ databaseEntities: options.databaseEntities as IDatabaseEntity,
+ chatflowid: options.chatflowid as string,
+ })
+ }
+}
+
+// ---------------------------------------------------------------------------
+// Extended memory class — implements MemoryMethods required by Flowise
+// ---------------------------------------------------------------------------
+
+interface DakeraMemoryExtendedInput extends BaseChatMemoryInput {
+ baseUrl: string
+ apiKey: string
+ agentId: string
+ topK: number
+ memoryKey: string
+ sessionId: string
+ orgId: string
+ input: string
+ appDataSource: DataSource
+ databaseEntities: IDatabaseEntity
+ chatflowid: string
+}
+
+class DakeraMemoryExtended extends BaseChatMemory implements MemoryMethods {
+ private client: DakeraClient
+ private agentId: string
+ private topK: number
+ private sessionId: string
+ private input: string
+ orgId: string
+ memoryKey: string
+ appDataSource: DataSource
+ databaseEntities: IDatabaseEntity
+ chatflowid: string
+
+ constructor(fields: DakeraMemoryExtendedInput) {
+ super({ returnMessages: true, inputKey: 'input', outputKey: 'output' })
+ this.client = new DakeraClient(fields.baseUrl, fields.apiKey)
+ this.agentId = fields.agentId
+ this.topK = fields.topK
+ this.memoryKey = fields.memoryKey
+ this.sessionId = fields.sessionId
+ this.orgId = fields.orgId
+ this.input = fields.input
+ this.appDataSource = fields.appDataSource
+ this.databaseEntities = fields.databaseEntities
+ this.chatflowid = fields.chatflowid
+ }
+
+ get memoryKeys(): string[] {
+ return [this.memoryKey]
+ }
+
+ async loadMemoryVariables(values: InputValues, overrideSessionId = ''): Promise {
+ const sid = overrideSessionId || this.sessionId || 'default'
+ const query = (values['input'] as string) || this.input || ''
+ try {
+ const results = await this.client.recall(query, sid, this.agentId, this.topK)
+ const historyText = results.map((r) => r.content).join('\n')
+ return { [this.memoryKey]: historyText }
+ } catch (err) {
+ console.error('[DakeraMemory] loadMemoryVariables failed:', err)
+ return { [this.memoryKey]: '' }
+ }
+ }
+
+ async saveContext(inputValues: InputValues, outputValues: OutputValues, overrideSessionId = ''): Promise {
+ const sid = overrideSessionId || this.sessionId || 'default'
+ const userMsg = (inputValues['input'] as string) || ''
+ const aiMsg = (outputValues['output'] as string) || ''
+ const stores: Promise[] = []
+ if (userMsg) stores.push(this.client.store(`User: ${userMsg}`, sid, this.agentId))
+ if (aiMsg) stores.push(this.client.store(`Assistant: ${aiMsg}`, sid, this.agentId))
+ try {
+ await Promise.all(stores)
+ } catch (err) {
+ console.error('[DakeraMemory] saveContext failed:', err)
+ }
+ }
+
+ async clear(overrideSessionId = ''): Promise {
+ const sid = overrideSessionId || this.sessionId || 'default'
+ try {
+ await this.client.forget(sid, this.agentId)
+ } catch (err) {
+ console.error('[DakeraMemory] clear failed:', err)
+ }
+ }
+
+ // ------------------------------------------------------------------
+ // MemoryMethods interface — required by Flowise runtime
+ // ------------------------------------------------------------------
+
+ async getChatMessages(
+ overrideSessionId = '',
+ returnBaseMessages = false,
+ prependMessages?: IMessage[]
+ ): Promise {
+ const sid = overrideSessionId || this.sessionId || 'default'
+
+ // Fetch most recent messages first (DESC), then reverse to chronological order
+ let chatMessages = await this.appDataSource
+ .getRepository(this.databaseEntities['ChatMessage'])
+ .find({
+ where: { sessionId: sid, chatflowid: this.chatflowid },
+ order: { createdDate: 'DESC' },
+ take: 20,
+ })
+ chatMessages = chatMessages.reverse()
+
+ let returnIMessages: IMessage[] = chatMessages.map((m) => ({
+ message: m.content as string,
+ type: m.role as MessageType,
+ }))
+
+ if (prependMessages?.length) {
+ returnIMessages.unshift(...prependMessages)
+ chatMessages.unshift(...(prependMessages as any))
+ }
+
+ if (returnBaseMessages) {
+ // Prepend Dakera semantic recall context ahead of the DB messages
+ try {
+ const query = this.input || 'conversation history'
+ const recalled = await this.client.recall(query, sid, this.agentId, this.topK)
+ if (recalled.length > 0) {
+ const memoryContext = recalled.map((r) => r.content).join('\n')
+ const systemMsg = {
+ role: 'apiMessage' as MessageType,
+ content: `Relevant memories from Dakera:\n${memoryContext}`,
+ } as any
+ chatMessages.unshift(systemMsg)
+ }
+ } catch (err) {
+ console.error('[DakeraMemory] getChatMessages recall failed:', err)
+ }
+ return await mapChatMessageToBaseMessage(chatMessages as any, this.orgId)
+ }
+
+ return returnIMessages
+ }
+
+ async addChatMessages(
+ msgArray: { text: string; type: MessageType }[],
+ overrideSessionId = ''
+ ): Promise {
+ const sid = overrideSessionId || this.sessionId || 'default'
+ // Store each message independently — don't require both user and AI to be present
+ const stores = msgArray
+ .filter((msg) => msg.text)
+ .map((msg) => {
+ const prefix = msg.type === 'userMessage' ? 'User' : 'Assistant'
+ return this.client.store(`${prefix}: ${msg.text}`, sid, this.agentId)
+ })
+ try {
+ await Promise.all(stores)
+ } catch (err) {
+ console.error('[DakeraMemory] addChatMessages failed:', err)
+ }
+ }
+
+ async clearChatMessages(overrideSessionId = ''): Promise {
+ const sid = overrideSessionId || this.sessionId || 'default'
+ await this.clear(sid)
+ if (sid) {
+ await this.appDataSource
+ .getRepository(this.databaseEntities['ChatMessage'])
+ .delete({ sessionId: sid, chatflowid: this.chatflowid })
+ }
+ }
+}
+
+module.exports = { nodeClass: DakeraMemory_Memory }
diff --git a/packages/components/nodes/memory/DakeraMemory/dakera.svg b/packages/components/nodes/memory/DakeraMemory/dakera.svg
new file mode 100644
index 00000000000..e7628995360
--- /dev/null
+++ b/packages/components/nodes/memory/DakeraMemory/dakera.svg
@@ -0,0 +1,4 @@
+