memanto 0.0.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.
Files changed (79) hide show
  1. memanto/__init__.py +3 -0
  2. memanto/__main__.py +8 -0
  3. memanto/app/__init__.py +5 -0
  4. memanto/app/_version.py +24 -0
  5. memanto/app/clients/moorcheh.py +51 -0
  6. memanto/app/config.py +100 -0
  7. memanto/app/constants.py +76 -0
  8. memanto/app/core.py +325 -0
  9. memanto/app/main.py +66 -0
  10. memanto/app/models/__init__.py +284 -0
  11. memanto/app/models/phase_d.py +212 -0
  12. memanto/app/models/session.py +156 -0
  13. memanto/app/models/universal_endpoints.py +99 -0
  14. memanto/app/routes/auth_deps.py +95 -0
  15. memanto/app/routes/context.py +148 -0
  16. memanto/app/routes/health.py +45 -0
  17. memanto/app/routes/memory.py +337 -0
  18. memanto/app/routes/memory_v2.py +813 -0
  19. memanto/app/routes/namespaces.py +110 -0
  20. memanto/app/routes/sessions.py +238 -0
  21. memanto/app/routes/universal_endpoints.py +227 -0
  22. memanto/app/services/__init__.py +1 -0
  23. memanto/app/services/agent_service.py +224 -0
  24. memanto/app/services/context_summarization_service.py +323 -0
  25. memanto/app/services/daily_summary_service.py +294 -0
  26. memanto/app/services/memory_export_service.py +217 -0
  27. memanto/app/services/memory_read_service.py +910 -0
  28. memanto/app/services/memory_validation_service.py +83 -0
  29. memanto/app/services/memory_write_service.py +369 -0
  30. memanto/app/services/namespace_service.py +87 -0
  31. memanto/app/services/session_service.py +450 -0
  32. memanto/app/services/summary_visualization_service.py +277 -0
  33. memanto/app/services/universal_services.py +392 -0
  34. memanto/app/ui/__init__.py +1 -0
  35. memanto/app/ui/routes/__init__.py +1 -0
  36. memanto/app/ui/routes/ui_router.py +257 -0
  37. memanto/app/ui/static/index.html +2060 -0
  38. memanto/app/ui/static/logo.svg +13 -0
  39. memanto/app/utils/auth.py +162 -0
  40. memanto/app/utils/errors.py +210 -0
  41. memanto/app/utils/idempotency.py +186 -0
  42. memanto/app/utils/ids.py +47 -0
  43. memanto/app/utils/logging.py +229 -0
  44. memanto/app/utils/metrics.py +187 -0
  45. memanto/app/utils/rate_limiting.py +141 -0
  46. memanto/app/utils/safe_deletion.py +264 -0
  47. memanto/app/utils/temporal_helpers.py +237 -0
  48. memanto/app/utils/tracing.py +205 -0
  49. memanto/app/utils/validation.py +161 -0
  50. memanto/cli/__init__.py +5 -0
  51. memanto/cli/client/__init__.py +0 -0
  52. memanto/cli/client/direct_client.py +1417 -0
  53. memanto/cli/client/sdk_client.py +1274 -0
  54. memanto/cli/commands/__init__.py +20 -0
  55. memanto/cli/commands/_shared.py +133 -0
  56. memanto/cli/commands/agent.py +620 -0
  57. memanto/cli/commands/config_cmd.py +51 -0
  58. memanto/cli/commands/connect.py +609 -0
  59. memanto/cli/commands/core.py +592 -0
  60. memanto/cli/commands/memory.py +850 -0
  61. memanto/cli/commands/memory_mgmt.py +185 -0
  62. memanto/cli/commands/schedule.py +72 -0
  63. memanto/cli/commands/session.py +91 -0
  64. memanto/cli/config/__init__.py +0 -0
  65. memanto/cli/config/manager.py +247 -0
  66. memanto/cli/connect/__init__.py +5 -0
  67. memanto/cli/connect/agent_registry.py +323 -0
  68. memanto/cli/connect/engine.py +394 -0
  69. memanto/cli/connect/templates.py +337 -0
  70. memanto/cli/main.py +17 -0
  71. memanto/cli/schedule_manager.py +186 -0
  72. memanto/cli/ui/__init__.py +3 -0
  73. memanto/cli/ui/display.py +192 -0
  74. memanto/cli/ui/theme.py +24 -0
  75. memanto-0.0.1.dist-info/METADATA +228 -0
  76. memanto-0.0.1.dist-info/RECORD +79 -0
  77. memanto-0.0.1.dist-info/WHEEL +4 -0
  78. memanto-0.0.1.dist-info/entry_points.txt +2 -0
  79. memanto-0.0.1.dist-info/licenses/LICENSE +18 -0
memanto/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ """
2
+ MemAnto - Universal Memory Layer for Agentic AI
3
+ """
memanto/__main__.py ADDED
@@ -0,0 +1,8 @@
1
+ """
2
+ MEMANTO - Main Entry Point
3
+ """
4
+
5
+ from memanto.cli.main import app
6
+
7
+ if __name__ == "__main__":
8
+ app()
@@ -0,0 +1,5 @@
1
+ """MEMANTO application package."""
2
+
3
+ from ._version import __version__
4
+
5
+ __all__ = ["__version__"]
@@ -0,0 +1,24 @@
1
+ # file generated by vcs-versioning
2
+ # don't change, don't track in version control
3
+ from __future__ import annotations
4
+
5
+ __all__ = [
6
+ "__version__",
7
+ "__version_tuple__",
8
+ "version",
9
+ "version_tuple",
10
+ "__commit_id__",
11
+ "commit_id",
12
+ ]
13
+
14
+ version: str
15
+ __version__: str
16
+ __version_tuple__: tuple[int | str, ...]
17
+ version_tuple: tuple[int | str, ...]
18
+ commit_id: str | None
19
+ __commit_id__: str | None
20
+
21
+ __version__ = version = '0.0.1'
22
+ __version_tuple__ = version_tuple = (0, 0, 1)
23
+
24
+ __commit_id__ = commit_id = None
@@ -0,0 +1,51 @@
1
+ """
2
+ Moorcheh Client Singleton
3
+ """
4
+
5
+ from moorcheh_sdk import AsyncMoorchehClient, MoorchehClient
6
+
7
+ from memanto.app.config import settings
8
+
9
+
10
+ class MoorchehClientSingleton:
11
+ """Singleton pattern for Moorcheh client"""
12
+
13
+ _instance = None
14
+ _client = None
15
+ _async_client = None
16
+
17
+ def __new__(cls):
18
+ if cls._instance is None:
19
+ cls._instance = super().__new__(cls)
20
+ return cls._instance
21
+
22
+ def get_client(self) -> MoorchehClient:
23
+ """Get or create Moorcheh client"""
24
+ if self._client is None:
25
+ self._client = MoorchehClient(api_key=settings.MOORCHEH_API_KEY)
26
+ return self._client
27
+
28
+ def get_async_client(self) -> AsyncMoorchehClient:
29
+ """Get or create Async Moorcheh client"""
30
+ if self._async_client is None:
31
+ self._async_client = AsyncMoorchehClient(api_key=settings.MOORCHEH_API_KEY)
32
+ return self._async_client
33
+
34
+ def reset_client(self):
35
+ """Reset client (useful for testing)"""
36
+ self._client = None
37
+ self._async_client = None
38
+
39
+
40
+ # Global client instance
41
+ moorcheh_client = MoorchehClientSingleton()
42
+
43
+
44
+ def get_moorcheh_client() -> MoorchehClient:
45
+ """Dependency injection function"""
46
+ return moorcheh_client.get_client()
47
+
48
+
49
+ def get_async_moorcheh_client() -> AsyncMoorchehClient:
50
+ """Dependency injection function for async client"""
51
+ return moorcheh_client.get_async_client()
memanto/app/config.py ADDED
@@ -0,0 +1,100 @@
1
+ """
2
+ MEMANTO Configuration
3
+
4
+ Server-side settings (loaded from .env via pydantic-settings).
5
+ CLI config models have been moved to cli/config/manager.py.
6
+ """
7
+
8
+ from pathlib import Path
9
+
10
+ from dotenv import load_dotenv
11
+ from pydantic import BaseModel
12
+ from pydantic_settings import BaseSettings, SettingsConfigDict
13
+
14
+ # Load project .env first, then ~/.memanto/.env for the API key
15
+ load_dotenv()
16
+ _memanto_env = Path.home() / ".memanto" / ".env"
17
+ if _memanto_env.exists():
18
+ load_dotenv(_memanto_env, override=True)
19
+
20
+
21
+ # CLI & YAML Format Models (kept for backward compat with config.yaml structure)
22
+ class ServerConfig(BaseModel):
23
+ """Server configuration"""
24
+
25
+ url: str = "localhost"
26
+ port: int = 8000
27
+ auto_start: bool = False
28
+
29
+
30
+ class SessionConfig(BaseModel):
31
+ """Session management configuration"""
32
+
33
+ default_duration_hours: int = 6
34
+ auto_extend: bool = True
35
+ extend_threshold_minutes: int = 30
36
+ warn_before_expiry_minutes: int = 15
37
+ auto_renew_enabled: bool = True
38
+ auto_renew_interval_hours: int = 6
39
+
40
+
41
+ class CLIConfig(BaseModel):
42
+ """CLI behavior configuration"""
43
+
44
+ interactive_mode: bool = True
45
+ smart_parse: bool = True
46
+ auto_title: bool = True
47
+ color_output: bool = True
48
+
49
+
50
+ class Settings(BaseSettings):
51
+ """Unified Settings: sourced from environment / .env files"""
52
+
53
+ # Moorcheh Configuration
54
+ MOORCHEH_API_KEY: str = ""
55
+
56
+ # Server Configuration
57
+ HOST: str = "0.0.0.0"
58
+ PORT: int = 8000
59
+ DEBUG: bool = False
60
+
61
+ # CORS Configuration
62
+ ALLOWED_ORIGINS: list[str] = ["*"]
63
+
64
+ # Session Configuration
65
+ MEMANTO_SECRET_KEY: str = "memanto-default-secret-change-in-production"
66
+ SESSION_DEFAULT_DURATION_HOURS: int = 6
67
+ SESSION_AUTO_EXTEND: bool = True
68
+ SESSION_EXTEND_THRESHOLD_MINUTES: int = 30
69
+ SESSION_AUTO_RENEW_ENABLED: bool = True
70
+ SESSION_AUTO_RENEW_INTERVAL_HOURS: int = 6
71
+
72
+ # Memory Configuration
73
+ DEFAULT_TTL_SECONDS: int = 3600 # 1 hour
74
+ MAX_MEMORY_SIZE: int = 500 # characters
75
+ MAX_TITLE_SIZE: int = 100 # characters
76
+
77
+ # Answer Configuration
78
+ ANSWER_MODEL: str = "anthropic.claude-sonnet-4-6"
79
+ ANSWER_TEMPERATURE: float = 0.7
80
+ ANSWER_LIMIT: int = 15 # number of context memories to retrieve
81
+ ANSWER_THRESHOLD: float = 0.01 # confidence threshold for memory relevance
82
+
83
+ # Recall / Search Configuration
84
+ RECALL_LIMIT: int = 10 # default top-N results for recall/search
85
+
86
+ # Validation Configuration
87
+ REQUIRE_VALIDATION_FOR: list[str] = ["fact", "preference"]
88
+ PROVISIONAL_TTL_SECONDS: int = 3600 # 1 hour
89
+ PROVISIONAL_MAX_CONFIDENCE: float = 0.5
90
+
91
+ # Schedule Configuration
92
+ MEMANTO_SCHEDULE_TIME: str = "23:55"
93
+
94
+ model_config = SettingsConfigDict(
95
+ env_file=".env", case_sensitive=True, extra="ignore"
96
+ )
97
+
98
+
99
+ # Global settings instance
100
+ settings = Settings()
@@ -0,0 +1,76 @@
1
+ from typing import Literal
2
+
3
+ # Memory Types
4
+ MemoryType = Literal[
5
+ "fact",
6
+ "preference",
7
+ "goal",
8
+ "decision",
9
+ "artifact",
10
+ "learning",
11
+ "event",
12
+ "instruction",
13
+ "relationship",
14
+ "context",
15
+ "observation",
16
+ "commitment",
17
+ "error",
18
+ ]
19
+
20
+ # Scope Types
21
+ ScopeType = Literal["user", "workspace", "agent", "session", "project", "task"]
22
+
23
+ # Source Types
24
+ SourceType = str # e.g., "user", "agent", "tool", "system", or specific "agent_name"
25
+
26
+ # Status Types
27
+ StatusType = Literal["active", "superseded", "deleted", "provisional"]
28
+
29
+ # Provenance Types
30
+ ProvenanceType = Literal[
31
+ "explicit_statement",
32
+ "inferred",
33
+ "corrected",
34
+ "validated",
35
+ "observed",
36
+ "imported",
37
+ ]
38
+
39
+ # Validation Modes
40
+ ValidationMode = Literal["strict", "lenient", "off"]
41
+
42
+ # Actor Types
43
+ ActorType = Literal["user", "agent", "system"]
44
+
45
+ # Source Enumerations for Provenance
46
+ ProvenanceSource = Literal["user", "agent", "tool", "system"]
47
+
48
+ # Valid Lists for runtime checks
49
+ VALID_MEMORY_TYPES = {
50
+ "fact",
51
+ "preference",
52
+ "goal",
53
+ "decision",
54
+ "artifact",
55
+ "learning",
56
+ "event",
57
+ "instruction",
58
+ "relationship",
59
+ "context",
60
+ "observation",
61
+ "commitment",
62
+ "error",
63
+ }
64
+
65
+ VALID_PROVENANCE_TYPES = {
66
+ "explicit_statement",
67
+ "inferred",
68
+ "corrected",
69
+ "validated",
70
+ "observed",
71
+ "imported",
72
+ }
73
+
74
+ VALID_SCOPE_TYPES = {"user", "workspace", "agent", "session", "project", "task"}
75
+
76
+ VALID_PATTERNS = {"support", "project", "tool"}
memanto/app/core.py ADDED
@@ -0,0 +1,325 @@
1
+ """
2
+ MEMANTO Core Architecture - Namespace Strategy & Memory Records
3
+ """
4
+
5
+ import re
6
+ import uuid
7
+ from datetime import datetime, timedelta
8
+ from typing import Any
9
+
10
+ from pydantic import BaseModel, Field
11
+
12
+ from memanto.app.constants import (
13
+ MemoryType,
14
+ ProvenanceType,
15
+ ScopeType,
16
+ SourceType,
17
+ StatusType,
18
+ )
19
+
20
+
21
+ class MemoryScope(BaseModel):
22
+ """Defines the scope for memory isolation"""
23
+
24
+ scope_type: ScopeType
25
+ scope_id: str
26
+
27
+ def to_namespace(self) -> str:
28
+ """Convert scope to Moorcheh namespace using deterministic mapping"""
29
+ # memanto_{scope_type}_{scope_id}
30
+ return f"memanto_{self.scope_type}_{self.scope_id}"
31
+
32
+ @classmethod
33
+ def from_namespace(cls, namespace: str) -> "MemoryScope":
34
+ """Parse namespace back to scope"""
35
+ from typing import cast
36
+
37
+ parts = namespace.split("_")
38
+ if len(parts) != 3 or parts[0] != "memanto":
39
+ raise ValueError(f"Invalid MEMANTO namespace format: {namespace}")
40
+ return cls(scope_type=cast(ScopeType, parts[1]), scope_id=parts[2])
41
+
42
+
43
+ class MemoryRecord(BaseModel):
44
+ """Structured memory record with standardized format"""
45
+
46
+ # Core fields
47
+ id: str = Field(default_factory=lambda: str(uuid.uuid4()))
48
+ type: MemoryType
49
+ title: str = Field(max_length=100)
50
+ content: str = Field(max_length=10000)
51
+
52
+ # Metadata fields
53
+ scope_type: ScopeType
54
+ scope_id: str
55
+ actor_id: str
56
+ source: SourceType
57
+ source_ref: str | None = None
58
+ confidence: float = Field(ge=0.0, le=1.0, default=0.8)
59
+ status: StatusType = "active"
60
+ tags: list[str] = Field(default_factory=list)
61
+
62
+ # Provenance & Trust fields
63
+ provenance: ProvenanceType = "explicit_statement"
64
+ superseded_by: str | None = None # Memory ID that supersedes this one
65
+ supersedes: str | None = None # Memory ID that this supersedes
66
+ validated_at: datetime | None = None # Last validation timestamp
67
+ validation_count: int = 0 # Number of times validated/confirmed
68
+ contradiction_detected: bool = False # Flag for contradictions
69
+
70
+ # Timestamps (auto-populated by server)
71
+ created_at: datetime = Field(default_factory=datetime.utcnow)
72
+ updated_at: datetime = Field(default_factory=datetime.utcnow)
73
+ expires_at: datetime | None = None
74
+ ttl_seconds: int | None = None
75
+
76
+ def to_moorcheh_document(self) -> dict[str, Any]:
77
+ """
78
+ Convert to Moorcheh document format with flat metadata fields.
79
+
80
+ Moorcheh stores metadata as flat fields on the document, which enables
81
+ powerful filtering using the # syntax (e.g., #memory_type:fact #confidence>0.8)
82
+ """
83
+ # Format text as standardized card for semantic search
84
+ text = f"[{self.type.upper()}] {self.title}\n\n{self.content}"
85
+ if self.tags:
86
+ text += f"\n\nTags: {', '.join(self.tags)}"
87
+
88
+ # Build document with flat metadata fields (not nested!)
89
+ document = {
90
+ "id": self.id,
91
+ "text": text,
92
+ # Metadata fields (flat structure for Moorcheh filtering)
93
+ "memory_type": self.type,
94
+ "scope_type": self.scope_type,
95
+ "scope_id": self.scope_id,
96
+ "actor_id": self.actor_id,
97
+ "source": self.source,
98
+ "confidence": self.confidence,
99
+ "status": self.status,
100
+ # Provenance & Trust fields
101
+ "provenance": self.provenance,
102
+ "validation_count": self.validation_count,
103
+ "contradiction_detected": self.contradiction_detected,
104
+ # Timestamps
105
+ "created_at": self.created_at.isoformat(),
106
+ "updated_at": self.updated_at.isoformat(),
107
+ }
108
+
109
+ # Add optional fields only if present
110
+ if self.source_ref:
111
+ document["source_ref"] = self.source_ref
112
+ if self.tags:
113
+ document["tags"] = ",".join(self.tags) # Comma-separated for filtering
114
+ if self.expires_at:
115
+ document["expires_at"] = self.expires_at.isoformat()
116
+ if self.ttl_seconds:
117
+ document["ttl_seconds"] = self.ttl_seconds
118
+ if self.superseded_by:
119
+ document["superseded_by"] = self.superseded_by
120
+ if self.supersedes:
121
+ document["supersedes"] = self.supersedes
122
+ if self.validated_at:
123
+ document["validated_at"] = self.validated_at.isoformat()
124
+
125
+ return document
126
+
127
+ def get_scope(self) -> MemoryScope:
128
+ """Get the memory scope"""
129
+ return MemoryScope(scope_type=self.scope_type, scope_id=self.scope_id)
130
+
131
+ def set_ttl(self, seconds: int):
132
+ """Set TTL and expiration"""
133
+ self.ttl_seconds = seconds
134
+ self.expires_at = datetime.utcnow() + timedelta(seconds=seconds)
135
+
136
+ def compute_confidence(self) -> float:
137
+ """
138
+ Compute confidence based on provenance, validation count, and age.
139
+
140
+ Returns adjusted confidence score considering:
141
+ - Provenance type (explicit > validated > observed > inferred)
142
+ - Validation count (more validations = higher confidence)
143
+ - Age decay for preferences (fresher = more trustworthy)
144
+ - Contradiction detection (contradicted = low confidence)
145
+ """
146
+ if self.contradiction_detected:
147
+ return max(
148
+ 0.1, self.confidence * 0.3
149
+ ) # Contradicted memories get very low confidence
150
+
151
+ if self.status == "superseded":
152
+ return 0.0 # Superseded memories have zero confidence
153
+
154
+ # Base confidence from provenance
155
+ provenance_weights = {
156
+ "explicit_statement": 1.0,
157
+ "validated": 0.95,
158
+ "observed": 0.85,
159
+ "corrected": 0.9,
160
+ "inferred": 0.7,
161
+ "imported": 0.8,
162
+ }
163
+ base = self.confidence * provenance_weights.get(self.provenance, 0.8)
164
+
165
+ # Validation boost (each validation adds confidence)
166
+ validation_boost = min(0.15, self.validation_count * 0.03)
167
+
168
+ # Age decay for preferences and observations (fresher = more trustworthy)
169
+ if self.type in ["preference", "observation"]:
170
+ age_days = (datetime.utcnow() - self.created_at).days
171
+ if age_days > 90: # 3 months
172
+ age_penalty = 0.2
173
+ elif age_days > 30: # 1 month
174
+ age_penalty = 0.1
175
+ else:
176
+ age_penalty = 0.0
177
+ else:
178
+ age_penalty = 0.0
179
+
180
+ # Compute final confidence
181
+ final = min(1.0, base + validation_boost - age_penalty)
182
+ return round(final, 2)
183
+
184
+ def validate(self):
185
+ """Mark memory as validated (increases trust)"""
186
+ self.validation_count += 1
187
+ self.validated_at = datetime.utcnow()
188
+ self.updated_at = datetime.utcnow()
189
+
190
+ # Upgrade provenance if inferred
191
+ if self.provenance == "inferred":
192
+ self.provenance = "validated"
193
+
194
+ def mark_superseded(self, superseded_by_id: str):
195
+ """Mark this memory as superseded by a newer one"""
196
+ self.superseded_by = superseded_by_id
197
+ self.status = "superseded"
198
+ self.updated_at = datetime.utcnow()
199
+
200
+ def detect_contradiction(self):
201
+ """Flag memory as contradicted (lowers trust)"""
202
+ self.contradiction_detected = True
203
+ self.updated_at = datetime.utcnow()
204
+
205
+ def trust_score(self) -> dict[str, Any]:
206
+ """
207
+ Calculate comprehensive trust score with explanation.
208
+
209
+ Returns dict with:
210
+ - computed_confidence: float
211
+ - provenance: str
212
+ - validation_count: int
213
+ - age_days: int
214
+ - is_superseded: bool
215
+ - contradiction_detected: bool
216
+ - trust_level: str (high/medium/low)
217
+ - recommendation: str
218
+ """
219
+ computed_conf = self.compute_confidence()
220
+ age_days = (datetime.utcnow() - self.created_at).days
221
+
222
+ # Determine trust level
223
+ if computed_conf >= 0.8 and not self.contradiction_detected:
224
+ trust_level = "high"
225
+ recommendation = "Safe to use"
226
+ elif computed_conf >= 0.5:
227
+ trust_level = "medium"
228
+ recommendation = "Use with caution"
229
+ else:
230
+ trust_level = "low"
231
+ recommendation = "Verify before using"
232
+
233
+ return {
234
+ "computed_confidence": computed_conf,
235
+ "original_confidence": self.confidence,
236
+ "provenance": self.provenance,
237
+ "validation_count": self.validation_count,
238
+ "age_days": age_days,
239
+ "is_superseded": self.status == "superseded",
240
+ "contradiction_detected": self.contradiction_detected,
241
+ "trust_level": trust_level,
242
+ "recommendation": recommendation,
243
+ "last_validated": self.validated_at.isoformat()
244
+ if self.validated_at
245
+ else None,
246
+ }
247
+
248
+
249
+ class ValidationPolicy:
250
+ """Memory validation policy to prevent poisoning"""
251
+
252
+ @staticmethod
253
+ def validate_memory(
254
+ memory: MemoryRecord, context: dict[str, Any] | None = None
255
+ ) -> dict[str, Any]:
256
+ """
257
+ Validate memory before storage
258
+ Returns: {"valid": bool, "action": str, "reason": str}
259
+ """
260
+ context = context or {}
261
+
262
+ # High-confidence types require validation
263
+ if memory.type in ["fact", "preference"]:
264
+ return ValidationPolicy._validate_critical_memory(memory, context)
265
+
266
+ # Other types are generally safe
267
+ return {"valid": True, "action": "store", "reason": "Non-critical memory type"}
268
+
269
+ @staticmethod
270
+ def _validate_critical_memory(
271
+ memory: MemoryRecord, context: dict[str, Any]
272
+ ) -> dict[str, Any]:
273
+ """Validate critical memory types (fact, preference)"""
274
+
275
+ # Check for explicit user confirmation
276
+ if context.get("user_confirmed"):
277
+ return {"valid": True, "action": "store", "reason": "User confirmed"}
278
+
279
+ # Check for repetition (same content seen before)
280
+ if context.get("repetition_count", 0) >= 2:
281
+ return {"valid": True, "action": "store", "reason": "Repeated content"}
282
+
283
+ # Check for tool-grounded source
284
+ if memory.source == "tool" and memory.source_ref:
285
+ return {"valid": True, "action": "store", "reason": "Tool-grounded"}
286
+
287
+ # Check for high confidence from reliable source
288
+ if memory.confidence >= 0.9 and memory.source in ["system", "tool"]:
289
+ return {
290
+ "valid": True,
291
+ "action": "store",
292
+ "reason": "High confidence system source",
293
+ }
294
+
295
+ # Default: store as provisional
296
+ return {
297
+ "valid": True,
298
+ "action": "store_provisional",
299
+ "reason": "Requires validation - storing as provisional",
300
+ }
301
+
302
+ @staticmethod
303
+ def make_provisional(memory: MemoryRecord) -> MemoryRecord:
304
+ """Convert memory to provisional status with short TTL"""
305
+ memory.status = "provisional"
306
+ memory.confidence = min(memory.confidence, 0.5) # Cap confidence
307
+ memory.set_ttl(3600) # 1 hour TTL
308
+ return memory
309
+
310
+
311
+ # Utility functions
312
+ def create_memory_scope(scope_type: ScopeType, scope_id: str) -> MemoryScope:
313
+ """Helper to create memory scope"""
314
+ return MemoryScope(scope_type=scope_type, scope_id=scope_id)
315
+
316
+
317
+ def parse_namespace(namespace: str) -> MemoryScope:
318
+ """Helper to parse namespace"""
319
+ return MemoryScope.from_namespace(namespace)
320
+
321
+
322
+ def validate_namespace_format(namespace: str) -> bool:
323
+ """Validate namespace follows MEMANTO convention"""
324
+ pattern = r"^memanto_(user|workspace|agent|session)_[a-zA-Z0-9_-]+$"
325
+ return bool(re.match(pattern, namespace))
memanto/app/main.py ADDED
@@ -0,0 +1,66 @@
1
+ """
2
+ MEMANTO FastAPI Application
3
+ """
4
+
5
+ from fastapi import FastAPI
6
+ from fastapi.middleware.cors import CORSMiddleware
7
+
8
+ from memanto.app import __version__
9
+ from memanto.app.config import settings
10
+ from memanto.app.routes import context, health, memory, namespaces, sessions
11
+ from memanto.app.ui.routes.ui_router import mount_ui_static
12
+ from memanto.app.ui.routes.ui_router import router as ui_router
13
+
14
+ # Create FastAPI app
15
+ app = FastAPI(
16
+ title="MemAnto - Universal Memory Layer for Agentic AI",
17
+ description="A memory layer service for agentic AI systems using Moorcheh SDK",
18
+ version=__version__,
19
+ docs_url="/docs",
20
+ redoc_url="/redoc",
21
+ )
22
+
23
+ # Add CORS middleware
24
+ app.add_middleware(
25
+ CORSMiddleware,
26
+ allow_origins=settings.ALLOWED_ORIGINS,
27
+ allow_credentials=True,
28
+ allow_methods=["*"],
29
+ allow_headers=["*"],
30
+ )
31
+
32
+ # Include routers
33
+ app.include_router(health.router, tags=["Health"])
34
+
35
+ # Session-Based API (Primary)
36
+ app.include_router(sessions.router, prefix="/api/v2", tags=["Sessions & Agents"])
37
+
38
+
39
+ # Internal/Advanced APIs
40
+ app.include_router(
41
+ namespaces.router, prefix="/api/v1/namespaces", tags=["Namespaces (Internal)"]
42
+ )
43
+ app.include_router(memory.router, prefix="/api/v1/memory", tags=["Memory (Internal)"])
44
+ app.include_router(
45
+ context.router, prefix="/api/v1/context", tags=["Context (Internal)"]
46
+ )
47
+
48
+ # Web UI Dashboard
49
+ app.include_router(ui_router, tags=["Web UI"])
50
+ mount_ui_static(app)
51
+
52
+
53
+ @app.get("/")
54
+ async def root():
55
+ return {
56
+ "service": "MEMANTO",
57
+ "description": "Universal Memory Layer for Agentic AI",
58
+ "version": __version__,
59
+ "docs": "/docs",
60
+ }
61
+
62
+
63
+ if __name__ == "__main__":
64
+ import uvicorn
65
+
66
+ uvicorn.run(app, host="0.0.0.0", port=8000)