Skip to content

Commit 637fd6e

Browse files
committed
feat(session): support cross-session note recall
1 parent 63defe7 commit 637fd6e

12 files changed

Lines changed: 1997 additions & 569 deletions

docs/SmokeTests.md

Lines changed: 82 additions & 47 deletions
Large diffs are not rendered by default.

docs/superpowers/plans/2026-04-19-session-notes-cross-session-recall.md

Lines changed: 790 additions & 0 deletions
Large diffs are not rendered by default.

docs/superpowers/specs/2026-04-11-session-notes-anti-drift-design.md

Lines changed: 342 additions & 317 deletions
Large diffs are not rendered by default.

src/index.test.ts

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import {
22
assertEquals,
33
assertRejects,
44
assertStrictEquals,
5+
assertStringIncludes,
56
} from "jsr:@std/assert@^1.0.0";
67
import { afterEach, describe, it } from "jsr:@std/testing@^1.0.0/bdd";
78
import {
@@ -15,6 +16,7 @@ import {
1516
SESSION_SEARCH_BASELINE_DESCRIPTION,
1617
SESSION_SEARCH_STRENGTHENED_DESCRIPTION,
1718
} from "./services/session-mcp-runtime.ts";
19+
import { createSessionMcpRuntime } from "./services/session-mcp-runtime.ts";
1820
import {
1921
setOpenCodeClient,
2022
setWarningTaskScheduler,
@@ -125,7 +127,7 @@ function createEntrypointHarnessWithOptions(options: {
125127
redisCacheInstances: [] as unknown[],
126128
sessionNotesArgs: [] as Array<[
127129
unknown,
128-
{ sessionTtlSeconds: number },
130+
{ groupId: string; sessionTtlSeconds: number },
129131
]>,
130132
sessionNotesInstances: [] as unknown[],
131133
batchDrainArgs: [] as Array<[
@@ -251,7 +253,10 @@ function createEntrypointHarnessWithOptions(options: {
251253
}
252254

253255
class MockSessionNotesService {
254-
constructor(redisClient: unknown, options: { sessionTtlSeconds: number }) {
256+
constructor(
257+
redisClient: unknown,
258+
options: { groupId: string; sessionTtlSeconds: number },
259+
) {
255260
records.sessionNotesArgs.push([redisClient, options]);
256261
records.sessionNotesInstances.push(this);
257262
}
@@ -894,6 +899,41 @@ describe("index", () => {
894899
});
895900

896901
describe("graphiti entrypoint", () => {
902+
it("exposes public note/search tool args without root_session_id", () => {
903+
const runtime = createSessionMcpRuntime();
904+
905+
try {
906+
assertStringIncludes(
907+
runtime.tools.session_notes_write.description,
908+
"delete on missing id is a no-op success returning deleted",
909+
);
910+
assertStringIncludes(
911+
runtime.tools.session_notes_write.description,
912+
"only ownership conflicts reject mutation",
913+
);
914+
assertStringIncludes(
915+
runtime.tools.session_notes_read.description,
916+
"returns `{ note: null }`",
917+
);
918+
assertStringIncludes(
919+
runtime.tools.session_search.description,
920+
'`id`, `root_session_id`, and `scope: "local" | "project"`',
921+
);
922+
assertEquals(Object.keys(runtime.tools.session_notes_write.args), [
923+
"text",
924+
"replace",
925+
]);
926+
assertEquals(Object.keys(runtime.tools.session_notes_read.args), [
927+
"id",
928+
]);
929+
assertEquals(Object.keys(runtime.tools.session_search.args), [
930+
"query",
931+
]);
932+
} finally {
933+
void runtime.dispose();
934+
}
935+
});
936+
897937
it("exports graphiti as the plugin entrypoint", () => {
898938
assertEquals(typeof graphiti, "function");
899939
});
@@ -982,6 +1022,7 @@ describe("index", () => {
9821022
records.redisClientInstances[0],
9831023
);
9841024
assertEquals(records.sessionNotesArgs[0][1], {
1025+
groupId: "group-id",
9851026
sessionTtlSeconds: config.redis.sessionTtlSeconds,
9861027
});
9871028
assertStrictEquals(

src/index.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,16 @@ export const graphiti: Plugin = (
224224
ttlSeconds: config.redis.cacheTtlSeconds,
225225
driftThreshold: config.graphiti.driftThreshold,
226226
});
227+
const defaultGroupId = dependencies.makeGroupId(
228+
config.graphiti.groupIdPrefix,
229+
input.directory,
230+
);
231+
const defaultUserGroupId = dependencies.makeUserGroupId(
232+
config.graphiti.groupIdPrefix,
233+
input.directory,
234+
);
227235
const notesService = new dependencies.SessionNotesService(redisClient, {
236+
groupId: defaultGroupId,
228237
sessionTtlSeconds: config.redis.sessionTtlSeconds,
229238
});
230239
const batchDrain = new dependencies.BatchDrainService(
@@ -236,15 +245,6 @@ export const graphiti: Plugin = (
236245
drainRetryMax: config.redis.drainRetryMax,
237246
},
238247
);
239-
const defaultGroupId = dependencies.makeGroupId(
240-
config.graphiti.groupIdPrefix,
241-
input.directory,
242-
);
243-
const defaultUserGroupId = dependencies.makeUserGroupId(
244-
config.graphiti.groupIdPrefix,
245-
input.directory,
246-
);
247-
248248
const graphitiAsync = new dependencies.GraphitiAsyncService(
249249
graphitiClient,
250250
redisCache,

0 commit comments

Comments
 (0)