Skip to content

Commit c8cb34c

Browse files
committed
feat: better memory package + landing page
1 parent 72d55cc commit c8cb34c

18 files changed

Lines changed: 1167 additions & 12 deletions

File tree

apps/api/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
"@cossistant/core": "workspace:*",
2121
"@cossistant/jobs": "workspace:*",
2222
"@cossistant/location": "workspace:*",
23+
"@cossistant/memory": "workspace:*",
2324
"@cossistant/redis": "workspace:*",
2425
"@cossistant/transactional": "workspace:*",
2526
"@cossistant/types": "workspace:*",

apps/api/src/ai-pipeline/shared/tools/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@ import type {
1010
} from "./contracts";
1111
import { wrapPipelineToolsWithTelemetry } from "./telemetry";
1212

13+
export {
14+
createConversationMemoryTools,
15+
createVisitorMemoryTools,
16+
createWebsiteMemoryTools,
17+
} from "./memory";
18+
1319
const FINISH_TOOL_NAME_SET = new Set<string>(FINISH_TOOL_IDS);
1420

1521
type BehaviorSettingKey = NonNullable<
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
import { beforeEach, describe, expect, it, mock } from "bun:test";
2+
import type { createMemoryTool } from "@cossistant/memory";
3+
4+
type GenericMemoryTools = ReturnType<typeof createMemoryTool>;
5+
6+
const rememberTool = {
7+
description: "remember tool",
8+
} as unknown as GenericMemoryTools["remember"];
9+
const recallMemoryTool = {
10+
description: "recall tool",
11+
} as unknown as GenericMemoryTools["recallMemory"];
12+
13+
const createMemoryToolMock = mock((() => ({
14+
remember: rememberTool,
15+
recallMemory: recallMemoryTool,
16+
})) as (...args: unknown[]) => {
17+
remember: typeof rememberTool;
18+
recallMemory: typeof recallMemoryTool;
19+
});
20+
21+
mock.module("@cossistant/memory", () => ({
22+
createMemoryTool: createMemoryToolMock,
23+
}));
24+
25+
const modulePromise = import("./memory");
26+
27+
describe("scoped memory tool wrappers", () => {
28+
beforeEach(() => {
29+
createMemoryToolMock.mockReset();
30+
createMemoryToolMock.mockReturnValue({
31+
remember: rememberTool,
32+
recallMemory: recallMemoryTool,
33+
});
34+
});
35+
36+
it("visitor wrapper binds visitor-only scope and aliases generic tools", async () => {
37+
const { createVisitorMemoryTools } = await modulePromise;
38+
const memory = {} as never;
39+
40+
const result = createVisitorMemoryTools({
41+
memory,
42+
organizationId: "org_1",
43+
websiteId: "site_1",
44+
aiAgentId: "agent_1",
45+
visitorId: "visitor_1",
46+
recallDefaults: {
47+
limit: 6,
48+
includeSummary: true,
49+
},
50+
});
51+
52+
expect(createMemoryToolMock).toHaveBeenCalledWith({
53+
memory,
54+
remember: {
55+
metadata: {
56+
organizationId: "org_1",
57+
websiteId: "site_1",
58+
aiAgentId: "agent_1",
59+
visitorId: "visitor_1",
60+
},
61+
description: expect.stringContaining("visitor"),
62+
},
63+
recall: {
64+
where: {
65+
organizationId: "org_1",
66+
websiteId: "site_1",
67+
aiAgentId: "agent_1",
68+
visitorId: "visitor_1",
69+
},
70+
defaults: {
71+
limit: 6,
72+
includeSummary: true,
73+
},
74+
description: expect.stringContaining("visitor"),
75+
},
76+
});
77+
expect(result).toEqual({
78+
rememberVisitor: rememberTool,
79+
recallVisitorMemory: recallMemoryTool,
80+
});
81+
});
82+
83+
it("conversation wrapper binds conversation scope on write and read", async () => {
84+
const { createConversationMemoryTools } = await modulePromise;
85+
const memory = {} as never;
86+
87+
const result = createConversationMemoryTools({
88+
memory,
89+
organizationId: "org_1",
90+
websiteId: "site_1",
91+
aiAgentId: "agent_1",
92+
visitorId: "visitor_1",
93+
conversationId: "conv_1",
94+
});
95+
96+
expect(createMemoryToolMock).toHaveBeenCalledWith({
97+
memory,
98+
remember: {
99+
metadata: {
100+
organizationId: "org_1",
101+
websiteId: "site_1",
102+
aiAgentId: "agent_1",
103+
visitorId: "visitor_1",
104+
conversationId: "conv_1",
105+
},
106+
description: expect.stringContaining("conversation"),
107+
},
108+
recall: {
109+
where: {
110+
organizationId: "org_1",
111+
websiteId: "site_1",
112+
aiAgentId: "agent_1",
113+
visitorId: "visitor_1",
114+
conversationId: "conv_1",
115+
},
116+
defaults: undefined,
117+
description: expect.stringContaining("conversation"),
118+
},
119+
});
120+
expect(result).toEqual({
121+
rememberConversation: rememberTool,
122+
recallConversationMemory: recallMemoryTool,
123+
});
124+
});
125+
126+
it("website wrapper binds website scope only", async () => {
127+
const { createWebsiteMemoryTools } = await modulePromise;
128+
const memory = {} as never;
129+
130+
const result = createWebsiteMemoryTools({
131+
memory,
132+
organizationId: "org_1",
133+
websiteId: "site_1",
134+
aiAgentId: "agent_1",
135+
recallDefaults: {
136+
limit: 4,
137+
includeSummary: false,
138+
},
139+
});
140+
141+
expect(createMemoryToolMock).toHaveBeenCalledWith({
142+
memory,
143+
remember: {
144+
metadata: {
145+
organizationId: "org_1",
146+
websiteId: "site_1",
147+
aiAgentId: "agent_1",
148+
},
149+
description: expect.stringContaining("website"),
150+
},
151+
recall: {
152+
where: {
153+
organizationId: "org_1",
154+
websiteId: "site_1",
155+
aiAgentId: "agent_1",
156+
},
157+
defaults: {
158+
limit: 4,
159+
includeSummary: false,
160+
},
161+
description: expect.stringContaining("website"),
162+
},
163+
});
164+
expect(result).toEqual({
165+
rememberWebsite: rememberTool,
166+
recallWebsiteMemory: recallMemoryTool,
167+
});
168+
});
169+
170+
it("wrappers only alias the generic package tools", async () => {
171+
const { createVisitorMemoryTools } = await modulePromise;
172+
const memory = {} as never;
173+
174+
const result = createVisitorMemoryTools({
175+
memory,
176+
organizationId: "org_1",
177+
websiteId: "site_1",
178+
aiAgentId: "agent_1",
179+
visitorId: "visitor_1",
180+
});
181+
182+
expect(result.rememberVisitor).toBe(rememberTool);
183+
expect(result.recallVisitorMemory).toBe(recallMemoryTool);
184+
expect(createMemoryToolMock).toHaveBeenCalledTimes(1);
185+
});
186+
});
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
import {
2+
type CreateMemoryToolOptions,
3+
createMemoryTool,
4+
type Memory,
5+
type MemoryMetadata,
6+
} from "@cossistant/memory";
7+
8+
type RecallDefaults = NonNullable<
9+
CreateMemoryToolOptions["recall"]["defaults"]
10+
>;
11+
12+
type BaseScopedMemoryToolOptions = {
13+
memory: Memory;
14+
organizationId: string;
15+
websiteId: string;
16+
aiAgentId: string;
17+
recallDefaults?: RecallDefaults;
18+
};
19+
20+
type VisitorScopedMemoryToolOptions = BaseScopedMemoryToolOptions & {
21+
visitorId: string;
22+
};
23+
24+
type ConversationScopedMemoryToolOptions = VisitorScopedMemoryToolOptions & {
25+
conversationId: string;
26+
};
27+
28+
function buildWebsiteScopeMetadata(params: {
29+
organizationId: string;
30+
websiteId: string;
31+
aiAgentId: string;
32+
}): MemoryMetadata {
33+
return {
34+
organizationId: params.organizationId,
35+
websiteId: params.websiteId,
36+
aiAgentId: params.aiAgentId,
37+
};
38+
}
39+
40+
function createScopedMemoryDescriptions(scopeLabel: string): {
41+
remember: string;
42+
recall: string;
43+
} {
44+
return {
45+
remember: `Store a durable memory scoped to this ${scopeLabel} only.
46+
Use this for stable facts, preferences, constraints, or decisions that should matter later.
47+
Do not store raw transcript copies or ephemeral one-turn noise.
48+
Keep memory operations invisible in user-facing replies.`,
49+
recall: `Recall durable memory already stored for this ${scopeLabel} when prior context may matter.
50+
Use a short natural-language query, not a full prompt dump.
51+
Keep memory operations invisible in user-facing replies.`,
52+
};
53+
}
54+
55+
export function createVisitorMemoryTools(
56+
params: VisitorScopedMemoryToolOptions
57+
): {
58+
rememberVisitor: ReturnType<typeof createMemoryTool>["remember"];
59+
recallVisitorMemory: ReturnType<typeof createMemoryTool>["recallMemory"];
60+
} {
61+
const scope = {
62+
...buildWebsiteScopeMetadata(params),
63+
visitorId: params.visitorId,
64+
};
65+
const descriptions = createScopedMemoryDescriptions("visitor");
66+
const { remember, recallMemory } = createMemoryTool({
67+
memory: params.memory,
68+
remember: {
69+
metadata: scope,
70+
description: descriptions.remember,
71+
},
72+
recall: {
73+
where: scope,
74+
defaults: params.recallDefaults,
75+
description: descriptions.recall,
76+
},
77+
});
78+
79+
return {
80+
rememberVisitor: remember,
81+
recallVisitorMemory: recallMemory,
82+
};
83+
}
84+
85+
export function createConversationMemoryTools(
86+
params: ConversationScopedMemoryToolOptions
87+
): {
88+
rememberConversation: ReturnType<typeof createMemoryTool>["remember"];
89+
recallConversationMemory: ReturnType<typeof createMemoryTool>["recallMemory"];
90+
} {
91+
const visitorScope = {
92+
...buildWebsiteScopeMetadata(params),
93+
visitorId: params.visitorId,
94+
};
95+
const conversationScope = {
96+
...visitorScope,
97+
conversationId: params.conversationId,
98+
};
99+
const descriptions = createScopedMemoryDescriptions("conversation");
100+
const { remember, recallMemory } = createMemoryTool({
101+
memory: params.memory,
102+
remember: {
103+
metadata: conversationScope,
104+
description: descriptions.remember,
105+
},
106+
recall: {
107+
where: conversationScope,
108+
defaults: params.recallDefaults,
109+
description: descriptions.recall,
110+
},
111+
});
112+
113+
return {
114+
rememberConversation: remember,
115+
recallConversationMemory: recallMemory,
116+
};
117+
}
118+
119+
export function createWebsiteMemoryTools(params: BaseScopedMemoryToolOptions): {
120+
rememberWebsite: ReturnType<typeof createMemoryTool>["remember"];
121+
recallWebsiteMemory: ReturnType<typeof createMemoryTool>["recallMemory"];
122+
} {
123+
const scope = buildWebsiteScopeMetadata(params);
124+
const descriptions = createScopedMemoryDescriptions("website");
125+
const { remember, recallMemory } = createMemoryTool({
126+
memory: params.memory,
127+
remember: {
128+
metadata: scope,
129+
description: descriptions.remember,
130+
},
131+
recall: {
132+
where: scope,
133+
defaults: params.recallDefaults,
134+
description: descriptions.recall,
135+
},
136+
});
137+
138+
return {
139+
rememberWebsite: remember,
140+
recallWebsiteMemory: recallMemory,
141+
};
142+
}

0 commit comments

Comments
 (0)