AkurAI Build
Menu

popagent

public

Latest change 6edc7e928c57dab23c0973ed3f7ee70b3ebb72d5 - Initial popagent baseline by AkurAI Build

import { beforeAll, describe, expect, test } from "bun:test";
import { LongTermMemoryStore } from "./long-term-memory";

const store = new LongTermMemoryStore();
const resourceId = `memory-test-${crypto.randomUUID()}`;

beforeAll(() => store.init());

describe("LongTermMemoryStore", () => {
  test("retains an episode and recalls it across sessions", async () => {
    await store.retainEpisode({
      resourceId,
      sessionId: "first-session",
      userText: "I always use dark mode and prefer concise answers",
      assistantText: "I will remember those preferences.",
    });

    const recalled = await store.recall({
      resourceId,
      query: "What display mode and answer style do I prefer?",
    });

    expect(recalled.some((memory) => memory.content.includes("dark mode"))).toBe(true);
    expect(recalled.some((memory) => memory.sessionId === "first-session")).toBe(true);
  });

  test("upserts explicit facts and ranks them above episodes", async () => {
    await store.rememberFact({
      resourceId,
      sessionId: "second-session",
      content: "The user's preferred runtime is Bun.",
      importance: 0.9,
    });
    await store.rememberFact({
      resourceId,
      sessionId: "third-session",
      content: "The user's preferred runtime is Bun.",
      importance: 0.9,
    });

    const recalled = await store.recall({ resourceId, query: "preferred runtime" });
    expect(recalled[0]).toEqual(expect.objectContaining({
      kind: "fact",
      content: "The user's preferred runtime is Bun.",
    }));
    expect(recalled.filter((memory) => memory.content.includes("preferred runtime is Bun"))).toHaveLength(1);
  });

  test("does not leak memories between resources", async () => {
    const recalled = await store.recall({
      resourceId: `${resourceId}-other`,
      query: "dark mode Bun",
    });
    expect(recalled).toEqual([]);
  });


  test("enforces the durable-fact size limit for every write path", async () => {
    const content = "x".repeat(2_001);
    await expect(store.rememberFact({
      resourceId,
      sessionId: "oversized-fact",
      content,
    })).rejects.toThrow("Fact content exceeds 2000 characters");

    const existing = (await store.list({ resourceId, kind: "fact", limit: 1 }))[0];
    expect(existing).toBeDefined();
    await expect(store.updateFact({
      id: existing!.id,
      resourceId,
      content,
      importance: 1,
    })).rejects.toThrow("Fact content exceeds 2000 characters");
  });

  test("truncates retained episodes and total rendered recall context", async () => {
    const marker = `budget${crypto.randomUUID().replaceAll("-", "")}`;
    await store.retainEpisode({
      resourceId,
      sessionId: "large-episode",
      userText: `${marker} ${"u".repeat(8_000)}`,
      assistantText: `${marker} ${"a".repeat(16_000)}`,
    });
    const episode = (await store.list({ resourceId, query: marker, kind: "episode", limit: 1 }))[0];
    expect(episode).toBeDefined();
    expect(episode!.content.length).toBeLessThanOrEqual(12_050);
    expect(episode!.content.match(/… \[truncated\]/g)).toHaveLength(2);

    for (let index = 0; index < 15; index++) {
      await store.rememberFact({
        resourceId,
        sessionId: "recall-budget",
        key: `${marker}.${index}`,
        content: `${marker} ${index} `.padEnd(2_000, String(index % 10)),
        importance: 1,
      });
    }
    const rendered = await store.formatRecall({ resourceId, query: marker, limit: 20 });
    expect(rendered).toBeDefined();
    expect(rendered!.length).toBeLessThanOrEqual(24_000);
    expect(rendered).toContain("… [truncated]");
  });
});