Skip to main content

OLAP-based memory engine for AI agents

Project description

EpisodicDB

An OLAP-based memory engine for AI agents.

Existing agent memory systems (Mem0, Zep, Letta) are designed as search systems. EpisodicDB treats agent memory as an analytics problem — aggregation, time-series patterns, causal tracing, temporal facts, and vector similarity all in a single query engine.

-- "Which tools failed most this week?"
SELECT tool_name, COUNT(*) AS failures
FROM tool_calls
WHERE outcome = 'failure'
  AND called_at >= NOW() - INTERVAL '7 days'
GROUP BY tool_name
ORDER BY failures DESC;

-- "Find past episodes similar to this context that succeeded"
SELECT *, array_cosine_distance(context_embedding, ?) AS dist
FROM episodes
WHERE status = 'success'
ORDER BY dist ASC
LIMIT 5;

The Problem

Query type Example Vector search?
Similarity "Seen a similar error before?" Yes
Aggregation "How many tool failures this week?" No
Time-series "Do failures spike in the afternoon?" No
Causal trace "What tool ran right before failures?" No
Comparison "Worse than last week?" No
Absence "Tools that never succeeded?" No
Temporal "What was the user's timezone last Tuesday?" No

EpisodicDB answers all of them. Vector similarity is just another SQL operator.

Architecture

EpisodicDB
├── WriterMixin      record_episode / record_tool_call / record_decision / record_fact
├── AnalyticsMixin   6 analytics methods + vector similarity
└── TemporalMixin    facts_as_of / fact_history

Engine: DuckDB (OLAP) + VSS extension (HNSW vector index)
Schema: episodes + tool_calls + decisions + facts

Install

pip install episodicdb

Quick Start

Python SDK

from episodicdb import EpisodicDB

with EpisodicDB(agent_id="my-agent") as db:
    # Record what happened
    ep_id = db.record_episode(
        status="failure",
        task_type="file_edit",
        context={"file": "auth.py", "error": "permission denied"},
    )
    db.record_tool_call(ep_id, "Edit", "failure",
                        duration_ms=120, error_message="permission denied")
    db.record_tool_call(ep_id, "Bash", "success", duration_ms=50)

    # Record temporal facts (auto-supersedes previous values)
    db.record_fact("user_timezone", "Asia/Seoul", episode_id=ep_id)
    db.record_fact("user_timezone", "America/New_York")  # closes previous

    # Analyze patterns
    print(db.top_failing_tools(days=7))
    # [{"tool_name": "Edit", "failures": 5}, ...]

    print(db.before_failure_sequence("Edit"))
    # [{"prev_tool": "Bash", "count": 4}, ...]

    print(db.compare_periods("failure_rate", days=7))
    # {"period_a": 0.32, "period_b": 0.18, "delta": 0.14}

    # Time-travel query
    from datetime import datetime
    print(db.facts_as_of(datetime(2025, 3, 15)))
    # [{"key": "user_timezone", "value": "Asia/Seoul", ...}]

MCP Server (Claude, OpenAI Agents SDK)

EpisodicDB ships an MCP server with 12 tools over stdio.

episodicdb-mcp --agent-id my-agent
episodicdb-mcp --agent-id my-agent --db ./memory.db

Claude Desktop (claude_desktop_config.json):

{
  "mcpServers": {
    "episodicdb": {
      "command": "episodicdb-mcp",
      "args": ["--agent-id", "my-agent"]
    }
  }
}

Claude Code:

claude mcp add --scope user episodicdb -- episodicdb-mcp --agent-id my-agent --client-type claude-code --daemon

OpenAI Agents SDK:

from agents import Agent
from agents.mcp import MCPServerStdio

agent = Agent(
    name="my-agent",
    instructions="You have access to episodic memory.",
    mcp_servers=[MCPServerStdio(
        command="episodicdb-mcp",
        args=["--agent-id", "my-agent"],
    )],
)

Daemon Mode (multi-session)

DuckDB has a single-writer lock. If you run multiple MCP clients (e.g. several Claude Code sessions), use --daemon so a single process owns the DB and clients route through HTTP:

# CLI flag — the MCP server auto-starts a daemon on localhost
episodicdb-mcp --agent-id my-agent --daemon

# Or run the daemon manually
python -m episodicdb.daemon --agent-id my-agent --port 7823
# Python SDK equivalent
from episodicdb import EpisodicDBClient

client = EpisodicDBClient(agent_id="my-agent")  # auto-starts daemon
client.record_episode(status="success", task_type="coding")

Auto-recording (recommended)

Add these rules to your CLAUDE.md (or system prompt) so the agent records episodes automatically:

## EpisodicDB — auto-recording rules

EpisodicDB MCP server is connected. Follow these rules to record work automatically.

### On session start
- Call `record_episode` (status: "partial", task_type: type of work)
- Remember the episode ID for subsequent tool call / decision records

### During work
- Record significant tool results with `record_tool_call` (success/failure, duration)
- Record important decisions with `record_decision` (rationale, alternatives)
- Record new user/project facts with `record_fact` (e.g. preferred_language, current_project)

### On session end
- Update the episode status to match the final outcome (success/failure/partial/aborted)

### Guidelines
- Don't interrupt the user's workflow to record — do it in the background
- Only record meaningful actions, not every small step
- Use english snake_case for fact keys (e.g. preferred_language, deploy_target)

API

Writer

db.record_episode(status, task_type=None, context=None,
                  embedding=None, tags=None,
                  started_at=None, ended_at=None) -> str  # episode UUID

db.record_tool_call(episode_id, tool_name, outcome,
                    parameters=None, result=None,
                    duration_ms=None, error_message=None) -> str

db.record_decision(episode_id, rationale,
                   decision_type=None, alternatives=None,
                   outcome=None) -> str

db.record_fact(key, value, episode_id=None,
               valid_from=None) -> str  # auto-supersedes previous

Analytics

Method Description
top_failing_tools(days, limit) Most-failed tools in the last N days
hourly_failure_rate(days) Failure count by hour of day
before_failure_sequence(tool_name, lookback) Tools that precede failures
compare_periods(metric, days) Period-over-period comparison
never_succeeded_tools() Tools with zero successful calls
similar_episodes(embedding, status, limit) Vector similarity + SQL filter

Temporal Facts

Facts are key-value pairs with automatic temporal validity. Recording a new value for the same key closes the previous one.

db.record_fact("preferred_model", "gpt-4o")
# later...
db.record_fact("preferred_model", "claude-sonnet")  # supersedes gpt-4o

db.facts_as_of(some_datetime)   # point-in-time snapshot
db.fact_history("preferred_model")  # full change log

Persistence

EpisodicDB(agent_id="my-agent")                    # ~/.episodicdb/my-agent.db
EpisodicDB(agent_id="my-agent", path="./x.db")    # explicit path
EpisodicDB(agent_id="my-agent", path=":memory:")  # in-memory (testing)

Embeddings & Similar Episodes

similar_episodes uses vector similarity to find past episodes. The flow:

  1. Record — embed the context when recording an episode
  2. Search — embed the query and call similar_episodes

Built-in helpers for popular providers (lazy imports, no hard dependencies):

pip install episodicdb[openai]   # OpenAI
pip install episodicdb[voyage]   # Voyage AI
pip install episodicdb[ollama]   # Ollama (local)
pip install episodicdb[all]      # all providers
from episodicdb import EpisodicDB, embeddings

db = EpisodicDB(agent_id="my-agent")

# 1. Record with embedding
vec = embeddings.openai("editing auth.py, got permission denied")
db.record_episode(
    status="failure",
    task_type="file_edit",
    context={"file": "auth.py", "error": "permission denied"},
    embedding=vec,
)

# 2. Later — find similar past episodes
query_vec = embeddings.openai("permission error when modifying files")
results = db.similar_episodes(query_vec, status="failure", limit=5)
# [{"id": "...", "context": {...}, "distance": 0.12, ...}, ...]

Or bring your own embedding function (must produce 1536-dim vectors):

db.record_episode(status="success", embedding=your_1536_dim_list)
db.similar_episodes(your_query_vector, limit=5)

Note: EpisodicDB stores and searches vectors — it does not generate embeddings. The caller is responsible for calling an embedding API. This keeps the core dependency-free.

Development

git clone https://github.com/KsPsD/EpisodicDB
cd EpisodicDB
pip install -e ".[dev]"
pytest

Stack

  • DuckDB — embedded OLAP engine
  • DuckDB VSS — HNSW vector index
  • MCP — Model Context Protocol server
  • Python 3.11+

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

episodicdb-0.1.5.tar.gz (21.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

episodicdb-0.1.5-py3-none-any.whl (20.9 kB view details)

Uploaded Python 3

File details

Details for the file episodicdb-0.1.5.tar.gz.

File metadata

  • Download URL: episodicdb-0.1.5.tar.gz
  • Upload date:
  • Size: 21.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for episodicdb-0.1.5.tar.gz
Algorithm Hash digest
SHA256 c1de540840066cb17ed483db1790dea6a323cda33b3ddce7663c7ca0feb8068d
MD5 d555e70a3981fe0d5e5fff49a64302a2
BLAKE2b-256 18a9162242d26f825c6cc58b6266071cdae04e6f660798543ffb67f703b79faf

See more details on using hashes here.

Provenance

The following attestation bundles were made for episodicdb-0.1.5.tar.gz:

Publisher: publish.yml on KsPsD/EpisodicDB

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file episodicdb-0.1.5-py3-none-any.whl.

File metadata

  • Download URL: episodicdb-0.1.5-py3-none-any.whl
  • Upload date:
  • Size: 20.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for episodicdb-0.1.5-py3-none-any.whl
Algorithm Hash digest
SHA256 8272cd5fb7ec6b53f9fc2a2664718a22ff879ae21d913e4decddc8596dca3a48
MD5 a4a5b0695573809d0e6a5bf16536bafd
BLAKE2b-256 67881281b68f25818e001e02e316960821c7d9d0804fc4b40f7e2a451368a238

See more details on using hashes here.

Provenance

The following attestation bundles were made for episodicdb-0.1.5-py3-none-any.whl:

Publisher: publish.yml on KsPsD/EpisodicDB

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page