npm package: @nativesquare/seshat
Description: Agentic memory system for Convex. Self-editing memory, episodic recall via vector search, and automatic compaction inspired by Letta and OpenClaw.
Latest version: 1.0.0

# Seshat

[![npm version](https://badge.fury.io/js/@nativesquare%2Fseshat.svg)](https://badge.fury.io/js/@nativesquare%2Fseshat)

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](https://github.com/letta-ai/letta) and [OpenClaw](https://github.com/openclaw/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

```bash
npm install @nativesquare/seshat
```

Add the component to your Convex app:

```ts
// 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

```ts
// 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

```ts
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:

```bash
pnpm install
pnpm run dev
```

Set `OPENAI_API_KEY` in your Convex dashboard environment variables.

## License

Apache-2.0