@nativesquare/seshat
v1.0.0
Published
Agentic memory system for Convex. Self-editing memory, episodic recall via vector search, and automatic compaction inspired by Letta and OpenClaw.
Readme
Seshat
Agentic memory system for Convex. Drop-in persistent memory for AI agents with self-editing core memory, episodic recall via hybrid search, and automatic compaction.
Inspired by Letta and OpenClaw.
What it does
Seshat gives your Convex-powered AI agent a memory that persists across conversations:
- Core Memory — A freeform Markdown document the agent maintains about each user. The model decides what headings and structure to use. Think of it as the agent's personal notes.
- Episodic Memory — Individual events and interactions stored with vector embeddings for semantic recall. Tagged, scored by importance, and searchable.
- Daily Logs — Per-day summaries written during compaction. Today's and yesterday's logs are automatically included in context.
- Compaction — When the token count crosses a threshold, Seshat runs a silent LLM turn to flush important context into memory, summarizes the conversation, and trims old messages.
Search pipeline
Episodic search uses a three-stage pipeline so the agent gets the most relevant memories, not just the closest vectors:
Query Text ──→ Full-Text Search (BM25) ──┐
├─→ Weighted Merge ─→ Temporal Decay ─→ MMR ─→ Top-K
Query Embedding ──→ Vector Search ────────┘- Hybrid search — Combines vector similarity (semantic meaning) with keyword matching (exact terms like error codes, names, IDs). On by default.
- Temporal decay — Exponential recency boost so recent memories rank higher than stale ones. 30-day half-life by default.
- MMR re-ranking — Reduces near-duplicate results for diverse context. Off by default, enable when needed.
Installation
npm install @nativesquare/seshatAdd the component to your Convex app:
// convex/convex.config.ts
import { defineApp } from "convex/server";
import seshat from "@nativesquare/seshat/convex.config.js";
const app = defineApp();
app.use(seshat);
export default app;Quick start
// convex/agent.ts
"use node";
import { internalAction } from "./_generated/server.js";
import { components } from "./_generated/api.js";
import { Seshat } from "@nativesquare/seshat";
const seshat = new Seshat({
component: components.seshat,
});
export const respond = internalAction({
args: { userId: v.string() },
handler: async (ctx, args) => {
// 1. Build the memory-enriched system prompt
const memoryContext = await seshat.assembleMemoryContext(ctx, {
userId: args.userId,
currentMessage: "the user's latest message",
});
// 2. Get memory tools for the LLM
const tools = await seshat.getMemoryTools(ctx, {
userId: args.userId,
});
// 3. Call your LLM with memory context and tools
const { text, usage } = await generateText({
model: openai("gpt-4o-mini"),
system: `You are a helpful assistant.\n\n${memoryContext}`,
messages: conversationMessages,
tools,
});
// 4. Track tokens and trigger compaction if needed
const result = await seshat.afterResponse(ctx, {
userId: args.userId,
tokensUsed: usage?.totalTokens ?? 0,
messages: allMessages,
});
if (result.compacted) {
// Trim old messages from your messages table
// result.archivedBeforeIndex tells you where to cut
}
},
});Configuration
const seshat = new Seshat({
component: components.seshat,
// Compaction
compactionThreshold: 80_000, // tokens before compaction triggers
flushTokenBudget: 8_000, // token budget for the memory flush LLM turn
compactionModel: "gpt-4o-mini", // model for compaction/summarization
embeddingModel: "text-embedding-3-small", // must output 1536-d vectors
// Search pipeline
search: {
hybrid: {
enabled: true, // combine vector + keyword search
vectorWeight: 0.7, // weight for semantic similarity
textWeight: 0.3, // weight for keyword matches
},
temporalDecay: {
enabled: true, // boost recent memories
halfLifeDays: 30, // score halves every 30 days
},
mmr: {
enabled: false, // diversity re-ranking
lambda: 0.7, // 0 = max diversity, 1 = max relevance
},
},
});All options have sensible defaults. new Seshat({ component: components.seshat }) works out of the box.
API
Memory operations
| Method | Context | Description |
|--------|---------|-------------|
| getCoreMemory(ctx, { userId }) | Query | Read the user's core memory document |
| writeCoreMemory(ctx, { userId, content }) | Mutation | Replace the core memory document |
| addEpisode(ctx, { userId, content, tags, importance, embedding? }) | Mutation | Store an episodic memory |
| searchEpisodes(ctx, { userId, query?, queryEmbedding, limit? }) | Action | Hybrid search over episodic memories |
| getDailyLog(ctx, { userId, date }) | Query | Read a specific day's log |
| getDailyLogs(ctx, { userId, dates }) | Query | Read multiple days' logs |
| writeDailyLog(ctx, { userId, date, content, metrics? }) | Mutation | Write/update a daily log |
| getAgentState(ctx, { userId }) | Query | Read token count, compaction stats |
Agent integration
| Method | Description |
|--------|-------------|
| getMemoryTools(ctx, { userId }) | Returns Vercel AI SDK tool definitions (memory_read, memory_write, memory_search) |
| assembleMemoryContext(ctx, { userId, currentMessage? }) | Builds a Markdown string with core memory, daily logs, and relevant episodes for the system prompt |
| afterResponse(ctx, { userId, tokensUsed, messages }) | Tracks tokens, triggers compaction when threshold is crossed |
Memory tools (given to the LLM)
| Tool | What it does |
|------|-------------|
| memory_read | Read core memory (full Markdown doc) or search episodic memory |
| memory_write | Update core memory (read-edit-write cycle) or save an episodic event |
| memory_search | Search episodic memory by semantic similarity + keywords |
Architecture
┌─────────────────────────────────────────────────────────┐
│ YOUR CONVEX APP │
│ │
│ convex.config.ts ──→ app.use(seshat) │
│ agent.ts ──→ Seshat class │
│ • assembleMemoryContext() ──→ system prompt │
│ • getMemoryTools() ──→ LLM tools │
│ • afterResponse() ──→ compaction │
└──────────────────────┬──────────────────────────────────┘
│
│ components.seshat
▼
┌─────────────────────────────────────────────────────────┐
│ SESHAT COMPONENT │
│ │
│ ┌─────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Core Memory │ │ Episodic │ │ Daily Logs │ │
│ │ (1 doc/ │ │ Memory │ │ (1 doc/ │ │
│ │ user) │ │ + vector │ │ user/day) │ │
│ │ │ │ + search │ │ │ │
│ │ │ │ index │ │ │ │
│ └─────────────┘ └──────────────┘ └──────────────┘ │
│ │
│ ┌─────────────┐ │
│ │ Agent State │ tokens, compaction count, version │
│ └─────────────┘ │
└─────────────────────────────────────────────────────────┘Example app
The repo includes a full chat demo with a live Memory Inspector sidebar. To run it:
pnpm install
pnpm run devSet OPENAI_API_KEY in your Convex dashboard environment variables.
License
Apache-2.0
