No description
  • TypeScript 100%
Find a file
2026-04-04 22:20:13 -03:00
src feat: add memory-journal-plugin — conversation journal with two-tier summarization, vector search, and memory injection 2026-04-04 22:20:13 -03:00
tests feat: add memory-journal-plugin — conversation journal with two-tier summarization, vector search, and memory injection 2026-04-04 22:20:13 -03:00
.gitignore feat: add memory-journal-plugin — conversation journal with two-tier summarization, vector search, and memory injection 2026-04-04 22:20:13 -03:00
package-lock.json feat: add memory-journal-plugin — conversation journal with two-tier summarization, vector search, and memory injection 2026-04-04 22:20:13 -03:00
package.json feat: add memory-journal-plugin — conversation journal with two-tier summarization, vector search, and memory injection 2026-04-04 22:20:13 -03:00
README.md feat: add memory-journal-plugin — conversation journal with two-tier summarization, vector search, and memory injection 2026-04-04 22:20:13 -03:00
tsconfig.json feat: add memory-journal-plugin — conversation journal with two-tier summarization, vector search, and memory injection 2026-04-04 22:20:13 -03:00
vitest.config.ts feat: add memory-journal-plugin — conversation journal with two-tier summarization, vector search, and memory injection 2026-04-04 22:20:13 -03:00

@fractal-synapse/memory-journal-plugin

Conversation log layer for the multi-plugin memory stack. Records every conversation turn, generates two-tier summaries (turn summaries and session summaries), and provides vector search across past sessions. Injects relevant context at session start and before each user turn via memory-injection-plugin.

What it does

  • Stores one row per user/assistant exchange (raw content + tool calls)
  • Generates 1-2 sentence turn summaries asynchronously after each exchange
  • Compiles 3-5 sentence session summaries when a conversation goes inactive
  • Embeds both summary tiers for semantic vector search
  • Implements IMemoryInjectionPlugin — injects relevant past context automatically
  • Exposes a memory_journal tool for direct agent access to the journal

Two-tier summary model

Tier Content When generated Storage
Turn summary 1-2 sentences per exchange Async queue after message:assistant {scope}_jnl_turns.summary
Session summary 3-5 sentences from all turn summaries Inactivity event + startup check {scope}_jnl_sessions.summary

Turn summaries capture the full picture: user message, all tool calls/results, and the final assistant response. Session summaries are compiled from turn summaries (not raw messages), making them cheap to regenerate.

Session summaries are triggered by ConversationMonitorPlugin.onInactivity() (default: 10 minutes of silence). On initializeAgent(), a startup check immediately summarizes any stale sessions from previous crashed or closed runs.

Prerequisites

  • sqlite-database-plugin — provides DatabaseInterface with SQLite + sqlite-vec
  • An embeddings plugin — any plugin implementing EmbeddingsInterface
  • conversation-monitor-plugin — fires inactivity events when conversations go silent
  • memory-injection-plugin — coordinator that calls getContext() at injection points

Installation

npm install @fractal-synapse/memory-journal-plugin

Usage

import { SqliteDatabasePlugin } from '@fractal-synapse/sqlite-database-plugin';
import { NomicEmbeddingsPlugin } from '@fractal-synapse/nomic-embeddings-plugin';
import { ConversationMonitorPlugin } from '@fractal-synapse/conversation-monitor-plugin';
import { MemoryInjectionPlugin } from '@fractal-synapse/memory-injection-plugin';
import { MemoryJournalPlugin } from '@fractal-synapse/memory-journal-plugin';

// 1. Set up dependencies
const db = new SqliteDatabasePlugin({ dbPath: '/path/to/data/agent.db' });
const embeddings = new NomicEmbeddingsPlugin();
const monitor = new ConversationMonitorPlugin({ storage });
const injection = new MemoryInjectionPlugin({ totalTokenBudget: 1250, modelRegistry });

// 2. Create the journal plugin
const journal = new MemoryJournalPlugin({
  db,
  embeddings,
  modelRegistry,
  conversationMonitor: monitor,
});

// 3. Register journal with the injection plugin
injection.register(journal);

// 4. Register the memory_journal tool
for (const def of journal.getToolDefinitions(agent)) {
  toolRegistry.registerTool(def.name, def);
}

// 5. Wire everything into the agent
await injection.initializeAgent(agent);

Config

Option Type Default Description
db DatabaseInterface SQLite database with sqlite-vec extension
embeddings EmbeddingsInterface Any embeddings provider
modelRegistry ModelRegistry Model registry for LLM summarization calls
conversationMonitor { onInactivity, offInactivity } Duck-typed reference to ConversationMonitorPlugin
summarizationModel string getExtractionModelName() Override model for turn and session summarization
sessionStartInjection boolean true Inject relevant session summaries at conversation start
turnInjection boolean true Inject relevant context on every user turn
recentSessionCount number 5 Max session summaries for session-start injection
turnInjectionCount number 3 Max results for per-turn injection
logger LoggingInterface Logger for diagnostic output

The memory_journal tool

The plugin exposes a single memory_journal tool with four actions:

Action Parameters Description
search query (required), session_id, limit Vector search across both turn and session summaries, merged by distance
list_sessions limit, offset Recent sessions with metadata and summary, paginated
get_session session_id (required), limit, offset All turn summaries for a specific session
get_turn turn_id (required) Full raw user_content, tool_calls, and assistant_content

get_turn is the only path that returns raw message content. All other actions operate on summaries.

Context injection

The journal implements IMemoryInjectionPlugin with two independently-toggled injection modes:

Session-start (sessionStartInjection) — queries session embeddings, ranks by semantic similarity to the opening message, and injects the top recentSessionCount (default: 5) session summaries into the system prompt. The injected content includes session IDs so the agent can reference them:

Past session summaries (most relevant first):

[2026-04-01] (session_id=severin-2026-04-01-0900) Implemented the memory journal plugin...

[2026-03-29] (session_id=severin-2026-03-29-1430) Debugged SQLite migration failures...

Per-turn (turnInjection) — on every user message, searches both turn and session embeddings, merges results by distance, and injects the top turnInjectionCount (default: 3) via userMessageAppend. Acts as a passive hint system — the agent sees relevant past context and can use the memory_journal tool to dig deeper.

Both injection paths use vector search for relevance ranking, not recency ordering. The injected fragment is eligible for LLM synthesis by the injection plugin.

Table-name scoping

All journal tables are prefixed with a safeScope derived from the agent identity:

const rawScope = agent.getParentDefinitionId() ?? agent.getDefinitionName() ?? 'default';
const safeScope = rawScope.replace(/[^a-zA-Z0-9]/g, '_');

For an agent named severin, tables become severin_jnl_turns, severin_jnl_sessions, severin_jnl_turn_embeddings, and severin_jnl_session_embeddings. The table name is the scope — there is no storage_scope column, and queries have no scoping WHERE filter. Dream agents operating on a parent agent's journal resolve to the same safeScope and share the same tables.

Tables are created idempotently in initializeAgent() using CREATE TABLE IF NOT EXISTS.

Database schema

Table Description
{scope}_jnl_turns One row per exchange: user content, tool calls (JSON), assistant content, summary
{scope}_jnl_sessions One row per conversation: summary, last activity, turn count
{scope}_jnl_turn_embeddings vec0 virtual table — turn summary embeddings
{scope}_jnl_session_embeddings vec0 virtual table — session summary embeddings