dreamguard 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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Manojkumar V
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,2 @@
1
+ include LICENSE README.md
2
+ recursive-include dreamguard/static *
@@ -0,0 +1,201 @@
1
+ Metadata-Version: 2.4
2
+ Name: dreamguard
3
+ Version: 0.1.0
4
+ Summary: Reversible memory compilation for AI agents: compress memory without losing the raw data, and roll back when recall drops.
5
+ Author: Manojkumar V
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/manojkumar16122/dreamguard
8
+ Project-URL: Issues, https://github.com/manojkumar16122/dreamguard/issues
9
+ Keywords: ai,agents,llm,memory,rollback,context-compression
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
18
+ Requires-Python: >=3.10
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE
21
+ Provides-Extra: groq
22
+ Requires-Dist: groq>=0.11; extra == "groq"
23
+ Provides-Extra: server
24
+ Requires-Dist: fastapi>=0.110; extra == "server"
25
+ Requires-Dist: uvicorn>=0.29; extra == "server"
26
+ Requires-Dist: python-dotenv>=1.0; extra == "server"
27
+ Requires-Dist: groq>=0.11; extra == "server"
28
+ Provides-Extra: dev
29
+ Requires-Dist: pytest>=8; extra == "dev"
30
+ Requires-Dist: httpx>=0.27; extra == "dev"
31
+ Requires-Dist: build; extra == "dev"
32
+ Requires-Dist: twine; extra == "dev"
33
+ Dynamic: license-file
34
+
35
+ # 🛡️ DreamGuard
36
+
37
+ **Reversible memory compilation for AI agents.**
38
+
39
+ [![Python](https://img.shields.io/badge/python-3.10%2B-blue)](#) [![License: MIT](https://img.shields.io/badge/license-MIT-green)](LICENSE) [![Tests](https://img.shields.io/badge/tests-pytest-brightgreen)](#testing)
40
+
41
+ Long-running agents need to compress their memory ("dreaming" / consolidation) or the context window bloats. The common approach rewrites memory **in place**: once a detail is summarised away, it is gone for good.
42
+
43
+ DreamGuard compiles raw *episodic* memory into compact *semantic* memory like a build step: **the raw episodes are never deleted**, and every compressed fact is linked back to its source episodes. If recall quality drops, you **roll back** and the exact raw context is restored.
44
+
45
+ ```
46
+ record() ──► episodes (immutable)
47
+ │ dream()
48
+ ▼
49
+ semantic_memories ──provenance──► episodes
50
+ │ rollback()
51
+ ▼
52
+ compressed facts deactivated, raw context restored
53
+ ```
54
+
55
+ ## Demo
56
+
57
+ Run the dashboard and open <http://localhost:8000>. It plots detail-recall accuracy over 50 turns for three systems and lets you trigger a rollback:
58
+
59
+ | System | What it does | Behaviour in the demo |
60
+ |---|---|---|
61
+ | Full raw context (reference) | Never compresses | 100%, but context grows without bound |
62
+ | In-place dreaming | Rewrites memory, deletes raw data | Loss compounds, cannot recover |
63
+ | **DreamGuard** | Compresses, keeps raw + provenance | Same loss as in-place, then **recovers to 100%** on rollback |
64
+
65
+ > 📸 Add a screenshot or GIF here: `docs/demo.gif`
66
+
67
+ ## Install
68
+
69
+ ```bash
70
+ pip install dreamguard # core library, zero dependencies
71
+ pip install "dreamguard[server]" # + dashboard, API and Groq support
72
+ pip install "dreamguard[groq]" # + Groq compressor only
73
+ ```
74
+
75
+ ## Quick start
76
+
77
+ ```bash
78
+ pip install "dreamguard[server]"
79
+ echo "GROQ_API_KEY=gsk_..." > .env # only needed for the /live demo
80
+ dreamguard serve # open http://localhost:8000
81
+ dreamguard benchmark # offline simulation in the terminal
82
+ ```
83
+
84
+ Development setup: `git clone ... && pip install -e ".[dev,server]" && pytest`.
85
+
86
+ ### Use it as a library
87
+
88
+ ```python
89
+ from dreamguard import MemoryEngine, HeuristicCompressor
90
+
91
+ mem = MemoryEngine(HeuristicCompressor(retention=0.7), destructive=False)
92
+ mem.record(1, "api_port=8421")
93
+ mem.record(2, "region=ap-south-1")
94
+
95
+ mem.dream() # compress; raw episodes + provenance are kept
96
+ print(mem.context()) # what your agent would see (digest)
97
+ mem.rollback() # back to exact raw episodes
98
+ print(mem.context())
99
+ ```
100
+
101
+ ### Live demo with a real LLM (Groq)
102
+
103
+ ```bash
104
+ # .env
105
+ GROQ_API_KEY=gsk_...
106
+ ```
107
+ Run `uvicorn dreamguard.api:app --reload`, open <http://localhost:8000/live>, then click:
108
+ **1 Load scenario → 2 Evaluate → 3 Dream → 2 Evaluate → 4 Rollback → 2 Evaluate.**
109
+
110
+ Groq compresses 12 engineering-log episodes into a short digest, then answers 8 factual questions (ports, ticket owners, hosts...) using *only* each system's remaining memory. Answers are graded against known ground truth. Both the in-place and DreamGuard engines get the same input; only DreamGuard can roll back. Set `GROQ_MODEL` to change the model (default `llama-3.1-8b-instant`).
111
+
112
+ Results are real LLM output, so they vary by model and run: a strong model may retain some details after compression. The rollback guarantee does not vary, because it restores the raw text.
113
+
114
+ ### Real LLM compression (optional)
115
+
116
+ ```bash
117
+ # .env
118
+ COMPRESSOR=groq
119
+ GROQ_API_KEY=gsk_...
120
+ ```
121
+
122
+ Uses Groq's `llama-3.1-8b-instant` to produce the digest. Note: the built-in **benchmark** always uses the heuristic compressor so that its results are reproducible.
123
+
124
+ ## How it works
125
+
126
+ | Table | Purpose |
127
+ |---|---|
128
+ | `episodes` | Raw, append-only memory (`turn`, `content`) |
129
+ | `semantic_memories` | Compressed digests, with an `active` flag |
130
+ | `provenance` | `memory_id → episode_id` links from each digest to its sources |
131
+
132
+ - `record(turn, content)` appends an episode.
133
+ - `dream()` compresses episodes not yet covered by an active digest.
134
+ - `rollback()` sets all digests to inactive. Their episodes become "uncovered" again, so `context()` returns the raw text.
135
+ - `context()` is what an agent reads: active digests plus any uncovered raw episodes.
136
+ - With `destructive=True` the engine mimics in-place dreaming: it deletes raw data and re-compresses digests together with new episodes, so loss compounds.
137
+
138
+ ## Benchmark methodology (read this before quoting numbers)
139
+
140
+ The benchmark is a **synthetic simulation**, not a measurement of any production system:
141
+
142
+ 1. Each turn records one exact detail, e.g. `param_7=48213`.
143
+ 2. Every 10 turns the memory is compressed with a seeded lossy compressor that keeps each value with probability 0.7 and otherwise keeps only the key.
144
+ 3. After every turn, **recall** = fraction of all details seen so far that appear verbatim (`key=value`) in the agent's visible context.
145
+
146
+ What this shows: *if* compression is lossy, an irreversible design loses information permanently, while a design that keeps raw data can restore it. What it does **not** show: how any specific LLM, product, or published system behaves. The absolute accuracy numbers come from the chosen retention parameter, not from real-world measurement.
147
+
148
+ **Known trade-off:** after a rollback, the agent context is raw again and therefore larger. The benefit of compression (smaller context) is traded for recall until the next dream.
149
+
150
+ ## API
151
+
152
+ | Method | Path | Description |
153
+ |---|---|---|
154
+ | POST | `/api/record` | `{"content": "..."}` record an episode |
155
+ | POST | `/api/dream` | Compress uncovered episodes |
156
+ | POST | `/api/rollback` | Deactivate compressed memory |
157
+ | POST | `/api/reset` | Clear the live engine |
158
+ | POST | `/api/demo/seed?n=10` | Add demo episodes |
159
+ | GET | `/api/memory` | Episodes, facts, provenance, context size |
160
+ | GET | `/api/benchmark` | Run benchmark (`seed`, `retention`, `auto_rollback`, `rollback_at`) |
161
+
162
+ Interactive docs at `/docs`.
163
+
164
+ ## Project layout
165
+
166
+ ```
167
+ dreamguard/
168
+ ├── dreamguard/
169
+ │ ├── db.py # SQLite schema
170
+ │ ├── compiler.py # Heuristic + Groq compressors
171
+ │ ├── engine.py # record / dream / rollback / evaluate
172
+ │ ├── benchmark.py # reproducible simulation
173
+ │ ├── live.py # live demo: Groq compression + Groq QA grading
174
+ │ ├── api.py # FastAPI app
175
+ │ ├── cli.py # `dreamguard` command
176
+ │ └── static/ # dashboard (HTML + CSS + Chart.js, no build step)
177
+ ├── tests/
178
+ └── pyproject.toml
179
+ ```
180
+
181
+ ## Testing
182
+
183
+ ```bash
184
+ pip install -e ".[dev,server]"
185
+ pytest
186
+ ```
187
+
188
+ ## Roadmap
189
+
190
+ - [ ] Selective rollback (restore only the episodes behind a specific fact)
191
+ - [ ] Benchmark with real LLM compression and an LLM-answered QA task
192
+ - [ ] Pluggable storage backends (Postgres, vector DBs)
193
+ - [ ] Rollback triggers driven by task-level eval signals
194
+
195
+ ## Contributing
196
+
197
+ Issues and PRs are welcome. Please run `pytest` before submitting.
198
+
199
+ ## License
200
+
201
+ MIT, see [LICENSE](LICENSE). Built by [Manojkumar V](https://github.com/manojkumar16122).
@@ -0,0 +1,167 @@
1
+ # 🛡️ DreamGuard
2
+
3
+ **Reversible memory compilation for AI agents.**
4
+
5
+ [![Python](https://img.shields.io/badge/python-3.10%2B-blue)](#) [![License: MIT](https://img.shields.io/badge/license-MIT-green)](LICENSE) [![Tests](https://img.shields.io/badge/tests-pytest-brightgreen)](#testing)
6
+
7
+ Long-running agents need to compress their memory ("dreaming" / consolidation) or the context window bloats. The common approach rewrites memory **in place**: once a detail is summarised away, it is gone for good.
8
+
9
+ DreamGuard compiles raw *episodic* memory into compact *semantic* memory like a build step: **the raw episodes are never deleted**, and every compressed fact is linked back to its source episodes. If recall quality drops, you **roll back** and the exact raw context is restored.
10
+
11
+ ```
12
+ record() ──► episodes (immutable)
13
+ │ dream()
14
+ ▼
15
+ semantic_memories ──provenance──► episodes
16
+ │ rollback()
17
+ ▼
18
+ compressed facts deactivated, raw context restored
19
+ ```
20
+
21
+ ## Demo
22
+
23
+ Run the dashboard and open <http://localhost:8000>. It plots detail-recall accuracy over 50 turns for three systems and lets you trigger a rollback:
24
+
25
+ | System | What it does | Behaviour in the demo |
26
+ |---|---|---|
27
+ | Full raw context (reference) | Never compresses | 100%, but context grows without bound |
28
+ | In-place dreaming | Rewrites memory, deletes raw data | Loss compounds, cannot recover |
29
+ | **DreamGuard** | Compresses, keeps raw + provenance | Same loss as in-place, then **recovers to 100%** on rollback |
30
+
31
+ > 📸 Add a screenshot or GIF here: `docs/demo.gif`
32
+
33
+ ## Install
34
+
35
+ ```bash
36
+ pip install dreamguard # core library, zero dependencies
37
+ pip install "dreamguard[server]" # + dashboard, API and Groq support
38
+ pip install "dreamguard[groq]" # + Groq compressor only
39
+ ```
40
+
41
+ ## Quick start
42
+
43
+ ```bash
44
+ pip install "dreamguard[server]"
45
+ echo "GROQ_API_KEY=gsk_..." > .env # only needed for the /live demo
46
+ dreamguard serve # open http://localhost:8000
47
+ dreamguard benchmark # offline simulation in the terminal
48
+ ```
49
+
50
+ Development setup: `git clone ... && pip install -e ".[dev,server]" && pytest`.
51
+
52
+ ### Use it as a library
53
+
54
+ ```python
55
+ from dreamguard import MemoryEngine, HeuristicCompressor
56
+
57
+ mem = MemoryEngine(HeuristicCompressor(retention=0.7), destructive=False)
58
+ mem.record(1, "api_port=8421")
59
+ mem.record(2, "region=ap-south-1")
60
+
61
+ mem.dream() # compress; raw episodes + provenance are kept
62
+ print(mem.context()) # what your agent would see (digest)
63
+ mem.rollback() # back to exact raw episodes
64
+ print(mem.context())
65
+ ```
66
+
67
+ ### Live demo with a real LLM (Groq)
68
+
69
+ ```bash
70
+ # .env
71
+ GROQ_API_KEY=gsk_...
72
+ ```
73
+ Run `uvicorn dreamguard.api:app --reload`, open <http://localhost:8000/live>, then click:
74
+ **1 Load scenario → 2 Evaluate → 3 Dream → 2 Evaluate → 4 Rollback → 2 Evaluate.**
75
+
76
+ Groq compresses 12 engineering-log episodes into a short digest, then answers 8 factual questions (ports, ticket owners, hosts...) using *only* each system's remaining memory. Answers are graded against known ground truth. Both the in-place and DreamGuard engines get the same input; only DreamGuard can roll back. Set `GROQ_MODEL` to change the model (default `llama-3.1-8b-instant`).
77
+
78
+ Results are real LLM output, so they vary by model and run: a strong model may retain some details after compression. The rollback guarantee does not vary, because it restores the raw text.
79
+
80
+ ### Real LLM compression (optional)
81
+
82
+ ```bash
83
+ # .env
84
+ COMPRESSOR=groq
85
+ GROQ_API_KEY=gsk_...
86
+ ```
87
+
88
+ Uses Groq's `llama-3.1-8b-instant` to produce the digest. Note: the built-in **benchmark** always uses the heuristic compressor so that its results are reproducible.
89
+
90
+ ## How it works
91
+
92
+ | Table | Purpose |
93
+ |---|---|
94
+ | `episodes` | Raw, append-only memory (`turn`, `content`) |
95
+ | `semantic_memories` | Compressed digests, with an `active` flag |
96
+ | `provenance` | `memory_id → episode_id` links from each digest to its sources |
97
+
98
+ - `record(turn, content)` appends an episode.
99
+ - `dream()` compresses episodes not yet covered by an active digest.
100
+ - `rollback()` sets all digests to inactive. Their episodes become "uncovered" again, so `context()` returns the raw text.
101
+ - `context()` is what an agent reads: active digests plus any uncovered raw episodes.
102
+ - With `destructive=True` the engine mimics in-place dreaming: it deletes raw data and re-compresses digests together with new episodes, so loss compounds.
103
+
104
+ ## Benchmark methodology (read this before quoting numbers)
105
+
106
+ The benchmark is a **synthetic simulation**, not a measurement of any production system:
107
+
108
+ 1. Each turn records one exact detail, e.g. `param_7=48213`.
109
+ 2. Every 10 turns the memory is compressed with a seeded lossy compressor that keeps each value with probability 0.7 and otherwise keeps only the key.
110
+ 3. After every turn, **recall** = fraction of all details seen so far that appear verbatim (`key=value`) in the agent's visible context.
111
+
112
+ What this shows: *if* compression is lossy, an irreversible design loses information permanently, while a design that keeps raw data can restore it. What it does **not** show: how any specific LLM, product, or published system behaves. The absolute accuracy numbers come from the chosen retention parameter, not from real-world measurement.
113
+
114
+ **Known trade-off:** after a rollback, the agent context is raw again and therefore larger. The benefit of compression (smaller context) is traded for recall until the next dream.
115
+
116
+ ## API
117
+
118
+ | Method | Path | Description |
119
+ |---|---|---|
120
+ | POST | `/api/record` | `{"content": "..."}` record an episode |
121
+ | POST | `/api/dream` | Compress uncovered episodes |
122
+ | POST | `/api/rollback` | Deactivate compressed memory |
123
+ | POST | `/api/reset` | Clear the live engine |
124
+ | POST | `/api/demo/seed?n=10` | Add demo episodes |
125
+ | GET | `/api/memory` | Episodes, facts, provenance, context size |
126
+ | GET | `/api/benchmark` | Run benchmark (`seed`, `retention`, `auto_rollback`, `rollback_at`) |
127
+
128
+ Interactive docs at `/docs`.
129
+
130
+ ## Project layout
131
+
132
+ ```
133
+ dreamguard/
134
+ ├── dreamguard/
135
+ │ ├── db.py # SQLite schema
136
+ │ ├── compiler.py # Heuristic + Groq compressors
137
+ │ ├── engine.py # record / dream / rollback / evaluate
138
+ │ ├── benchmark.py # reproducible simulation
139
+ │ ├── live.py # live demo: Groq compression + Groq QA grading
140
+ │ ├── api.py # FastAPI app
141
+ │ ├── cli.py # `dreamguard` command
142
+ │ └── static/ # dashboard (HTML + CSS + Chart.js, no build step)
143
+ ├── tests/
144
+ └── pyproject.toml
145
+ ```
146
+
147
+ ## Testing
148
+
149
+ ```bash
150
+ pip install -e ".[dev,server]"
151
+ pytest
152
+ ```
153
+
154
+ ## Roadmap
155
+
156
+ - [ ] Selective rollback (restore only the episodes behind a specific fact)
157
+ - [ ] Benchmark with real LLM compression and an LLM-answered QA task
158
+ - [ ] Pluggable storage backends (Postgres, vector DBs)
159
+ - [ ] Rollback triggers driven by task-level eval signals
160
+
161
+ ## Contributing
162
+
163
+ Issues and PRs are welcome. Please run `pytest` before submitting.
164
+
165
+ ## License
166
+
167
+ MIT, see [LICENSE](LICENSE). Built by [Manojkumar V](https://github.com/manojkumar16122).
@@ -0,0 +1,6 @@
1
+ """DreamGuard: reversible memory compilation for AI agents."""
2
+ from .engine import MemoryEngine
3
+ from .compiler import HeuristicCompressor, GroqCompressor, get_compressor
4
+
5
+ __version__ = "0.1.0"
6
+ __all__ = ["MemoryEngine", "HeuristicCompressor", "GroqCompressor", "get_compressor"]
@@ -0,0 +1,3 @@
1
+ from .cli import main
2
+
3
+ main()
@@ -0,0 +1,141 @@
1
+ """FastAPI backend + static dashboard. Run: uvicorn dreamguard.api:app --reload"""
2
+ from pathlib import Path
3
+
4
+ from dotenv import load_dotenv
5
+ from fastapi import FastAPI, HTTPException
6
+ from fastapi.responses import FileResponse
7
+ from fastapi.staticfiles import StaticFiles
8
+ from pydantic import BaseModel, Field
9
+
10
+ from .benchmark import run_benchmark
11
+ from .compiler import get_compressor
12
+ from .engine import MemoryEngine
13
+ from .live import LiveDemo
14
+
15
+ load_dotenv()
16
+ STATIC = Path(__file__).resolve().parent / "static"
17
+
18
+ app = FastAPI(title="DreamGuard", version="0.1.0")
19
+ app.mount("/static", StaticFiles(directory=STATIC), name="static")
20
+
21
+ _state = {"engine": None, "turn": 0}
22
+
23
+
24
+ def _engine() -> MemoryEngine:
25
+ if _state["engine"] is None:
26
+ _state["engine"] = MemoryEngine(get_compressor(), destructive=False)
27
+ return _state["engine"]
28
+
29
+
30
+ class Episode(BaseModel):
31
+ content: str = Field(min_length=1, max_length=2000)
32
+
33
+
34
+ @app.get("/")
35
+ def index():
36
+ return FileResponse(STATIC / "index.html")
37
+
38
+
39
+ @app.post("/api/record")
40
+ def record(ep: Episode):
41
+ _state["turn"] += 1
42
+ _engine().record(_state["turn"], ep.content)
43
+ return {"turn": _state["turn"]}
44
+
45
+
46
+ @app.post("/api/dream")
47
+ def dream():
48
+ try:
49
+ fact = _engine().dream()
50
+ except Exception as e: # e.g. missing GROQ key / network error
51
+ raise HTTPException(500, f"Compression failed: {e}")
52
+ return {"fact": fact}
53
+
54
+
55
+ @app.post("/api/rollback")
56
+ def rollback():
57
+ return {"rolled_back": _engine().rollback()}
58
+
59
+
60
+ @app.post("/api/reset")
61
+ def reset():
62
+ _state.update(engine=None, turn=0)
63
+ return {"ok": True}
64
+
65
+
66
+ @app.post("/api/demo/seed")
67
+ def seed(n: int = 10):
68
+ eng = _engine()
69
+ for _ in range(n):
70
+ _state["turn"] += 1
71
+ t = _state["turn"]
72
+ eng.record(t, f"param_{t}={(t * 7919) % 90000 + 10000}")
73
+ return {"turn": _state["turn"]}
74
+
75
+
76
+ @app.get("/api/memory")
77
+ def memory():
78
+ return _engine().snapshot()
79
+
80
+
81
+ @app.get("/api/benchmark")
82
+ def benchmark(seed: int = 42, retention: float = 0.7, auto_rollback: bool = False,
83
+ rollback_at: int | None = 40):
84
+ return run_benchmark(seed=seed, retention=retention, auto_rollback=auto_rollback,
85
+ rollback_at=None if auto_rollback else rollback_at)
86
+
87
+
88
+ # ---- live demo (real Groq compression + Groq question answering) ----------
89
+ _live: dict = {"demo": None}
90
+
91
+
92
+ def _demo() -> LiveDemo:
93
+ if _live["demo"] is None:
94
+ raise HTTPException(400, "Click 'Load scenario' first.")
95
+ return _live["demo"]
96
+
97
+
98
+ def _guard(fn):
99
+ try:
100
+ return fn()
101
+ except HTTPException:
102
+ raise
103
+ except Exception as e: # missing key, rate limit, network, bad JSON...
104
+ raise HTTPException(502, f"Groq error: {e}")
105
+
106
+
107
+ @app.get("/live")
108
+ def live_page():
109
+ return FileResponse(STATIC / "live.html")
110
+
111
+
112
+ @app.post("/api/live/start")
113
+ def live_start():
114
+ _live["demo"] = _guard(LiveDemo)
115
+ return _live["demo"].state()
116
+
117
+
118
+ @app.post("/api/live/dream")
119
+ def live_dream():
120
+ d = _demo()
121
+ _guard(d.dream)
122
+ return d.state()
123
+
124
+
125
+ @app.post("/api/live/rollback")
126
+ def live_rollback():
127
+ d = _demo()
128
+ d.rollback()
129
+ return d.state()
130
+
131
+
132
+ @app.post("/api/live/evaluate")
133
+ def live_evaluate(label: str = "check"):
134
+ d = _demo()
135
+ _guard(lambda: d.evaluate(label[:40]))
136
+ return d.state()
137
+
138
+
139
+ @app.get("/api/live/state")
140
+ def live_state():
141
+ return _demo().state()
@@ -0,0 +1,76 @@
1
+ """Synthetic detail-retention benchmark.
2
+
3
+ Each turn the agent records an episode containing one exact detail
4
+ (`param_7=48213`). Every `dream_every` turns the memory is compressed. After
5
+ each turn we probe recall of ALL details seen so far (exact match against the
6
+ agent's visible context). No LLM is involved, so results are deterministic.
7
+
8
+ Systems compared:
9
+ raw - never compresses (accuracy reference; context grows unbounded)
10
+ in_place - compresses by rewriting memory, raw data deleted
11
+ dreamguard - same compressor, raw data kept. Rolls back either manually at
12
+ `rollback_at` (default: turn 40, for a visible drop-and-recovery
13
+ demo) or automatically when probe accuracy < threshold
14
+ (`auto_rollback=True`). Dreaming then pauses for `cooldown` turns.
15
+ """
16
+ import json
17
+ import random
18
+ import sys
19
+
20
+ from .compiler import HeuristicCompressor
21
+ from .engine import MemoryEngine
22
+
23
+
24
+ def run_benchmark(turns: int = 50, dream_every: int = 10, retention: float = 0.7,
25
+ threshold: float = 0.85, cooldown: int = 15, seed: int = 42,
26
+ auto_rollback: bool = False, rollback_at: int | None = 40) -> dict:
27
+ rng = random.Random(seed)
28
+ mk = lambda destructive: MemoryEngine(HeuristicCompressor(retention, seed), destructive)
29
+ systems = {"raw": mk(False), "in_place": mk(True), "dreamguard": mk(False)}
30
+ truth: dict[str, str] = {}
31
+ rows, events = [], []
32
+ paused_until = 0
33
+
34
+ for t in range(1, turns + 1):
35
+ key, val = f"param_{t}", str(rng.randint(10000, 99999))
36
+ truth[key] = val
37
+ for eng in systems.values():
38
+ eng.record(t, f"{key}={val}")
39
+
40
+ if t % dream_every == 0:
41
+ systems["in_place"].dream()
42
+ if t >= paused_until:
43
+ systems["dreamguard"].dream()
44
+ events.append({"turn": t, "type": "dream"})
45
+
46
+ dg = systems["dreamguard"]
47
+ manual = rollback_at is not None and t == rollback_at
48
+ auto = auto_rollback and t >= paused_until and dg.evaluate(truth) < threshold
49
+ if manual or auto:
50
+ dg.rollback()
51
+ paused_until = t + cooldown
52
+ events.append({"turn": t, "type": "rollback"})
53
+
54
+ rows.append({
55
+ "turn": t,
56
+ **{f"{n}_acc": round(100 * e.evaluate(truth), 1) for n, e in systems.items()},
57
+ **{f"{n}_chars": len(e.context()) for n, e in systems.items()},
58
+ })
59
+
60
+ return {"config": {"turns": turns, "dream_every": dream_every, "retention": retention,
61
+ "threshold": threshold, "cooldown": cooldown, "seed": seed,
62
+ "auto_rollback": auto_rollback, "rollback_at": rollback_at},
63
+ "rows": rows, "events": events}
64
+
65
+
66
+ if __name__ == "__main__":
67
+ auto = "--auto" in sys.argv
68
+ res = run_benchmark(auto_rollback=auto, rollback_at=None if auto else 40)
69
+ print(f"{'turn':>4} {'raw':>6} {'in_place':>9} {'dreamguard':>11}")
70
+ for r in res["rows"]:
71
+ if r["turn"] % 5 == 0:
72
+ print(f"{r['turn']:>4} {r['raw_acc']:>5}% {r['in_place_acc']:>8}% {r['dreamguard_acc']:>10}%")
73
+ print("events:", res["events"])
74
+ if "--json" in sys.argv:
75
+ with open("benchmark_results.json", "w") as f:
76
+ json.dump(res, f, indent=2)
@@ -0,0 +1,34 @@
1
+ """Command line: `dreamguard serve` | `dreamguard benchmark [--auto]`."""
2
+ import argparse
3
+ import sys
4
+
5
+
6
+ def main(argv=None):
7
+ p = argparse.ArgumentParser(prog="dreamguard", description="Reversible memory compilation for AI agents")
8
+ sub = p.add_subparsers(dest="cmd", required=True)
9
+ s = sub.add_parser("serve", help="start the dashboard + API")
10
+ s.add_argument("--host", default="127.0.0.1")
11
+ s.add_argument("--port", type=int, default=8000)
12
+ b = sub.add_parser("benchmark", help="run the offline simulation")
13
+ b.add_argument("--auto", action="store_true", help="auto-rollback instead of manual at turn 40")
14
+ args = p.parse_args(argv)
15
+
16
+ if args.cmd == "serve":
17
+ try:
18
+ import uvicorn
19
+ from .api import app
20
+ except ImportError:
21
+ sys.exit('Server extras missing. Install with: pip install "dreamguard[server]"')
22
+ uvicorn.run(app, host=args.host, port=args.port)
23
+ else:
24
+ from .benchmark import run_benchmark
25
+ res = run_benchmark(auto_rollback=args.auto, rollback_at=None if args.auto else 40)
26
+ print(f"{'turn':>4} {'raw':>6} {'in_place':>9} {'dreamguard':>11}")
27
+ for r in res["rows"]:
28
+ if r["turn"] % 5 == 0:
29
+ print(f"{r['turn']:>4} {r['raw_acc']:>5}% {r['in_place_acc']:>8}% {r['dreamguard_acc']:>10}%")
30
+ print("events:", res["events"])
31
+
32
+
33
+ if __name__ == "__main__":
34
+ main()