runtime-memory 3.0.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.
- runtime_memory/__init__.py +28 -0
- runtime_memory/claude_code/__init__.py +48 -0
- runtime_memory/claude_code/commands.py +698 -0
- runtime_memory/claude_code/daemon.py +852 -0
- runtime_memory/claude_code/hooks.py +722 -0
- runtime_memory/cli/__init__.py +8 -0
- runtime_memory/cli/main.py +1936 -0
- runtime_memory/core/__init__.py +216 -0
- runtime_memory/core/config.py +473 -0
- runtime_memory/core/embeddings.py +908 -0
- runtime_memory/core/engine.py +1007 -0
- runtime_memory/core/exceptions.py +547 -0
- runtime_memory/core/legacy_env.py +39 -0
- runtime_memory/core/logging.py +160 -0
- runtime_memory/core/models.py +1051 -0
- runtime_memory/core/observability.py +725 -0
- runtime_memory/core/paths.py +30 -0
- runtime_memory/core/resilience.py +511 -0
- runtime_memory/core/retrieval.py +819 -0
- runtime_memory/core/storage.py +1105 -0
- runtime_memory/extraction/__init__.py +36 -0
- runtime_memory/extraction/extractor.py +1143 -0
- runtime_memory/hermes/__init__.py +39 -0
- runtime_memory/hermes/_base.py +154 -0
- runtime_memory/hermes/bridge.py +119 -0
- runtime_memory/hermes/plugin.yaml +13 -0
- runtime_memory/hermes/provider.py +536 -0
- runtime_memory/hermes/tools.py +230 -0
- runtime_memory/hermes/trace.py +177 -0
- runtime_memory/plugin/__init__.py +646 -0
- runtime_memory/sdk/__init__.py +97 -0
- runtime_memory/sdk/client.py +1577 -0
- runtime_memory/server/__init__.py +75 -0
- runtime_memory/server/api.py +1665 -0
- runtime_memory/server/mcp.py +1574 -0
- runtime_memory/server/static/css/styles.css +1110 -0
- runtime_memory/server/static/index.html +264 -0
- runtime_memory/server/static/js/api.js +294 -0
- runtime_memory/server/static/js/app.js +771 -0
- runtime_memory/tasks/__init__.py +114 -0
- runtime_memory/tasks/adapter.py +501 -0
- runtime_memory/tasks/claude_code_adapter.py +495 -0
- runtime_memory/tasks/claude_code_parser.py +339 -0
- runtime_memory/tasks/cli_bridge.py +415 -0
- runtime_memory/tasks/linking.py +397 -0
- runtime_memory/tasks/models.py +520 -0
- runtime_memory/tasks/outcomes.py +320 -0
- runtime_memory/tasks/parser.py +305 -0
- runtime_memory/tasks/unified_adapter.py +661 -0
- runtime_memory-3.0.0.dist-info/METADATA +497 -0
- runtime_memory-3.0.0.dist-info/RECORD +54 -0
- runtime_memory-3.0.0.dist-info/WHEEL +4 -0
- runtime_memory-3.0.0.dist-info/entry_points.txt +6 -0
- runtime_memory-3.0.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
"""Core memory engine components.
|
|
2
|
+
|
|
3
|
+
This module contains the foundational components for Runtime Memory:
|
|
4
|
+
- Data models and enums
|
|
5
|
+
- SQLite storage layer
|
|
6
|
+
- Vector embeddings
|
|
7
|
+
- Hybrid retrieval system
|
|
8
|
+
- Memory engine orchestrator
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from runtime_memory.core.embeddings import (
|
|
14
|
+
APIEmbeddingProvider,
|
|
15
|
+
APIError,
|
|
16
|
+
BatchEmbeddingResult,
|
|
17
|
+
Embedding,
|
|
18
|
+
EmbeddingCache,
|
|
19
|
+
EmbeddingConfig,
|
|
20
|
+
EmbeddingError,
|
|
21
|
+
EmbeddingProvider,
|
|
22
|
+
EmbeddingResult,
|
|
23
|
+
LocalEmbeddingProvider,
|
|
24
|
+
MockEmbeddingProvider,
|
|
25
|
+
ModelNotFoundError,
|
|
26
|
+
NullEmbeddingProvider,
|
|
27
|
+
get_embedding_provider,
|
|
28
|
+
)
|
|
29
|
+
from runtime_memory.core.engine import (
|
|
30
|
+
EngineConfig,
|
|
31
|
+
EngineError,
|
|
32
|
+
EngineNotInitializedError,
|
|
33
|
+
EngineStats,
|
|
34
|
+
LastSearchInfo,
|
|
35
|
+
MemoryEngine,
|
|
36
|
+
create_engine,
|
|
37
|
+
)
|
|
38
|
+
from runtime_memory.core.exceptions import (
|
|
39
|
+
AuthenticationError,
|
|
40
|
+
BeadsNotFoundError,
|
|
41
|
+
CircuitOpenError,
|
|
42
|
+
ConfigurationError,
|
|
43
|
+
DaemonAlreadyRunningError,
|
|
44
|
+
DaemonError,
|
|
45
|
+
DatabaseConnectionError,
|
|
46
|
+
DatabaseIntegrityError,
|
|
47
|
+
ErrorCode,
|
|
48
|
+
ExtractionError,
|
|
49
|
+
HookError,
|
|
50
|
+
HookNotInstalledError,
|
|
51
|
+
InitializationError,
|
|
52
|
+
InvalidResponseError,
|
|
53
|
+
LLMAPIError,
|
|
54
|
+
MCPError,
|
|
55
|
+
RuntimeMemoryError,
|
|
56
|
+
RateLimitError,
|
|
57
|
+
RetrievalError,
|
|
58
|
+
SDKError,
|
|
59
|
+
SearchTimeoutError,
|
|
60
|
+
ServerError,
|
|
61
|
+
TaskError,
|
|
62
|
+
TaskLinkError,
|
|
63
|
+
TaskSyncError,
|
|
64
|
+
ValidationError,
|
|
65
|
+
format_error,
|
|
66
|
+
is_recoverable,
|
|
67
|
+
)
|
|
68
|
+
from runtime_memory.core.logging import get_logger, setup_logging
|
|
69
|
+
from runtime_memory.core.models import (
|
|
70
|
+
OUTCOME_SCORE_ADJUSTMENTS,
|
|
71
|
+
ContextResponse,
|
|
72
|
+
ContextResponseModel,
|
|
73
|
+
Memory,
|
|
74
|
+
MemoryCategory,
|
|
75
|
+
MemoryCreate,
|
|
76
|
+
MemoryResponse,
|
|
77
|
+
MemoryScope,
|
|
78
|
+
MemorySource,
|
|
79
|
+
MemoryUpdate,
|
|
80
|
+
Outcome,
|
|
81
|
+
OutcomeRecord,
|
|
82
|
+
SearchQuery,
|
|
83
|
+
SearchResult,
|
|
84
|
+
SearchResultResponse,
|
|
85
|
+
StatsResponse,
|
|
86
|
+
)
|
|
87
|
+
from runtime_memory.core.resilience import (
|
|
88
|
+
CircuitBreaker,
|
|
89
|
+
CircuitBreakerConfig,
|
|
90
|
+
CircuitState,
|
|
91
|
+
RetryConfig,
|
|
92
|
+
calculate_backoff,
|
|
93
|
+
get_circuit_breaker,
|
|
94
|
+
reset_all_circuit_breakers,
|
|
95
|
+
retry,
|
|
96
|
+
retry_sync,
|
|
97
|
+
try_multiple,
|
|
98
|
+
with_fallback,
|
|
99
|
+
with_fallback_sync,
|
|
100
|
+
with_timeout,
|
|
101
|
+
)
|
|
102
|
+
from runtime_memory.core.retrieval import (
|
|
103
|
+
BM25Index,
|
|
104
|
+
CategoryRouter,
|
|
105
|
+
HybridRetriever,
|
|
106
|
+
RetrievalConfig,
|
|
107
|
+
)
|
|
108
|
+
from runtime_memory.core.storage import (
|
|
109
|
+
ConnectionError,
|
|
110
|
+
MemoryNotFoundError,
|
|
111
|
+
MemoryStorage,
|
|
112
|
+
StorageError,
|
|
113
|
+
StorageStats,
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
__all__ = [
|
|
117
|
+
# Logging
|
|
118
|
+
"get_logger",
|
|
119
|
+
"setup_logging",
|
|
120
|
+
# Enums
|
|
121
|
+
"MemoryCategory",
|
|
122
|
+
"MemoryScope",
|
|
123
|
+
"MemorySource",
|
|
124
|
+
"Outcome",
|
|
125
|
+
"OUTCOME_SCORE_ADJUSTMENTS",
|
|
126
|
+
# Dataclasses
|
|
127
|
+
"Memory",
|
|
128
|
+
"SearchResult",
|
|
129
|
+
"ContextResponse",
|
|
130
|
+
# Pydantic models
|
|
131
|
+
"MemoryCreate",
|
|
132
|
+
"MemoryUpdate",
|
|
133
|
+
"SearchQuery",
|
|
134
|
+
"OutcomeRecord",
|
|
135
|
+
"MemoryResponse",
|
|
136
|
+
"SearchResultResponse",
|
|
137
|
+
"ContextResponseModel",
|
|
138
|
+
"StatsResponse",
|
|
139
|
+
# Storage
|
|
140
|
+
"MemoryStorage",
|
|
141
|
+
"StorageStats",
|
|
142
|
+
"StorageError",
|
|
143
|
+
"MemoryNotFoundError",
|
|
144
|
+
"ConnectionError",
|
|
145
|
+
# Embeddings
|
|
146
|
+
"EmbeddingProvider",
|
|
147
|
+
"LocalEmbeddingProvider",
|
|
148
|
+
"APIEmbeddingProvider",
|
|
149
|
+
"MockEmbeddingProvider",
|
|
150
|
+
"NullEmbeddingProvider",
|
|
151
|
+
"EmbeddingConfig",
|
|
152
|
+
"EmbeddingResult",
|
|
153
|
+
"BatchEmbeddingResult",
|
|
154
|
+
"EmbeddingCache",
|
|
155
|
+
"Embedding",
|
|
156
|
+
"EmbeddingError",
|
|
157
|
+
"ModelNotFoundError",
|
|
158
|
+
"APIError",
|
|
159
|
+
"get_embedding_provider",
|
|
160
|
+
# Retrieval
|
|
161
|
+
"BM25Index",
|
|
162
|
+
"HybridRetriever",
|
|
163
|
+
"RetrievalConfig",
|
|
164
|
+
"CategoryRouter",
|
|
165
|
+
# Engine
|
|
166
|
+
"MemoryEngine",
|
|
167
|
+
"EngineConfig",
|
|
168
|
+
"EngineStats",
|
|
169
|
+
"EngineError",
|
|
170
|
+
"EngineNotInitializedError",
|
|
171
|
+
"LastSearchInfo",
|
|
172
|
+
"create_engine",
|
|
173
|
+
# Exceptions
|
|
174
|
+
"RuntimeMemoryError",
|
|
175
|
+
"ErrorCode",
|
|
176
|
+
"DatabaseConnectionError",
|
|
177
|
+
"DatabaseIntegrityError",
|
|
178
|
+
"RetrievalError",
|
|
179
|
+
"SearchTimeoutError",
|
|
180
|
+
"ExtractionError",
|
|
181
|
+
"LLMAPIError",
|
|
182
|
+
"RateLimitError",
|
|
183
|
+
"InvalidResponseError",
|
|
184
|
+
"ServerError",
|
|
185
|
+
"MCPError",
|
|
186
|
+
"HookError",
|
|
187
|
+
"HookNotInstalledError",
|
|
188
|
+
"DaemonError",
|
|
189
|
+
"DaemonAlreadyRunningError",
|
|
190
|
+
"SDKError",
|
|
191
|
+
"AuthenticationError",
|
|
192
|
+
"ValidationError",
|
|
193
|
+
"TaskError",
|
|
194
|
+
"BeadsNotFoundError",
|
|
195
|
+
"TaskSyncError",
|
|
196
|
+
"TaskLinkError",
|
|
197
|
+
"ConfigurationError",
|
|
198
|
+
"InitializationError",
|
|
199
|
+
"CircuitOpenError",
|
|
200
|
+
"format_error",
|
|
201
|
+
"is_recoverable",
|
|
202
|
+
# Resilience
|
|
203
|
+
"RetryConfig",
|
|
204
|
+
"calculate_backoff",
|
|
205
|
+
"retry",
|
|
206
|
+
"retry_sync",
|
|
207
|
+
"CircuitState",
|
|
208
|
+
"CircuitBreakerConfig",
|
|
209
|
+
"CircuitBreaker",
|
|
210
|
+
"get_circuit_breaker",
|
|
211
|
+
"reset_all_circuit_breakers",
|
|
212
|
+
"with_fallback",
|
|
213
|
+
"with_fallback_sync",
|
|
214
|
+
"try_multiple",
|
|
215
|
+
"with_timeout",
|
|
216
|
+
]
|
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Configuration management for Runtime Memory.
|
|
3
|
+
|
|
4
|
+
Provides:
|
|
5
|
+
- Environment-based configuration
|
|
6
|
+
- Validation with Pydantic
|
|
7
|
+
- Support for multiple environments (dev, test, prod)
|
|
8
|
+
- Secrets management via environment variables
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import os
|
|
14
|
+
from enum import Enum
|
|
15
|
+
from functools import lru_cache
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import Any
|
|
18
|
+
|
|
19
|
+
from pydantic import BaseModel, Field, field_validator
|
|
20
|
+
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
21
|
+
|
|
22
|
+
from runtime_memory.core.paths import default_db_path, store_dir
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class Environment(str, Enum):
|
|
26
|
+
"""Application environment."""
|
|
27
|
+
|
|
28
|
+
DEVELOPMENT = "development"
|
|
29
|
+
TESTING = "testing"
|
|
30
|
+
PRODUCTION = "production"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class DatabaseConfig(BaseModel):
|
|
34
|
+
"""Database configuration."""
|
|
35
|
+
|
|
36
|
+
path: Path = Field(
|
|
37
|
+
default_factory=default_db_path,
|
|
38
|
+
description="Path to SQLite database file",
|
|
39
|
+
)
|
|
40
|
+
echo: bool = Field(
|
|
41
|
+
default=False,
|
|
42
|
+
description="Echo SQL queries (for debugging)",
|
|
43
|
+
)
|
|
44
|
+
pool_size: int = Field(
|
|
45
|
+
default=5,
|
|
46
|
+
ge=1,
|
|
47
|
+
le=20,
|
|
48
|
+
description="Connection pool size",
|
|
49
|
+
)
|
|
50
|
+
timeout: float = Field(
|
|
51
|
+
default=30.0,
|
|
52
|
+
ge=1.0,
|
|
53
|
+
le=300.0,
|
|
54
|
+
description="Database operation timeout in seconds",
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
@field_validator("path", mode="before")
|
|
58
|
+
@classmethod
|
|
59
|
+
def expand_path(cls, v: Any) -> Path:
|
|
60
|
+
"""Expand user home directory in path."""
|
|
61
|
+
if isinstance(v, str):
|
|
62
|
+
v = Path(v).expanduser()
|
|
63
|
+
return v
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class EmbeddingConfig(BaseModel):
|
|
67
|
+
"""Embedding model configuration."""
|
|
68
|
+
|
|
69
|
+
model_name: str = Field(
|
|
70
|
+
default="all-MiniLM-L6-v2",
|
|
71
|
+
description="Sentence transformer model name",
|
|
72
|
+
)
|
|
73
|
+
cache_enabled: bool = Field(
|
|
74
|
+
default=True,
|
|
75
|
+
description="Enable embedding cache",
|
|
76
|
+
)
|
|
77
|
+
cache_size: int = Field(
|
|
78
|
+
default=10000,
|
|
79
|
+
ge=100,
|
|
80
|
+
le=100000,
|
|
81
|
+
description="Maximum number of cached embeddings",
|
|
82
|
+
)
|
|
83
|
+
batch_size: int = Field(
|
|
84
|
+
default=32,
|
|
85
|
+
ge=1,
|
|
86
|
+
le=128,
|
|
87
|
+
description="Batch size for embedding generation",
|
|
88
|
+
)
|
|
89
|
+
device: str = Field(
|
|
90
|
+
default="cpu",
|
|
91
|
+
description="Device for embedding model (cpu/cuda/mps)",
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class RetrievalConfig(BaseModel):
|
|
96
|
+
"""Retrieval system configuration."""
|
|
97
|
+
|
|
98
|
+
semantic_weight: float = Field(
|
|
99
|
+
default=0.35,
|
|
100
|
+
ge=0.0,
|
|
101
|
+
le=1.0,
|
|
102
|
+
description="Weight for semantic similarity (0-1)",
|
|
103
|
+
)
|
|
104
|
+
outcome_weight: float = Field(
|
|
105
|
+
default=0.25,
|
|
106
|
+
ge=0.0,
|
|
107
|
+
le=1.0,
|
|
108
|
+
description="Weight for outcome score (0-1)",
|
|
109
|
+
)
|
|
110
|
+
recency_weight: float = Field(
|
|
111
|
+
default=0.15,
|
|
112
|
+
ge=0.0,
|
|
113
|
+
le=1.0,
|
|
114
|
+
description="Weight for recency (0-1)",
|
|
115
|
+
)
|
|
116
|
+
frequency_weight: float = Field(
|
|
117
|
+
default=0.15,
|
|
118
|
+
ge=0.0,
|
|
119
|
+
le=1.0,
|
|
120
|
+
description="Weight for usage frequency (0-1)",
|
|
121
|
+
)
|
|
122
|
+
confidence_weight: float = Field(
|
|
123
|
+
default=0.10,
|
|
124
|
+
ge=0.0,
|
|
125
|
+
le=1.0,
|
|
126
|
+
description="Weight for confidence score (0-1)",
|
|
127
|
+
)
|
|
128
|
+
recency_half_life_days: int = Field(
|
|
129
|
+
default=30,
|
|
130
|
+
ge=1,
|
|
131
|
+
le=365,
|
|
132
|
+
description="Half-life for recency decay in days",
|
|
133
|
+
)
|
|
134
|
+
default_limit: int = Field(
|
|
135
|
+
default=10,
|
|
136
|
+
ge=1,
|
|
137
|
+
le=100,
|
|
138
|
+
description="Default number of results to return",
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
class ServerConfig(BaseModel):
|
|
143
|
+
"""Server configuration."""
|
|
144
|
+
|
|
145
|
+
host: str = Field(
|
|
146
|
+
default="127.0.0.1",
|
|
147
|
+
description="Server bind address",
|
|
148
|
+
)
|
|
149
|
+
port: int = Field(
|
|
150
|
+
default=8080,
|
|
151
|
+
ge=1024,
|
|
152
|
+
le=65535,
|
|
153
|
+
description="Server port number",
|
|
154
|
+
)
|
|
155
|
+
workers: int = Field(
|
|
156
|
+
default=1,
|
|
157
|
+
ge=1,
|
|
158
|
+
le=16,
|
|
159
|
+
description="Number of server workers",
|
|
160
|
+
)
|
|
161
|
+
cors_origins: list[str] = Field(
|
|
162
|
+
default_factory=lambda: ["http://localhost:*", "http://127.0.0.1:*"],
|
|
163
|
+
description="Allowed CORS origins",
|
|
164
|
+
)
|
|
165
|
+
request_timeout: float = Field(
|
|
166
|
+
default=30.0,
|
|
167
|
+
ge=1.0,
|
|
168
|
+
le=300.0,
|
|
169
|
+
description="Request timeout in seconds",
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
class ExtractionConfig(BaseModel):
|
|
174
|
+
"""LLM extraction configuration."""
|
|
175
|
+
|
|
176
|
+
model: str = Field(
|
|
177
|
+
default="claude-3-haiku-20240307",
|
|
178
|
+
description="LLM model for extraction",
|
|
179
|
+
)
|
|
180
|
+
max_tokens: int = Field(
|
|
181
|
+
default=1000,
|
|
182
|
+
ge=100,
|
|
183
|
+
le=4000,
|
|
184
|
+
description="Maximum tokens for extraction response",
|
|
185
|
+
)
|
|
186
|
+
temperature: float = Field(
|
|
187
|
+
default=0.0,
|
|
188
|
+
ge=0.0,
|
|
189
|
+
le=1.0,
|
|
190
|
+
description="LLM temperature for extraction",
|
|
191
|
+
)
|
|
192
|
+
timeout: float = Field(
|
|
193
|
+
default=30.0,
|
|
194
|
+
ge=5.0,
|
|
195
|
+
le=120.0,
|
|
196
|
+
description="API call timeout in seconds",
|
|
197
|
+
)
|
|
198
|
+
max_retries: int = Field(
|
|
199
|
+
default=3,
|
|
200
|
+
ge=0,
|
|
201
|
+
le=5,
|
|
202
|
+
description="Maximum retry attempts for API calls",
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
class LoggingConfig(BaseModel):
|
|
207
|
+
"""Logging configuration."""
|
|
208
|
+
|
|
209
|
+
level: str = Field(
|
|
210
|
+
default="WARNING",
|
|
211
|
+
description="Logging level (DEBUG, INFO, WARNING, ERROR)",
|
|
212
|
+
)
|
|
213
|
+
format: str = Field(
|
|
214
|
+
default="text",
|
|
215
|
+
description="Log format (text or json)",
|
|
216
|
+
)
|
|
217
|
+
include_timestamp: bool = Field(
|
|
218
|
+
default=True,
|
|
219
|
+
description="Include timestamp in log output",
|
|
220
|
+
)
|
|
221
|
+
log_file: Path | None = Field(
|
|
222
|
+
default=None,
|
|
223
|
+
description="Optional log file path",
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
@field_validator("level")
|
|
227
|
+
@classmethod
|
|
228
|
+
def validate_level(cls, v: str) -> str:
|
|
229
|
+
"""Validate logging level."""
|
|
230
|
+
valid_levels = {"DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"}
|
|
231
|
+
v = v.upper()
|
|
232
|
+
if v not in valid_levels:
|
|
233
|
+
raise ValueError(f"Invalid log level: {v}. Must be one of {valid_levels}")
|
|
234
|
+
return v
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
class Settings(BaseSettings):
|
|
238
|
+
"""Main application settings.
|
|
239
|
+
|
|
240
|
+
Configuration is loaded in priority order:
|
|
241
|
+
1. Environment variables (highest priority)
|
|
242
|
+
2. .env file in current directory
|
|
243
|
+
3. Default values (lowest priority)
|
|
244
|
+
|
|
245
|
+
Environment variables use RUNTIME_MEMORY_ prefix. Nested settings use a
|
|
246
|
+
double-underscore delimiter:
|
|
247
|
+
- RUNTIME_MEMORY_ENV=production
|
|
248
|
+
- RUNTIME_MEMORY_DATABASE__PATH=/path/to/db
|
|
249
|
+
- RUNTIME_MEMORY_LOG_LEVEL=DEBUG
|
|
250
|
+
|
|
251
|
+
The ``mem`` CLI accepts either RUNTIME_MEMORY_DATABASE__PATH or the shorter
|
|
252
|
+
RUNTIME_MEMORY_DB. Note that RUNTIME_MEMORY_DB_PATH is *not* recognised; it was
|
|
253
|
+
documented here in error and silently fell through to the default database.
|
|
254
|
+
"""
|
|
255
|
+
|
|
256
|
+
model_config = SettingsConfigDict(
|
|
257
|
+
env_prefix="RUNTIME_MEMORY_",
|
|
258
|
+
env_file=".env",
|
|
259
|
+
env_file_encoding="utf-8",
|
|
260
|
+
env_nested_delimiter="__",
|
|
261
|
+
extra="ignore",
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
# Environment
|
|
265
|
+
env: Environment = Field(
|
|
266
|
+
default=Environment.DEVELOPMENT,
|
|
267
|
+
description="Application environment",
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
# API Keys (from environment only, never stored)
|
|
271
|
+
anthropic_api_key: str | None = Field(
|
|
272
|
+
default=None,
|
|
273
|
+
description="Anthropic API key for extraction",
|
|
274
|
+
)
|
|
275
|
+
openai_api_key: str | None = Field(
|
|
276
|
+
default=None,
|
|
277
|
+
description="OpenAI API key (fallback)",
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
# Component configs
|
|
281
|
+
database: DatabaseConfig = Field(default_factory=DatabaseConfig)
|
|
282
|
+
embedding: EmbeddingConfig = Field(default_factory=EmbeddingConfig)
|
|
283
|
+
retrieval: RetrievalConfig = Field(default_factory=RetrievalConfig)
|
|
284
|
+
server: ServerConfig = Field(default_factory=ServerConfig)
|
|
285
|
+
extraction: ExtractionConfig = Field(default_factory=ExtractionConfig)
|
|
286
|
+
logging: LoggingConfig = Field(default_factory=LoggingConfig)
|
|
287
|
+
|
|
288
|
+
# Feature flags
|
|
289
|
+
enable_extraction: bool = Field(
|
|
290
|
+
default=True,
|
|
291
|
+
description="Enable LLM-based extraction",
|
|
292
|
+
)
|
|
293
|
+
enable_metrics: bool = Field(
|
|
294
|
+
default=False,
|
|
295
|
+
description="Enable Prometheus metrics",
|
|
296
|
+
)
|
|
297
|
+
enable_tracing: bool = Field(
|
|
298
|
+
default=False,
|
|
299
|
+
description="Enable distributed tracing",
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
# Project context
|
|
303
|
+
project_name: str | None = Field(
|
|
304
|
+
default=None,
|
|
305
|
+
description="Current project name (auto-detected if not set)",
|
|
306
|
+
)
|
|
307
|
+
project_path: Path | None = Field(
|
|
308
|
+
default=None,
|
|
309
|
+
description="Current project path",
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
@classmethod
|
|
313
|
+
def for_testing(cls) -> "Settings":
|
|
314
|
+
"""Create settings optimized for testing."""
|
|
315
|
+
return cls(
|
|
316
|
+
env=Environment.TESTING,
|
|
317
|
+
database=DatabaseConfig(
|
|
318
|
+
path=Path(":memory:"),
|
|
319
|
+
echo=False,
|
|
320
|
+
),
|
|
321
|
+
embedding=EmbeddingConfig(
|
|
322
|
+
cache_enabled=False,
|
|
323
|
+
),
|
|
324
|
+
logging=LoggingConfig(
|
|
325
|
+
level="DEBUG",
|
|
326
|
+
),
|
|
327
|
+
enable_extraction=False,
|
|
328
|
+
enable_metrics=False,
|
|
329
|
+
enable_tracing=False,
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
@classmethod
|
|
333
|
+
def for_production(cls) -> "Settings":
|
|
334
|
+
"""Create settings optimized for production."""
|
|
335
|
+
return cls(
|
|
336
|
+
env=Environment.PRODUCTION,
|
|
337
|
+
logging=LoggingConfig(
|
|
338
|
+
level="WARNING",
|
|
339
|
+
format="json",
|
|
340
|
+
),
|
|
341
|
+
enable_metrics=True,
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
def is_development(self) -> bool:
|
|
345
|
+
"""Check if running in development mode."""
|
|
346
|
+
return self.env == Environment.DEVELOPMENT
|
|
347
|
+
|
|
348
|
+
def is_testing(self) -> bool:
|
|
349
|
+
"""Check if running in testing mode."""
|
|
350
|
+
return self.env == Environment.TESTING
|
|
351
|
+
|
|
352
|
+
def is_production(self) -> bool:
|
|
353
|
+
"""Check if running in production mode."""
|
|
354
|
+
return self.env == Environment.PRODUCTION
|
|
355
|
+
|
|
356
|
+
def get_db_path(self) -> Path:
|
|
357
|
+
"""Get resolved database path."""
|
|
358
|
+
path = self.database.path.expanduser()
|
|
359
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
360
|
+
return path
|
|
361
|
+
|
|
362
|
+
def validate_for_extraction(self) -> bool:
|
|
363
|
+
"""Check if extraction is properly configured."""
|
|
364
|
+
return bool(self.anthropic_api_key or self.openai_api_key)
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
@lru_cache()
|
|
368
|
+
def get_settings() -> Settings:
|
|
369
|
+
"""Get cached application settings.
|
|
370
|
+
|
|
371
|
+
Settings are loaded once and cached for the lifetime of the application.
|
|
372
|
+
Use clear_settings_cache() to reload settings.
|
|
373
|
+
|
|
374
|
+
Returns:
|
|
375
|
+
The application settings
|
|
376
|
+
"""
|
|
377
|
+
return Settings()
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
def clear_settings_cache() -> None:
|
|
381
|
+
"""Clear the settings cache, forcing reload on next access."""
|
|
382
|
+
get_settings.cache_clear()
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
def get_config_path() -> Path:
|
|
386
|
+
"""Get the configuration directory path."""
|
|
387
|
+
config_dir = store_dir()
|
|
388
|
+
config_dir.mkdir(parents=True, exist_ok=True)
|
|
389
|
+
return config_dir
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
def get_data_path() -> Path:
|
|
393
|
+
"""Get the data directory path."""
|
|
394
|
+
data_dir = store_dir() / "data"
|
|
395
|
+
data_dir.mkdir(parents=True, exist_ok=True)
|
|
396
|
+
return data_dir
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
def get_cache_path() -> Path:
|
|
400
|
+
"""Get the cache directory path."""
|
|
401
|
+
cache_dir = store_dir() / "cache"
|
|
402
|
+
cache_dir.mkdir(parents=True, exist_ok=True)
|
|
403
|
+
return cache_dir
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
# Environment variable helpers
|
|
407
|
+
def get_env(key: str, default: str | None = None) -> str | None:
|
|
408
|
+
"""Get environment variable with RUNTIME_MEMORY_ prefix.
|
|
409
|
+
|
|
410
|
+
Args:
|
|
411
|
+
key: Variable name (without prefix)
|
|
412
|
+
default: Default value if not set
|
|
413
|
+
|
|
414
|
+
Returns:
|
|
415
|
+
The environment variable value or default
|
|
416
|
+
"""
|
|
417
|
+
return os.getenv(f"RUNTIME_MEMORY_{key}", default)
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
def get_env_bool(key: str, default: bool = False) -> bool:
|
|
421
|
+
"""Get boolean environment variable.
|
|
422
|
+
|
|
423
|
+
Recognizes: true, 1, yes, on (case-insensitive)
|
|
424
|
+
|
|
425
|
+
Args:
|
|
426
|
+
key: Variable name (without prefix)
|
|
427
|
+
default: Default value if not set
|
|
428
|
+
|
|
429
|
+
Returns:
|
|
430
|
+
Boolean value
|
|
431
|
+
"""
|
|
432
|
+
value = get_env(key)
|
|
433
|
+
if value is None:
|
|
434
|
+
return default
|
|
435
|
+
return value.lower() in ("true", "1", "yes", "on")
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
def get_env_int(key: str, default: int = 0) -> int:
|
|
439
|
+
"""Get integer environment variable.
|
|
440
|
+
|
|
441
|
+
Args:
|
|
442
|
+
key: Variable name (without prefix)
|
|
443
|
+
default: Default value if not set or invalid
|
|
444
|
+
|
|
445
|
+
Returns:
|
|
446
|
+
Integer value
|
|
447
|
+
"""
|
|
448
|
+
value = get_env(key)
|
|
449
|
+
if value is None:
|
|
450
|
+
return default
|
|
451
|
+
try:
|
|
452
|
+
return int(value)
|
|
453
|
+
except ValueError:
|
|
454
|
+
return default
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
def get_env_float(key: str, default: float = 0.0) -> float:
|
|
458
|
+
"""Get float environment variable.
|
|
459
|
+
|
|
460
|
+
Args:
|
|
461
|
+
key: Variable name (without prefix)
|
|
462
|
+
default: Default value if not set or invalid
|
|
463
|
+
|
|
464
|
+
Returns:
|
|
465
|
+
Float value
|
|
466
|
+
"""
|
|
467
|
+
value = get_env(key)
|
|
468
|
+
if value is None:
|
|
469
|
+
return default
|
|
470
|
+
try:
|
|
471
|
+
return float(value)
|
|
472
|
+
except ValueError:
|
|
473
|
+
return default
|