import { randomUUID } from "node:crypto"; import type { Server } from "node:http"; import { createServer } from "node:http"; import type { AddressInfo } from "node:net"; import type { AdapterContext } from "@rakazo/adapter-kit"; import type { PrismaClient } from "@rakazo/db"; import { bootstrapUserSpace, createDb, createRepos, ensureMainBot, requireMembership, } from "@rakazo/db"; import { afterAll, beforeAll, describe, expect, it } from "vitest"; import { BuiltinMemoryProvider } from "./builtin-memory-provider.js"; import { EmulatorEmbeddingProvider, emulatedEmbedding } from "./embedding-emulator.js"; import { memoryProviderResolveRequest, prepareMemoryProviderConnection, SpaceMemoryProviderResolver, } from "./memory-provider-factory.js"; import { EncryptedSecretStore } from "./secrets.js"; const databaseUrl = process.env.DATABASE_URL; const describePostgres = process.env.VERIFY_DATABASE && databaseUrl ? describe.sequential : describe.skip; const signupEnv = { signupsEnabled: "true", signupAllowlist: "" }; /** A real loopback OpenAI-compatible embeddings server, so the guarded HTTP path runs too. */ function startEmbeddingsServer(): Promise<{ server: Server; baseUrl: string; calls: string[][] }> { const calls: string[][] = []; const server = createServer((request, response) => { let body = ""; request.on("data", (chunk) => { body += chunk; }); request.on("end", () => { if (request.method !== "POST" || request.url !== "/v1/embeddings") { response.writeHead(404).end(); return; } const input = JSON.parse(body).input as string[]; calls.push(input); response.writeHead(200, { "content-type": "application/json" }); response.end( JSON.stringify({ data: input.map((text, index) => ({ index, embedding: emulatedEmbedding(text) })), }), ); }); }); return new Promise((resolve) => { server.listen(0, "127.0.0.1", () => { const { port } = server.address() as AddressInfo; resolve({ server, baseUrl: `http://127.0.0.1:${port}`, calls }); }); }); } describePostgres("built-in semantic memory (PostgreSQL)", () => { let prisma: PrismaClient; let close: () => Promise; const userIds: string[] = []; async function newUser() { const id = `bm${randomUUID().replaceAll("-", "")}`; userIds.push(id); await prisma.user.create({ data: { id, name: "Ada", email: `${id}@rakazo.test` } }); const { spaceId } = await bootstrapUserSpace(prisma, { id }, signupEnv, { claimDeploymentOwner: false, }); const main = await ensureMainBot(prisma, id); const specialist = await createRepos(prisma).createBot(await requireMembership(prisma, id), { name: "Scout", title: "Researcher", description: "", instructions: "", notifyOnFinish: false, }); return { userId: id, spaceId, mainBotId: main.id, specialistId: specialist.id }; } function contextFor(spaceId: string, userId: string, botId: string): AdapterContext { return { operationId: "op", traceId: "trace", spaceId, userId, botId, signal: new AbortController().signal, }; } beforeAll(() => { const db = createDb(databaseUrl!); prisma = db.prisma; close = async () => { await db.prisma.$disconnect(); await db.pool.end(); }; }); afterAll(async () => { if (!prisma) return; const memberships = await prisma.member.findMany({ where: { userId: { in: userIds } }, select: { organizationId: true }, }); await prisma.organization.deleteMany({ where: { id: { in: memberships.map((member) => member.organizationId) } }, }); await prisma.user.deleteMany({ where: { id: { in: userIds } } }); await close(); }); it("keeps isolated, shared, and history items apart and stores exact vectors", async () => { const { userId, spaceId, mainBotId, specialistId } = await newUser(); const memory = new BuiltinMemoryProvider(prisma, new EmulatorEmbeddingProvider()); const main = contextFor(spaceId, userId, mainBotId); const scout = contextFor(spaceId, userId, specialistId); const durable = { kind: "durable" as const }; await memory.save( { content: "Prefers green tea", scope: "isolated", botId: mainBotId, source: durable }, main, ); await memory.save( { content: "Team standup happens at nine", scope: "shared", botId: mainBotId, source: durable, }, main, ); await memory.save( { content: "Planned the launch party in the old gym. ".repeat(40), scope: "isolated", botId: mainBotId, source: { kind: "history", generation: 0 }, }, main, ); const recall = async ( context: AdapterContext, query: string, scope: "isolated" | "shared", historyGeneration?: number, ) => { const result = await memory.recall( { query, scope, botId: context.botId!, limit: 5, ...(historyGeneration === undefined ? {} : { historyGeneration }), }, context, ); if (!result.ok) throw new Error(result.error); return result.value.map((entry) => entry.memory); }; expect(await recall(main, "green tea", "isolated")).toEqual(["Prefers green tea"]); // Shared items stay visible to their own bot in isolated scope. expect(await recall(main, "standup at nine", "isolated")).toEqual([ "Team standup happens at nine", ]); expect(await recall(scout, "standup at nine", "isolated")).toEqual([]); expect(await recall(scout, "standup at nine", "shared")).toEqual([ "Team standup happens at nine", ]); expect(await recall(scout, "green tea", "shared")).toEqual([]); expect(await recall(main, "launch party gym", "isolated")).toEqual([]); const history = await recall(main, "launch party gym", "isolated", 0); expect(history.length).toBeGreaterThan(0); expect(history[0]).toContain("launch party"); const stored = await prisma.semanticMemoryItem.findFirstOrThrow({ where: { botId: mainBotId, content: "Prefers green tea" }, }); expect(stored.embedding).toEqual(emulatedEmbedding("Prefers green tea")); expect(stored).toMatchObject({ spaceId, shared: false, generation: null }); await expect( memory.purgeHistory({ botId: mainBotId, generations: [0] }, main), ).resolves.toEqual({ ok: true, value: undefined }); expect(await recall(main, "launch party gym", "isolated", 0)).toEqual([]); // Another bot cannot forget an isolated item it could not recall; its own bot can. await expect(memory.forget({ id: stored.id }, scout)).resolves.toMatchObject({ ok: true, value: { expired: false }, }); await expect(memory.forget({ id: stored.id }, main)).resolves.toMatchObject({ ok: true, value: { expired: true }, }); expect(await recall(main, "green tea", "isolated")).toEqual([]); // Vectors from another embedding model are never compared. const otherModel = new BuiltinMemoryProvider(prisma, new EmulatorEmbeddingProvider("other")); const result = await otherModel.recall( { query: "standup at nine", scope: "shared", botId: specialistId, limit: 5 }, scout, ); expect(result).toEqual({ ok: true, value: [] }); }); it("removes a bot's items with the bot and never crosses Spaces", async () => { const first = await newUser(); const second = await newUser(); const memory = new BuiltinMemoryProvider(prisma, new EmulatorEmbeddingProvider()); await memory.save( { content: "Scout keeps the research notes", scope: "shared", botId: first.specialistId, source: { kind: "durable" }, }, contextFor(first.spaceId, first.userId, first.specialistId), ); const elsewhere = await memory.recall( { query: "research notes", scope: "shared", botId: second.mainBotId, limit: 5 }, contextFor(second.spaceId, second.userId, second.mainBotId), ); expect(elsewhere).toEqual({ ok: true, value: [] }); await prisma.bot.delete({ where: { id: first.specialistId } }); expect(await prisma.semanticMemoryItem.count({ where: { botId: first.specialistId } })).toBe(0); }); it("connects, resolves, and recalls through a real loopback embeddings server", async () => { const { userId, spaceId, mainBotId } = await newUser(); const { server, baseUrl, calls } = await startEmbeddingsServer(); try { const prepared = await prepareMemoryProviderConnection({ provider: "builtin", settings: { baseUrl, model: "nomic-embed-text" }, credentials: {}, }); expect(prepared.settings).toEqual({ baseUrl: `${baseUrl}/v1`, model: "nomic-embed-text" }); const secrets = new EncryptedSecretStore("test-encryption-key-test-encryption-key"); const stored = await secrets.put( JSON.stringify(prepared.credentials), contextFor(spaceId, userId, mainBotId), ); const secret = await prisma.secret.create({ data: { id: stored.id, userId, spaceId, kind: "memory-provider", ciphertext: stored.ciphertext, }, }); await prisma.spaceMemoryConfig.create({ data: { spaceId, userId, provider: prepared.provider, settings: prepared.settings, secretId: secret.id, defaultMemoryScope: "isolated", }, }); await prisma.deploymentSettings.upsert({ where: { id: "default" }, create: { id: "default", ownerUserId: userId }, update: { ownerUserId: userId }, }); const configured = await new SpaceMemoryProviderResolver(prisma, secrets).resolve( memoryProviderResolveRequest(spaceId, userId), ); expect(configured?.provider.describe().id).toBe("builtin"); expect(configured?.embeddings?.describe().capabilities.model).toBe("nomic-embed-text"); const context = contextFor(spaceId, userId, mainBotId); await expect( configured!.provider.save( { content: "Keeps houseplants alive with a watering schedule", scope: "isolated", botId: mainBotId, source: { kind: "durable" }, }, context, ), ).resolves.toEqual({ ok: true, value: undefined }); const recalled = await configured!.provider.recall( { query: "watering schedule", scope: "isolated", botId: mainBotId, limit: 5 }, context, ); expect(recalled.ok && recalled.value.map((entry) => entry.memory)).toEqual([ "Keeps houseplants alive with a watering schedule", ]); expect(calls.flat()).toContain("watering schedule"); } finally { await new Promise((resolve) => server.close(resolve)); } }); });