@one710/recollect
v1.7.0
Published
Auto-summarizing memory layer for AI agents using Node.js native SQLite.
Maintainers
Readme
@one710/recollect
Recollect gives your AI agents "infinite memory" while keeping your context window lean and your bills low.
It's a provider-agnostic memory layer that automatically compacts long-running conversations into high-density summaries, protecting your core instructions while ensuring the agent never "forgets" the goal.
🚀 Why Use Recollect?
- Universal Compatibility: Works with any message schema. OpenAI, Anthropic, Gemini, or your own custom format—Recollect handles them all as generic dictionaries.
- Recursive Summarization: As history grows, Recollect merges old turns into a rolling thread of "checkpoint summaries," preserving intent without bloating tokens.
- Instruction Guardrails: Never lose your system prompt. Recollect intelligently protects "pinned" roles (like
systemordeveloper) from being summarized away. - Configurable Summary Roles: Need summaries to be
systemmessages? Ordeveloperroles for o1/o3 models? Or evenusermessages? You decide. - Fast Persistence: Built-in SQLite support for production-ready persistence, or a high-speed In-Memory adapter for ephemeral workers.
📦 Installation
npm install @one710/recollectIf you want persistent storage (recommended):
npm install sqlite3🛠️ Quick Start
import { MemoryLayer } from "@one710/recollect";
const memory = new MemoryLayer({
maxTokens: 4096, // Maximum context budget
// Mandatory: Use any tokenizer or simple length check
countTokens: (msg) => JSON.stringify(msg).length / 4,
// High-density summarizer callback
summarize: async ({ summaryPrompt }) => {
// Call your LLM here (OpenAI, Anthropic, local, etc.)
return "The user is asking about building agentic systems...";
},
});
const sessionId = "agent:researcher-1";
const runId = "run-2026-03-19-evt-123"; // one id per agent run/event
// Add arbitrary message shapes
await memory.addMessage(sessionId, runId, {
role: "user",
content: "Analyze the latest trends in autonomous agents.",
});
// Retrieve the compact, ready-to-send prompt
const messages = await memory.getPromptMessages(sessionId);🌍 Universal Provider Support
Because Recollect treats messages as generic objects, you can use it with any provider:
OpenAI / Anthropic
await memory.addMessage(id, null, {
role: "assistant",
content: "Understood.",
});Multimodal / Complex Content
await memory.addMessage(id, null, {
role: "user",
content: [{ type: "image_url", image_url: { url: "..." } }],
});🧵 Run-Aware Compaction (runId)
Recollect supports run-scoped compaction using a dedicated runId field in storage.
- Use a unique
runIdper agent run/event. - Pass the same
runIdtoaddMessage/addMessagesfor all messages generated in that run. - When compaction triggers for that run, Recollect keeps that run as the tail and compacts older history first.
- This helps avoid splitting in-progress tool chains (e.g. tool call/result pairs) during compaction.
Example
const sessionId = "slack:C123:thread-abc";
const runId = crypto.randomUUID();
await memory.addMessages(sessionId, runId, [
{
type: "message",
role: "user",
content: [{ type: "input_text", text: "check this" }],
},
{ type: "function_call", callId: "call_1", name: "my_tool", arguments: "{}" },
{
type: "function_call_result",
callId: "call_1",
name: "my_tool",
output: "ok",
},
]);If you don't need run scoping for a call, pass null for runId.
🗄️ Storage Model (Updated)
SQLite messages now stores:
sessionIdrunId(nullable, dedicated column)data(JSON payload)
The public prompt/history methods (getMessages, getPromptMessages) return message payloads only; run metadata stays in storage internals.
🔧 Core API Signatures
addMessage(sessionId: string, runId: string | null, message: Record<string, any>): Promise<void>
addMessages(sessionId: string, runId: string | null, messages: Record<string, any>[]): Promise<void>
getMessages(sessionId: string, runId?: string | null): Promise<Record<string, any>[]>
getPromptMessages(sessionId: string): Promise<Record<string, any>[]>
compactNow(sessionId: string, runId?: string | null): Promise<void>
compactIfNeeded(sessionId: string, options?: { mode?: "manual" | "auto-pre" | "auto-post" | "ingest"; reason?: string; runId?: string | null; force?: boolean }): Promise<void>⚙️ Advanced Configuration
| Option | Type | Default | Description |
| :---------------------- | :------------------ | :--------------- | :----------------------------------------------------- |
| maxTokens | number | Required | The token budget before compaction triggers. |
| countTokens | TokenCounter | Required | (message: any) => number. Your specific token logic. |
| summarize | SummarizeCallable | Required | Async function that performs the summarization. |
| summaryRole | string | "system" | Role assigned to generated summary messages. |
| threshold | number | 0.9 | Trigger compaction at 90% of maxTokens. |
| keepRecentUserTurns | number | 4 | Number of recent user turns to keep unsummarized. |
| keepRecentMessagesMin | number | 8 | Minimum messages to keep at the tail of the history. |
| renderMessage | MessageRenderer | JSON.stringify | Custom formatting for the summarizer's input. |
| storage | Adapter | SQLite | Persistent sqlite3 or ephemeral InMemory. |
🧪 Development
npm install
npm run build
npm test📜 License
MIT © one710
