| """ |
| ENGRAM Protocol — ENGRAM Server |
| |
| |
| FastAPI application factory with lifespan management. |
| Initializes storage, index, extractor, and retriever on startup. |
| """ |
|
|
| from __future__ import annotations |
|
|
| import logging |
| from contextlib import asynccontextmanager |
|
|
| from fastapi import FastAPI |
|
|
| from kvcos.api import routes |
| from kvcos.core.config import get_config |
| from kvcos.core.serializer import EngramSerializer |
| from kvcos.core.types import ENGRAM_VERSION, StateExtractionMode |
| from kvcos.core.manifold_index import ManifoldIndex |
| from kvcos.core.retriever import EGRRetriever |
| from kvcos.core.state_extractor import MARStateExtractor |
| from kvcos.storage.local import LocalStorageBackend |
|
|
| logger = logging.getLogger(__name__) |
|
|
|
|
| @asynccontextmanager |
| async def lifespan(app: FastAPI): |
| """Initialize ENGRAM components on startup, clean up on shutdown.""" |
| config = get_config() |
|
|
| |
| storage = LocalStorageBackend(data_dir=config.data_dir) |
|
|
| |
| index_path = config.index_dir / "egr.faiss" |
| index = ManifoldIndex(dim=config.state_vec_dim, index_path=index_path) |
|
|
| |
| extractor = MARStateExtractor( |
| mode=StateExtractionMode.SVD_PROJECT, |
| rank=config.state_vec_dim, |
| ) |
|
|
| |
| serializer = EngramSerializer() |
| retriever = EGRRetriever( |
| extractor=extractor, |
| index=index, |
| storage=storage, |
| serializer=serializer, |
| ) |
|
|
| |
| routes._storage = storage |
| routes._index = index |
| routes._retriever = retriever |
|
|
| logger.info("ENGRAM v%s started", ENGRAM_VERSION) |
| logger.info(" Storage: %s (%d entries)", config.data_dir, storage.stats()["total_entries"]) |
| logger.info(" Index: %s (%d vectors, dim=%d)", config.index_dir, index.n_entries, config.state_vec_dim) |
| logger.info(" Backend: %s", config.backend.value) |
|
|
| yield |
|
|
| |
| try: |
| index.save(index_path) |
| logger.info("Index saved to %s", index_path) |
| except Exception as e: |
| logger.warning("Failed to save index: %s", e) |
|
|
| |
| routes._storage = None |
| routes._index = None |
| routes._retriever = None |
|
|
| logger.info("ENGRAM shutdown complete") |
|
|
|
|
| def create_app() -> FastAPI: |
| """Create the ENGRAM FastAPI application.""" |
| app = FastAPI( |
| title="ENGRAM Protocol API", |
| description="ENGRAM Protocol: Cognitive state, persisted.", |
| version=ENGRAM_VERSION, |
| lifespan=lifespan, |
| docs_url="/docs", |
| redoc_url="/redoc", |
| ) |
| app.include_router(routes.router) |
| return app |
|
|
|
|
| def main() -> None: |
| """Entry point for `engram-server` console script.""" |
| import uvicorn |
|
|
| config = get_config() |
| application = create_app() |
| uvicorn.run( |
| application, |
| host=config.host, |
| port=config.port, |
| log_level="info", |
| ) |
|
|
|
|
| def _get_app() -> FastAPI: |
| """Lazy app factory for `uvicorn kvcos.api.server:app`. |
| |
| Defers create_app() until the attribute is actually accessed, |
| avoiding side effects on module import. |
| """ |
| return create_app() |
|
|
|
|
| def __getattr__(name: str) -> FastAPI: |
| if name == "app": |
| return _get_app() |
| raise AttributeError(f"module {__name__!r} has no attribute {name!r}") |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|