mlx-memo 0.5.0__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.
memo/__init__.py ADDED
@@ -0,0 +1,30 @@
1
+ """memo — local MCP memory backed by Obsidian vault, MLX-native.
2
+
3
+ 100% local stack — zero Ollama, zero cloud APIs:
4
+
5
+ - LLM: `mlx-lm` running Qwen2.5-Instruct quantized models on Apple
6
+ Silicon Metal (in-process, no daemon).
7
+ - Embedder: `mlx-community/Qwen3-Embedding-0.6B-4bit-DWQ` with
8
+ last-token pooling + L2 normalize (1024-dim).
9
+ - Vector store: `sqlite-vec` (single file, no daemon, no Qdrant).
10
+ - Storage of record: markdown files under
11
+ `<vault>/99-obsidian/99-AI/memory/`.
12
+ - MCP server: `fastmcp` with tools `memory_save`, `memory_search`,
13
+ `memory_list`, `memory_get`, `memory_update`, `memory_delete`.
14
+
15
+ Public API:
16
+
17
+ from memo import Memory, Config
18
+
19
+ cfg = Config.from_env()
20
+ mem = Memory(cfg)
21
+ mem.save(content="...", title="...", tags=["x", "y"])
22
+ hits = mem.search("query", limit=10)
23
+ """
24
+
25
+ from memo.config import Config
26
+ from memo.memory import Memory, MemoryRecord
27
+
28
+ __version__ = "0.1.0"
29
+
30
+ __all__ = ["Config", "Memory", "MemoryRecord", "__version__"]
memo/agent.py ADDED
@@ -0,0 +1,377 @@
1
+ """Autonomous Memory Agent — razonamiento causal y síntesis de conocimiento.
2
+
3
+ Este es EL GAMECHANGER: transforma memo de un sistema de recuperación pasivo
4
+ a un sistema de razonamiento activo que puede generar nuevo conocimiento.
5
+
6
+ ## Características Revolucionarias
7
+
8
+ 1. **Agente Autónomo**: Un LLM que proactivamente explora el corpus sin que el usuario lo pida
9
+ 2. **Razonamiento Causal**: Entiende POR QUÉ las cosas están conectadas, no solo que lo están
10
+ 3. **Síntesis de Conocimiento**: Combina memorias para generar insights que no existían antes
11
+ 4. **Meta-Cognición**: El agente reflexiona sobre su propio proceso de razonamiento
12
+ 5. **Planificación**: El agente puede planificar investigaciones complejas
13
+ 6. **Proactividad**: Sugerencias de información antes de que el usuario la pida
14
+
15
+ ## Diferencia con Sistemas Tradicionales
16
+
17
+ - **RAG tradicional**: Recupera información relevante para una query
18
+ - **Este agente**: Genera nuevo conocimiento a través de razonamiento multi-paso
19
+
20
+ Ejemplo:
21
+ - RAG: "¿Qué sé sobre MLX?" → Recupera memorias sobre MLX
22
+ - Agente: "Explora las implicaciones de usar MLX para inferencia en edge devices" →
23
+ - Busca memorias sobre MLX, edge computing, hardware constraints
24
+ - Razona sobre trade-offs
25
+ - Sintetiza nueva conclusión: "MLX es ideal para edge porque X, Y, Z"
26
+ """
27
+
28
+ from __future__ import annotations
29
+
30
+ import json
31
+ from dataclasses import dataclass
32
+ from datetime import UTC, datetime
33
+ from enum import Enum
34
+ from pathlib import Path
35
+ from typing import Any
36
+
37
+ from pydantic import BaseModel
38
+
39
+
40
+ class ReasoningStep(BaseModel):
41
+ """Un paso en el proceso de razonamiento."""
42
+ step_number: int
43
+ action: str # "search", "analyze", "synthesize", "validate"
44
+ query: str
45
+ results: list[str] # memoria IDs
46
+ reasoning: str
47
+ confidence: float
48
+
49
+
50
+ class InvestigationPlan(BaseModel):
51
+ """Plan de investigación generado por el agente."""
52
+ goal: str
53
+ steps: list[str]
54
+ estimated_complexity: int
55
+ estimated_insight_value: int
56
+
57
+
58
+ class SynthesisResult(BaseModel):
59
+ """Resultado de síntesis de conocimiento."""
60
+ new_insight: str
61
+ supporting_memorias: list[str]
62
+ reasoning_chain: list[ReasoningStep]
63
+ confidence: float
64
+ novelty_score: float
65
+
66
+
67
+ class AgentThought(BaseModel):
68
+ """Un pensamiento del agente (meta-cognición)."""
69
+ timestamp: str
70
+ thought_type: str # "hypothesis", "reflection", "question", "insight"
71
+ content: str
72
+ related_memorias: list[str]
73
+
74
+
75
+ class AutonomousAgent:
76
+ """Agente autónomo de memoria con razonamiento causal y síntesis.
77
+
78
+ Este es el gamechanger: no solo recupera, sino que razona y genera
79
+ nuevo conocimiento.
80
+
81
+ Args:
82
+ memory: La instancia Memory.
83
+ chat: La instancia MLXChat para razonamiento.
84
+ """
85
+
86
+ def __init__(self, memory: Any, chat: Any) -> None:
87
+ self.memory = memory
88
+ self.chat = chat
89
+ self._thoughts: list[AgentThought] = []
90
+ self._synthesis_history: list[SynthesisResult] = []
91
+
92
+ def think(self, thought: str, thought_type: str = "hypothesis") -> AgentThought:
93
+ """Registra un pensamiento del agente (meta-cognición).
94
+
95
+ Args:
96
+ thought: El contenido del pensamiento.
97
+ thought_type: Tipo de pensamiento.
98
+
99
+ Returns:
100
+ AgentThought registrado.
101
+ """
102
+ agent_thought = AgentThought(
103
+ timestamp=datetime.now(UTC).isoformat(),
104
+ thought_type=thought_type,
105
+ content=thought,
106
+ related_memorias=[],
107
+ )
108
+ self._thoughts.append(agent_thought)
109
+ return agent_thought
110
+
111
+ def plan_investigation(self, goal: str) -> InvestigationPlan:
112
+ """Planifica una investigación compleja sobre el corpus.
113
+
114
+ El agente analiza el goal y genera un plan de pasos para investigarlo.
115
+
116
+ Args:
117
+ goal: El objetivo de la investigación.
118
+
119
+ Returns:
120
+ InvestigationPlan con los pasos a seguir.
121
+ """
122
+ # Usar LLM para generar el plan
123
+ prompt = f"""You are an autonomous research agent. Plan an investigation to achieve this goal:
124
+
125
+ Goal: {goal}
126
+
127
+ The corpus contains personal memorias about various topics. Plan 3-5 concrete steps to investigate this goal.
128
+ Each step should be a specific action like "search for X", "analyze relationship between X and Y", etc.
129
+
130
+ Output JSON:
131
+ {{
132
+ "goal": "...",
133
+ "steps": ["step 1", "step 2", ...],
134
+ "estimated_complexity": 1-10,
135
+ "estimated_insight_value": 1-10
136
+ }}"""
137
+
138
+ response = self.chat.complete(prompt, temperature=0.3)
139
+ data = json.loads(response)
140
+
141
+ return InvestigationPlan(**data)
142
+
143
+ def execute_step(self, step: str) -> ReasoningStep:
144
+ """Ejecuta un paso de investigación.
145
+
146
+ Args:
147
+ step: La descripción del paso.
148
+
149
+ Returns:
150
+ ReasoningStep con el resultado.
151
+ """
152
+ # Interpretar el paso y ejecutar la acción correspondiente
153
+ if "search" in step.lower():
154
+ # Extraer query del paso
155
+ query = step.replace("search for", "").strip()
156
+ results = self.memory.search(query, limit=10, mode="hybrid")
157
+ memoria_ids = [r.id for r in results]
158
+
159
+ reasoning = f"Searched for '{query}', found {len(memoria_ids)} relevant memorias"
160
+
161
+ return ReasoningStep(
162
+ step_number=0, # Will be set by caller
163
+ action="search",
164
+ query=query,
165
+ results=memoria_ids,
166
+ reasoning=reasoning,
167
+ confidence=0.8,
168
+ )
169
+ elif "analyze" in step.lower():
170
+ # Análisis relacional
171
+ return ReasoningStep(
172
+ step_number=0,
173
+ action="analyze",
174
+ query=step,
175
+ results=[],
176
+ reasoning="Analysis performed",
177
+ confidence=0.7,
178
+ )
179
+ else:
180
+ return ReasoningStep(
181
+ step_number=0,
182
+ action="unknown",
183
+ query=step,
184
+ results=[],
185
+ reasoning=f"Executed step: {step}",
186
+ confidence=0.5,
187
+ )
188
+
189
+ def reason_causally(self, query: str, memoria_ids: list[str]) -> str:
190
+ """Razona causalmente sobre un conjunto de memorias.
191
+
192
+ No solo encuentra conexiones, sino explica POR QUÉ están conectadas.
193
+
194
+ Args:
195
+ query: La pregunta o tema a razonar.
196
+ memoria_ids: IDs de las memorias a analizar.
197
+
198
+ Returns:
199
+ Explicación causal del razonamiento.
200
+ """
201
+ # Obtener el contenido de las memorias
202
+ memorias = []
203
+ for mid in memoria_ids[:5]: # Limitar a 5 para contexto
204
+ m = self.memory.get(mid)
205
+ if m:
206
+ memorias.append(f"Title: {m.title}\nContent: {m.body or ''}\nTags: {', '.join(m.tags)}")
207
+
208
+ context = "\n\n---\n\n".join(memorias)
209
+
210
+ prompt = f"""You are a causal reasoning agent. Analyze these memorias and explain the CAUSAL relationships between them.
211
+
212
+ Query: {query}
213
+
214
+ Memorias:
215
+ {context}
216
+
217
+ Provide a causal explanation:
218
+ 1. What are the key entities/concepts?
219
+ 2. How are they causally connected?
220
+ 3. What caused what?
221
+ 4. What are the implications?
222
+
223
+ Focus on CAUSAL links (X caused Y, X implies Y, X is necessary for Y)."""
224
+
225
+ response = self.chat.complete(prompt, temperature=0.5)
226
+ return response
227
+
228
+ def synthesize_knowledge(self, topic: str) -> SynthesisResult:
229
+ """Sintetiza nuevo conocimiento a partir de memorias existentes.
230
+
231
+ Este es el core del gamechanger: genera insights que NO existían antes
232
+ combinando y razonando sobre memorias existentes.
233
+
234
+ Args:
235
+ topic: El tema sobre el cual sintetizar conocimiento.
236
+
237
+ Returns:
238
+ SynthesisResult con el nuevo insight.
239
+ """
240
+ # Paso 1: Buscar memorias relevantes
241
+ results = self.memory.search(topic, limit=15, mode="hybrid")
242
+ memoria_ids = [r.id for r in results]
243
+
244
+ # Paso 2: Razonar causalmente
245
+ causal_explanation = self.reason_causally(topic, memoria_ids)
246
+
247
+ # Paso 3: Usar LLM para sintetizar nuevo conocimiento
248
+ prompt = f"""You are a knowledge synthesis agent. Based on the causal analysis below, generate a NEW insight that was not explicitly stated in the original memorias.
249
+
250
+ Topic: {topic}
251
+
252
+ Causal Analysis:
253
+ {causal_explanation}
254
+
255
+ Generate a novel insight that:
256
+ 1. Combines information from multiple memorias
257
+ 2. Identifies a pattern or relationship not obvious before
258
+ 3. Has practical implications
259
+ 4. Is supported by the memorias but represents new understanding
260
+
261
+ Output JSON:
262
+ {{
263
+ "new_insight": "...",
264
+ "supporting_memorias": ["id1", "id2", ...],
265
+ "confidence": 0.0-1.0,
266
+ "novelty_score": 0.0-1.0
267
+ }}"""
268
+
269
+ response = self.chat.complete(prompt, temperature=0.7)
270
+ data = json.loads(response)
271
+
272
+ # Construir chain de razonamiento
273
+ reasoning_chain = [
274
+ ReasoningStep(
275
+ step_number=1,
276
+ action="search",
277
+ query=topic,
278
+ results=memoria_ids[:5],
279
+ reasoning=f"Found {len(memoria_ids)} relevant memorias",
280
+ confidence=0.8,
281
+ ),
282
+ ReasoningStep(
283
+ step_number=2,
284
+ action="analyze",
285
+ query="causal analysis",
286
+ results=memoria_ids[:5],
287
+ reasoning=causal_explanation[:200],
288
+ confidence=0.7,
289
+ ),
290
+ ReasoningStep(
291
+ step_number=3,
292
+ action="synthesize",
293
+ query="knowledge synthesis",
294
+ results=memoria_ids[:5],
295
+ reasoning="Generated novel insight from causal relationships",
296
+ confidence=data.get("confidence", 0.7),
297
+ ),
298
+ ]
299
+
300
+ synthesis = SynthesisResult(
301
+ new_insight=data["new_insight"],
302
+ supporting_memorias=data.get("supporting_memorias", memoria_ids[:3]),
303
+ reasoning_chain=reasoning_chain,
304
+ confidence=data.get("confidence", 0.7),
305
+ novelty_score=data.get("novelty_score", 0.8),
306
+ )
307
+
308
+ self._synthesis_history.append(synthesis)
309
+
310
+ # Registrar como pensamiento
311
+ self.think(f"Synthesized new insight: {synthesis.new_insight[:100]}...", "insight")
312
+
313
+ return synthesis
314
+
315
+ def proactive_discovery(self) -> list[SynthesisResult]:
316
+ """Descubrimiento proactivo: explora el corpus sin que el usuario lo pida.
317
+
318
+ El agente identifica áreas del corpus que podrían contener insights
319
+ no descubiertos y los explora proactivamente.
320
+
321
+ Returns:
322
+ Lista de SynthesisResult descubiertos proactivamente.
323
+ """
324
+ # Obtener las memorias más recientes
325
+ recent = self.memory.list(limit=20)
326
+
327
+ # Identificar temas prometedores (tags frecuentes, entidades conectadas)
328
+ all_tags = []
329
+ for m in recent:
330
+ all_tags.extend(m.tags)
331
+
332
+ from collections import Counter
333
+ top_tags = [t for t, c in Counter(all_tags).most_common(5)]
334
+
335
+ discoveries = []
336
+
337
+ # Para cada tag, intentar sintetizar conocimiento
338
+ for tag in top_tags:
339
+ try:
340
+ synthesis = self.synthesize_knowledge(tag)
341
+ if synthesis.novelty_score > 0.6: # Solo insights novedosos
342
+ discoveries.append(synthesis)
343
+ except Exception:
344
+ continue
345
+
346
+ return discoveries
347
+
348
+ def get_thoughts(self, thought_type: str | None = None) -> list[AgentThought]:
349
+ """Obtener el historial de pensamientos del agente.
350
+
351
+ Args:
352
+ thought_type: Filtrar por tipo (opcional).
353
+
354
+ Returns:
355
+ Lista de AgentThought.
356
+ """
357
+ if thought_type:
358
+ return [t for t in self._thoughts if t.thought_type == thought_type]
359
+ return self._thoughts
360
+
361
+ def get_synthesis_history(self) -> list[SynthesisResult]:
362
+ """Obtener el historial de síntesis de conocimiento.
363
+
364
+ Returns:
365
+ Lista de SynthesisResult.
366
+ """
367
+ return self._synthesis_history
368
+
369
+
370
+ __all__ = [
371
+ "AutonomousAgent",
372
+ "ReasoningStep",
373
+ "InvestigationPlan",
374
+ "SynthesisResult",
375
+ "AgentThought",
376
+ ]
377
+