diff --git a/mem0-ts/src/oss/src/memory/index.ts b/mem0-ts/src/oss/src/memory/index.ts index a8dde5d01..f41d161ac 100644 --- a/mem0-ts/src/oss/src/memory/index.ts +++ b/mem0-ts/src/oss/src/memory/index.ts @@ -750,7 +750,13 @@ export class Memory { // getLastMessages not supported — proceed without context } } - const parsedMessages = messages.map((m) => m.content).join("\n"); + // Preserve role on the messages being extracted so the prompt's role-aware + // logic and the required `attributed_to` output have the speaker to work + // with. Matches the Python oss `parse_messages` helper (`role: content`); + // without this, assistant statements get attributed to the user. + const parsedMessages = messages + .map((m) => `${m.role}: ${m.content}`) + .join("\n"); // Phase 1: Existing memory retrieval const queryEmbedding = await this.embedder.embed(parsedMessages); diff --git a/mem0-ts/src/oss/tests/memory.add.test.ts b/mem0-ts/src/oss/tests/memory.add.test.ts index f3c637e5a..4e7ebe773 100644 --- a/mem0-ts/src/oss/tests/memory.add.test.ts +++ b/mem0-ts/src/oss/tests/memory.add.test.ts @@ -126,6 +126,22 @@ describe("Memory - add()", () => { expect(result.results.length).toBeGreaterThan(0); }); + test("preserves message roles in the extraction prompt (## New Messages)", async () => { + // The OpenAI LLM mock echoes the `## New Messages` section of the prompt back + // as the extracted text, so the stored memory reveals what the LLM received. + // Roles must survive into that section, otherwise the prompt's role-aware + // logic and required `attributed_to` output have no speaker to attribute to + // and assistant statements get stored as user facts. + const messages = [ + { role: "user", content: "I want to sleep earlier." }, + { role: "assistant", content: "Aim for 00:30 sleep / 08:30 wake." }, + ]; + const result: SearchResult = await memory.add(messages, { userId }); + const seen = result.results.map((r) => r.memory).join("\n"); + expect(seen).toContain("user: I want to sleep earlier."); + expect(seen).toContain("assistant: Aim for 00:30 sleep / 08:30 wake."); + }); + test("works with agentId instead of userId", async () => { const result: SearchResult = await memory.add("test", { agentId: "agent_1",