Linea Docs

Memory System

SuperMemory-inspired atomic fact storage with pgvector hybrid search and conflict resolution.

Memory System

Linea's memory system is inspired by SuperMemory and targets ~45% feature parity. It provides persistent, structured memory for AI agents across workflow runs: enabling agents to remember facts, user preferences, and past events.

Design Principles

  • Atomic facts: raw text is split into the smallest self-contained statements before storing
  • Semantic deduplication: new facts semantically equivalent to existing ones supersede them rather than duplicating
  • Hybrid retrieval: pgvector cosine similarity + keyword search, merged with score boosting
  • Temporal grounding: eventDate is separate from createdAt so facts can reference when something happened
  • User profiling: scope=user memories grouped by factType form a user's behavioral profile

Schema

memories: {
  id: uuid PK
  workspaceId: uuid FK → workspaces
  userId: uuid FKusers (nullable)
  threadId: text (nullable)         // agent conversation thread
  workflowId: uuid FKworkflows (nullable)
  scope: enum('thread','workflow','user')
  content: text                     // the atomic fact
  embedding: vector(1536)           // OpenAI text-embedding-3-small
  source: enum('manual','extracted','ingested')  default: 'manual'
  factType: enum('fact','preference','event','profile','system') nullable
  eventDate: timestamp (nullable)   // when the fact occurred (≠ createdAt)
  supersededById: uuid (nullable)   // points to the replacement memory
  confidence: real                  // 0.0–1.0, default 1.0
  metadata: jsonb                   // arbitrary structured data
  createdAt: timestamp
  updatedAt: timestamp
}
 
memory_sessions: {
  id: uuid PK
  workspaceId: uuid FK → workspaces
  rawContent: text                  // original input text
  memoriesExtracted: text[]         // IDs of newly created memories
  memoriesUpdated: text[]           // IDs of superseded memories
  createdAt: timestamp
}

Ingest Pipeline

POST /workspaces/:id/memories/ingest

Extract atomic facts

Claude Haiku (claude-haiku-4-5) receives the raw content and returns a JSON array of facts:

[
  { "content": "User prefers dark mode", "factType": "preference", "confidence": 0.97 },
  { "content": "User's name is Alex", "factType": "profile", "confidence": 0.99 },
  { "content": "Deployed to production on 2024-03-15", "factType": "event",
    "confidence": 0.95, "eventDate": "2024-03-15" }
]

Embed each fact

Each fact is embedded with OpenAI text-embedding-3-small (1536 dimensions).

Check for conflicts

For each embedding, query for the nearest existing memory (excluding superseded):

SELECT id, 1 - (embedding <=> $1::vector) AS similarity
FROM memories
WHERE workspace_id = $2 AND superseded_by_id IS NULL
ORDER BY embedding <=> $1::vector
LIMIT 1

If similarity >= 0.88 → the new fact supersedes the old one.

Insert and supersede

  • Insert the new memory row
  • If superseding: set old_memory.superseded_by_id = new_memory.id

Log the session

Record the ingest batch in memory_sessions with extracted/updated IDs.

Response

{
  "memoriesCreated": 3,
  "memoriesUpdated": 1,
  "sessionId": "uuid",
  "memories": [...]
}

POST /workspaces/:id/memories/search

{
  "query": "what does the user prefer?",
  "scope": "user",
  "limit": 10
}

Algorithm:

  1. Embed the query with text-embedding-3-small
  2. pgvector cosine → top 20 candidates with scores
  3. ilike keyword search → top 20 candidates
  4. Merge scores:
    • Vector-only result: use raw cosine similarity
    • Keyword match on top of vector: score + 0.2
    • Keyword-only (not in vector results): score = 0.3
  5. Exclude supersededById IS NOT NULL
  6. Return top-K sorted by merged score

User Profile

GET /workspaces/:id/memories/profile?userId=...

Returns scope=user memories (excluding superseded) grouped by factType:

{
  "facts": [...],
  "preferences": [...],
  "events": [...],
  "profile": [...],
  "system": [...],
  "uncategorized": [...]
}

Services

EmbeddingService

Wraps OpenAI text-embedding-3-small. Zero-vector fallback if OPENAI_API_KEY is not set: useful for local dev without AI billing.

ExtractionService

Wraps Claude Haiku with a strict extraction prompt. Falls back to storing the entire input as a single fact if:

  • ANTHROPIC_API_KEY is not set
  • Claude returns non-JSON
  • Any parse error occurs

Feature Parity vs SuperMemory

FeatureSuperMemoryLinea
Atomic extraction✅ Claude Haiku
Hybrid retrieval✅ Sub-300ms✅ pgvector + ilike
Conflict resolutionsupersededById
User profiles✅ Behavioral✅ grouped by factType
Temporal grounding✅ Dual-layereventDate column
Memory graph✅ Neo4j❌ Not implemented
Web/audio connectors❌ Not implemented

Enabling vector search requires the pgvector PostgreSQL extension. Run CREATE EXTENSION IF NOT EXISTS vector; before db:push.

On this page