Installation
OpenLCM requires Python 3.10+ and SQLite (stdlib). One install — all adapters and providers included.
All framework adapters (LangGraph, Google ADK, AutoGen, CrewAI, LlamaIndex, Haystack), all provider SDKs (OpenAI, Anthropic, Gemini), and the live dashboard are included. No extras needed.
llm= kwarg so you can pass your existing model client instead of configuring a separate one for summarization. No extra API keys needed.
Quick Start
The minimal pattern: create an engine, bind a session, call compress() before each LLM turn.
compress() expects and returns a list of dicts: {"role": "user"|"assistant"|"system"|"tool", "content": "string"}. Tool calls are serialized as JSON in the content field. Use the framework message converters (see Message Converters) to convert from framework-native types.
Configuration
All parameters can be set in code via LCMConfig, via environment variables, or via a config.yaml file.
Environment variables
| Variable | Type | Default | Description |
|---|---|---|---|
| LCM_CONTEXT_THRESHOLD | float | 0.75 | Compression trigger as fraction of context window |
| LCM_FRESH_TAIL_COUNT | int | 64 | Messages protected from compression at tail |
| LCM_LEAF_CHUNK_TOKENS | int | 20000 | Tokens per D0 leaf summary chunk |
| LCM_CONDENSATION_FANIN | int | 4 | D0 nodes required before D1 arc is created |
| LCM_DB_PATH | str | ~/.openlcm/lcm.db | SQLite database path |
| LCM_AUTO_INJECT_MEMORY | bool | false | Auto-inject relevant facts & history into context before each compression (no LLM call — keyword-based) |
| LCM_AUTO_INJECT_TOP_K | int | 5 | Max facts injected per compression call when auto-inject is enabled |
| LCM_EXTRACTION_TO_FACTS_ENABLED | bool | false | Auto-extract decisions/constraints/preferences from each new D0 summary into the fact store |
| LCM_AUTO_PIN_PATTERNS | str | "" | Comma-separated groups to auto-pin: constraint, error, correction |
| LCM_EMBEDDING_MODEL | str | "" | LiteLLM model for embeddings (e.g. openai/text-embedding-3-small). Enables lcm_semantic_search. |
Core Concepts
Two-layer architecture
LCM has two independent stores that work together:
store_id. Never modified, never deleted. FTS5-indexed for full-text search.DAG depth levels
| Depth | Name | Created when |
|---|---|---|
| D0 | Leaf node | Context threshold exceeded; oldest messages outside fresh tail are summarized |
| D1 | Session arc | condensation_fanin D0 nodes have accumulated |
| D2+ | Durable history | condensation_fanin D1 nodes have accumulated (unbounded depth) |
Active context formula
Sessions
One SQLite DB file holds all sessions. bind_session() sets the active session and context window size. Multiple agents can share one DB with different session IDs.
LangGraph
Two integration points: LCMCheckpointer (graph persistence) and LangChainMessages (explicit compression inside a node).
Option A — LCMCheckpointer (recommended)
Drop-in replacement for MemorySaver. LCM compresses checkpoint state automatically before each graph run.
Option B — Manual compression inside a node
Use LangChainMessages to convert messages, check pressure, and compress explicitly. Gives you full control over when compression fires.
Google ADK
Two components work together: LCMSessionService persists every ADK event to SQLite, and lcm_compress_callback compresses context before each Gemini API call.
GOOGLE_API_KEY in your environment. Install with pip install openlcm[google-adk].
break after is_final_response(). ADK's run_async generator runs inside OpenTelemetry spans — breaking early throws GeneratorExit into those spans and corrupts the session state. Always drain all events to natural completion.
How it works
| Component | Interface | What it does |
|---|---|---|
| LCMSessionService | BaseSessionService | Wraps InMemorySessionService; mirrors every append_event call to SQLite for dashboard visibility |
| lcm_compress_callback | before_model_callback | Intercepts LlmRequest.contents before each Gemini API call and replaces it with compressed context |
AutoGen
LCMContext is a ChatCompletionContext subclass. Pass it as model_context to any AutoGen agent — no other changes needed.
LCMContext methods
Satisfies the full ChatCompletionContext ABC:
| Method | Behaviour |
|---|---|
| add_message(msg) | Persists to SQLite, triggers compression if threshold exceeded |
| get_messages() | Returns LCM-optimised context as typed AutoGen LLMMessage objects |
| clear() | Resets in-memory list and deletes session messages from store |
| message_count() | Returns count of messages currently held |
| save_state() | Returns serialisable state dict for checkpointing |
| load_state(state) | Restores context from a saved state dict |
CrewAI
LCMStorage plugs into LongTermMemory as a storage backend. All crew memory goes through LCM's immutable store.
OpenAI SDK
OpenAIMessages converts between the OpenAI message format and LCM's internal format. Compatible with Groq, Together, Mistral, Azure, Ollama, vLLM, and any OpenAI-compatible endpoint.
OpenAIMessages converter — the message format is identical. Just change the client's base_url and the LCMEngine(model=...) string.
Anthropic SDK
AnthropicMessages handles Anthropic's content block format. from_lcm() returns a (system_str, messages) tuple because Anthropic takes system as a separate parameter.
LlamaIndex
LlamaIndexMessages converts between ChatMessage objects (with MessageRole enum) and LCM's internal format.
Haystack
HaystackMessages handles both Haystack ≥2.3 ToolCall dataclass style and legacy additional_kwargs style.
Gemini (raw google-genai)
GeminiMessages converts between types.Content objects (Gemini's native format) and LCM. Also used internally by lcm_compress_callback.
Message Converters
Every framework adapter ships a static converter class with to_lcm() and from_lcm() methods you can use independently of the higher-level adapters.
LCM internal format
All converters normalise to this format. Tool calls are JSON-serialised into the content string.
Persistent Memory
LCM's fact store gives agents durable memory that survives session boundaries — preferences, constraints, decisions, and project facts that stay true across conversations.
The problem it solves
The DAG compresses and retrieves conversation history. But some things aren't history — they're standing truths: "the user prefers pytest", "don't push to production without a review", "we chose Postgres on 2025-04-01". These facts should be present at the start of every new session, not buried under 40 turns of old conversation.
The fact store is a separate key-value layer in the same SQLite database, queryable independently of the message store or DAG.
Agent tools
| Tool | Description |
|---|---|
lcm_remember(key, value, …) | Store or update a fact. Supports tags, related_keys, category, scope. Upserts on (scope, key). Returns previous_value if the fact changed. |
lcm_recall(…) | Retrieve facts — filter by key, query, category, tag, or related_to. No args returns all. |
lcm_forget(key) | Delete a fact when it's no longer true. |
lcm_link(key1, key2) | Bidirectionally link two facts. Each fact's related_keys list is updated. Use to capture causal chains. |
lcm_semantic_search(query) | Cosine-similarity search over DAG nodes and facts. Requires LCM_EMBEDDING_MODEL. Falls back gracefully with a hint to use lcm_grep. |
Storing facts
Recalling facts
Scope
scope="global" (default) makes a fact visible across all sessions. scope="current" scopes it to the current session only. You can also pass any explicit session_id string as a scope for finer isolation.
Categories
| Category | Use for |
|---|---|
preference | User style/tooling choices (test framework, code style, verbosity) |
constraint | Hard rules the agent must not violate |
decision | Recorded choices with rationale and date |
fact | General project or user knowledge (default) |
Direct Python API
Recommended session start pattern
Call lcm_recall() as the first tool call in every new session. This re-injects preferences, constraints, and decisions that would otherwise be invisible to the model at turn 1.
Fact Graph
Facts can carry tags and bidirectional related_keys links, forming a lightweight knowledge graph directly in SQLite — no extra infrastructure.
Tags
Any fact can be tagged with a list of strings. Tags survive updates — calling lcm_remember without a tags argument preserves existing tags.
Linking facts
lcm_link creates a bidirectional related_keys connection between two facts. lcm_recall(related_to=key) traverses both explicit links and shared tags.
Contradiction detection
When a fact is updated with a substantially different value, the response includes the old value so the agent can surface the conflict.
Python API
Auto Memory
Three automatic features that populate and inject memory without the agent needing to call tools: Auto Injection, Auto Extraction, and Salience Pinning.
Auto Memory Injection
Before each compression, LCM extracts keywords from the last 2–3 user messages, searches the fact store and message history per keyword, and prepends a compact [Recalled Memory] block to the system message. The agent never needs to call lcm_recall manually.
Auto-Extraction to Facts
After every new D0 summary node is created, LCM fires an async LLM pass over the summary text to extract decisions, preferences, and constraints — and auto-populates the fact store. The fact store self-fills as a side-effect of compression.
asyncio.ensure_future() after the summary node is written. It does not block the compression path or the agent turn.
Salience Auto-Pinning
During message ingestion, messages that match high-salience patterns are automatically pinned via the existing pin() mechanism. Pinned messages are never eligible for compression and always appear in the fresh tail.
Semantic Search
Optional vector embeddings on DAG summary nodes and facts, stored in the same SQLite file via sqlite-vec. Zero extra infrastructure. Off by default — enable with a single env var.
How it works
LCM_EMBEDDING_MODEL.float32 blobs in lcm_embeddings(content_type, content_id, embedding) in the same .db file.lcm_semantic_search(query) embeds the query and returns cosine-similarity ranked hits enriched with summary text or fact values.Graceful degradation
If sqlite-vec is not installed or LCM_EMBEDDING_MODEL is not set, EmbeddingStore is a complete no-op — no errors, no warnings at startup. lcm_semantic_search returns a hint message pointing to lcm_grep. No existing functionality is affected.
Live Dashboard
Every agent automatically gets a live browser dashboard. No config required.
Start the dashboard
Dashboard panels
| Panel | Shows |
|---|---|
| Token Pressure Gauge | Live prompt token count vs threshold and max. Green → amber → red. |
| Summary DAG Viewer | Live tree of all DAG nodes grouped by depth (D0/D1/D2). Compression ratio per node. Click any node to view full summary text. |
| Persistent Memory | All stored facts for the current session and global scope. Tag chips shown on each row. Filter by text or category. + Add button opens a modal with key, value, category, scope, and tags fields. |
| SQLite Store | Every raw message with role badge, token estimate, and full content viewer. Tool calls shown with amber TOOL badge. |
| Event Log | Chronological stream: session_bound, compaction_start, node_added, compaction_end, token_pressure. |
| Sessions List | All sessions in the DB. Click to drill into any session's full history. |
CLI Reference
LST — Lossless Semantic Tree
Parse a repository once (AST → semantic graph) and store it in the same SQLite database. Agents query the graph instead of reading files — so the codebase never needs to live in the context window.
lcm_lst_find("PaymentService") returns signatures + docstrings in ~200 tokens. One query. No re-discovery.
Scan a repo
LST Agent Tools (13 tools)
| Tool | Purpose |
|---|---|
lcm_lst_scan | Scan a repo path or URL, populate the graph (incremental) |
lcm_lst_find | FTS5 search — find any symbol by name, kind, or file |
lcm_lst_file | All symbols in a file (classes, functions, imports) |
lcm_lst_class | Class definition + all methods + signatures + linked facts |
lcm_lst_callers | Who calls a function (call-graph inbound edges) |
lcm_lst_callees | What a function calls (call-graph outbound edges) |
lcm_lst_refs | All edge references to a symbol name |
lcm_lst_path | Shortest dependency path between two symbols (networkx) |
lcm_lst_ancestors | All symbols that transitively call a function |
lcm_lst_descendants | Full dependency footprint of a function |
lcm_lst_context | Full repo orientation block — call once at session start |
lcm_read_file | Smart file read: full content first time, compact LST view on repeats |
lcm_lst_facts | Retrieve all agent discoveries pinned to a symbol |
Multi-language support
Python files use the stdlib ast module (rich: docstrings, call edges, full signatures). All other languages use Universal Ctags as a subprocess — 100+ languages including TypeScript, Go, Java, Rust, Ruby, C/C++, PHP, Swift, Kotlin.
Session Context & No Re-discovery
Three mechanisms that ensure agents never re-discover the codebase and never lose structural knowledge across sessions or context pressure.
1 — Boot context injection
At session start, inject a ~500-token structural summary into your system prompt. The agent immediately knows key classes, entry points, and recent session history — without reading a single file.
The orientation block contains: repo stats, key classes with docstrings, entry-point functions, most active files, recent session history from the DAG, and tool hints.
2 — Smart file reads (dedup)
Use lcm_read_file instead of the native Read tool. First read returns full content; every subsequent read of the same file in the same session returns a compact LST structural summary (~200 tokens vs ~3000).
3 — Symbol-pinned facts
Pin agent discoveries to specific symbols so they surface automatically in future sessions when those symbols are queried.
Zero-config setup via env vars
Code Graph Visualizer
Generate an interactive HTML force-directed graph of the codebase — all files, classes, functions, and edges. Canvas-rendered for smooth zoom/pan at any scale.
The HTML graph includes: node type legend with toggle filters, edge type toggles (calls / imports / inherits), hover tooltips with signatures and docstrings, click-to-highlight connected nodes, detail panel with callers/callees, sidebar node list with search, and fit/reset controls. Powered by D3.js force simulation with Canvas rendering.
Benchmarks
Standalone scripts that measure OpenLCM compression quality against established memory benchmarks. Run them to see the improvement over naive truncation.
| Benchmark | What it tests | Key metric |
|---|---|---|
| LoCoMo | Single-session long-context retention — can the agent answer questions about turn 5 when on turn 200? | Token F1, Exact Match, ROUGE-L |
| LongMemEval | Multi-session cross-session memory — can the agent connect facts from session 1 and session 4? | F1 by question type: cross_session, knowledge_update, temporal |
Run
Expected output
API Reference
LCMEngine
| Method / Property | Signature | Description |
|---|---|---|
| LCMEngine() | model=, config=, db_path=, summarize_fn=, llm= | Create engine. Pass a LiteLLM model string, or an existing LLM via llm= or summarize_fn=. |
| bind_session() | (session_id, context_length=, platform="") | Activate a session and set its context window size. |
| compress() | async (messages: list[dict]) → list[dict] | Compress messages if threshold exceeded. No-op if not. Always returns a valid message list. |
| should_compress_preflight() | (messages: list[dict]) → bool | Check whether compression would fire without actually compressing. |
| update_from_response() | (usage: dict) | Feed token usage from the LLM response back to the engine for pressure tracking. |
| get_status() | () → dict | Returns store_messages, dag_nodes, compression_count, last_prompt_tokens, tokens_freed. |
| _ingest_messages() | (messages: list[dict]) | Write messages directly to the SQLite store without triggering compression. Used by ADK adapter. |
LCMConfig fields
| Field | Type | Default | Description |
|---|---|---|---|
| context_threshold | float | 0.75 | Fraction of context_length at which compression fires. |
| fresh_tail_count | int | 64 | Messages at the tail protected from compression. |
| leaf_chunk_tokens | int | 20000 | Approximate token budget per D0 leaf summary. |
| condensation_fanin | int | 4 | Number of D0 nodes before a D1 arc is created. |
| dynamic_leaf_chunk_enabled | bool | False | Auto-tune leaf_chunk_tokens based on observed turn sizes. |
| dynamic_leaf_chunk_max | int | 40000 | Upper bound for dynamic leaf chunk tuning. |
| auto_inject_memory | bool | False | Auto-inject relevant facts and history into system message before each compression. |
| auto_inject_top_k | int | 5 | Max facts to surface per compression when auto-injection is enabled. |
| extraction_to_facts_enabled | bool | False | Auto-extract facts from each new D0 summary node into the persistent fact store. |
| auto_pin_patterns | list[str] | [] | Named pattern groups to auto-pin matching messages: constraint, error, correction. |
| embedding_model | str | "" | LiteLLM model string for vector embeddings. Empty = semantic search disabled. |
Copy for Your AI Agent
Paste this into your agent's system prompt or give it to any AI assistant. It contains everything needed to integrate OpenLCM — architecture, all tools, all imports, all adapters, and best practices.