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.
- memanto/__init__.py +3 -0
- memanto/__main__.py +8 -0
- memanto/app/__init__.py +5 -0
- memanto/app/_version.py +24 -0
- memanto/app/clients/moorcheh.py +51 -0
- memanto/app/config.py +100 -0
- memanto/app/constants.py +76 -0
- memanto/app/core.py +325 -0
- memanto/app/main.py +66 -0
- memanto/app/models/__init__.py +284 -0
- memanto/app/models/phase_d.py +212 -0
- memanto/app/models/session.py +156 -0
- memanto/app/models/universal_endpoints.py +99 -0
- memanto/app/routes/auth_deps.py +95 -0
- memanto/app/routes/context.py +148 -0
- memanto/app/routes/health.py +45 -0
- memanto/app/routes/memory.py +337 -0
- memanto/app/routes/memory_v2.py +813 -0
- memanto/app/routes/namespaces.py +110 -0
- memanto/app/routes/sessions.py +238 -0
- memanto/app/routes/universal_endpoints.py +227 -0
- memanto/app/services/__init__.py +1 -0
- memanto/app/services/agent_service.py +224 -0
- memanto/app/services/context_summarization_service.py +323 -0
- memanto/app/services/daily_summary_service.py +294 -0
- memanto/app/services/memory_export_service.py +217 -0
- memanto/app/services/memory_read_service.py +910 -0
- memanto/app/services/memory_validation_service.py +83 -0
- memanto/app/services/memory_write_service.py +369 -0
- memanto/app/services/namespace_service.py +87 -0
- memanto/app/services/session_service.py +450 -0
- memanto/app/services/summary_visualization_service.py +277 -0
- memanto/app/services/universal_services.py +392 -0
- memanto/app/ui/__init__.py +1 -0
- memanto/app/ui/routes/__init__.py +1 -0
- memanto/app/ui/routes/ui_router.py +257 -0
- memanto/app/ui/static/index.html +2060 -0
- memanto/app/ui/static/logo.svg +13 -0
- memanto/app/utils/auth.py +162 -0
- memanto/app/utils/errors.py +210 -0
- memanto/app/utils/idempotency.py +186 -0
- memanto/app/utils/ids.py +47 -0
- memanto/app/utils/logging.py +229 -0
- memanto/app/utils/metrics.py +187 -0
- memanto/app/utils/rate_limiting.py +141 -0
- memanto/app/utils/safe_deletion.py +264 -0
- memanto/app/utils/temporal_helpers.py +237 -0
- memanto/app/utils/tracing.py +205 -0
- memanto/app/utils/validation.py +161 -0
- memanto/cli/__init__.py +5 -0
- memanto/cli/client/__init__.py +0 -0
- memanto/cli/client/direct_client.py +1417 -0
- memanto/cli/client/sdk_client.py +1274 -0
- memanto/cli/commands/__init__.py +20 -0
- memanto/cli/commands/_shared.py +133 -0
- memanto/cli/commands/agent.py +620 -0
- memanto/cli/commands/config_cmd.py +51 -0
- memanto/cli/commands/connect.py +609 -0
- memanto/cli/commands/core.py +592 -0
- memanto/cli/commands/memory.py +850 -0
- memanto/cli/commands/memory_mgmt.py +185 -0
- memanto/cli/commands/schedule.py +72 -0
- memanto/cli/commands/session.py +91 -0
- memanto/cli/config/__init__.py +0 -0
- memanto/cli/config/manager.py +247 -0
- memanto/cli/connect/__init__.py +5 -0
- memanto/cli/connect/agent_registry.py +323 -0
- memanto/cli/connect/engine.py +394 -0
- memanto/cli/connect/templates.py +337 -0
- memanto/cli/main.py +17 -0
- memanto/cli/schedule_manager.py +186 -0
- memanto/cli/ui/__init__.py +3 -0
- memanto/cli/ui/display.py +192 -0
- memanto/cli/ui/theme.py +24 -0
- memanto-0.0.1.dist-info/METADATA +228 -0
- memanto-0.0.1.dist-info/RECORD +79 -0
- memanto-0.0.1.dist-info/WHEEL +4 -0
- memanto-0.0.1.dist-info/entry_points.txt +2 -0
- memanto-0.0.1.dist-info/licenses/LICENSE +18 -0
memanto/__init__.py
ADDED
memanto/__main__.py
ADDED
memanto/app/__init__.py
ADDED
memanto/app/_version.py
ADDED
|
@@ -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()
|
memanto/app/constants.py
ADDED
|
@@ -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)
|