[build-system] requires = ["maturin>=1.7,<2"] build-backend = "maturin" [project] name = "loci-mem" version = "0.6.0" description = "Scoped memory for coding agents: a router in front of a structure store and an episode store." readme = "README.md" requires-python = ">=3.10" license = "MIT" authors = [{ name = "Yigit Yildiz" }] keywords = ["memory", "rag", "knowledge-graph", "mcp", "agents", "retrieval"] classifiers = [ "Development Status :: 3 - Alpha", "Intended Audience :: Developers", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Topic :: Software Development :: Libraries", "Topic :: Text Processing :: Indexing", "Environment :: Console", "Operating System :: OS Independent", ] # The base install stays light on purpose: routing and lexical episode search # work with no model download and no vector database. # numpy alone. rank-bm25, scikit-learn and joblib are gone from the runtime: # BM25 and the char-gram matrix are Rust now, and nothing under src/ imports # them. That takes scipy out of the base install with scikit-learn, which was # the bulk of it. They remain DEV dependencies, as the reference the parity # tests compare against. dependencies = [ "numpy>=1.21", ] [project.optional-dependencies] # Structure backend. graphify is a dependency, never a fork: loci reads its # graph.json data contract through an adapter, so a different extractor can be # substituted without touching anything above backends/. graphify = ["graphifyy>=0.9.9"] # ONNX Runtime rather than torch, and the reason is measured rather than # stylistic: torch cost 6.2s of a 7.77s query, segfaulted inside the scope # fan-out (three locks did not close it -- see embed.py), and drew a progress # bar on the stderr that hosts read errors from. embeddings = ["onnxruntime>=1.17", "tokenizers>=0.15", "huggingface-hub>=0.20"] rerank = ["onnxruntime>=1.17", "tokenizers>=0.15", "huggingface-hub>=0.20"] # Upper-bounded on purpose. mcp 2.0 removed the low-level `@server.list_tools()` # and `@server.call_tool()` decorators that mcp_server.py is built on, leaving # only add_request_handler or a FastMCP rewrite. An unbounded `mcp>=1.0` resolves # to 2.x and the server dies at import with AttributeError -- a crash the CLI # never sees, because nothing else in loci imports mcp. mcp = ["mcp>=1.0,<2"] all = ["graphifyy>=0.9.9", "onnxruntime>=1.17", "tokenizers>=0.15", "huggingface-hub>=0.20", "mcp>=1.0,<2"] # sentence-transformers is a DEV dependency and stays one. tests/test_embed.py # compares the ONNX encoder against it, and deleting the reference # implementation deletes the proof that loci still encodes the way the nine # floors in calibration.json were fitted to. dev = ["pytest>=8.0", "sentence-transformers>=3.0", "scikit-learn>=1.3", "rank-bm25>=0.2.2", "joblib>=1.3"] [project.urls] Homepage = "https://github.com/3M1RY33T/loci" Issues = "https://github.com/3M1RY33T/loci/issues" # The distribution is `loci-mem` (PyPI `loci` is an abandoned 2018 # outlier-detection package); the command, the import and the project are # all `loci`. Same split as python-dateutil -> dateutil. [project.scripts] loci = "loci.cli:main" # A Python package with ONE compiled module inside it, not a Rust package with # Python attached: router.py and everything above backends/ stay Python, and # src/loci remains the source of truth for them. [tool.maturin] python-source = "src" module-name = "loci._core" manifest-path = "rust/loci-py/Cargo.toml" features = ["pyo3/extension-module"] # The parity harness is TRACKED, because it needs review and history, but it is # development scaffolding rather than distribution. .gitignore cannot say that # -- ignoring it would untrack it -- and the build backend filters by # .gitignore, so without this it ships to PyPI. Same failure as the .superpowers # review diffs in the 0.1.0 sdist, arriving by a different route. exclude = ["parity/**", "target/**", "rust/target/**"] [tool.pytest.ini_options] testpaths = ["tests"] markers = [ "reference: compares against sentence-transformers, the encoder being replaced", # Must run in its own PROCESS, which means its own FILE: importorskip runs # at collection, so deselecting by marker still imports scikit-learn. See # tests/test_lexical_reference.py. Two passes: # pytest tests --ignore=tests/test_lexical_reference.py # pytest tests/test_lexical_reference.py "lexical_reference: compares against sklearn and rank_bm25; own file and process", ]