Graph memory (Neo4j)
Per-user long-term memory backed by a Neo4j knowledge graph, instead of replaying the full conversation transcript on every turn. A private memory agent writes facts to each user's graph in the background; your main agent only ever sees a plain-text summary of what's relevant.
Optional peer dependency
neo4j-driver (dynamically imported, same pattern as RedisSessionStore/ioredis — the rest of the SDK works fine without it installed).npm install neo4j-driverSetup
import {
createClient, anthropic, defineAgent, createSession, InMemorySessionStore,
enableGraphMemory, chatWithMemory, ensureGraphConstraints,
} from "samai-sdk";
const client = createClient({ provider: anthropic({ apiKey: process.env.ANTHROPIC_API_KEY }) });
const session = createSession("user-123", new InMemorySessionStore());
const memory = enableGraphMemory({
client,
creds: { uri: process.env.NEO4J_URI!, username: process.env.NEO4J_USERNAME!, password: process.env.NEO4J_PASSWORD! },
userId: "user-123",
session,
}).build();
const assistant = defineAgent({
name: "assistant",
instructions: "You are a helpful assistant.",
model: "claude-sonnet-4-6",
});
await ensureGraphConstraints(await memory.driverPromise); // once at startup, idempotent
memory.start(); // background sweep begins (default every 3 min)
const result = await chatWithMemory(client, assistant, memory, "Planning a trip to Kyoto next month.", { session });Serving many users
enableGraphMemory({ creds }) creates its own driver — fine for one user, wasteful for many. createGraphMemoryManager() shares a single driver (and its connection pool) across every user instead.
import { createGraphMemoryManager, chatWithMemory } from "samai-sdk";
const manager = createGraphMemoryManager({ client, creds: { uri, username, password } });
// call per incoming request — creates on first contact, reuses (same driver,
// same background sweep timer) after
const memory = manager.getOrCreate(userId, session);
if (!manager.has(userId)) memory.start();
await manager.stopAll(); // stops every user's sweep, closes the ONE shared driverTimestamped facts & contradictions
The memory agent writes ordinary facts through upsert_fact automatically (wired in already — nothing to configure), which guarantees timestamping and can retire a contradicting old fact in the same call. applyRecencyDecay() fades old, unreinforced facts and prunes anything past a threshold.
// what the memory agent does when it hears "I don't hike anymore":
// upsert_fact({ relation: "DISLIKES", objectLabel: "Topic", objectName: "hiking",
// contradicts: ["LIKES"] })
// -> old LIKES->hiking edge deleted, THEN DISLIKES->hiking written.
import { applyRecencyDecay } from "samai-sdk";
const report = await applyRecencyDecay({
driverPromise: memory.driverPromise,
userId: "user-123",
halfLifeDays: 30, // a fact's weight halves every 30 days without reinforcement
pruneThreshold: 0.05, // facts decayed below this are deleted outright
});Self-correction
runSelfCorrection() / startSelfCorrectionLoop() run real Cypher diagnostics (duplicate nodes, overly generic relationship types, relationship-count overload) and only invoke a curator agent — with the specific findings, not the whole graph — when there's something to actually fix.
Feed ranking
createFeedEngine() ranks content with a hybrid score — social proximity, interest-graph affinity (pulled from the same memory graph upsert_fact writes to), and log-scaled engagement — rather than pure follow-graph or raw like-count ranking.
import { createFeedEngine } from "samai-sdk";
const feed = createFeedEngine({ driverPromise: memory.driverPromise });
await feed.upsertPost({ id: "post-1", niche: "hiking", topics: ["trail running", "gear"] });
await feed.recordFollow("user-123", "user-456");
await feed.recordLike("user-456", "post-1");
const ranked = await feed.getFeed({ userId: "user-123", limit: 20 });
// ranked[0] is highest-scored: social proximity + interest-graph match + log-scaled engagementThe pieces
| Need | Function |
|---|---|
| Give an agent per-user long-term memory | enableGraphMemory() |
| Share one DB connection across many users | createGraphMemoryManager() |
| Inject memory into your main agent's turn | chatWithMemory() |
| Fade/prune stale facts | applyRecencyDecay() |
| Clean up duplicate/flat relationships | runSelfCorrection() / startSelfCorrectionLoop() |
| Rank content for a feed | createFeedEngine() |
| DB constraints + right-to-be-forgotten | ensureGraphConstraints() / deleteUserGraph() |
| Observability across all of the above | createMetricsCollector() |
Not run against a real Neo4j instance