memoryagent-lib 0.1.1__py3-none-any.whl

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,35 @@
1
+ from memoryagent.config import MemorySystemConfig
2
+ from memoryagent.models import (
3
+ ConfidenceReport,
4
+ MemoryBlock,
5
+ MemoryEvent,
6
+ MemoryItem,
7
+ MemoryQuery,
8
+ MemoryType,
9
+ RetrievalPlan,
10
+ )
11
+ from memoryagent.policy import (
12
+ ConversationMemoryPolicy,
13
+ HeuristicMemoryPolicy,
14
+ MemoryDecision,
15
+ MemoryRoutingPolicy,
16
+ RoutingDecision,
17
+ )
18
+ from memoryagent.system import MemorySystem
19
+
20
+ __all__ = [
21
+ "MemorySystem",
22
+ "MemorySystemConfig",
23
+ "MemoryEvent",
24
+ "MemoryItem",
25
+ "MemoryQuery",
26
+ "MemoryType",
27
+ "MemoryBlock",
28
+ "RetrievalPlan",
29
+ "ConfidenceReport",
30
+ "ConversationMemoryPolicy",
31
+ "HeuristicMemoryPolicy",
32
+ "MemoryDecision",
33
+ "MemoryRoutingPolicy",
34
+ "RoutingDecision",
35
+ ]
@@ -0,0 +1,82 @@
1
+ from __future__ import annotations
2
+
3
+ from datetime import datetime, timezone
4
+ from typing import List
5
+
6
+ from memoryagent.models import ConfidenceReport, MemoryQuery, ScoredMemory
7
+ from memoryagent.utils import clamp, safe_div, unique_tokens
8
+
9
+
10
+ def _semantic_relevance(results: List[ScoredMemory]) -> float:
11
+ if not results:
12
+ return 0.0
13
+ top_scores = [r.score for r in results[:5]]
14
+ return sum(top_scores) / len(top_scores)
15
+
16
+
17
+ def _coverage(query: MemoryQuery, results: List[ScoredMemory]) -> float:
18
+ query_tokens = unique_tokens(query.text)
19
+ if not query_tokens:
20
+ return 0.0
21
+ covered = set()
22
+ for item in results[:5]:
23
+ covered |= unique_tokens(item.item.text())
24
+ return safe_div(len(query_tokens & covered), len(query_tokens))
25
+
26
+
27
+ def _temporal_fit(results: List[ScoredMemory]) -> float:
28
+ if not results:
29
+ return 0.0
30
+ now = datetime.now(timezone.utc)
31
+ scores = []
32
+ for item in results[:5]:
33
+ age_days = max(0.0, (now - item.item.created_at).total_seconds() / 86400)
34
+ scores.append(1.0 / (1.0 + age_days))
35
+ return sum(scores) / len(scores)
36
+
37
+
38
+ def _authority(results: List[ScoredMemory]) -> float:
39
+ if not results:
40
+ return 0.0
41
+ scores = [0.5 * r.item.authority + 0.5 * r.item.stability for r in results[:5]]
42
+ return sum(scores) / len(scores)
43
+
44
+
45
+ def _consistency(results: List[ScoredMemory]) -> float:
46
+ if len(results) < 2:
47
+ return 0.5
48
+ tag_sets = [set(r.item.tags) for r in results[:5] if r.item.tags]
49
+ if not tag_sets:
50
+ return 0.4
51
+ overlap = set.intersection(*tag_sets) if len(tag_sets) > 1 else tag_sets[0]
52
+ union = set.union(*tag_sets) if len(tag_sets) > 1 else tag_sets[0]
53
+ return safe_div(len(overlap), len(union))
54
+
55
+
56
+ def evaluate_confidence(query: MemoryQuery, results: List[ScoredMemory]) -> ConfidenceReport:
57
+ semantic = _semantic_relevance(results)
58
+ coverage = _coverage(query, results)
59
+ temporal = _temporal_fit(results)
60
+ authority = _authority(results)
61
+ consistency = _consistency(results)
62
+
63
+ total = clamp(0.35 * semantic + 0.2 * coverage + 0.2 * temporal + 0.15 * authority + 0.1 * consistency)
64
+
65
+ if total >= 0.75:
66
+ recommendation = "accept"
67
+ elif total >= 0.6:
68
+ recommendation = "escalate_archive"
69
+ elif total >= 0.45:
70
+ recommendation = "fetch_cold"
71
+ else:
72
+ recommendation = "uncertain"
73
+
74
+ return ConfidenceReport(
75
+ total=total,
76
+ semantic_relevance=semantic,
77
+ coverage=coverage,
78
+ temporal_fit=temporal,
79
+ authority=authority,
80
+ consistency=consistency,
81
+ recommendation=recommendation,
82
+ )
memoryagent/config.py ADDED
@@ -0,0 +1,35 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+ from typing import Optional
5
+
6
+ from pydantic import BaseModel, Field
7
+
8
+ from memoryagent.models import RetrievalPlan
9
+
10
+
11
+ class ConsolidationConfig(BaseModel):
12
+ archive_on_flush: bool = True
13
+ semantic_min_count: int = 2
14
+ perceptual_summary_limit: int = 5
15
+
16
+
17
+ class MemorySystemConfig(BaseModel):
18
+ """System-wide configuration with sane local defaults."""
19
+
20
+ working_ttl_seconds: int = 3600
21
+ retrieval_plan: RetrievalPlan = Field(default_factory=RetrievalPlan)
22
+ consolidation: ConsolidationConfig = Field(default_factory=ConsolidationConfig)
23
+ cold_store_path: Path = Field(default_factory=lambda: Path(".memoryagent_cold"))
24
+ metadata_db_path: Path = Field(default_factory=lambda: Path(".memoryagent_hot.sqlite"))
25
+ feature_db_path: Path = Field(default_factory=lambda: Path(".memoryagent_features.sqlite"))
26
+ vector_db_path: Path = Field(default_factory=lambda: Path(".memoryagent_vectors.sqlite"))
27
+ vector_dim: int = 384
28
+ use_sqlite_vec: bool = False
29
+ sqlite_vec_extension_path: Optional[Path] = None
30
+ archive_index_path: Optional[Path] = None
31
+
32
+ def resolved_archive_path(self) -> Path:
33
+ if self.archive_index_path is not None:
34
+ return self.archive_index_path
35
+ return self.cold_store_path / "archive_index.json"
@@ -0,0 +1,5 @@
1
+ from __future__ import annotations
2
+
3
+ from memoryagent.workers import ConsolidationWorker
4
+
5
+ __all__ = ["ConsolidationWorker"]
@@ -0,0 +1,110 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import sqlite3
5
+ from pathlib import Path
6
+ from typing import Any, Dict, List
7
+
8
+ ROOT = Path(__file__).resolve().parents[2]
9
+ COLD_ROOT = ROOT / ".memoryagent_cold"
10
+ HOT_DB = ROOT / ".memoryagent_hot.sqlite"
11
+ FEATURE_DB = ROOT / ".memoryagent_features.sqlite"
12
+ ARCHIVE_INDEX = COLD_ROOT / "archive_index.json"
13
+
14
+
15
+ def load_hot() -> List[Dict[str, Any]]:
16
+ if not HOT_DB.exists():
17
+ return []
18
+ with sqlite3.connect(HOT_DB) as conn:
19
+ rows = conn.execute(
20
+ "SELECT id, type, owner, summary, content_json, tags_json, created_at, updated_at, last_accessed, tier, pointer_json, ttl_seconds, confidence, authority, stability FROM memory_items"
21
+ ).fetchall()
22
+ items = []
23
+ for row in rows:
24
+ (
25
+ item_id,
26
+ item_type,
27
+ owner,
28
+ summary,
29
+ content_json,
30
+ tags_json,
31
+ created_at,
32
+ updated_at,
33
+ last_accessed,
34
+ tier,
35
+ pointer_json,
36
+ ttl_seconds,
37
+ confidence,
38
+ authority,
39
+ stability,
40
+ ) = row
41
+ items.append(
42
+ {
43
+ "id": item_id,
44
+ "type": item_type,
45
+ "owner": owner,
46
+ "summary": summary,
47
+ "content": json.loads(content_json) if content_json else None,
48
+ "tags": json.loads(tags_json) if tags_json else [],
49
+ "created_at": created_at,
50
+ "updated_at": updated_at,
51
+ "last_accessed": last_accessed,
52
+ "tier": tier,
53
+ "pointer": json.loads(pointer_json) if pointer_json else {},
54
+ "ttl_seconds": ttl_seconds,
55
+ "confidence": confidence,
56
+ "authority": authority,
57
+ "stability": stability,
58
+ }
59
+ )
60
+ return items
61
+
62
+
63
+ def load_features() -> List[Dict[str, Any]]:
64
+ if not FEATURE_DB.exists():
65
+ return []
66
+ with sqlite3.connect(FEATURE_DB) as conn:
67
+ rows = conn.execute("SELECT owner, created_at, payload_json FROM features").fetchall()
68
+ features = []
69
+ for owner, created_at, payload_json in rows:
70
+ features.append(
71
+ {
72
+ "owner": owner,
73
+ "created_at": created_at,
74
+ "payload": json.loads(payload_json),
75
+ }
76
+ )
77
+ return features
78
+
79
+
80
+ def load_cold_records() -> List[Dict[str, Any]]:
81
+ if not COLD_ROOT.exists():
82
+ return []
83
+ records = []
84
+ records_root = COLD_ROOT / "records"
85
+ if not records_root.exists():
86
+ return []
87
+ for path in records_root.rglob("*.json"):
88
+ try:
89
+ records.append({"path": str(path.relative_to(ROOT)), "payload": json.loads(path.read_text())})
90
+ except Exception:
91
+ continue
92
+ return records
93
+
94
+
95
+ def load_archive_index() -> Dict[str, Any]:
96
+ if not ARCHIVE_INDEX.exists():
97
+ return {}
98
+ return json.loads(ARCHIVE_INDEX.read_text())
99
+
100
+
101
+ def get_memory_payload() -> Dict[str, Any]:
102
+ return {
103
+ "hot_items": load_hot(),
104
+ "features": load_features(),
105
+ "cold_records": load_cold_records(),
106
+ "archive_index": load_archive_index(),
107
+ }
108
+
109
+
110
+ __all__ = ["get_memory_payload"]
@@ -0,0 +1,223 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from http.server import BaseHTTPRequestHandler, HTTPServer
5
+ from pathlib import Path
6
+
7
+ import os
8
+ from memoryagent.examples.export_memory import get_memory_payload
9
+ from uuid import uuid4
10
+
11
+ from memoryagent import (
12
+ HeuristicMemoryPolicy,
13
+ MemoryItem,
14
+ MemoryRoutingPolicy,
15
+ MemorySystem,
16
+ MemorySystemConfig,
17
+ MemoryType,
18
+ )
19
+ from memoryagent.utils import hash_embed, tokenize
20
+
21
+ from dotenv import load_dotenv
22
+
23
+ load_dotenv()
24
+
25
+ try:
26
+ from openai import OpenAI
27
+ except Exception:
28
+ OpenAI = None
29
+
30
+ _history = {}
31
+
32
+
33
+ def _get_openai_client():
34
+ if OpenAI is None:
35
+ raise RuntimeError("openai package is not installed")
36
+ return OpenAI()
37
+
38
+
39
+ def _openai_embedder(client, model: str, dim: int):
40
+ def _embed(text: str):
41
+ try:
42
+ response = client.embeddings.create(model=model, input=text)
43
+ return response.data[0].embedding
44
+ except Exception:
45
+ return hash_embed(text, dim)
46
+
47
+ return _embed
48
+
49
+
50
+ def _history_entry_text(entry) -> str:
51
+ if isinstance(entry, str):
52
+ return entry
53
+ if isinstance(entry, dict):
54
+ if "user" in entry and "assistant" in entry:
55
+ return f"User: {entry['user']} Assistant: {entry['assistant']}"
56
+ if "role" in entry and "text" in entry:
57
+ return f"{entry['role']}: {entry['text']}"
58
+ return str(entry)
59
+
60
+
61
+ def _get_memory_system():
62
+ model = os.environ.get("OPENAI_MODEL", "gpt-4o-mini")
63
+ embedding_model = os.environ.get("OPENAI_EMBED_MODEL", "text-embedding-3-small")
64
+ vector_dim = int(os.environ.get("OPENAI_EMBED_DIM", "1536"))
65
+
66
+ config = MemorySystemConfig(
67
+ use_sqlite_vec=True,
68
+ vector_dim=vector_dim,
69
+ sqlite_vec_extension_path=os.environ.get("SQLITE_VEC_PATH"),
70
+ )
71
+ client = _get_openai_client()
72
+ memory = MemorySystem(
73
+ config=config,
74
+ embedding_fn=_openai_embedder(client, embedding_model, config.vector_dim),
75
+ )
76
+ return memory, client, model
77
+
78
+
79
+ class MemoryAPIHandler(BaseHTTPRequestHandler):
80
+ def _send_json(self, payload, status=200):
81
+ data = json.dumps(payload, ensure_ascii=True).encode("utf-8")
82
+ self.send_response(status)
83
+ self.send_header("Content-Type", "application/json")
84
+ self.send_header("Content-Length", str(len(data)))
85
+ self.send_header("Access-Control-Allow-Origin", "*")
86
+ self.end_headers()
87
+ self.wfile.write(data)
88
+
89
+ def do_GET(self):
90
+ if self.path.startswith("/api/memory"):
91
+ payload = get_memory_payload()
92
+ owner = None
93
+ if "?" in self.path:
94
+ _, query = self.path.split("?", 1)
95
+ for part in query.split("&"):
96
+ if part.startswith("owner="):
97
+ owner = part.split("=", 1)[1]
98
+ break
99
+ if owner:
100
+ payload["hot_items"] = [item for item in payload.get("hot_items", []) if item.get("owner") == owner]
101
+ payload["features"] = [
102
+ item for item in payload.get("features", []) if item.get("owner") == owner
103
+ ]
104
+ payload["cold_records"] = [
105
+ item for item in payload.get("cold_records", [])
106
+ if f"/{owner}/" in item.get("path", "")
107
+ ]
108
+ self._send_json(payload)
109
+ return
110
+ if self.path in {"/", "/memory_viz.html"}:
111
+ html_path = Path(__file__).resolve().parent / "memory_viz.html"
112
+ if not html_path.exists():
113
+ self.send_error(404, "memory_viz.html not found")
114
+ return
115
+ data = html_path.read_bytes()
116
+ self.send_response(200)
117
+ self.send_header("Content-Type", "text/html")
118
+ self.send_header("Content-Length", str(len(data)))
119
+ self.end_headers()
120
+ self.wfile.write(data)
121
+ return
122
+ self.send_error(404, "Not found")
123
+
124
+ def do_POST(self):
125
+ if self.path not in {"/api/chat", "/api/chat/"}:
126
+ self.send_error(404, "Not found")
127
+ return
128
+ length = int(self.headers.get("Content-Length", "0"))
129
+ body = self.rfile.read(length).decode("utf-8") if length else "{}"
130
+ try:
131
+ payload = json.loads(body)
132
+ except Exception:
133
+ self.send_error(400, "Invalid JSON")
134
+ return
135
+
136
+ owner = payload.get("owner", "user-001")
137
+ message = payload.get("message", "").strip()
138
+ if not message:
139
+ self.send_error(400, "Missing message")
140
+ return
141
+
142
+ try:
143
+ memory, client, model = _get_memory_system()
144
+ except Exception as exc:
145
+ self.send_error(500, str(exc))
146
+ return
147
+
148
+ session = _history.setdefault(owner, {"turns": [], "working_id": str(uuid4())})
149
+ history = session["turns"]
150
+ bundle = memory.retrieve(message, owner=owner)
151
+ context_blocks = []
152
+ token_budget = memory.config.retrieval_plan.max_context_tokens
153
+ used_tokens = 0
154
+ for block in bundle.blocks:
155
+ block_text = f"- [{block.memory_type}] {block.text}"
156
+ if not isinstance(block_text, str):
157
+ block_text = str(block_text)
158
+ block_tokens = len(tokenize(block_text))
159
+ if used_tokens + block_tokens > token_budget:
160
+ break
161
+ context_blocks.append(block_text)
162
+ used_tokens += block_tokens
163
+ memory_context = "\n".join(str(item) for item in context_blocks) if context_blocks else "None."
164
+ recent_turns = history[-6:]
165
+ history_text_entries = [_history_entry_text(entry) for entry in recent_turns]
166
+ history_text = "\n".join(history_text_entries) if history_text_entries else "None."
167
+ prompt = (
168
+ "You are a helpful assistant.\n"
169
+ "Use the following memory context and recent chat history if relevant.\n"
170
+ f"Memory context:\n{memory_context}\n\n"
171
+ f"Recent chat:\n{history_text}\n\n"
172
+ f"User: {message}\n"
173
+ "Assistant:"
174
+ )
175
+ response = client.responses.create(model=model, input=prompt)
176
+ assistant_message = response.output_text
177
+
178
+ history.append({"user": message, "assistant": assistant_message})
179
+
180
+ working_item = MemoryItem(
181
+ id=session["working_id"],
182
+ type=MemoryType.WORKING,
183
+ owner=owner,
184
+ summary=f"Session transcript ({len(history)} turns)",
185
+ content={"turns": history},
186
+ tags=["conversation", "session-log"],
187
+ ttl_seconds=memory.config.working_ttl_seconds,
188
+ confidence=0.6,
189
+ )
190
+ memory.write(working_item)
191
+
192
+ policy = HeuristicMemoryPolicy()
193
+ routing_policy = MemoryRoutingPolicy()
194
+ history_for_policy = [_history_entry_text(entry) for entry in history]
195
+ decision = policy.should_store(owner, history_for_policy, message, assistant_message)
196
+ event = policy.to_event(owner, decision)
197
+ if event:
198
+ routing = routing_policy.route(event.to_item())
199
+ if routing.write_hot or routing.write_vector or routing.write_features:
200
+ memory.write(event)
201
+
202
+ self._send_json(
203
+ {
204
+ "reply": assistant_message,
205
+ "trace": {
206
+ "steps": bundle.trace.steps,
207
+ "escalations": bundle.trace.escalations,
208
+ "sources": bundle.trace.sources,
209
+ "confidence": bundle.confidence.total,
210
+ },
211
+ }
212
+ )
213
+
214
+
215
+ def main() -> None:
216
+ server = HTTPServer(("127.0.0.1", 8000), MemoryAPIHandler)
217
+ print("Serving memory API at http://127.0.0.1:8000/api/memory")
218
+ print("Open http://127.0.0.1:8000/memory_viz.html")
219
+ server.serve_forever()
220
+
221
+
222
+ if __name__ == "__main__":
223
+ main()
@@ -0,0 +1,47 @@
1
+ from memoryagent import MemoryEvent, MemorySystem
2
+
3
+
4
+ def main() -> None:
5
+ memory = MemorySystem()
6
+
7
+ owner = "session-123"
8
+ memory.write(
9
+ MemoryEvent(
10
+ content="User prefers concise summaries about climate policy.",
11
+ type="semantic",
12
+ owner=owner,
13
+ tags=["preference", "summary"],
14
+ confidence=0.7,
15
+ stability=0.8,
16
+ )
17
+ )
18
+
19
+ memory.write(
20
+ MemoryEvent(
21
+ content="Discussed EU carbon border adjustment mechanism.",
22
+ type="episodic",
23
+ owner=owner,
24
+ tags=["eu", "policy"],
25
+ )
26
+ )
27
+
28
+ memory.write_perceptual(
29
+ {
30
+ "content": "Audio signal indicates frustration when asked about timelines.",
31
+ "owner": owner,
32
+ "tags": ["sentiment", "frustration"],
33
+ }
34
+ )
35
+
36
+ bundle = memory.retrieve("What policy topics did we cover?", owner=owner)
37
+
38
+ print("Confidence:", bundle.confidence.total)
39
+ print("Blocks:")
40
+ for block in bundle.blocks:
41
+ print(f"- [{block.memory_type}] {block.text}")
42
+
43
+ memory.flush(owner)
44
+
45
+
46
+ if __name__ == "__main__":
47
+ main()
@@ -0,0 +1,137 @@
1
+ import os
2
+ import time
3
+ from uuid import uuid4
4
+ from typing import List
5
+
6
+ from openai import OpenAI
7
+
8
+ from memoryagent import (
9
+ HeuristicMemoryPolicy,
10
+ MemoryEvent,
11
+ MemoryItem,
12
+ MemoryRoutingPolicy,
13
+ MemorySystem,
14
+ MemorySystemConfig,
15
+ MemoryType,
16
+ )
17
+ from memoryagent.utils import hash_embed, tokenize
18
+ from dotenv import load_dotenv
19
+
20
+ load_dotenv()
21
+
22
+
23
+ def openai_embedder(client: OpenAI, model: str, dim: int):
24
+ def _embed(text: str):
25
+ try:
26
+ response = client.embeddings.create(model=model, input=text)
27
+ return response.data[0].embedding
28
+ except Exception:
29
+ return hash_embed(text, dim)
30
+
31
+ return _embed
32
+
33
+
34
+ def main() -> None:
35
+ # Requires: pip install openai sqlite-vec (or set SQLITE_VEC_PATH)
36
+ client = OpenAI()
37
+
38
+ model = os.environ.get("OPENAI_MODEL", "gpt-4o-mini")
39
+ embedding_model = os.environ.get("OPENAI_EMBED_MODEL", "text-embedding-3-small")
40
+ vector_dim = int(os.environ.get("OPENAI_EMBED_DIM", "1536"))
41
+
42
+ config = MemorySystemConfig(
43
+ use_sqlite_vec=True,
44
+ vector_dim=vector_dim,
45
+ sqlite_vec_extension_path=os.environ.get("SQLITE_VEC_PATH"),
46
+ )
47
+ memory = MemorySystem(
48
+ config=config,
49
+ embedding_fn=openai_embedder(client, embedding_model, config.vector_dim),
50
+ )
51
+ policy = HeuristicMemoryPolicy()
52
+ routing_policy = MemoryRoutingPolicy()
53
+ session_working_id = str(uuid4())
54
+
55
+ owner = "user-001"
56
+ history: List[str] = []
57
+
58
+ while True:
59
+ user_message = input("User: ").strip()
60
+ if user_message.lower() in {"exit", "quit"}:
61
+ break
62
+
63
+ turn_start = time.time()
64
+ bundle = memory.retrieve(user_message, owner=owner)
65
+ print(
66
+ f"[trace] retrieve count={len(bundle.blocks)} confidence={bundle.confidence.total:.2f} tiers={bundle.used_tiers}"
67
+ )
68
+ if bundle.trace.escalations:
69
+ print(f"[trace] escalations={bundle.trace.escalations}")
70
+ if bundle.trace.steps:
71
+ print(f"[trace] steps={bundle.trace.steps}")
72
+ if bundle.trace.sources:
73
+ print(f"[trace] sources={bundle.trace.sources}")
74
+ context_blocks = []
75
+ token_budget = memory.config.retrieval_plan.max_context_tokens
76
+ used_tokens = 0
77
+ for block in bundle.blocks:
78
+ block_text = f"- [{block.memory_type}] {block.text}"
79
+ block_tokens = len(tokenize(block_text))
80
+ if used_tokens + block_tokens > token_budget:
81
+ break
82
+ context_blocks.append(block_text)
83
+ used_tokens += block_tokens
84
+ memory_context = "\n".join(context_blocks) if context_blocks else "None."
85
+ recent_turns = history[-6:]
86
+ history_text = "\n".join(recent_turns) if recent_turns else "None."
87
+ prompt = (
88
+ "You are a helpful assistant.\n"
89
+ "Use the following memory context and recent chat history if relevant.\n"
90
+ f"Memory context:\n{memory_context}\n\n"
91
+ f"Recent chat:\n{history_text}\n\n"
92
+ f"User: {user_message}\n"
93
+ "Assistant:"
94
+ )
95
+
96
+ response = client.responses.create(
97
+ model=model,
98
+ input=prompt,
99
+ )
100
+ assistant_message = response.output_text
101
+ print(f"[trace] llm_latency_ms={(time.time() - turn_start) * 1000:.0f}")
102
+ print(f"Assistant: {assistant_message}")
103
+
104
+ history.append(f"User: {user_message}")
105
+ history.append(f"Assistant: {assistant_message}")
106
+
107
+ turns = [
108
+ {"role": "user", "text": entry.replace("User: ", "")}
109
+ if entry.startswith("User: ")
110
+ else {"role": "assistant", "text": entry.replace("Assistant: ", "")}
111
+ for entry in history
112
+ ]
113
+ working_item = MemoryItem(
114
+ id=session_working_id,
115
+ type=MemoryType.WORKING,
116
+ owner=owner,
117
+ summary=f"Session transcript ({len(turns)} turns)",
118
+ content={"turns": turns},
119
+ tags=["conversation", "session-log"],
120
+ ttl_seconds=memory.config.working_ttl_seconds,
121
+ confidence=0.6,
122
+ )
123
+ memory.write(working_item)
124
+
125
+ decision = policy.should_store(owner, history, user_message, assistant_message)
126
+ print(decision)
127
+ event = policy.to_event(owner, decision)
128
+ if event:
129
+ routing = routing_policy.route(event.to_item())
130
+ print(f"[memory] store={decision.store} routing={routing.reasons}")
131
+ memory.write(event)
132
+
133
+ memory.flush(owner)
134
+
135
+
136
+ if __name__ == "__main__":
137
+ main()