- TypeScript 100%
|
|
||
|---|---|---|
| src | ||
| tests | ||
| .gitignore | ||
| package-lock.json | ||
| package.json | ||
| README.md | ||
| tsconfig.json | ||
| vitest.config.ts | ||
@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_journaltool 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— providesDatabaseInterfacewith SQLite + sqlite-vec- An embeddings plugin — any plugin implementing
EmbeddingsInterface conversation-monitor-plugin— fires inactivity events when conversations go silentmemory-injection-plugin— coordinator that callsgetContext()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 |