semvec 0.1.0a2__cp310-abi3-win_amd64.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 (59) hide show
  1. semvec/__init__.py +82 -0
  2. semvec/_core.pyd +0 -0
  3. semvec/_core.pyi +415 -0
  4. semvec/api/__init__.py +26 -0
  5. semvec/api/app.py +127 -0
  6. semvec/api/auth.py +153 -0
  7. semvec/api/cli.py +55 -0
  8. semvec/api/cluster_manager.py +287 -0
  9. semvec/api/cluster_routes.py +221 -0
  10. semvec/api/database.py +55 -0
  11. semvec/api/event_bus.py +87 -0
  12. semvec/api/global_observer.py +398 -0
  13. semvec/api/literal_cache_routes.py +107 -0
  14. semvec/api/metrics_export.py +77 -0
  15. semvec/api/models.py +132 -0
  16. semvec/api/network_manager.py +189 -0
  17. semvec/api/network_routes.py +118 -0
  18. semvec/api/observer_routes.py +115 -0
  19. semvec/api/regional_manager.py +294 -0
  20. semvec/api/regional_routes.py +150 -0
  21. semvec/api/routes.py +284 -0
  22. semvec/api/schemas.py +453 -0
  23. semvec/api/session_delta_routes.py +241 -0
  24. semvec/api/session_manager.py +804 -0
  25. semvec/audit.py +163 -0
  26. semvec/benchmarks/__init__.py +6 -0
  27. semvec/benchmarks/longmemeval/__init__.py +42 -0
  28. semvec/benchmarks/longmemeval/__main__.py +6 -0
  29. semvec/benchmarks/longmemeval/call_counter.py +95 -0
  30. semvec/benchmarks/longmemeval/cli.py +299 -0
  31. semvec/benchmarks/longmemeval/evaluator.py +298 -0
  32. semvec/benchmarks/longmemeval/loader.py +112 -0
  33. semvec/benchmarks/longmemeval/runner.py +356 -0
  34. semvec/benchmarks/longmemeval/schema.py +103 -0
  35. semvec/coding/__init__.py +43 -0
  36. semvec/coding/engine.py +180 -0
  37. semvec/coding/hooks/__init__.py +22 -0
  38. semvec/coding/hooks/pre_compact.py +118 -0
  39. semvec/coding/hooks/session_start.py +108 -0
  40. semvec/coding/mcp_server.py +316 -0
  41. semvec/coding/persistence.py +94 -0
  42. semvec/coding/transcript_parser.py +186 -0
  43. semvec/cortex/__init__.py +98 -0
  44. semvec/cortex/service.py +203 -0
  45. semvec/literal_cache.py +35 -0
  46. semvec/metrics.py +12 -0
  47. semvec/py.typed +0 -0
  48. semvec/token_reduction/__init__.py +93 -0
  49. semvec/token_reduction/chat_proxy.py +253 -0
  50. semvec/token_reduction/llm_client.py +170 -0
  51. semvec/token_reduction/phase_prompts.py +43 -0
  52. semvec/token_reduction/serializer.py +117 -0
  53. semvec/token_reduction/token_counter.py +173 -0
  54. semvec-0.1.0a2.dist-info/METADATA +294 -0
  55. semvec-0.1.0a2.dist-info/RECORD +59 -0
  56. semvec-0.1.0a2.dist-info/WHEEL +4 -0
  57. semvec-0.1.0a2.dist-info/entry_points.txt +2 -0
  58. semvec-0.1.0a2.dist-info/licenses/LICENSE +6 -0
  59. semvec-0.1.0a2.dist-info/sboms/semvec.cyclonedx.json +4101 -0
semvec/__init__.py ADDED
@@ -0,0 +1,82 @@
1
+ """Semvec — patent-pending persistent semantic state engine.
2
+
3
+ The public surface is PEP-8 compliant PascalCase. Legacy PSS-prefixed
4
+ names (``PSS_State_V4``, ``PSSConfig``, ``PSSError``) remain importable
5
+ as deprecated aliases — accessing them raises :class:`DeprecationWarning`.
6
+ Aliases are scheduled for removal in semvec 0.2.0.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import warnings as _warnings
12
+ from typing import Any as _Any
13
+
14
+ from semvec._core import (
15
+ AdaptiveParameters,
16
+ CodeEntity,
17
+ ConfigurationError,
18
+ EmbeddingError,
19
+ EmbeddingService,
20
+ LicenseError,
21
+ LicenseExpiredError,
22
+ LiteralCache,
23
+ MemoryUnit,
24
+ MultiResolutionMemory,
25
+ PhaseDetector,
26
+ RateLimitError,
27
+ SemvecConfig,
28
+ SemvecError,
29
+ SemvecState,
30
+ StateCorruptionError,
31
+ safe_cosine_similarity,
32
+ )
33
+ from semvec.literal_cache import EntityKind
34
+
35
+ __version__ = "0.1.0a2"
36
+ __author__ = "Michael Neuberger"
37
+
38
+ __all__ = [
39
+ "AdaptiveParameters",
40
+ "CodeEntity",
41
+ "ConfigurationError",
42
+ "EmbeddingError",
43
+ "EmbeddingService",
44
+ "EntityKind",
45
+ "LicenseError",
46
+ "LicenseExpiredError",
47
+ "LiteralCache",
48
+ "MemoryUnit",
49
+ "MultiResolutionMemory",
50
+ "PhaseDetector",
51
+ "RateLimitError",
52
+ "SemvecConfig",
53
+ "SemvecError",
54
+ "SemvecState",
55
+ "StateCorruptionError",
56
+ "safe_cosine_similarity",
57
+ ]
58
+
59
+
60
+ # ---------------------------------------------------------------------------
61
+ # Deprecated aliases — emit a DeprecationWarning on access, resolve to the
62
+ # current PEP-8 names. Scheduled for removal in 0.2.0.
63
+
64
+ _DEPRECATED_ALIASES: dict[str, tuple[str, _Any]] = {
65
+ "PSS_State_V4": ("SemvecState", SemvecState),
66
+ "PSSConfig": ("SemvecConfig", SemvecConfig),
67
+ "PSSError": ("SemvecError", SemvecError),
68
+ }
69
+
70
+
71
+ def __getattr__(name: str) -> _Any:
72
+ alias = _DEPRECATED_ALIASES.get(name)
73
+ if alias is not None:
74
+ replacement_name, obj = alias
75
+ _warnings.warn(
76
+ f"{name} is deprecated and will be removed in semvec 0.2.0. "
77
+ f"Use {replacement_name} instead.",
78
+ DeprecationWarning,
79
+ stacklevel=2,
80
+ )
81
+ return obj
82
+ raise AttributeError(f"module 'semvec' has no attribute {name!r}")
semvec/_core.pyd ADDED
Binary file
semvec/_core.pyi ADDED
@@ -0,0 +1,415 @@
1
+ """Type stubs for the Rust-backed `semvec._core` extension.
2
+
3
+ Covers the public surface used by typed consumers (mypy, pyright, IDE
4
+ auto-complete). Kept at the level of published shapes — implementation
5
+ details live in the Rust source.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from typing import Any, Iterable, Mapping
11
+
12
+ import numpy as np
13
+ from numpy.typing import NDArray
14
+
15
+ # ---------------------------------------------------------------------------
16
+ # Core
17
+
18
+ class SemvecConfig:
19
+ dimension: int
20
+ short_term_size: int
21
+ medium_term_size: int
22
+ long_term_size: int
23
+ history_length: int
24
+ model_name: str
25
+ device: str
26
+
27
+ def __init__(
28
+ self,
29
+ dimension: int = 384,
30
+ short_term_size: int = 20,
31
+ medium_term_size: int = 100,
32
+ long_term_size: int = 500,
33
+ history_length: int = 50,
34
+ model_name: str = ...,
35
+ device: str = "cpu",
36
+ **kwargs: Any,
37
+ ) -> None: ...
38
+
39
+ class AdaptiveParameters:
40
+ def __init__(self, *args: Any, **kwargs: Any) -> None: ...
41
+
42
+ class SemvecState:
43
+ semantic_state: NDArray[np.float64]
44
+ interaction_count: int
45
+ alpha_hit_count: int
46
+ timestamp: int
47
+ memory: "MultiResolutionMemory"
48
+ phase_detector: "PhaseDetector"
49
+ literal_cache: "LiteralCache"
50
+ similarity_history: list[float]
51
+ beta_history: list[float]
52
+ fsm_history: list[float]
53
+ phase_history: list[str]
54
+
55
+ def __init__(self, config: SemvecConfig | None = None) -> None: ...
56
+ def update(self, embedding: NDArray[np.float64], text: str) -> dict[str, Any]: ...
57
+ def to_dict(self) -> dict[str, Any]: ...
58
+ @staticmethod
59
+ def from_dict(data: Mapping[str, Any]) -> "SemvecState": ...
60
+ def set_retrieval_projection_weights(
61
+ self, matrix: list[list[float]] | NDArray[np.float64]
62
+ ) -> None: ...
63
+ def get_retrieval_projection_weights(self) -> list[list[float]]: ...
64
+ def get_retrieval_projection_w_up(self) -> list[list[float]]: ...
65
+
66
+ # ---------------------------------------------------------------------------
67
+ # Memory
68
+
69
+ class MemoryUnit:
70
+ embedding: NDArray[np.float64]
71
+ text: str
72
+ importance: float
73
+ access_count: int
74
+ timestamp: float
75
+ semantic_hash: str
76
+ protection_score: float
77
+
78
+ def __init__(self, embedding: NDArray[np.float64], weight: float, promote: bool) -> None: ...
79
+ def to_dict(self) -> dict[str, Any]: ...
80
+ @staticmethod
81
+ def from_dict(data: Mapping[str, Any]) -> "MemoryUnit": ...
82
+
83
+ class MultiResolutionMemory:
84
+ short_term: list[MemoryUnit]
85
+ medium_term: list[MemoryUnit]
86
+ long_term: list[MemoryUnit]
87
+
88
+ def get_relevant_memories(
89
+ self, query: NDArray[np.float64], top_k: int = 10
90
+ ) -> list[MemoryUnit]: ...
91
+
92
+ # ---------------------------------------------------------------------------
93
+ # Phase + attention + cache
94
+
95
+ class PhaseDetector:
96
+ current_phase: str
97
+ phase_transitions: list[dict[str, Any]]
98
+
99
+ def __init__(self, window_size: int, switch_cost: float, min_dwell: int) -> None: ...
100
+ def update(self, metrics: Mapping[str, float], timestamp: int) -> str | None: ...
101
+ def signal_topic_switch(self, magnitude: float) -> None: ...
102
+
103
+ # ---------------------------------------------------------------------------
104
+ # Literal cache
105
+
106
+ class CodeEntity:
107
+ def __init__(self, *args: Any, **kwargs: Any) -> None: ...
108
+
109
+ class EntityKind:
110
+ FUNCTION: "EntityKind"
111
+ CLASS: "EntityKind"
112
+ METHOD: "EntityKind"
113
+ MODULE: "EntityKind"
114
+
115
+ class LiteralCache:
116
+ def __init__(self, *args: Any, **kwargs: Any) -> None: ...
117
+
118
+ # ---------------------------------------------------------------------------
119
+ # Cortex — aggregation
120
+
121
+ class WeightedAverageAggregation:
122
+ def __init__(self, dimension: int = 384) -> None: ...
123
+ def aggregate(
124
+ self,
125
+ local_states: list[NDArray[np.float64]],
126
+ weights: NDArray[np.float64] | None = None,
127
+ ) -> NDArray[np.float64]: ...
128
+
129
+ class AttentionAggregation:
130
+ def __init__(self, attention_dim: int = 64, dimension: int = 384) -> None: ...
131
+ def aggregate(
132
+ self,
133
+ local_states: list[NDArray[np.float64]],
134
+ weights: NDArray[np.float64] | None = None,
135
+ ) -> NDArray[np.float64]: ...
136
+
137
+ # ---------------------------------------------------------------------------
138
+ # Cortex — consensus + packets
139
+
140
+ class ConsensusLevel:
141
+ SIMPLE_MAJORITY: "ConsensusLevel"
142
+ QUALIFIED_MAJORITY: "ConsensusLevel"
143
+ UNANIMOUS: "ConsensusLevel"
144
+ WEIGHTED_VOTE: "ConsensusLevel"
145
+ ADAPTIVE_THRESHOLD: "ConsensusLevel"
146
+ value: str
147
+
148
+ class TransferType:
149
+ FULL_STATE: "TransferType"
150
+ PARTIAL_STATE: "TransferType"
151
+ SEMANTIC_DELTA: "TransferType"
152
+ MEMORY_FRAGMENT: "TransferType"
153
+ COHERENCE_FIELD: "TransferType"
154
+ value: str
155
+
156
+ class StateVectorPacket:
157
+ packet_id: str
158
+ source_instance: str
159
+ target_instance: str
160
+ transfer_type: TransferType
161
+ state_vector: NDArray[np.float64]
162
+ metadata: dict[str, Any]
163
+ timestamp: float
164
+ size_bytes: int
165
+ checksum: str
166
+
167
+ def __init__(
168
+ self,
169
+ packet_id: str,
170
+ source_instance: str,
171
+ target_instance: str,
172
+ transfer_type: TransferType,
173
+ state_vector: NDArray[np.float64],
174
+ metadata: Mapping[str, Any] | None = None,
175
+ ) -> None: ...
176
+ def verify_integrity(self) -> bool: ...
177
+ def serialize(self) -> bytes: ...
178
+ @classmethod
179
+ def deserialize(cls, data: bytes) -> "StateVectorPacket": ...
180
+
181
+ class ConsensusProposal:
182
+ proposal_id: str
183
+ proposer_instance: str
184
+ proposal_type: str
185
+ proposed_state: NDArray[np.float64]
186
+ rationale: str
187
+ consensus_level: str
188
+ voting_deadline: float
189
+ votes: dict[str, bool]
190
+ vote_weights: dict[str, float]
191
+ status: str
192
+ creation_time: float
193
+
194
+ def __init__(
195
+ self,
196
+ proposal_id: str,
197
+ proposer_instance: str,
198
+ proposal_type: str,
199
+ proposed_state: NDArray[np.float64],
200
+ rationale: str,
201
+ consensus_level: str = "simple_majority",
202
+ voting_deadline: float = 0.0,
203
+ ) -> None: ...
204
+ def add_vote(self, instance_id: str, vote: bool, weight: float = 1.0) -> None: ...
205
+ def calculate_consensus(self) -> tuple[bool, float]: ...
206
+ def is_expired(self) -> bool: ...
207
+
208
+ class ConsensusEngine:
209
+ local_instance_id: str
210
+ network_id: str
211
+ consensus_id: str
212
+
213
+ def __init__(self, local_instance_id: str, network_id: str) -> None: ...
214
+ def register_instance(self, instance_id: str, weight: float = 1.0) -> None: ...
215
+ def create_proposal(
216
+ self,
217
+ proposal_type: str,
218
+ proposed_state: NDArray[np.float64],
219
+ rationale: str,
220
+ consensus_level: str = "simple_majority",
221
+ voting_timeout: float = 300.0,
222
+ ) -> ConsensusProposal: ...
223
+ def vote_on_proposal(
224
+ self,
225
+ proposal_id: str,
226
+ vote: bool,
227
+ voting_instance: str | None = None,
228
+ ) -> bool: ...
229
+ def check_expired_proposals(self) -> None: ...
230
+ def get_statistics(self) -> dict[str, Any]: ...
231
+
232
+ # ---------------------------------------------------------------------------
233
+ # Cortex — local/meta
234
+
235
+ class SemvecAgent:
236
+ instance_id: str
237
+ local_coherence: float
238
+ interaction_count: int
239
+ global_influence: float
240
+ last_update_time: float
241
+ min_coherence_threshold: float
242
+ pss_state: SemvecState
243
+
244
+ def __init__(
245
+ self,
246
+ instance_id: str,
247
+ embedder: object | None = None,
248
+ dimension: int = 384,
249
+ ) -> None: ...
250
+ def process_input(
251
+ self,
252
+ text: str,
253
+ global_feedback: NDArray[np.float64] | None = None,
254
+ ) -> dict[str, Any]: ...
255
+ def process_input_embedding(
256
+ self,
257
+ embedding: NDArray[np.float64] | list[float],
258
+ text: str,
259
+ global_feedback: NDArray[np.float64] | None = None,
260
+ ) -> dict[str, Any]: ...
261
+ def get_state_vector(self) -> NDArray[np.float64]: ...
262
+
263
+ class SemvecCortexObserver:
264
+ global_state: list[float]
265
+ global_coherence: float
266
+ network_resonance: float
267
+ beta_global: float
268
+ update_count: int
269
+
270
+ def __init__(
271
+ self,
272
+ aggregation_strategy: WeightedAverageAggregation | AttentionAggregation | None = None,
273
+ dimension: int = 384,
274
+ ) -> None: ...
275
+ def update_global_state(
276
+ self, local_instances: Iterable[LocalPSSInstance]
277
+ ) -> dict[str, Any]: ...
278
+ def get_feedback_for_instance(self, instance_id: str) -> NDArray[np.float64] | None: ...
279
+
280
+ class SemvecAgentNetwork:
281
+ local_instances: dict[str, LocalPSSInstance]
282
+ meta_pss: MetaPSSInstance
283
+ enable_feedback: bool
284
+ feedback_strength: float
285
+ max_instances: int
286
+
287
+ def __init__(
288
+ self,
289
+ aggregation_strategy: WeightedAverageAggregation | AttentionAggregation | None = None,
290
+ enable_feedback: bool = True,
291
+ feedback_strength: float = 0.3,
292
+ max_instances: int = 100,
293
+ ) -> None: ...
294
+ def add_instance(self, instance_id: str) -> bool: ...
295
+ def add_local_instance(self, instance_id: str) -> bool: ...
296
+ def remove_local_instance(self, instance_id: str) -> bool: ...
297
+ def process_input(self, instance_id: str, text: str) -> dict[str, Any]: ...
298
+ def get_network_state(self) -> dict[str, Any]: ...
299
+
300
+ # SemvecCortexService lives in semvec.cortex.service (Python layer).
301
+
302
+ # ---------------------------------------------------------------------------
303
+ # Coding primitives
304
+
305
+ class CodePointer:
306
+ intent_vector: NDArray[np.float64]
307
+ file_path: str
308
+ signature: str
309
+ importance: float
310
+ access_count: int
311
+ timestamp: float
312
+ semantic_hash: str
313
+
314
+ def __init__(
315
+ self,
316
+ intent_vector: NDArray[np.float64] | list[float],
317
+ file_path: str,
318
+ signature: str,
319
+ importance: float = 1.0,
320
+ access_count: int = 0,
321
+ timestamp: float | None = None,
322
+ semantic_hash: str = "",
323
+ ) -> None: ...
324
+ def to_dict(self) -> dict[str, Any]: ...
325
+ @staticmethod
326
+ def from_dict(data: Mapping[str, Any]) -> "CodePointer": ...
327
+
328
+ class CodePointerIndex:
329
+ size: int
330
+ capacity: int
331
+ all_pointers: list[CodePointer]
332
+
333
+ def __init__(
334
+ self,
335
+ capacity: int = 100,
336
+ w_importance: float = 0.4,
337
+ w_recency: float = 0.35,
338
+ w_access: float = 0.25,
339
+ ) -> None: ...
340
+ def register(self, pointer: CodePointer) -> None: ...
341
+ def retrieve(
342
+ self, query: NDArray[np.float64], top_k: int = 10
343
+ ) -> list[CodePointer]: ...
344
+ def retention_score(self, pointer: CodePointer) -> float: ...
345
+ def to_dict(self) -> dict[str, Any]: ...
346
+ @staticmethod
347
+ def from_dict(data: Mapping[str, Any]) -> "CodePointerIndex": ...
348
+
349
+ class NegativeAttractor:
350
+ error_vector: NDArray[np.float64]
351
+ description: str
352
+ source: str
353
+ severity: float
354
+ created_at: float
355
+
356
+ def __init__(
357
+ self,
358
+ error_vector: NDArray[np.float64] | list[float],
359
+ description: str,
360
+ source: str,
361
+ severity: float = 1.0,
362
+ created_at: float | None = None,
363
+ ) -> None: ...
364
+
365
+ class NegativeAttractorSet:
366
+ size: int
367
+ capacity: int
368
+ all_attractors: list[NegativeAttractor]
369
+
370
+ def __init__(self, capacity: int = 30, min_severity: float = 0.01) -> None: ...
371
+ def add(
372
+ self,
373
+ error_vector: NDArray[np.float64] | list[float],
374
+ description: str,
375
+ source: str,
376
+ severity: float = 1.0,
377
+ ) -> None: ...
378
+ def check(
379
+ self, proposal: NDArray[np.float64], threshold: float = 0.7
380
+ ) -> list[NegativeAttractor]: ...
381
+ def decay(self, factor: float = 0.95) -> None: ...
382
+
383
+ class PromptBuilder:
384
+ def __init__(self, budget: int = 6000) -> None: ...
385
+ def build(
386
+ self,
387
+ code_pointers: CodePointerIndex,
388
+ negative_attractors: NegativeAttractorSet,
389
+ task_vector: NDArray[np.float64],
390
+ *,
391
+ invariants: list[str] | None = None,
392
+ test_summary: str | None = None,
393
+ git_diff: str | None = None,
394
+ pss_memories: list[str] | None = None,
395
+ phase: str | None = None,
396
+ ) -> str: ...
397
+
398
+ class CompactionEngine:
399
+ """Placeholder for isinstance checks; the real orchestrator is `semvec.coding.CodingEngine`."""
400
+
401
+ def __init__(self, *args: Any, **kwargs: Any) -> None: ...
402
+
403
+ # ---------------------------------------------------------------------------
404
+ # Misc
405
+
406
+ def safe_cosine_similarity(
407
+ a: NDArray[np.float64] | list[float],
408
+ b: NDArray[np.float64] | list[float],
409
+ eps: float = 1e-8,
410
+ ) -> float: ...
411
+
412
+ class EmbeddingService:
413
+ """Placeholder stub for isinstance compatibility with pss."""
414
+
415
+ def __init__(self, *args: Any, **kwargs: Any) -> None: ...
semvec/api/__init__.py ADDED
@@ -0,0 +1,26 @@
1
+ """Semvec REST API — framework-agnostic HTTP access to the Rust core.
2
+
3
+ The API is auth-gated by the bundled Ed25519 JWT licensing system.
4
+ Clients send their license JWT via ``Authorization: Bearer <jwt>`` or
5
+ ``X-API-Key: <jwt>``; the server verifies the signature, extracts the
6
+ tier (community/pro/enterprise), and enforces rate limits accordingly.
7
+
8
+ A SQLite database (``DATABASE_URL``, default ``sqlite:///semvec.db``)
9
+ stores session/cluster/member/audit metadata — no password store, no
10
+ separate API-key table. The license JWT is the single source of truth.
11
+
12
+ Start with::
13
+
14
+ semvec serve --host 0.0.0.0 --port 8080
15
+
16
+ or programmatically::
17
+
18
+ from semvec.api import create_app
19
+ app = create_app()
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ from semvec.api.app import create_app
25
+
26
+ __all__ = ["create_app"]
semvec/api/app.py ADDED
@@ -0,0 +1,127 @@
1
+ """FastAPI application factory for the Semvec REST API.
2
+
3
+ Wires auth + rate limiting + CORS + routers. CORS origins come from the
4
+ ``CORS_ORIGINS`` env var (comma-separated); an empty value blocks
5
+ cross-origin requests by default.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import os
11
+
12
+ from fastapi import Depends, FastAPI, Request
13
+ from fastapi.middleware.cors import CORSMiddleware
14
+ from fastapi.responses import JSONResponse, Response
15
+ from slowapi.errors import RateLimitExceeded
16
+
17
+ from semvec.api.auth import verify_basic_auth
18
+ from semvec.api.metrics_export import get_metrics_response, setup_metrics
19
+
20
+ from semvec.api.cluster_manager import ClusterManager
21
+ from semvec.api.cluster_routes import cluster_router, set_cluster_manager
22
+ from semvec.api.database import create_tables
23
+ from semvec.api.event_bus import DriftEventBus
24
+ from semvec.api.global_observer import ObserverManager
25
+ from semvec.api.literal_cache_routes import (
26
+ literal_router,
27
+ set_literal_session_manager,
28
+ )
29
+ from semvec.api.network_manager import NetworkManager
30
+ from semvec.api.network_routes import network_router, set_network_manager
31
+ from semvec.api.observer_routes import observer_router, set_observer_manager
32
+ from semvec.api.regional_manager import RegionalManager
33
+ from semvec.api.regional_routes import regional_router, set_regional_manager
34
+ from semvec.api.routes import (
35
+ limiter,
36
+ router as core_router,
37
+ set_layer3_managers,
38
+ set_session_manager,
39
+ )
40
+ from semvec.api.session_delta_routes import (
41
+ delta_router,
42
+ set_delta_session_manager,
43
+ )
44
+ from semvec.api.session_manager import SessionManager
45
+
46
+
47
+ def _get_cors_origins() -> list[str]:
48
+ raw = os.environ.get("CORS_ORIGINS", "")
49
+ if not raw.strip():
50
+ return []
51
+ return [origin.strip() for origin in raw.split(",") if origin.strip()]
52
+
53
+
54
+ def create_app() -> FastAPI:
55
+ """Build and return the Semvec REST API FastAPI application."""
56
+ app = FastAPI(
57
+ title="Semvec REST API — Persistent Semantic State",
58
+ description=(
59
+ "Framework-agnostic semantic memory service. Feed text into "
60
+ "Semvec sessions and retrieve context-aware memories. Auth "
61
+ "via Ed25519-signed license JWTs (SEMVEC_LICENSE_KEY)."
62
+ ),
63
+ version="1.0.0",
64
+ )
65
+
66
+ app.state.limiter = limiter
67
+
68
+ def _rate_limit_handler(request: Request, exc: RateLimitExceeded) -> JSONResponse:
69
+ return JSONResponse(
70
+ status_code=429,
71
+ content={"detail": "Too many requests"},
72
+ headers={"Retry-After": "60"},
73
+ )
74
+
75
+ app.add_exception_handler(RateLimitExceeded, _rate_limit_handler)
76
+
77
+ app.add_middleware(
78
+ CORSMiddleware,
79
+ allow_origins=_get_cors_origins(),
80
+ allow_methods=["GET", "POST", "PATCH", "DELETE", "PUT", "OPTIONS"],
81
+ allow_headers=["Content-Type", "Authorization", "X-API-Key"],
82
+ )
83
+
84
+ setup_metrics(app)
85
+
86
+ @app.get("/metrics")
87
+ def _metrics(_: bool = Depends(verify_basic_auth)) -> Response:
88
+ return get_metrics_response()
89
+
90
+ create_tables()
91
+
92
+ manager = SessionManager()
93
+ set_session_manager(manager)
94
+ set_delta_session_manager(manager)
95
+ set_literal_session_manager(manager)
96
+ app.state.session_manager = manager
97
+
98
+ cluster_manager = ClusterManager(manager)
99
+ set_cluster_manager(cluster_manager)
100
+ app.state.cluster_manager = cluster_manager
101
+
102
+ event_bus = DriftEventBus()
103
+ regional_manager = RegionalManager(manager, cluster_manager, event_bus)
104
+ set_regional_manager(regional_manager)
105
+ app.state.regional_manager = regional_manager
106
+ app.state.drift_event_bus = event_bus
107
+
108
+ # routes.run() publishes drift events through these managers.
109
+ set_layer3_managers(cluster_manager, regional_manager)
110
+
111
+ observer_manager = ObserverManager(cluster_manager, regional_manager, manager)
112
+ set_observer_manager(observer_manager)
113
+ app.state.observer_manager = observer_manager
114
+
115
+ network_manager = NetworkManager(manager)
116
+ set_network_manager(network_manager)
117
+ app.state.network_manager = network_manager
118
+
119
+ app.include_router(core_router)
120
+ app.include_router(delta_router)
121
+ app.include_router(literal_router)
122
+ app.include_router(cluster_router)
123
+ app.include_router(regional_router)
124
+ app.include_router(observer_router)
125
+ app.include_router(network_router)
126
+
127
+ return app