openlcm 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- openlcm-0.1.0/.gitignore +67 -0
- openlcm-0.1.0/PKG-INFO +226 -0
- openlcm-0.1.0/README.md +187 -0
- openlcm-0.1.0/openlcm/__init__.py +80 -0
- openlcm-0.1.0/openlcm/adapters/__init__.py +256 -0
- openlcm-0.1.0/openlcm/adapters/anthropic.py +283 -0
- openlcm-0.1.0/openlcm/adapters/autogen.py +342 -0
- openlcm-0.1.0/openlcm/adapters/base.py +64 -0
- openlcm-0.1.0/openlcm/adapters/crewai.py +144 -0
- openlcm-0.1.0/openlcm/adapters/gemini.py +310 -0
- openlcm-0.1.0/openlcm/adapters/google_adk.py +381 -0
- openlcm-0.1.0/openlcm/adapters/haystack.py +272 -0
- openlcm-0.1.0/openlcm/adapters/langchain.py +235 -0
- openlcm-0.1.0/openlcm/adapters/langgraph.py +246 -0
- openlcm-0.1.0/openlcm/adapters/llamaindex.py +234 -0
- openlcm-0.1.0/openlcm/adapters/openai.py +241 -0
- openlcm-0.1.0/openlcm/backends/__init__.py +14 -0
- openlcm-0.1.0/openlcm/backends/anthropic.py +83 -0
- openlcm-0.1.0/openlcm/backends/base.py +42 -0
- openlcm-0.1.0/openlcm/backends/callable.py +131 -0
- openlcm-0.1.0/openlcm/backends/litellm.py +98 -0
- openlcm-0.1.0/openlcm/backends/openai.py +91 -0
- openlcm-0.1.0/openlcm/cli/__init__.py +0 -0
- openlcm-0.1.0/openlcm/cli/main.py +319 -0
- openlcm-0.1.0/openlcm/core/__init__.py +1 -0
- openlcm-0.1.0/openlcm/core/config.py +350 -0
- openlcm-0.1.0/openlcm/core/dag.py +620 -0
- openlcm-0.1.0/openlcm/core/db_bootstrap.py +453 -0
- openlcm-0.1.0/openlcm/core/engine.py +1782 -0
- openlcm-0.1.0/openlcm/core/escalation.py +298 -0
- openlcm-0.1.0/openlcm/core/externalize.py +427 -0
- openlcm-0.1.0/openlcm/core/extraction.py +232 -0
- openlcm-0.1.0/openlcm/core/ingest_protection.py +1198 -0
- openlcm-0.1.0/openlcm/core/lifecycle_state.py +572 -0
- openlcm-0.1.0/openlcm/core/message_content.py +91 -0
- openlcm-0.1.0/openlcm/core/message_patterns.py +116 -0
- openlcm-0.1.0/openlcm/core/model_routing.py +104 -0
- openlcm-0.1.0/openlcm/core/presets.py +294 -0
- openlcm-0.1.0/openlcm/core/schemas.py +319 -0
- openlcm-0.1.0/openlcm/core/search_query.py +283 -0
- openlcm-0.1.0/openlcm/core/session_patterns.py +52 -0
- openlcm-0.1.0/openlcm/core/store.py +1058 -0
- openlcm-0.1.0/openlcm/core/tokens.py +61 -0
- openlcm-0.1.0/openlcm/core/tools.py +1855 -0
- openlcm-0.1.0/openlcm/viz/__init__.py +0 -0
- openlcm-0.1.0/openlcm/viz/events.py +138 -0
- openlcm-0.1.0/openlcm/viz/server.py +389 -0
- openlcm-0.1.0/openlcm/viz/static/dashboard.js +967 -0
- openlcm-0.1.0/openlcm/viz/static/index.html +294 -0
- openlcm-0.1.0/openlcm/viz/static/styles.css +477 -0
- openlcm-0.1.0/pyproject.toml +63 -0
openlcm-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Ignore the hermes-lcm subproject (it has its own git repo)
|
|
2
|
+
hermes-lcm/
|
|
3
|
+
|
|
4
|
+
# Python
|
|
5
|
+
__pycache__/
|
|
6
|
+
*.py[cod]
|
|
7
|
+
*.pyo
|
|
8
|
+
*.pyd
|
|
9
|
+
.Python
|
|
10
|
+
*.egg
|
|
11
|
+
*.egg-info/
|
|
12
|
+
dist/
|
|
13
|
+
build/
|
|
14
|
+
.eggs/
|
|
15
|
+
.installed.cfg
|
|
16
|
+
*.egg-link
|
|
17
|
+
pip-wheel-metadata/
|
|
18
|
+
share/python-wheels/
|
|
19
|
+
|
|
20
|
+
# Virtual environments
|
|
21
|
+
.venv/
|
|
22
|
+
venv/
|
|
23
|
+
env/
|
|
24
|
+
ENV/
|
|
25
|
+
|
|
26
|
+
# Testing
|
|
27
|
+
.pytest_cache/
|
|
28
|
+
.coverage
|
|
29
|
+
htmlcov/
|
|
30
|
+
.tox/
|
|
31
|
+
coverage.xml
|
|
32
|
+
*.cover
|
|
33
|
+
|
|
34
|
+
# Type checking
|
|
35
|
+
.mypy_cache/
|
|
36
|
+
.dmypy.json
|
|
37
|
+
.pytype/
|
|
38
|
+
.pyre/
|
|
39
|
+
|
|
40
|
+
# IDEs
|
|
41
|
+
.vscode/
|
|
42
|
+
.idea/
|
|
43
|
+
*.sublime-workspace
|
|
44
|
+
|
|
45
|
+
# OS
|
|
46
|
+
.DS_Store
|
|
47
|
+
Thumbs.db
|
|
48
|
+
|
|
49
|
+
# OpenLCM runtime data (local databases, extractions)
|
|
50
|
+
*.db
|
|
51
|
+
*.db-shm
|
|
52
|
+
*.db-wal
|
|
53
|
+
*.sqlite3
|
|
54
|
+
*.sqlite
|
|
55
|
+
lcm-large-outputs/
|
|
56
|
+
lcm-extractions/
|
|
57
|
+
|
|
58
|
+
# Examples (local demo scripts, not part of the published SDK)
|
|
59
|
+
example/
|
|
60
|
+
|
|
61
|
+
# Claude / agent tooling
|
|
62
|
+
.agents/
|
|
63
|
+
skills-lock.json
|
|
64
|
+
skills/
|
|
65
|
+
|
|
66
|
+
# Misc
|
|
67
|
+
docs.html
|
openlcm-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: openlcm
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Framework-agnostic Lossless Context Management SDK for LLM agents
|
|
5
|
+
Project-URL: Homepage, https://github.com/akshay-eng/OpenLCM
|
|
6
|
+
Project-URL: Repository, https://github.com/akshay-eng/OpenLCM
|
|
7
|
+
Project-URL: Bug Tracker, https://github.com/akshay-eng/OpenLCM/issues
|
|
8
|
+
License: MIT
|
|
9
|
+
Keywords: agents,autogen,context,context-management,crewai,google-adk,langchain,langgraph,llm,lossless,memory
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
16
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
17
|
+
Classifier: Typing :: Typed
|
|
18
|
+
Requires-Python: >=3.11
|
|
19
|
+
Requires-Dist: anthropic>=0.30
|
|
20
|
+
Requires-Dist: autogen-agentchat>=0.4
|
|
21
|
+
Requires-Dist: autogen-core>=0.4
|
|
22
|
+
Requires-Dist: crewai>=0.80
|
|
23
|
+
Requires-Dist: fastapi>=0.111
|
|
24
|
+
Requires-Dist: google-adk>=1.0
|
|
25
|
+
Requires-Dist: google-genai>=1.0
|
|
26
|
+
Requires-Dist: haystack-ai>=2.3
|
|
27
|
+
Requires-Dist: langchain-core>=0.2
|
|
28
|
+
Requires-Dist: langgraph-checkpoint>=2.0
|
|
29
|
+
Requires-Dist: langgraph>=0.2
|
|
30
|
+
Requires-Dist: litellm>=1.0
|
|
31
|
+
Requires-Dist: llama-index-core>=0.10
|
|
32
|
+
Requires-Dist: openai>=1.0
|
|
33
|
+
Requires-Dist: uvicorn[standard]>=0.30
|
|
34
|
+
Provides-Extra: dev
|
|
35
|
+
Requires-Dist: httpx>=0.27; extra == 'dev'
|
|
36
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
37
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
38
|
+
Description-Content-Type: text/markdown
|
|
39
|
+
|
|
40
|
+
# OpenLCM — Lossless Context Management for LLM Agents
|
|
41
|
+
|
|
42
|
+
**Unbounded memory. Bounded context.**
|
|
43
|
+
|
|
44
|
+
OpenLCM is a framework-agnostic Python SDK that gives your AI agents a permanent, lossless memory — without ever hitting the context limit. Every message is persisted verbatim in SQLite and compressed into a hierarchical DAG of summaries. Nothing is ever lost. Any past moment is recoverable.
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pip install openlcm
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## The problem
|
|
53
|
+
|
|
54
|
+
LLMs have a hard token limit. As conversations grow, agents either crash or replace old turns with a flat, irreversible summary. Details fall out permanently — decisions, constraints, file paths, tool results.
|
|
55
|
+
|
|
56
|
+
## How LCM works
|
|
57
|
+
|
|
58
|
+
OpenLCM maintains two layers:
|
|
59
|
+
|
|
60
|
+
1. **Immutable message store** — every message written verbatim to SQLite with a stable `store_id`. FTS5-indexed. Never modified.
|
|
61
|
+
2. **Summary DAG** — older messages are compressed into D0 leaf nodes → D1 session arcs → D2 durable history. Each node points back to its source messages for exact recovery.
|
|
62
|
+
|
|
63
|
+
The model always sees: `system + highest DAG node + recent D0 nodes + fresh tail (last N raw messages)`.
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## Quick start
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
from openlcm import LCMEngine
|
|
71
|
+
|
|
72
|
+
engine = LCMEngine(model="anthropic/claude-haiku-4-5-20251001")
|
|
73
|
+
engine.bind_session("my-session", context_length=200_000)
|
|
74
|
+
|
|
75
|
+
# Call before every LLM turn — compresses only when needed
|
|
76
|
+
messages = await engine.compress(messages)
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Pass any [LiteLLM](https://github.com/BerriAI/litellm) model string: `openai/gpt-4o`, `gemini/gemini-2.0-flash`, `azure/gpt-4o`, `bedrock/...`, `ollama/llama3`, etc.
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## Framework adapters
|
|
84
|
+
|
|
85
|
+
All adapters are included — one install, no extras needed.
|
|
86
|
+
|
|
87
|
+
### LangGraph
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
from openlcm.adapters.langgraph import LCMCheckpointer
|
|
91
|
+
|
|
92
|
+
graph = StateGraph(MyState).compile(
|
|
93
|
+
checkpointer=LCMCheckpointer(llm=my_llm)
|
|
94
|
+
)
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Google ADK
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
from openlcm.adapters.google_adk import LCMSessionService, lcm_compress_callback
|
|
101
|
+
|
|
102
|
+
agent = LlmAgent(
|
|
103
|
+
name="assistant",
|
|
104
|
+
model="gemini-2.0-flash",
|
|
105
|
+
tools=[...],
|
|
106
|
+
before_model_callback=lcm_compress_callback(engine),
|
|
107
|
+
)
|
|
108
|
+
runner = Runner(agent=agent, session_service=LCMSessionService(engine))
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### AutoGen
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
from openlcm.adapters.autogen import LCMContext
|
|
115
|
+
|
|
116
|
+
agent = AssistantAgent(
|
|
117
|
+
"assistant",
|
|
118
|
+
model_client=client,
|
|
119
|
+
model_context=LCMContext(llm=client),
|
|
120
|
+
)
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### CrewAI
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
from openlcm.adapters.crewai import LCMStorage
|
|
127
|
+
|
|
128
|
+
crew = Crew(
|
|
129
|
+
memory=True,
|
|
130
|
+
long_term_memory=LongTermMemory(storage=LCMStorage(engine))
|
|
131
|
+
)
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### OpenAI / Groq / Mistral / Ollama
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
from openlcm.adapters.openai import OpenAIMessages
|
|
138
|
+
|
|
139
|
+
lcm = OpenAIMessages.to_lcm(messages)
|
|
140
|
+
if engine.should_compress_preflight(lcm):
|
|
141
|
+
lcm = await engine.compress(lcm)
|
|
142
|
+
messages = OpenAIMessages.from_lcm(lcm)
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Anthropic
|
|
146
|
+
|
|
147
|
+
```python
|
|
148
|
+
from openlcm.adapters.anthropic import AnthropicMessages
|
|
149
|
+
|
|
150
|
+
lcm = AnthropicMessages.to_lcm(messages, system=system_prompt)
|
|
151
|
+
lcm = await engine.compress(lcm)
|
|
152
|
+
system_out, anthropic_msgs = AnthropicMessages.from_lcm(lcm)
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### LlamaIndex / Haystack / Gemini
|
|
156
|
+
|
|
157
|
+
```python
|
|
158
|
+
from openlcm.adapters.llamaindex import LlamaIndexMessages
|
|
159
|
+
from openlcm.adapters.haystack import HaystackMessages
|
|
160
|
+
from openlcm.adapters.gemini import GeminiMessages
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
All follow the same `to_lcm()` / `from_lcm()` interface.
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## Configuration
|
|
168
|
+
|
|
169
|
+
```python
|
|
170
|
+
from openlcm.core.config import LCMConfig
|
|
171
|
+
|
|
172
|
+
config = LCMConfig.from_env()
|
|
173
|
+
config.context_threshold = 0.75 # compress at 75% of context window
|
|
174
|
+
config.fresh_tail_count = 64 # protect last 64 messages from compression
|
|
175
|
+
config.leaf_chunk_tokens = 20_000 # tokens per D0 leaf summary
|
|
176
|
+
config.condensation_fanin = 4 # D0 nodes before a D1 arc is created
|
|
177
|
+
|
|
178
|
+
engine = LCMEngine(model="...", config=config)
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
| Env var | Default | Description |
|
|
182
|
+
|---|---|---|
|
|
183
|
+
| `LCM_CONTEXT_THRESHOLD` | `0.75` | Compression trigger as fraction of context window |
|
|
184
|
+
| `LCM_FRESH_TAIL_COUNT` | `64` | Messages protected from compression at tail |
|
|
185
|
+
| `LCM_LEAF_CHUNK_TOKENS` | `20000` | Tokens per D0 summary chunk |
|
|
186
|
+
| `LCM_CONDENSATION_FANIN` | `4` | D0 nodes required before D1 arc is created |
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
## Live dashboard
|
|
191
|
+
|
|
192
|
+
```python
|
|
193
|
+
import threading
|
|
194
|
+
from openlcm.viz.server import create_app, serve as viz_serve
|
|
195
|
+
|
|
196
|
+
threading.Thread(
|
|
197
|
+
target=lambda: viz_serve(create_app(engine), port=7842, open_browser=True),
|
|
198
|
+
daemon=True
|
|
199
|
+
).start()
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
Or from the CLI:
|
|
203
|
+
|
|
204
|
+
```bash
|
|
205
|
+
openlcm viz # opens http://localhost:7842
|
|
206
|
+
openlcm grep "query" # full-text search across all sessions
|
|
207
|
+
openlcm status # session stats
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
The dashboard shows token pressure, DAG graph, SQLite message store, and a live event log — all updating in real time.
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
## Guarantees
|
|
215
|
+
|
|
216
|
+
- **Lossless** — every message persisted with stable `store_id`. Recoverable even after 100 compactions.
|
|
217
|
+
- **Deterministic** — summarization always terminates. L1 → L2 → L3 escalation with circuit breaker.
|
|
218
|
+
- **Zero-cost** — compression fires only when the threshold is exceeded. Short conversations pay zero overhead.
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
## License
|
|
223
|
+
|
|
224
|
+
MIT — see [LICENSE](LICENSE).
|
|
225
|
+
|
|
226
|
+
Built on the LCM paper by Ehrlich & Blackman (Voltropy, 2026).
|
openlcm-0.1.0/README.md
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# OpenLCM — Lossless Context Management for LLM Agents
|
|
2
|
+
|
|
3
|
+
**Unbounded memory. Bounded context.**
|
|
4
|
+
|
|
5
|
+
OpenLCM is a framework-agnostic Python SDK that gives your AI agents a permanent, lossless memory — without ever hitting the context limit. Every message is persisted verbatim in SQLite and compressed into a hierarchical DAG of summaries. Nothing is ever lost. Any past moment is recoverable.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install openlcm
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## The problem
|
|
14
|
+
|
|
15
|
+
LLMs have a hard token limit. As conversations grow, agents either crash or replace old turns with a flat, irreversible summary. Details fall out permanently — decisions, constraints, file paths, tool results.
|
|
16
|
+
|
|
17
|
+
## How LCM works
|
|
18
|
+
|
|
19
|
+
OpenLCM maintains two layers:
|
|
20
|
+
|
|
21
|
+
1. **Immutable message store** — every message written verbatim to SQLite with a stable `store_id`. FTS5-indexed. Never modified.
|
|
22
|
+
2. **Summary DAG** — older messages are compressed into D0 leaf nodes → D1 session arcs → D2 durable history. Each node points back to its source messages for exact recovery.
|
|
23
|
+
|
|
24
|
+
The model always sees: `system + highest DAG node + recent D0 nodes + fresh tail (last N raw messages)`.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Quick start
|
|
29
|
+
|
|
30
|
+
```python
|
|
31
|
+
from openlcm import LCMEngine
|
|
32
|
+
|
|
33
|
+
engine = LCMEngine(model="anthropic/claude-haiku-4-5-20251001")
|
|
34
|
+
engine.bind_session("my-session", context_length=200_000)
|
|
35
|
+
|
|
36
|
+
# Call before every LLM turn — compresses only when needed
|
|
37
|
+
messages = await engine.compress(messages)
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Pass any [LiteLLM](https://github.com/BerriAI/litellm) model string: `openai/gpt-4o`, `gemini/gemini-2.0-flash`, `azure/gpt-4o`, `bedrock/...`, `ollama/llama3`, etc.
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Framework adapters
|
|
45
|
+
|
|
46
|
+
All adapters are included — one install, no extras needed.
|
|
47
|
+
|
|
48
|
+
### LangGraph
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
from openlcm.adapters.langgraph import LCMCheckpointer
|
|
52
|
+
|
|
53
|
+
graph = StateGraph(MyState).compile(
|
|
54
|
+
checkpointer=LCMCheckpointer(llm=my_llm)
|
|
55
|
+
)
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Google ADK
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
from openlcm.adapters.google_adk import LCMSessionService, lcm_compress_callback
|
|
62
|
+
|
|
63
|
+
agent = LlmAgent(
|
|
64
|
+
name="assistant",
|
|
65
|
+
model="gemini-2.0-flash",
|
|
66
|
+
tools=[...],
|
|
67
|
+
before_model_callback=lcm_compress_callback(engine),
|
|
68
|
+
)
|
|
69
|
+
runner = Runner(agent=agent, session_service=LCMSessionService(engine))
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### AutoGen
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
from openlcm.adapters.autogen import LCMContext
|
|
76
|
+
|
|
77
|
+
agent = AssistantAgent(
|
|
78
|
+
"assistant",
|
|
79
|
+
model_client=client,
|
|
80
|
+
model_context=LCMContext(llm=client),
|
|
81
|
+
)
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### CrewAI
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
from openlcm.adapters.crewai import LCMStorage
|
|
88
|
+
|
|
89
|
+
crew = Crew(
|
|
90
|
+
memory=True,
|
|
91
|
+
long_term_memory=LongTermMemory(storage=LCMStorage(engine))
|
|
92
|
+
)
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### OpenAI / Groq / Mistral / Ollama
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
from openlcm.adapters.openai import OpenAIMessages
|
|
99
|
+
|
|
100
|
+
lcm = OpenAIMessages.to_lcm(messages)
|
|
101
|
+
if engine.should_compress_preflight(lcm):
|
|
102
|
+
lcm = await engine.compress(lcm)
|
|
103
|
+
messages = OpenAIMessages.from_lcm(lcm)
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Anthropic
|
|
107
|
+
|
|
108
|
+
```python
|
|
109
|
+
from openlcm.adapters.anthropic import AnthropicMessages
|
|
110
|
+
|
|
111
|
+
lcm = AnthropicMessages.to_lcm(messages, system=system_prompt)
|
|
112
|
+
lcm = await engine.compress(lcm)
|
|
113
|
+
system_out, anthropic_msgs = AnthropicMessages.from_lcm(lcm)
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### LlamaIndex / Haystack / Gemini
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
from openlcm.adapters.llamaindex import LlamaIndexMessages
|
|
120
|
+
from openlcm.adapters.haystack import HaystackMessages
|
|
121
|
+
from openlcm.adapters.gemini import GeminiMessages
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
All follow the same `to_lcm()` / `from_lcm()` interface.
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## Configuration
|
|
129
|
+
|
|
130
|
+
```python
|
|
131
|
+
from openlcm.core.config import LCMConfig
|
|
132
|
+
|
|
133
|
+
config = LCMConfig.from_env()
|
|
134
|
+
config.context_threshold = 0.75 # compress at 75% of context window
|
|
135
|
+
config.fresh_tail_count = 64 # protect last 64 messages from compression
|
|
136
|
+
config.leaf_chunk_tokens = 20_000 # tokens per D0 leaf summary
|
|
137
|
+
config.condensation_fanin = 4 # D0 nodes before a D1 arc is created
|
|
138
|
+
|
|
139
|
+
engine = LCMEngine(model="...", config=config)
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
| Env var | Default | Description |
|
|
143
|
+
|---|---|---|
|
|
144
|
+
| `LCM_CONTEXT_THRESHOLD` | `0.75` | Compression trigger as fraction of context window |
|
|
145
|
+
| `LCM_FRESH_TAIL_COUNT` | `64` | Messages protected from compression at tail |
|
|
146
|
+
| `LCM_LEAF_CHUNK_TOKENS` | `20000` | Tokens per D0 summary chunk |
|
|
147
|
+
| `LCM_CONDENSATION_FANIN` | `4` | D0 nodes required before D1 arc is created |
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## Live dashboard
|
|
152
|
+
|
|
153
|
+
```python
|
|
154
|
+
import threading
|
|
155
|
+
from openlcm.viz.server import create_app, serve as viz_serve
|
|
156
|
+
|
|
157
|
+
threading.Thread(
|
|
158
|
+
target=lambda: viz_serve(create_app(engine), port=7842, open_browser=True),
|
|
159
|
+
daemon=True
|
|
160
|
+
).start()
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Or from the CLI:
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
openlcm viz # opens http://localhost:7842
|
|
167
|
+
openlcm grep "query" # full-text search across all sessions
|
|
168
|
+
openlcm status # session stats
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
The dashboard shows token pressure, DAG graph, SQLite message store, and a live event log — all updating in real time.
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## Guarantees
|
|
176
|
+
|
|
177
|
+
- **Lossless** — every message persisted with stable `store_id`. Recoverable even after 100 compactions.
|
|
178
|
+
- **Deterministic** — summarization always terminates. L1 → L2 → L3 escalation with circuit breaker.
|
|
179
|
+
- **Zero-cost** — compression fires only when the threshold is exceeded. Short conversations pay zero overhead.
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## License
|
|
184
|
+
|
|
185
|
+
MIT — see [LICENSE](LICENSE).
|
|
186
|
+
|
|
187
|
+
Built on the LCM paper by Ehrlich & Blackman (Voltropy, 2026).
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"""OpenLCM — Framework-agnostic Lossless Context Management SDK.
|
|
2
|
+
|
|
3
|
+
One install, every provider::
|
|
4
|
+
|
|
5
|
+
pip install openlcm
|
|
6
|
+
|
|
7
|
+
Quick start — pass any LiteLLM model string::
|
|
8
|
+
|
|
9
|
+
from openlcm import LCMEngine
|
|
10
|
+
|
|
11
|
+
# Anthropic
|
|
12
|
+
engine = LCMEngine(model="anthropic/claude-haiku-4-5-20251001")
|
|
13
|
+
|
|
14
|
+
# Azure OpenAI
|
|
15
|
+
engine = LCMEngine(model="azure/gpt-4o")
|
|
16
|
+
|
|
17
|
+
# AWS Bedrock
|
|
18
|
+
engine = LCMEngine(model="bedrock/anthropic.claude-3-5-sonnet-20241022-v2:0")
|
|
19
|
+
|
|
20
|
+
# Google Gemini
|
|
21
|
+
engine = LCMEngine(model="gemini/gemini-2.0-flash")
|
|
22
|
+
|
|
23
|
+
# Vertex AI
|
|
24
|
+
engine = LCMEngine(model="vertex_ai/gemini-pro")
|
|
25
|
+
|
|
26
|
+
# Ollama (local)
|
|
27
|
+
engine = LCMEngine(model="ollama/llama3.2", api_base="http://localhost:11434")
|
|
28
|
+
|
|
29
|
+
# WatsonX
|
|
30
|
+
engine = LCMEngine(model="watsonx/ibm/granite-13b-chat-v2")
|
|
31
|
+
|
|
32
|
+
# Custom OpenAI-compatible endpoint
|
|
33
|
+
engine = LCMEngine(model="openai/my-model", api_base="http://my-server/v1")
|
|
34
|
+
|
|
35
|
+
Bind a session and compress each turn::
|
|
36
|
+
|
|
37
|
+
engine.bind_session("session-abc", context_length=200_000)
|
|
38
|
+
compressed = await engine.compress(messages) # call before every LLM turn
|
|
39
|
+
|
|
40
|
+
Already using a framework LLM? Pass it directly — no separate model config needed::
|
|
41
|
+
|
|
42
|
+
from langchain_anthropic import ChatAnthropic
|
|
43
|
+
from openlcm.adapters.langgraph import LCMCheckpointer
|
|
44
|
+
|
|
45
|
+
llm = ChatAnthropic(model="claude-3-haiku-20240307") # your existing LLM
|
|
46
|
+
checkpointer = LCMCheckpointer(llm=llm) # reuses it for summarization
|
|
47
|
+
|
|
48
|
+
# Works the same way for CrewAI, AutoGen, Google ADK:
|
|
49
|
+
LCMStorage(llm=my_crewai_llm)
|
|
50
|
+
LCMContext(llm=my_autogen_client)
|
|
51
|
+
LCMSessionService(llm=my_gemini_model)
|
|
52
|
+
|
|
53
|
+
# Or pass any callable to LCMEngine directly:
|
|
54
|
+
engine = LCMEngine(summarize_fn=llm)
|
|
55
|
+
engine = LCMEngine(summarize_fn=lambda prompt, max_tokens: llm.invoke(prompt).content)
|
|
56
|
+
|
|
57
|
+
Live dashboard::
|
|
58
|
+
|
|
59
|
+
openlcm viz # http://localhost:7842
|
|
60
|
+
|
|
61
|
+
Advanced — bring your own backend (custom inference server, etc.)::
|
|
62
|
+
|
|
63
|
+
from openlcm.backends.base import SummaryBackend
|
|
64
|
+
|
|
65
|
+
class MyBackend(SummaryBackend):
|
|
66
|
+
async def summarize(self, prompt, max_tokens, model="", timeout=None):
|
|
67
|
+
...
|
|
68
|
+
|
|
69
|
+
engine = LCMEngine(backend=MyBackend())
|
|
70
|
+
|
|
71
|
+
Full provider list: https://docs.litellm.ai/docs/providers
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
from .core.engine import LCMEngine
|
|
75
|
+
from .core.config import LCMConfig
|
|
76
|
+
from .backends.base import SummaryBackend
|
|
77
|
+
from .backends.callable import CallableBackend
|
|
78
|
+
|
|
79
|
+
__version__ = "0.1.0"
|
|
80
|
+
__all__ = ["LCMEngine", "LCMConfig", "SummaryBackend", "CallableBackend"]
|