Docs / Design
Edit on GitHub

Memory Architecture

Memory in OpenViber refers to persistent knowledge that outlives a single conversation. This document defines the memory model, storage format, and injection strategies.

Related: Memory is one part of the Four-File Personalization Pattern. See that document for how MEMORY.md works together with SOUL.md, USER.md, and IDENTITY.md to create a coherent viber configuration.

1. Design Principles

  1. Per-viber storage: Memory lives in ~/.openviber/vibers/{id}/ as human-readable files
  2. Board-controlled injection: The Viber Board decides what memory to include in each request
  3. No implicit retrieval: The daemon does not autonomously search memory — it receives memory as context
  4. Optional indexing: Semantic search is opt-in; flat files work without it

2. Memory Tiers

┌─────────────────────────────────────────────────────────────┐
│                     Memory Hierarchy                         │
├─────────────────────────────────────────────────────────────┤
│  Tier 1: Conversation Context (ephemeral)                   │
│  • Current session messages                                  │
│  • Managed by Viber Board, sent per-request                 │
├─────────────────────────────────────────────────────────────┤
│  Tier 2: Working Memory (task-scoped)                       │
│  • task.md — current plan and progress                       │
│  • Active for duration of task                               │
├─────────────────────────────────────────────────────────────┤
│  Tier 3: Long-term Memory (persistent)                      │
│  • MEMORY.md — curated notes and preferences                │
│  • memory/YYYY-MM-DD.md — daily logs                        │
│  • Persists across tasks and sessions                        │
├─────────────────────────────────────────────────────────────┤
│  Tier 4: Semantic Index (optional)                          │
│  • memory/{viber-id}.sqlite — vector embeddings             │
│  • Enables similarity search over Tier 3                     │
└─────────────────────────────────────────────────────────────┘

3. File Formats

MEMORY.md (Curated Long-term Notes)

# Memory

## User Preferences
- Prefers concise responses
- Uses TypeScript for all projects
- Dark mode preferred in generated UIs

## Project Context
- Main project: OpenViber framework
- Repository: github.com/dustland/openviber
- Package manager: pnpm (never npm or yarn)

## Important Decisions
- 2024-01-10: Chose stateless daemon architecture
- 2024-01-15: Adopted workspace-first storage model

## Learned Patterns
- User often asks for code reviews before commits
- Prefers function components over class components in React

Daily Logs (memory/YYYY-MM-DD.md)

# 2024-01-15

## Session Summary
- Worked on authentication flow
- Fixed bug in WebSocket reconnection
- User approved new error handling approach

## Key Decisions
- Use JWT for session tokens (not cookies)
- Set default timeout to 30 seconds

## Notes for Future
- User mentioned wanting to add rate limiting next week
- Remember to check test coverage before PR

task.md (Working Memory)

# Current Task

## Goal
Build a landing page with contact form

## Plan
- [x] Set up Next.js project
- [x] Create hero section
- [ ] Add contact form (in progress)
- [ ] Deploy to Vercel

## Context
- Using Tailwind CSS for styling
- Form should validate email before submit
- User wants dark theme only

## Blockers
None currently

4. Memory Injection

When to Inject Memory

ScenarioWhat to Inject
New conversationMEMORY.md (full or excerpt)
Continuing tasktask.md + relevant MEMORY.md sections
Reference questionRelevant daily logs via search
Complex taskMEMORY.md + recent daily logs

Injection Format

Memory is injected as a system message or user message prefix:

// Option 1: System message section
const systemPrompt = `
You are Viber, a helpful assistant.

<memory>
${memoryContent}
</memory>

<task>
${taskContent}
</task>
`;

// Option 2: Separate user message
const messages = [
  { role: "system", content: baseSystemPrompt },
  { role: "user", content: `Context from memory:\n${memoryContent}` },
  { role: "user", content: actualUserMessage },
];

Size Management

Memory injection should respect context limits:

interface MemoryInjectionConfig {
  max_memory_tokens: number;      // Default: 2000
  max_task_tokens: number;        // Default: 1000
  max_daily_logs: number;         // Default: 3 (most recent)
  truncation_strategy: "head" | "tail" | "smart";
}

5. Memory Updates

Automatic Updates (by Viber Board)

The Viber Board can automatically update memory based on conversation:

// After task completion
async function updateMemory(result: TaskResult) {
  // 1. Append to daily log
  await appendToDailyLog({
    date: new Date(),
    summary: result.summary,
    decisions: extractDecisions(result.text),
  });

  // 2. Optionally update MEMORY.md if significant
  if (isSignificantLearning(result)) {
    await appendToMemory({
      section: "Learned Patterns",
      content: extractLearning(result),
    });
  }
}

Manual Curation

Users can edit memory files directly. The daemon reads them as static context:

# Edit long-term memory
vim ~/.openviber/vibers/default/MEMORY.md

# View recent daily logs
ls ~/.openviber/vibers/default/memory/
cat ~/.openviber/vibers/default/memory/2024-01-15.md

Viber-Suggested Updates

The viber can suggest memory updates in its response:

I've completed the task. 

<memory_suggestion>
Add to MEMORY.md under "Project Context":
- Email validation uses zod schema with custom regex
</memory_suggestion>

The Viber Board parses this and prompts the user to accept/reject.

6. Semantic Index (Optional)

For large memory stores, an optional SQLite-based semantic index enables similarity search.

Schema

-- ~/.openviber/vibers/{id}/memory.sqlite

CREATE TABLE chunks (
  id INTEGER PRIMARY KEY,
  source_file TEXT NOT NULL,      -- e.g., "MEMORY.md" or "2024-01-15.md"
  content TEXT NOT NULL,          -- Original text chunk
  embedding BLOB NOT NULL,        -- Vector embedding (float32 array)
  created_at TEXT DEFAULT CURRENT_TIMESTAMP,
  metadata JSON                   -- Optional: section headers, tags
);

CREATE INDEX idx_source ON chunks(source_file);

-- Virtual table for vector search (using sqlite-vss or similar)
CREATE VIRTUAL TABLE chunks_vss USING vss0(embedding(1536));

Indexing Process

async function indexMemoryFile(filePath: string) {
  const content = await readFile(filePath);
  const chunks = splitIntoChunks(content, {
    maxChunkSize: 500,
    overlap: 50,
    splitOn: ["##", "\n\n", ". "],
  });

  for (const chunk of chunks) {
    const embedding = await getEmbedding(chunk.text);
    await db.run(`
      INSERT INTO chunks (source_file, content, embedding)
      VALUES (?, ?, ?)
    `, [filePath, chunk.text, embedding]);
  }
}

Search Query

async function searchMemory(query: string, limit: number = 5) {
  const queryEmbedding = await getEmbedding(query);
  
  const results = await db.all(`
    SELECT c.content, c.source_file, c.metadata
    FROM chunks c
    JOIN chunks_vss v ON c.id = v.rowid
    WHERE vss_search(v.embedding, ?)
    LIMIT ?
  `, [queryEmbedding, limit]);

  return results;
}

When to Use Semantic Search

Memory SizeRecommendation
< 10 files, < 50KBDirect file read, no index needed
10-100 filesIndex recommended for daily logs
> 100 filesIndex required for practical retrieval

7. Memory Flush (Pre-Compaction)

Before compacting conversation history, a “memory flush” extracts durable notes:

async function memoryFlush(conversation: Message[]) {
  // 1. Identify key information to preserve
  const extraction = await viber.generateText({
    messages: [
      {
        role: "system",
        content: `Extract key decisions, preferences, and facts from this conversation 
                  that should be remembered for future sessions. Format as bullet points.`,
      },
      ...conversation,
    ],
  });

  // 2. Append to daily log
  await appendToDailyLog({
    date: new Date(),
    content: extraction.text,
    source: "memory_flush",
  });

  // 3. Optionally update MEMORY.md
  if (containsLongTermLearnings(extraction)) {
    await promptUserToUpdateMemory(extraction);
  }
}

8. Privacy & Retention

Data Retention

# In config.yaml
memory:
  retention:
    daily_logs_days: 90          # Delete logs older than 90 days
    index_rebuild_days: 7        # Rebuild index weekly
  
  # What to exclude from indexing
  exclude_patterns:
    - "*.secret.md"
    - "credentials/*"

Memory Isolation

In multi-viber setups, each viber has isolated memory:

~/.openviber/vibers/
├── dev/
│   ├── MEMORY.md                # Dev viber's curated memory
│   ├── memory/                  # Dev viber's daily logs
│   └── memory.sqlite            # Dev viber's semantic index
└── researcher/
    ├── MEMORY.md
    ├── memory/
    └── memory.sqlite

9. Implementation Status

ComponentStatusNotes
MEMORY.md file format✅ DefinedHuman-editable markdown
Daily log format✅ DefinedAuto-created per day
task.md format✅ DefinedBoard-managed
Memory injection🔶 PartialViber Board responsibility
Semantic index⏳ PlannedOptional enhancement
Auto-extraction⏳ PlannedMemory flush on compaction

Summary

Memory in OpenViber is file-first and Board-controlled:

  1. Files are the source of truth — MEMORY.md and daily logs under ~/.openviber/vibers/{id}/
  2. Viber Board injects memory — Node receives memory as context, doesn’t retrieve autonomously
  3. Semantic search is optional — SQLite index for large memory stores
  4. Human-readable and editable — Users can directly edit memory files

This design keeps the daemon stateless while enabling rich, persistent context across sessions.