[project] name = "basic-memory" dynamic = ["version"] description = "Local-first knowledge management combining Zettelkasten with knowledge graphs" readme = "README.md" requires-python = ">=3.12" license = { text = "AGPL-3.0-or-later" } authors = [ { name = "Basic Machines", email = "hello@basic-machines.co" } ] dependencies = [ "sqlalchemy>=2.0.0", "pyyaml>=6.0.1", "typer>=0.9.0", "aiosqlite>=0.20.0", "greenlet>=3.1.1", "pydantic[email,timezone]>=2.12.0", "mcp>=2,<3", "pydantic-settings>=2.6.1", "loguru>=0.7.3", "pyright>=1.1.390", "markdown-it-py>=3.0.0", "python-frontmatter>=1.1.0", "rich>=13.9.4", "unidecode>=1.3.8", "dateparser>=1.2.0", "watchfiles>=1.0.4", "fastapi[standard]==0.162.0", "alembic>=1.14.1", "pillow>=11.1.0", "pybars3>=0.9.7", "fastmcp==4.1.3", "pyjwt>=2.15.0", "python-dotenv>=1.1.0", "pytest-aio>=1.9.0", "aiofiles>=24.1.0", "asyncpg>=0.30.0", "pytest-asyncio>=1.2.0", "psycopg==3.3.1", "sniffio>=1.3.1", "anyio>=4.10.0", "httpx>=0.28.0", "fastembed>=0.7.4", "sqlite-vec>=0.1.6", "openai>=1.100.2", # The fastapi extra lets read-cache tests instrument the app as production does. "logfire[fastapi]>=4.19.0", "psutil>=5.9.0", # uvloop's C event loop has no self._ready.popleft() codepath, so the # asyncpg engine-dispose race ("IndexError: pop from an empty deque") that # crashes the Postgres backend cannot fire under it. Not available on Windows. "uvloop>=0.21.0; sys_platform != 'win32'", # Cross-platform advisory file lock (already resolved transitively via # huggingface-hub; declared directly since the hook flush path imports it). # Serializes concurrent `bm hook flush` runs so a stale sweep can't overwrite # an artifact without a sibling flush's just-retired event. "filelock>=3.12", "fastapi-redis-sdk==0.8.1", "pathspec>=1.1.1,<2", ] [project.optional-dependencies] # Explicit opt-in: default local and OpenAI embeddings do not need this SDK. # Preserve the Python 3.14-compatible range; 1.92 introduced an incompatible Rust bridge. litellm = [ "litellm>=1.91.5,<1.92.0", ] milvus = [ "pymilvus>=3.0.0,<4; sys_platform == 'win32'", "pymilvus[milvus-lite]>=3.0.0,<4; sys_platform != 'win32'", ] redis = [ "redis>=8.0.0,<9", ] # Native PDF extraction for document ingestion. Optional so the default install # stays parser-free; the worker records the installed version in run identity. pdf = [ "pdf-inspector>=0.2.6,<2", ] documents = [ "markitdown[docx,pptx]>=0.1.7,<0.2", # Footnote parsing for the document page-citation tests. "mdit-py-plugins>=0.6.1", ] [project.urls] Homepage = "https://github.com/basicmachines-co/basic-memory" Repository = "https://github.com/basicmachines-co/basic-memory" Documentation = "https://github.com/basicmachines-co/basic-memory#readme" [project.scripts] basic-memory = "basic_memory.cli.main:app" bm = "basic_memory.cli.main:app" [build-system] requires = ["hatchling", "uv-dynamic-versioning>=0.7.0"] build-backend = "hatchling.build" [tool.pytest.ini_options] pythonpath = ["src", "tests"] # plugins/ carries the co-located uv hook-script tests (one per script). testpaths = ["tests", "test-int", "plugins"] asyncio_mode = "strict" asyncio_default_fixture_loop_scope = "function" # Any test hanging >120s fails with a stack dump instead of stalling the CI job # until the runner times out (the FastMCP/asyncpg cleanup-hang family). timeout = 120 timeout_method = "thread" filterwarnings = [ "ignore:The @wait_container_is_ready decorator is deprecated.*:DeprecationWarning:testcontainers\\.core\\.waiting_utils", "ignore:The default datetime adapter is deprecated as of Python 3\\.12.*:DeprecationWarning:aiosqlite\\.core", "ignore:codecs\\.open\\(\\) is deprecated\\. Use open\\(\\) instead\\.:DeprecationWarning:frontmatter", "ignore:Parsing dates involving a day of month without a year specified is ambiguous.*:DeprecationWarning:dateparser\\.utils\\.strptime", ] markers = [ "benchmark: Performance benchmark tests (deselect with '-m \"not benchmark\"')", "slow: Slow-running tests (deselect with '-m \"not slow\"')", "postgres: Tests that run against Postgres backend (deselect with '-m \"not postgres\"')", "redis: Tests that run against a real Redis server", "windows: Windows-specific tests (deselect with '-m \"not windows\"')", "smoke: Fast end-to-end smoke tests for MCP flows", "semantic: Tests requiring semantic dependencies (fastembed, sqlite-vec, openai)", "real_embedder: Tests that exercise the real FastEmbed provider instead of the test embedder", "live: Tests that call external provider APIs and require explicit opt-in", ] [tool.ruff] extend-exclude = ["*.md"] line-length = 100 target-version = "py312" [tool.ruff.lint] # Ruff 0.16 expanded its defaults; preserve the repository's established lint baseline. select = ["E4", "E7", "E9", "F"] [dependency-groups] dev = [ # The fastapi extra lets read-cache tests instrument the app as production does. "logfire[fastapi]>=4.19.0", "icecream>=2.1.3", "pytest>=8.3.4", "pytest-cov>=4.1.0", "pytest-mock>=3.12.0", "pytest-asyncio>=0.24.0", "pytest-xdist>=3.0.0", "ruff>=0.16.0", "freezegun>=1.5.5", "testcontainers[postgres]>=4.0.0", "redis>=8.0.0,<9", "pdf-inspector>=0.2.6,<2", "psycopg>=3.2.0", "pyright>=1.1.408", "pytest-testmon>=2.2.0", "ty>=0.0.64", "cst-lsp>=0.1.3", "libcst>=1.8.6", "pytest-timeout>=2.4.0", "pytest-split>=0.11.0", "markitdown[docx,pptx]>=0.1.7,<0.2", # Footnote parsing for the document page-citation tests. "mdit-py-plugins>=0.6.1", ] [tool.hatch.version] source = "uv-dynamic-versioning" # Explicit resources only: never ship integration venvs, caches, or test data. [tool.hatch.build.targets.wheel.force-include] "integrations/tau/Basic Memory.py" = "basic_memory/data/tau/extension/Basic Memory.py" "integrations/tau/extension.py" = "basic_memory/data/tau/extension/extension.py" "integrations/tau/bridge.py" = "basic_memory/data/tau/extension/bridge.py" "integrations/tau/continuity.py" = "basic_memory/data/tau/extension/continuity.py" "integrations/tau/knowledge.py" = "basic_memory/data/tau/extension/knowledge.py" "integrations/tau/privacy.py" = "basic_memory/data/tau/extension/privacy.py" "integrations/tau/results.py" = "basic_memory/data/tau/extension/results.py" "integrations/tau/pyproject.toml" = "basic_memory/data/tau/extension/pyproject.toml" "integrations/tau/uv.lock" = "basic_memory/data/tau/extension/uv.lock" "integrations/tau/README.md" = "basic_memory/data/tau/extension/README.md" "integrations/tau/schemas" = "basic_memory/data/tau/extension/schemas" "integrations/tau/prompts" = "basic_memory/data/tau/prompts" # Distributed skills, for hosts that load SKILL.md files from the installed package # (Cloud agents). Same set as the release archives: internal skills, evals/ and # agents/ stay out. tests/test_skills_dist.py fails when this list drifts. # Skills the Tau mapping below also copies are keyed by SKILL.md: TOML keys are unique. "skills/memory-agents" = "basic_memory/data/skills/memory-agents" "skills/memory-capture" = "basic_memory/data/skills/memory-capture" "skills/memory-comark" = "basic_memory/data/skills/memory-comark" "skills/memory-continue/SKILL.md" = "basic_memory/data/skills/memory-continue/SKILL.md" "skills/memory-curate" = "basic_memory/data/skills/memory-curate" "skills/memory-doctor" = "basic_memory/data/skills/memory-doctor" "skills/memory-defrag" = "basic_memory/data/skills/memory-defrag" "skills/memory-ingest" = "basic_memory/data/skills/memory-ingest" "skills/memory-lifecycle" = "basic_memory/data/skills/memory-lifecycle" "skills/memory-literary-analysis" = "basic_memory/data/skills/memory-literary-analysis" "skills/memory-metadata-search" = "basic_memory/data/skills/memory-metadata-search" "skills/memory-notes/SKILL.md" = "basic_memory/data/skills/memory-notes/SKILL.md" "skills/memory-okf" = "basic_memory/data/skills/memory-okf" "skills/memory-onboarding/SKILL.md" = "basic_memory/data/skills/memory-onboarding/SKILL.md" "skills/memory-onboarding/references" = "basic_memory/data/skills/memory-onboarding/references" "skills/memory-quest/SKILL.md" = "basic_memory/data/skills/memory-quest/SKILL.md" "skills/memory-reflect" = "basic_memory/data/skills/memory-reflect" "skills/memory-research" = "basic_memory/data/skills/memory-research" "skills/memory-schema/SKILL.md" = "basic_memory/data/skills/memory-schema/SKILL.md" "skills/memory-start" = "basic_memory/data/skills/memory-start" "skills/memory-tasks/SKILL.md" = "basic_memory/data/skills/memory-tasks/SKILL.md" "integrations/tau/skills/basic-memory-setup" = "basic_memory/data/tau/skills/basic-memory-setup" "skills/memory-continue" = "basic_memory/data/tau/skills/memory-continue" "skills/memory-tasks" = "basic_memory/data/tau/skills/memory-tasks" "skills/memory-notes" = "basic_memory/data/tau/skills/memory-notes" "skills/memory-schema" = "basic_memory/data/tau/skills/memory-schema" "integrations/pi/extensions" = "basic_memory/data/pi/package/extensions" "integrations/pi/skills" = "basic_memory/data/pi/package/skills" "integrations/pi/skill-references" = "basic_memory/data/pi/package/skill-references" "integrations/pi/package.json" = "basic_memory/data/pi/package/package.json" "integrations/pi/package-lock.json" = "basic_memory/data/pi/package/package-lock.json" "integrations/pi/README.md" = "basic_memory/data/pi/package/README.md" "integrations/pi/SKILLS.md" = "basic_memory/data/pi/package/SKILLS.md" "integrations/pi/LICENSE" = "basic_memory/data/pi/package/LICENSE" [tool.uv-dynamic-versioning] vcs = "git" style = "pep440" bump = true fallback-version = "0.0.0" [tool.ty.environment] # Mirrors pytest's pythonpath, which puts tests/ on the import path for shared helpers. extra-paths = ["tests"] [tool.ty.rules] all = "error" [tool.coverage.run] # Nothing in basic_memory uses gevent. Declaring it forced the slower C tracer on # every coverage run and doubled test time; sysmon (COVERAGE_CORE) needs it gone. concurrency = ["thread"] parallel = true source = ["basic_memory"] [tool.coverage.report] # A ratchet, not the goal: CI's combined coverage over all four suites measured # 96.34% when it started enforcing it (97.16% locally on macOS). Raise it as gaps # close; the target is 100%. fail_under = 96 exclude_lines = [ "pragma: no cover", "def __repr__", "if self.debug:", "if settings.DEBUG", "raise AssertionError", "raise NotImplementedError", "if 0:", "if __name__ == .__main__.:", "class .*\\bProtocol\\):", "@(abc\\.)?abstractmethod", ] # Exclude specific modules that are difficult to test comprehensively omit = [ "*/external_auth_provider.py", # External HTTP calls to OAuth providers "*/supabase_auth_provider.py", # External HTTP calls to Supabase APIs "*/watch_service.py", # File system watching - complex integration testing "*/background_sync.py", # Background processes "*/cli/**", # CLI is an interactive wrapper; core logic is covered via API/MCP/service tests "*/db.py", # Backend/runtime-dependent (sqlite/postgres/windows tuning); validated via integration tests "*/services/initialization.py", # Startup orchestration + background tasks (watchers); exercised indirectly in entrypoints "*/sync/sync_service.py", # Heavy filesystem/db integration; covered by integration suite, not enforced in unit coverage "*/services/migration_service.py", # Complex migration scenarios ]